macOS stopped running i386 Mach-O programs in Catalina, and Rosetta never ran
them. m32 runs them under AArchX: an executable with an i386 slice and no
x86_64 one is handed to m32_run instead of being refused, and runs in native
mode against the Mac’s own arm64 frameworks.
make apis32 # i386 API databases from your SDK (and make apis, which m32 needs too)
make steam32 # the Steam bridge's interface database, for Steam games
tools/fetch_guest32_cxx.sh # only for programs built with GCC's libstdc++
tools/fetch_guest32_x11.sh # only for programs that link X11's fontconfig or freetype
./ocerz -native "Some Game.app/Contents/MacOS/Some Game"
The two fetch scripts need no Xcode tools: they unpack MacPorts’ universal
packages as MacPorts lays them out, under runtime/guest32/opt/local, where m32
looks first for the /opt/local names the libraries give one another, and link
the /usr/lib and /usr/X11R6/lib names games import to them; m32 reads the
i386 slice of a universal binary itself. A release carries both scripts in
tools/, and the installer and the Homebrew cask run them.
Playing a 32-bit game from Steam, step by step, is in 32-bit Mac games from Steam.
Batman: Arkham Asylum Game of the Year Edition (Feral, 2014; Unreal Engine 3, Objective-C 1, tcmalloc, Bink, FMOD Ex, OpenGL through Feral’s IndirectX) plays its title screen, its menus with the mouse, and gameplay with keyboard, mouse and sound in MacShack, where m32 comes from. On this tree its New Game opening runs at about 61 frames a second on an M4 Max. What got it there from 31: shifts by cl, MMX, shld and shrd, and the high-byte registers are translated in 32-bit code, an import stub calls its export from inside the block (emit_bridge_fastcall32), and small libc, CGL and OpenGL exports are called as leaves with no crossing at all (src/m32_leaf.c).
Started from Steam with ocerz -native %command% as its launch options, it
reaches the user’s own Steam through the Steam bridge below. Feral’s options
dialog, a legacy WebView, draws nothing under m32; the game starts after it,
and its first run writes default settings and key bindings to Feral’s
registry (~/Library/Application Support/Feral Interactive/<game>/Preferences
Data) by itself.
Kega Fusion 3.63 (a UPX-packed Carbon program drawing through AGL) opens its window, and so does VLC 1.1.12’s 32-bit Intel build (Cocoa, with Sparkle and Growl).
AssaultCube 1.1.0.4 (2010; SDL 1.2, OpenGL, OpenAL, Ogg Vorbis) plays: its window, menus, maps and sound, and a bot match started from its console. SuperTuxKart 0.6.2a (2009; SDL 1.2, GLUT, its own OpenAL, Ogg music) runs, races started from its command line included, with its music and sound. Neverball and Neverputt 1.5.3 (SDL 1.2, SDL_ttf, OpenGL) run with their sound. Battle for Wesnoth 1.8.6 (2011; SDL 1.2 and its mixer, image, net and ttf libraries, Pango, cairo, GLib, Lua, Boost) opens its title screen and runs its test scenario. It finds none of the fonts it bundles, as its Mac build registers none, and falls back to the system’s. Teeworlds 0.5.2 (SDL 1.2) plays, a local server and client included.
Nexuiz 2.5.2 (2009; DarkPlaces on AGL, with GLSL) starts from its .app,
whose executable is a shell script, loads a map and its bots play.
ScummVM 1.4.1 (2012; SDL 1.2 and Cocoa) runs Beneath a Steel Sky, which
scummvm.org gives away: scummvm --path=<game> --no-fullscreen sky. Its Cocoa
code was built by GCC without constant CFStrings, so its @"..." literals are
Objective-C 1’s own 12-byte NSConstantStrings in __OBJC,__cstring_object,
which m32 makes host strings as it does __cfstring ones. DOSBox 0.74 (2010;
SDL 1.2) runs DOS programs: a batch file run with -c writes its files to a
mounted folder and DOSBox exits when it does. Warzone 2100 2.3.9 (2011; SDL
1.2, OpenAL, PhysFS, QuesoGLC) opens its window and menus once
tools/fetch_guest32_x11.sh has put the X11 libraries Mac OS X 10.5 and 10.6
shipped in /usr/X11R6/lib, fontconfig and freetype among them, as i386
slices of MacPorts’ universal builds; m32 points FONTCONFIG_FILE at the
fonts.conf installed beside them, which names the Mac’s font folders.
PhysFS allocates through CFAllocatorAllocate, which with a system allocator
now hands out guest heap, as NSAllocateMemoryPages hands out window pages.
Armagetron Advanced 0.2.8.3 (SDL 1.2) runs (-w for a window) and saves its
settings. Apple’s GCC 4 put an ofstream’s basic_filebuf on an 8-byte
boundary, as Apple’s libstdc++.6.dylib did, which puts the stream’s
basic_ios 4 bytes later than in GCC 15’s libstdc++. The game builds its
streams inline at Apple’s offsets, so m32 gives the runtime’s libstdc++
Apple’s layout for ofstream and fstream: the offsets in their vtables, and
the members that use the offsets directly (src/m32_fstream.c).
Chocolate Doom 1.7.0 (2012; SDL 1.2) plays Freedoom: -iwad freedoom1.wad
-window -width 640 -height 480 -timedemo demo1 runs its demos at about 350
frames a second. Enigma 1.01 (2011; SDL 1.2, C++) opens its menus and its
1,060 levels. Pingus 0.7.6 (2011; SDL 1.2, Boost) plays its levels with the
libstdc++ and libgcc_s it carries, built by GCC rather than Apple: their
exceptions unwind through m32’s unwinder too. LinCity-NG 2.0 (2009; SDL 1.2,
PhysFS, libxml2) runs in a window (-w); its Mac build links libiconv, libz,
libstdc++, libgcc_s and libSystem from its own Frameworks folder, which does
not hold them, and m32 falls back to the leaf names in /usr/lib as dyld
does. Asked for full screen, SDL 1.2 needs CGDisplayBestModeForParameters to
find the mode exactly; today’s CoreGraphics never reports an exact match, so
m32 chooses among the display’s modes itself (src/m32_cg.c).
Cube 2: Sauerbraten (Justice Edition, 2011; SDL 1.2, OpenGL with GLSL
shaders) runs its i386 slice: sauerbraten -nolauncher -t -w800 -h600
-q<home> with a script such as -x"map complex; addbot 50" loads a map and
its bots play, at about 65 frames a second in a window. SDL_image reads the
images that come from an SDL_RWops through CGDataProviderCreate, Mac OS X
10.4’s callback provider, which m32 builds from today’s sequential one.
Adium 1.4.5 (2011; Cocoa and Objective-C 1 across twenty bundled frameworks: AIUtilities, AutoHyperlinks, Sparkle, Growl, libpurple and GLib) opens its setup assistant, its contact list and Sparkle’s update prompt.
ocerz_mem_init(0, 4 GB)) and never holds a host address. Host objects
reach it as 8-byte handle cells at 0xE0000000-0xF0000000; guest data with a
host twin (CFSTR literals, CF-typed data it only takes the address of) as
aliases (src/m32_handle.c).ocerz_dyldapi_dispatch hands a 32-bit cpu’s trap to
m32_trap. Each export’s two notations come from runtime/apis32, which
tools/sdkgen.sh --guest i386 writes by parsing the SDK headers as i386
macOS 10.14 beside arm64. Guest-only classes: s a C string, P one pointer
written back, W/V one long written back, S one 16-bit integer the host
writes as 32 bits (FSIORefNum *), Q data whose layouts differ,
T a dispatch_time_t, converted between the Intel and Apple silicon clocks.
The generic crossing is src/m32_cross.c; the calls no notation describes
have hand-written specials (m32_libsystem.c, m32_stdio.c, m32_thread.c,
m32_cf.c, m32_dyld.c, m32_gl.c, m32_audio.c, m32_zlib.c).@executable_path, @loader_path,
@rpath and runtime/guest32 (OCERZ_GUEST32_ROOT), with dyld info or
classic relocations; weak symbols coalesce to the first definition in load
order, as dyld does.sysenter or int 0x80, which m32 answers for the few a
loader makes), then opens /usr/lib/dyld and jumps to it with the program’s
mach header pushed, as the kernel would. m32 answers that open with a dyld
of its own whose entry is a trap; there it takes the unpacked program from
memory and starts it like any other (src/m32_selfload.c). The low 256 MB
of the window are kept free for the unpacking until then.crt1.o reaches dyld through the
__DATA,__dyld section, whose words held dyld’s fixed addresses of the time
(0x8fe01000 and 0x8fe01008); m32 points them at its own _dyld_func_lookup
and lazy-binding trap. GCC’s crt3.o keeps its atexit list with keymgr, which
has no header in today’s SDK; m32 keeps keymgr’s pointers itself.OCERZ_M32_HEAP_MB, default
1024, growing on demand) until the program registers a malloc zone of its
own (tcmalloc in Batman); malloc then runs the guest’s zone through
generated i386 thunks, and free goes by who owns the pointer.+load and +initialize, sends converted by the
i386 and host encodings (<leaf>.objc32 beside each database), exceptions
with guest frames, blocks both ways, and swizzling with host IMPs
(src/m32_objc*.c, src/m32_blocks.c, src/m32_catch.m). A paired class
names the guest image it came from to class_getImageName, so
bundleForClass: finds a framework’s nibs, and answers key-value coding
from the guest’s instance variables when no accessor does (Sparkle’s
bindings); a nib outlet with no setter is set in the guest instance
variable of its name, as AppKit sets one. A twin goes with its host object
(an associated object takes the alias back), so the next object malloc
places there gets a twin of its own. A guest image’s protocols get host
twins of the same name, adopting the same protocols. The garbage
collector’s write barriers (objc_assign_global and the rest), which code
built to support it calls for every store of an object, are the stores
themselves. NSCreateMapTable and
NSCreateHashTable take their callback structures by value.NSNotFound and NSIntegerMax are
INT64_MAX on the host and INT32_MAX in the guest, and cross as each other,
in results and in an NSRange either way. A message’s id * arguments
(NSError **, intoString:) cross as a guest id the callee stores, and
its pointers to one structure (longestEffectiveRange:, an
NSRangePointer) as an i386 copy written back; the host used to store 8
and 16 bytes into the guest’s 4 and 8. The other way, a guest method the
host calls with one (validateValue:forKey:error:, a document’s
readFromURL:ofType:error:) gets a guest copy, written back after. AppKit’s tracking-rectangle and
tool-tip tags are 64-bit values, and the guest gets a small number for each.
sortUsingFunction:context: and its siblings call the guest’s comparator,
and method_exchangeImplementations swaps guest methods.tsearch, tfind, tdelete and twalk keep their trees in
guest memory, calling the guest’s comparison (src/m32_tsearch.c), the
printf family places positional arguments (%2$@), and __sF, the 10.4
SDK’s stdin, stdout and stderr, is three FILEs standing for the host’s.
A call that sets no errno leaves the guest’s as it was: the host’s is
loaded from it before each call (libvorbisfile clears errno, reads, and
takes a short read with a non-zero errno for an error). __srget, which
i386’s getc macros call for every character, reads the next one (Pango’s
config files). localeconv answers an i386 struct lconv, and valloc
aligns to the page size getpagesize and sysconf report.DYLD_LIBRARY_PATH and execs the game) runs on its interpreter’s
x86_64 slice under ocerz, so the programs it starts are ocerz’s to run;
natively, /bin/sh handed the i386 game to the kernel, which refused it.
A script that starts its game through $GAME_DEBUGGER (Valve’s hl2.sh)
still runs natively with that naming ocerz.MH_TWOLEVEL (built with
-flat_namespace, as Wesnoth’s GLib and Pango are) looks every import up in
every image and then the system, as dyld does. Images are fixed up and
initialized after their dependencies, and an LC_RPATH may name a
directory relative to @loader_path or @executable_path (Adium’s
frameworks). dlopen of a leaf name alone (libz.dylib, as DarkPlaces asks
for zlib) looks in DYLD_LIBRARY_PATH, then DYLD_FALLBACK_LIBRARY_PATH
(by default ~/lib, /usr/local/lib and /usr/lib), as dyld does.FPCR.AH for x86 NaN
results, and host frameworks must not run with it: every call into the host
clears it and every callback into the guest sets it again (AppKit’s TextKit
upgrade of an archived text view faulted with it set). Those two writes cost
about 17 ns a call on an M5, more than the NaN checks the bit saves, so an
i386 program runs in native mode unless -cache or OCERZ_MODE says
otherwise, with translated NaN checks and no FPCR writes; Nexuiz’s benchmark
demo took 8% fewer cycles.dlsym’s, a GL extension’s) is its trap, and a call through it used to
leave translated code for the run loop and come back, every call.
DarkPlaces calls all of OpenGL that way. A trap is translated as the import
stub it stands for, so the call is made from inside a block, as a leaf when
it can be, and returns through the shadow stack. With the fast calls m32
had kept to -native now made in either mode, Nexuiz’s benchmark demo takes
about 20% fewer cycles than before (OCERZ_NO_M32_TRAP_BLOCKS=1 turns the
trap blocks off). An adc or sbb into memory whose flags are dead, i386’s
way of adding a 64-bit value to memory after an add, is translated
(another 7%; OCERZ_NO_ADC_MEM=1 sends it to the slow path again), and the
memcmp and strlen leaves are libc’s own.GL_ARRAY_BUFFER, a
gl*Pointer’s pointer is an offset into it, and with one bound to
GL_ELEMENT_ARRAY_BUFFER, so is a glDraw*Elements’ indices pointer; m32
keeps each context’s two bindings as the guest makes them and passes such a
pointer through as it is. Turned into a window address, it had GL read
past the buffer, and Nexuiz’s benchmark faulted whenever a surface drew from
one. A GL call made as a leaf clears FPCR.AH too, as crossings do.NSAddImage, NSLookupSymbolInImage,
NSCreateObjectFileImageFromFile and NSLinkModule load and look up on
m32’s own loader, as dlopen and dlsym do (Firefox 3.6 finds XUL this way).src/m32_atsu.c): font tables
(ATSFontGetTable), glyph info and bounds, platform fonts made without ATS
(which stops in an alert on today’s macOS), and the system font a program
asks AppKit for is Lucida Grande, as on the Macs it was written for.
CoreGraphics’ private font calls cairo looks up with dlsym
(CGFontGetGlyphsForUnichars, CGFontGetGlyphPath, CGFontGetHMetrics)
are made on CoreText; without CGFontGetGlyphPath cairo makes no font.
A layout can be made empty and given its text and style runs afterwards
(ATSUCreateTextLayout, ATSUSetTextPointerLocation, ATSUSetRunStyle), as
Pango’s ATSUI renderer makes them.CGFunctionCreate’s and CGPatternCreate’s
callbacks are called as guest code, a function’s values as floats, and
CGPathApply hands its applier guest copies of the elements
(src/m32_cg.c). cairo’s private CGContextSetCompositeOperation is a
blend mode and CGContextSetCTM a concatenation. A guest callback can
return a structure (an NSSize from windowWillResize:toSize:), and
getRectsBeingDrawn:count: hands back guest copies of AppKit’s rectangles.#pragma pack(2), which leaves an arm64 pointer after a 4-byte field at
offset 4. A notation says so with a digit after the brace (R{2up}, an
AEDesc read and written through a pointer), and tools/sdkgen writes it
when a structure’s offsets match clang’s only so; the Apple Event, OSA, menu
tracking and 160 other calls whose structures crossed as the guest’s bytes
are copied between the two layouts. HIToolbox’s theme structures whose i386
and arm64 layouts differ (HIThemeTrackDrawInfo, HIThemeMenuDrawInfo,
HIThemeTextInfo, HIThemeWindowDrawInfo and two more), some holding a
union, are converted for the calls that take them (src/m32_cross.c), and
so is GetProcessInformation’s ProcessInfoRec (src/m32_carbon.c).
OSAtomicEnqueue, OSAtomicFifoEnqueue and their dequeues link the
guest’s elements through its own 4-byte pointers, under one lock, and a
pthread_rwlockattr_t is the guest’s 16 bytes (src/m32_thread.c).smKCHRCache answers 0, as on a Mac with Unicode layouts alone), the
private CPSGetCurrentProcess, CPSEnableForegroundOperation and
CPSSetFrontProcess bring a program to the foreground, an
NSBitmapImageRep’s planes (SDL’s cursors) are in guest memory, and
NSApplicationMain gets an argument vector AppKit can read.libopenal*.dylib or
OpenAL.framework is the system’s OpenAL, and the Component Manager finds
audio units (FindNextComponent, OpenAComponent) through AudioComponent,
Mac OS X 10.1’s descriptions ('aunt') and render callback
(kAudioUnitProperty_SetInputCallback, which SDL 1.2’s CoreAudio driver
uses) included.
GLUT crosses (glutInit takes the whole argument vector), and a variable a
program imports has the host variable’s address (GLUT’s fonts). A program
linked against GCC’s libgcc_s unwinds through m32’s unwinder whichever
library it imports _Unwind_* from.dlsym answers NULL for a function m32
cannot call (no declaration, or a shape it cannot carry), so a program takes
its own fallback. libcrypto’s MD5_* are CommonCrypto’s, GNU libiconv’s
libiconv* names are iconv*, and LaunchServices’ private
_LSCopyDefaultSchemeHandlerURL, StillDown and the scroll bar theme
styles answer.__eh_frame with the guest’s personality routine (src/m32_unwind.c).mach_absolute_time counts nanoseconds with a 1/1 timebase and
UpTime follows it, as on an Intel Mac; Bink times its movies with them.steamclient since 2019, so a game’s
own i386 libsteam_api finds the running Steam (which tells it where its
client lives only if Steam launched it) and then a client it cannot load.
When an i386 program dlopens a steamclient.dylib with no i386 slice, m32
opens the host’s own and answers its exports itself (src/m32_steam.c):
CreateInterface, and every method that returns an interface, hand back a
proxy, a guest object whose vtable slots cross into the host object’s by the
notations make steam32 writes into runtime/apis32/.../steam.api from the
Steamworks headers of every SDK (tools/steamgen.sh fetches them from
Valve’s Proton at a pinned commit; they are never committed). Strings a
method returns are copied into guest memory, and the callback queue’s
messages are converted, their payloads copied. A method the database cannot
describe (one taking a function pointer, mostly) answers 0.make check-m32 runs the programs in tests/m32 against their
.expected output; add OCERZ_NOJIT=1 for the interpreter alone. The
programs are committed in tests/m32/bin, because linking i386 needs Xcode
26’s ld-classic (CLT 27’s linker dropped i386): M32_REBUILD=1 rebuilds them
with tests/m32/build.sh, using M32_DEVELOPER_DIR (default
/Applications/Xcode.app). The SDK has no i386 libraries any more, so
build.sh links against stub .tbd files made from the SDK’s own lists; they
stay in the untracked tests/m32/sdk. cxx and cxxexc need
runtime/guest32 and are skipped without it. tests/m32/dbcheck.sh spot
checks a generated database. steambridge runs the Steam bridge against
tests/m32/steamfake, an arm64 stand-in for Steam’s client, and is skipped
without make steam32.
| Variable | Effect |
|---|---|
OCERZ_M32LOG=imports,calls,images,objc,throws,glstat |
imports as they resolve, every crossing, image loads, Objective-C sends, C++ throws and host exceptions raised into the guest, OpenGL calls and frames per 10 s |
OCERZ_M32_KEEP_GOING=1 |
run with unresolved imports, which then return 0 |
OCERZ_M32_HEAP_MB=<n> |
the guest heap’s first size |
OCERZ_M32_MUTE=1 |
the CoreAudio render callback outputs silence |
OCERZ_GUEST32_ROOT=<dir> |
where guest system libraries are looked for, instead of runtime/guest32 |
OCERZ_M32LOG=steam |
the Steam bridge: the client it opened, each interface it bridged, every call to a method it cannot cross, and each callback it hands the game |
OCERZ_M32LOG=dlsym |
each dlsym that found nothing, with its caller |
OCERZ_M32LOG=render |
each audio unit render callback the host makes, its frames and bytes |
OCERZ_M32_HUD=<overlay dylib> |
an FPS overlay that exports fake_dlsym (simpleoverlay) draws on each frame the game presents, through CGLFlushDrawable or -[NSOpenGLContext flushBuffer] |
OCERZ_NO_M32_STEAM=1 |
a steamclient.dylib with no i386 slice is left to the ordinary loader, which refuses it |
OCERZ_NO_M32_CARBON_WRAP=1 |
a window an i386 program creates is made as it asked, without kWindowCompositingAttribute, which 64-bit HIToolbox refuses |
OCERZ_NO_M32_UPP=1 |
the New...UPP and Dispose...UPP calls stay unresolved |
OCERZ_NO_M32_EVENT_PARAMS=1 |
Carbon event parameters cross in their raw layouts |
OCERZ_NO_M32_FLOAT_ARRAYS=1 |
CoreGraphics’ CGFloat arrays cross as the guest’s floats |
OCERZ_NO_M32_LUCIDA=1 |
+[NSFont systemFontOfSize:] and its siblings answer today’s system font |
OCERZ_NO_M32_KVC=1 |
a paired class answers no key-value coding from guest instance variables, and nib outlets with no setter stay unset |
OCERZ_NO_M32_REAP=1 |
a twin outlives its host object |
OCERZ_M32_SENDLOG=<part>[\|<part>...] |
each message to a host object whose selector contains a part: its arguments (a string’s characters too) and result, then every message the objects it returned receive |
OCERZ_NO_M32_IMAGE_NAME=1 |
class_getImageName knows no image for a paired class |
OCERZ_NO_M32_PACKED=1 |
HIToolbox’s theme structures cross as the guest’s bytes |
OCERZ_NO_M32_SYSTEM_OPENAL=1 |
a program’s own OpenAL library is loaded instead of the system’s |
OCERZ_NO_M32_DLSYM_NULL=1 |
dlsym hands out the trap of a function m32 cannot call, which answers 0 |
OCERZ_NO_M32_APPLE_FSTREAM=1 |
the runtime’s libstdc++ keeps GCC’s layout of ofstream and fstream instead of Apple’s |
OCERZ_NO_M32_SIGNALS=1 |
a guest’s signal handlers are recorded only, never run, and the host’s default action stays |
OCERZ_NO_M32_FAULT_HANDLERS=1 |
a fault in i386 code ends in ocerz’s crash report instead of the program’s own handler (SDL’s parachute, say) |
OCERZ_M32_HOST_PAGES=1 |
i386 programs are told the host’s 16 KB pages instead of 4 KB |
OCERZ_NO_M32_ENCODING_CACHE=1 |
each message to a host object looks its i386 method encoding up again instead of keeping it per class and selector |
OCERZ_M32_NO_SELFLOAD=1 |
start a program with no libraries and no dyld at its entry like any other, instead of as one that loads itself |
OCERZ_NO_M32_FASTCALL=1 |
imports leave the block for the trap instead of crossing from inside it |
OCERZ_NO_M32_LEAF=1 |
leaf exports cross like any other import |
OCERZ_NO_M32_TRAP_BLOCKS=1 |
a call through a function pointer to an export goes through the run loop, as before |
OCERZ_NO_HIGH8_ARITH=1 |
instructions on ah, ch, dh and bh go to the interpreter again (64-bit code too; docs/reference.md) |
OCERZ_NO_SHIFT_MEM_IMM=1 |
shifts of memory by an immediate go to the interpreter again (64-bit code too) |
Much of Carbon’s window, menu, control and event API is declared for 32-bit
code alone yet still exported, and working, in Apple silicon’s HIToolbox.
tools/sdkgen gives each such function an arm64 declaration made from its i386
one, so it crosses like any other (Carbon’s database crosses 847 functions
where it crossed 364). Windows an i386 program creates get
kWindowCompositingAttribute, the only kind 64-bit HIToolbox makes.
QuickDraw’s region, rectangle and point math and cursor visibility are still
exported and cross (tools/sdkgen/headers/QuickDrawRetained.h declares them);
m32 provides the rest on CoreGraphics (src/m32_qd.c). A window’s port draws
into a bitmap in guest memory that the window’s content view shows, a GWorld is
a bitmap over guest pixels (32-bit xRGB and 16-bit x555, big-endian as on Intel
Macs), and pens, lines, rectangle and region fills, clipping, SetOrigin,
CopyBits, QDBeginCGContext, the mouse, cursors and the graphics devices
work. QuickDraw text is not drawn (the port keeps its font, size and style),
transfer modes other than srcCopy copy, 8-bit and smaller GWorlds are refused
with cDepthErr, and color cursors from resources show the arrow.
AGL still works on Apple silicon, without headers in any SDK: m32 crosses it
by hand (src/m32_agl.c), and an AGLDrawable, a QuickDraw port, becomes its
window (aglSetWindowRef) or its GWorld’s pixels (aglSetOffScreen). The
New...UPP calls hand back their function and Dispose...UPP does nothing, as
on Intel. Carbon event parameters whose i386 and arm64 layouts differ convert
(WindowRef, ControlRef, RgnHandle and the other pointers, HIPoint,
HIRect, HICommand, CFIndex), and a draw event’s GrafPtr, which 64-bit
Carbon lacks, is the window’s QuickDraw port. EventRecords are copied between
their 16-byte i386 and 20-byte arm64 layouts. CoreGraphics’ CGFloat arrays
(colors, dashes, point and rectangle lists, gradients, CoreText’s advances)
cross as doubles. Kega Fusion runs and opens its window.
The menu and control calls Apple silicon dropped are made from their HIView and
menu successors (src/m32_controls.c): EnableMenuCommand, SetItemCmd, the
hierarchical menu IDs, the menu bar handle, EmbedControl, control reference
values, GetControlRegion, the 32-bit values, SetControlBounds,
TestControl, FindControl, HandleControlClick and user panes, whose draw,
hit-test, tracking, activate and focus procedures are called and whose QuickDraw
drawing the pane shows. QuickDraw’s and the Font Manager’s font families
(GetFNum, FMGetFontFamilyFromName, FMGetFontFromFontFamilyInstance,
GetThemeFont) are CoreText’s, with the classic IDs (the system font Lucida
Grande 0, Geneva 3, Helvetica 21 and the rest), and the Text Encoding Converter’s conversions widen their counts.
The rest of the 32-bit-only Carbon UI does not exist on Apple silicon and m32
does not provide it yet: of the control kinds, only push buttons, check boxes,
static text and icons remain (radio and bevel buttons, sliders, tabs, pop-up
buttons, edit text, scroll bars and the Data Browser are gone), nor do
Navigation Services dialogs and MLTE. long double calls have no notation and stay unresolved. OpenCL is not
bridged (clCreateContextFromType answers 0, and Batman does without it).
A handler for an asynchronous signal (SIGINT, SIGTERM, SIGALRM, SIGCHLD and
the like) runs at the program’s next call into a host library, so an SDL game
asked to stop with SIGTERM or Ctrl-C quits as on a Mac and saves its settings.
A handler for a fault (SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGTRAP) runs at the
fault, in the frame the kernel builds for i386, and the context it changes is
the one the program goes on with, as Mono turns a null access into a
NullReferenceException.
WineHQ’s 32-bit Wine 5.0 for macOS, what a TransGaming Cider port carries
inside, runs Windows programs: wine cmd /c "echo hi && ver" creates a prefix
(services.exe, the setupapi install of wine.inf, explorer.exe) and prints
Microsoft Windows 6.1.7601 in about four seconds, one second once the prefix
exists. WINEDLLOVERRIDES="mscoree,mshtml=" skips the Wine Mono and Gecko
install prompts a new prefix otherwise waits on. i386 programs are told 4 KB
pages, as on the Intel Macs: told the host’s 16 KB, wineserver took every PE
DLL with 4 KB sections for one to map flat and ntdll refused it. IOKit’s
registry calls that name a plane or a class (io_name_t) are made as they
stand (src/m32_iokit.c); Wine’s Mac driver climbs from its GPU to the
IOPCIDevice above it with them.
Firefox 3.6.28 (i386) starts, finds its fonts and draws its browser window, but the window does not reach the screen: AppKit has it visible and the window server has it off screen. The same window settings made natively show, and the process has a second window server connection, opened when Gecko installs a handler on Carbon’s event monitor target; without that handler the window reached the screen in some runs and not in others.