From ae1884cc0595c70792ee0aa3ebff696b1a3a7991 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 17 Sep 2026 00:35:30 -0700 Subject: [PATCH] Implement macOS (Intel) support --- CMakeLists.txt | 123 +++++++++++++++++++++++++++++++--- README.md | 10 +-- docs/building.md | 22 ++++-- docs/porting-notes.md | 24 +++++++ port/fast3d/gfx_sdl2.cpp | 7 ++ port/include/pc_protos.h | 11 +++ port/shim/PR/os.h | 12 ++++ port/src/crash.c | 22 ++++-- port/src/dram.c | 17 ++++- port/src/libultra.c | 2 +- port/src/macho_weak_aliases.s | 25 +++++++ port/src/romdata.c | 11 ++- port/src/video.c | 8 ++- scripts/gen_macho_syms.py | 44 ++++++++++++ scripts/strip_weak_pragmas.py | 25 +++++++ 15 files changed, 328 insertions(+), 35 deletions(-) create mode 100644 port/src/macho_weak_aliases.s create mode 100644 scripts/gen_macho_syms.py create mode 100644 scripts/strip_weak_pragmas.py diff --git a/CMakeLists.txt b/CMakeLists.txt index fe0c9500..1d481989 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,11 +25,26 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) # name as an inherited one — CCTVRecord's `pad` — see docs/internals.md.) # An explicit -DCMAKE_C_COMPILER=... always wins. if(NOT DEFINED CMAKE_C_COMPILER) - find_program(_GE007_GCC NAMES gcc) + if(CMAKE_HOST_APPLE) + # Homebrew installs real GNU GCC with a version suffix; /usr/bin/gcc is + # Apple Clang and does not support the Plan 9 struct extensions required + # by the decomp's `inherits` pattern. + find_program(_GE007_GCC + NAMES gcc-16 gcc-15 gcc-14 gcc-13 gcc-12 gcc-11 gcc) + else() + find_program(_GE007_GCC NAMES gcc) + endif() if(_GE007_GCC) set(CMAKE_C_COMPILER "${_GE007_GCC}" CACHE FILEPATH "C compiler" FORCE) get_filename_component(_GE007_GCC_DIR "${_GE007_GCC}" DIRECTORY) - find_program(_GE007_GXX NAMES g++ PATHS "${_GE007_GCC_DIR}" NO_DEFAULT_PATH) + if(CMAKE_HOST_APPLE) + find_program(_GE007_GXX + NAMES g++-16 g++-15 g++-14 g++-13 g++-12 g++-11 g++ + PATHS "${_GE007_GCC_DIR}" NO_DEFAULT_PATH) + else() + find_program(_GE007_GXX NAMES g++ + PATHS "${_GE007_GCC_DIR}" NO_DEFAULT_PATH) + endif() if(_GE007_GXX) set(CMAKE_CXX_COMPILER "${_GE007_GXX}" CACHE FILEPATH "CXX compiler" FORCE) endif() @@ -163,7 +178,18 @@ configure_file( # the toolchain include dir from the compiler location (MinGW/MSYS2 layout: # bin/../include) and generate an absolute-path include. get_filename_component(_PORT_GCC_DIR "${CMAKE_C_COMPILER}" DIRECTORY) -get_filename_component(PORT_HOST_STRING_H "${_PORT_GCC_DIR}/../include/string.h" ABSOLUTE) +if(APPLE) + execute_process( + COMMAND xcrun --show-sdk-path + OUTPUT_VARIABLE _PORT_MACOS_SDK OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(NOT _PORT_MACOS_SDK) + message(FATAL_ERROR "Cannot locate the macOS SDK; install the Xcode Command Line Tools") + endif() + set(PORT_HOST_STRING_H "${_PORT_MACOS_SDK}/usr/include/string.h") +else() + get_filename_component(PORT_HOST_STRING_H "${_PORT_GCC_DIR}/../include/string.h" ABSOLUTE) +endif() configure_file( "${CMAKE_SOURCE_DIR}/port/include/hoststring.h.in" "${CMAKE_BINARY_DIR}/port/include/hoststring.h" @ONLY) @@ -172,7 +198,11 @@ configure_file( # the include path and shadows the system header; libstdc++'s /gthr # headers in the fast3d C++ TUs need the real (cpu_set_t, # sched_yield). Both MinGW and Linux keep at bin/../include. -get_filename_component(PORT_HOST_SCHED_H "${_PORT_GCC_DIR}/../include/sched.h" ABSOLUTE) +if(APPLE) + set(PORT_HOST_SCHED_H "${_PORT_MACOS_SDK}/usr/include/sched.h") +else() + get_filename_component(PORT_HOST_SCHED_H "${_PORT_GCC_DIR}/../include/sched.h" ABSOLUTE) +endif() configure_file( "${CMAKE_SOURCE_DIR}/port/include/hostsched.h.in" "${CMAKE_BINARY_DIR}/port/include/hostsched.h" @ONLY) @@ -182,7 +212,11 @@ configure_file( # one C++ TU (fast3d): MinGW's own sec_api/stdlib_s.h does `#include # `, which resolves to the N64 header via -I. The shim routes C++ # TUs to the host header by absolute path (same trick as hoststring.h). -get_filename_component(PORT_HOST_STDLIB_H "${_PORT_GCC_DIR}/../include/stdlib.h" ABSOLUTE) +if(APPLE) + set(PORT_HOST_STDLIB_H "${_PORT_MACOS_SDK}/usr/include/stdlib.h") +else() + get_filename_component(PORT_HOST_STDLIB_H "${_PORT_GCC_DIR}/../include/stdlib.h" ABSOLUTE) +endif() configure_file( "${CMAKE_SOURCE_DIR}/port/include/hoststdlib.h.in" "${CMAKE_BINARY_DIR}/port/include/hoststdlib.h" @ONLY) @@ -328,6 +362,13 @@ elseif(UNIX AND NOT APPLE) # relocations are fine (unlike a 0x140000000 base, which needs mcmodel=large). # This is the ELF analogue of the Windows build's fixed 0x140000000 base. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x20000000") +elseif(APPLE) + # Mach-O executables normally reserve [0, 4 GiB) as __PAGEZERO. The port + # needs fixed mappings at 0x10000000 (ROM), 0x70000000 (s32-safe DRAM), and + # 0x80000000 (KSEG0 mirror), so retain null/low-pointer protection only up + # while leaving ample space before the first required mapping. + set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -Wl,-pagezero_size,0x10000") endif() # port/shim first: PC-only shadow headers (bondtypes.h, bondconstants.h, @@ -342,6 +383,7 @@ include_directories( "${CMAKE_SOURCE_DIR}/src/game" "${CMAKE_SOURCE_DIR}/src/libultra" "${CMAKE_SOURCE_DIR}/src/libultra/audio" + "${CMAKE_SOURCE_DIR}/src/libultra/gu" "${CMAKE_SOURCE_DIR}/port/include" "${CMAKE_BINARY_DIR}/port/include" ) @@ -395,6 +437,8 @@ if(NOT EXTRA_LIBRARIES) set(EXTRA_LIBRARIES dbghelp -static-libstdc++ pthread) elseif(WIN32) set(EXTRA_LIBRARIES dbghelp -static-libstdc++) + elseif(APPLE) + set(EXTRA_LIBRARIES stdc++ m pthread) else() set(EXTRA_LIBRARIES stdc++ m dl pthread) endif() @@ -414,14 +458,41 @@ file(GLOB_RECURSE SRC_FAST3D "${CMAKE_SOURCE_DIR}/port/fast3d/*.cpp" # scripts/gen_romassets.py). Defines every obseg/ramrom/music label and the # ge007.ld segment markers as 0x10000000 + rom_offset, so `&symbol` / romCopy() # work once romdata.c maps the .z64 at 0x10000000. -set(SRC_ROMASSETS "${CMAKE_SOURCE_DIR}/port/src/romassets_${ASSET_REGION}.s") -if(NOT EXISTS ${SRC_ROMASSETS}) - message(FATAL_ERROR "Missing ${SRC_ROMASSETS} — run: python3 scripts/gen_romassets.py ${ASSET_REGION}") +set(SRC_ROMASSETS_INPUT "${CMAKE_SOURCE_DIR}/port/src/romassets_${ASSET_REGION}.s") +if(NOT EXISTS ${SRC_ROMASSETS_INPUT}) + message(FATAL_ERROR "Missing ${SRC_ROMASSETS_INPUT} — run: python3 scripts/gen_romassets.py ${ASSET_REGION}") endif() # Absolute symbols inside the reserved N64-DRAM region (port/src/dram.c): # cfb_16 (replaces src/cfb.c) and _bssSegmentEnd (mempool start marker). -set(SRC_DRAM_SYMS "${CMAKE_SOURCE_DIR}/port/src/dram_syms.s") +set(SRC_DRAM_SYMS_INPUT "${CMAKE_SOURCE_DIR}/port/src/dram_syms.s") + +if(APPLE) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + set(SRC_ROMASSETS "${CMAKE_BINARY_DIR}/port/src/romassets_${ASSET_REGION}_macho.s") + set(SRC_DRAM_SYMS "${CMAKE_BINARY_DIR}/port/src/dram_syms_macho.s") + add_custom_command( + OUTPUT "${SRC_ROMASSETS}" + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/scripts/gen_macho_syms.py" + "${SRC_ROMASSETS_INPUT}" "${SRC_ROMASSETS}" + DEPENDS "${SRC_ROMASSETS_INPUT}" + "${CMAKE_SOURCE_DIR}/scripts/gen_macho_syms.py" + VERBATIM) + add_custom_command( + OUTPUT "${SRC_DRAM_SYMS}" + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/scripts/gen_macho_syms.py" + "${SRC_DRAM_SYMS_INPUT}" "${SRC_DRAM_SYMS}" + DEPENDS "${SRC_DRAM_SYMS_INPUT}" + "${CMAKE_SOURCE_DIR}/scripts/gen_macho_syms.py" + VERBATIM) + set_source_files_properties(${SRC_ROMASSETS} ${SRC_DRAM_SYMS} + PROPERTIES GENERATED TRUE) +else() + set(SRC_ROMASSETS "${SRC_ROMASSETS_INPUT}") + set(SRC_DRAM_SYMS "${SRC_DRAM_SYMS_INPUT}") +endif() # Game code — compiled unmodified. file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c") @@ -452,6 +523,39 @@ file(GLOB SRC_LIBULTRARE_AUDIO "${CMAKE_SOURCE_DIR}/src/libultrare/audio/*.c") # gu matrix helpers. file(GLOB SRC_GU "${CMAKE_SOURCE_DIR}/src/libultra/gu/*.c") +# GCC for Darwin crashes while lowering the decomp's ELF-style +# `#pragma weak alias = target` directives. Compile generated copies with +# those directives removed, then provide their aliases in Mach-O assembly. +set(SRC_MACHO_ALIASES "") +if(APPLE) + set(_MACHO_GENERATED_SOURCES "") + set(_MACHO_WEAK_INPUTS + "${CMAKE_SOURCE_DIR}/src/game/lv.c" + "${CMAKE_SOURCE_DIR}/src/game/objective_status.c" + "${CMAKE_SOURCE_DIR}/src/game/spectrum.c" + "${CMAKE_SOURCE_DIR}/src/libultra/gu/sinf.c" + "${CMAKE_SOURCE_DIR}/src/libultra/gu/cosf.c") + foreach(_INPUT IN LISTS _MACHO_WEAK_INPUTS) + get_filename_component(_NAME "${_INPUT}" NAME) + set(_OUTPUT "${CMAKE_BINARY_DIR}/port/src/macho_${_NAME}") + add_custom_command( + OUTPUT "${_OUTPUT}" + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/scripts/strip_weak_pragmas.py" + "${_INPUT}" "${_OUTPUT}" + DEPENDS "${_INPUT}" "${CMAKE_SOURCE_DIR}/scripts/strip_weak_pragmas.py" + VERBATIM) + list(APPEND SRC_MACHO_ALIASES "${_OUTPUT}") + list(APPEND _MACHO_GENERATED_SOURCES "${_OUTPUT}") + list(REMOVE_ITEM SRC_GAME "${_INPUT}") + list(REMOVE_ITEM SRC_GU "${_INPUT}") + endforeach() + list(APPEND SRC_MACHO_ALIASES + "${CMAKE_SOURCE_DIR}/port/src/macho_weak_aliases.s") + set_source_files_properties(${_MACHO_GENERATED_SOURCES} + PROPERTIES GENERATED TRUE) +endif() + # VI mode table (DATA, not a driver): fr.c references osViModeTable ~15x. # This is the only libultra/io file we compile — the rest are hardware # drivers that get shimmed in port/src/libultra.c. @@ -569,6 +673,7 @@ set(SRC ${SRC_LIBULTRARE_LIBC} ${SRC_ASSETS} ${SRC_GU} + ${SRC_MACHO_ALIASES} ${SRC_ENGINE} ) diff --git a/README.md b/README.md index bfa84066..c6a627c8 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,8 @@ is right the large majority of the time now, at most a small drift when off; fil by default (F10 → *All unlocked* opens every level, 007 mode and the full cheat menu); F10 in-game options overlay (video, input, gameplay, HUD, graphics, audio; frame cap, MSAA, filtering, FOV, sensitivity, key rebinding, -crosshair, vibration); Windows and -Linux, including Steam Deck. +crosshair, vibration); Windows, Linux and macOS (Intel and Apple Silicon), +including Steam Deck. **Known issues:** @@ -152,8 +152,8 @@ Linux, including Steam Deck. original progression you need to preserve. - Assorted further cosmetic defects are tracked in [`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md). -- No macOS or ARM support. Keyboard/mouse rebinding shipped in v0.4.0; - controller-button rebinding is not supported yet. +- Keyboard/mouse rebinding shipped in v0.4.0; controller-button rebinding is + not supported yet. No Windows-on-ARM support. Root causes and fix status for every item: the [release notes](https://github.com/jkdansereau/goldeneye-pc-port/releases) and the finding log in [`docs/dev/findings.md`](docs/dev/findings.md). @@ -198,7 +198,7 @@ directionally, on the way to v1.0: [`docs/dev/findings.md`](docs/dev/findings.md). - **PAL and JP ROM support** ([issue #85](https://github.com/jkdansereau/goldeneye-pc-port/issues/85)); NTSC-U is the only supported region today. - **Controller (pad) button rebinding UI** (keyboard/mouse rebinding shipped - in v0.4.0), and macOS/ARM builds. + in v0.4.0). - **LAN multiplayer**: reviving GoldenEye's original split-screen/deathmatch netplay across multiple PCs on a local network. Genuinely under consideration, but early and not started; no ETA. diff --git a/docs/building.md b/docs/building.md index 5fdf93b4..36e30d36 100644 --- a/docs/building.md +++ b/docs/building.md @@ -1,6 +1,6 @@ --- title: Building -description: Full build and asset-extraction guide for the GoldenEye 007 PC port, covering Windows (MSYS2) and Linux. +description: Full build and asset-extraction guide for the GoldenEye 007 PC port on Windows, Linux, and macOS. --- ## Building the PC port @@ -27,21 +27,29 @@ for accepted versions and hashes. ### Port build -> **The Windows (MSYS2 MINGW64) path is the primary one.** The Linux build is -> compiled by CI on every push and ships in the release bundle (also -> playtested on Steam Deck hardware); the macOS column is best-effort -> guidance and has never been built or run there. Expect to fix build breaks -> yourself on untested platforms. +> **The Windows (MSYS2 MINGW64) path is the primary one.** Linux is compiled +> by CI and ships in the release bundle. Native Intel macOS builds are also +> supported; Apple Silicon remains untested. | Need | Windows (MSYS2 MINGW64) | Debian/Ubuntu | macOS (Homebrew) | |------|------------------------|---------------|------------------| -| toolchain | `mingw-w64-x86_64-toolchain` | `build-essential` | Xcode CLT / `gcc` | +| toolchain | `mingw-w64-x86_64-toolchain` | `build-essential` | Xcode CLT + Homebrew `gcc` | | CMake | `mingw-w64-x86_64-cmake` | `cmake` | `cmake` | | SDL2 | `mingw-w64-x86_64-SDL2` | `libsdl2-dev` | `sdl2` | | zlib | `mingw-w64-x86_64-zlib` | `zlib1g-dev` | `zlib` | | OpenGL | (in the toolchain) | `libgl1-mesa-dev` | (system) | | Python 3 | `mingw-w64-x86_64-python` | `python3` | `python3` | +On macOS, install the build dependencies with: + +```sh +brew install cmake gcc sdl2 zlib python3 +``` + +The project requires real GNU GCC for the decomp's Plan 9 struct extensions. +Apple's `/usr/bin/gcc` is Clang and is not compatible. CMake automatically +selects Homebrew's versioned `gcc-N`/`g++-N` executables. + ### Asset extraction (decompilation toolchain) The extraction scripts need `binutils-mips-linux-gnu` (or an equivalent MIPS diff --git a/docs/porting-notes.md b/docs/porting-notes.md index 05598fdd..f43ae5ce 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -580,6 +580,30 @@ through a converter or a runtime bswap fixup reads scrambled. ## C2. Port-layer / SDL shims +- **Darwin assembly uses Mach-O symbol spelling.** GAS absolute symbols that + are `.global foo` / `.set foo, address` on ELF/PE must become `.globl _foo` + / `.set _foo, address`; Darwin also rejects the ELF `.section .data` + spelling used by these symbol-only files. Generate transformed build-tree + copies rather than changing the cross-platform source files (D294). +- **Darwin GCC cannot lower ELF-style `#pragma weak alias = target`.** It can + ICE in `assemble_alias`; compile generated copies with those pragmas removed + and provide Mach-O indirect symbols (`.set _alias, _target`) separately + (D294). +- **Mach-O's default `__PAGEZERO` reserves the entire low 4 GiB.** A build + can compile and link successfully yet fail every fixed N64 mapping at + runtime (`0x10000000`, `0x70000000`, `0x80000000`). Link with + `-Wl,-pagezero_size,0x10000` so null-page pointers remain protected while + the cartridge and DRAM ranges are available (D294). +- **Darwin has no `MAP_FIXED_NOREPLACE`; a fixed-address hint can relocate.** + Once `__PAGEZERO` is deliberately bounded below the cart range, use + `MAP_FIXED` for the `0x10000000` cartridge mapping. Retaining the advisory + hint silently falls back to a heap ROM and later crashes on direct absolute + ROM-symbol reads (D294). +- **AppKit event polling is main-thread-only.** This port has both a host-main + `videoPumpEvents()` loop and fast3d's render-thread event pump. The latter + must be disabled on macOS or `SDL_PollEvent` raises + `NSInternalInconsistencyException`; the main-thread loop already handles + the complete event set (D294). - `#include ` in a port `.c`/`.h` that also sees `` breaks: `OSContStatus`/`OSContPad` have a `u8 errno;` field vs errno.h's macro. libultra.c wraps the include in `#pragma push_macro("errno")` / diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index e32611e5..d4dfa525 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -311,6 +311,12 @@ static void gfx_sdl_get_dimensions(uint32_t* width, uint32_t* height, int32_t* p } static void gfx_sdl_handle_events(void) { +#if defined(__APPLE__) + /* AppKit requires event polling on the process main thread. The host + * loop in videoPumpEvents owns the complete SDL queue, so the scheduler + * render thread must not call SDL_PollEvent on macOS. */ + return; +#else SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { @@ -361,6 +367,7 @@ static void gfx_sdl_handle_events(void) { break; } } +#endif } /* The window/context are created on the host main thread, but the game's diff --git a/port/include/pc_protos.h b/port/include/pc_protos.h index 2bccf078..06e1f0f7 100644 --- a/port/include/pc_protos.h +++ b/port/include/pc_protos.h @@ -28,9 +28,20 @@ * "using typedef-name after 'struct'"), and no C++ TU has an implicit * declaration needing a fix here. C++ TUs keep their pre-D38 header exposure. */ + #ifndef _PC_PROTOS_H_ #define _PC_PROTOS_H_ +#if defined(__APPLE__) +/* Darwin SDK headers expose these libc calls as fortify function-like + * macros. This legacy declaration list needs their actual symbol names. */ +#undef memcpy +#undef snprintf +#undef strcat +#undef strcpy +#undef strncpy +#endif + #if defined(PORT) && defined(__x86_64__) && !defined(__cplusplus) #include /* u8..s32, f32, size_t (host on PC) */ diff --git a/port/shim/PR/os.h b/port/shim/PR/os.h index 5378e3ec..56d01a6c 100644 --- a/port/shim/PR/os.h +++ b/port/shim/PR/os.h @@ -28,7 +28,19 @@ #define _PORT_SHIM_OS_H_ #if defined(PORT) +# if defined(__APPLE__) +/* Darwin exposes errno and sprintf as function-like macros. Suppress them + * while parsing the N64 API's errno fields and sprintf declaration. */ +# pragma push_macro("errno") +# pragma push_macro("sprintf") +# undef errno +# undef sprintf +# endif # include "include/PR/os.h" +# if defined(__APPLE__) +# pragma pop_macro("sprintf") +# pragma pop_macro("errno") +# endif # undef OS_K0_TO_PHYSICAL # define OS_K0_TO_PHYSICAL(x) ((u32)((char *)(x) - 0x70000000)) diff --git a/port/src/crash.c b/port/src/crash.c index 9cfab783..136efec3 100644 --- a/port/src/crash.c +++ b/port/src/crash.c @@ -11,6 +11,12 @@ #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif +#if defined(__APPLE__) && !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 700 +#endif +#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE) +#define _DARWIN_C_SOURCE 1 +#endif #include #include @@ -303,7 +309,7 @@ static LONG __stdcall crashHandler(PEXCEPTION_POINTERS exinfo) return EXCEPTION_CONTINUE_EXECUTION; } -#elif defined(PLATFORM_LINUX) +#elif defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS) #include #include @@ -341,7 +347,7 @@ static void crashStackTrace(char *msg, int sig, void *pc, ucontext_t *ucontext, if (siginfo && (sig == SIGSEGV || sig == SIGBUS)) { CRASH_MSG("FAULT ADDR: %p\n", (void *)siginfo->si_addr); } -#if defined(PLATFORM_X86_64) +#if defined(PLATFORM_LINUX) && defined(PLATFORM_X86_64) if (ucontext) { CRASH_MSG("REGS: Rax=%p Rcx=%p Rdx=%p Rsi=%p Rdi=%p R8=%p R9=%p R10=%p R11=%p\n", (void *)ucontext->uc_mcontext.gregs[REG_RAX], @@ -410,10 +416,12 @@ static void crashHandler(int sig, siginfo_t *siginfo, void *ctx) ucontext_t *ucontext = ctx ? (ucontext_t *)ctx : NULL; void *pc = NULL; if (ucontext) { -#ifdef PLATFORM_X86 +#if defined(PLATFORM_LINUX) && defined(PLATFORM_X86) pc = (void *)ucontext->uc_mcontext.gregs[REG_EIP]; -#elif defined(PLATFORM_X86_64) +#elif defined(PLATFORM_LINUX) && defined(PLATFORM_X86_64) pc = (void *)ucontext->uc_mcontext.gregs[REG_RIP]; +#elif defined(PLATFORM_MACOS) && defined(PLATFORM_X86_64) + pc = (void *)ucontext->uc_mcontext->__ss.__rip; #endif } @@ -470,7 +478,7 @@ void crashDumpThreads(const unsigned long *tids, const char **names, int count) } } -#endif /* PLATFORM_WINDOWS / PLATFORM_LINUX */ +#endif /* PLATFORM_WINDOWS / POSIX */ int g_CrashEnabled = 0; @@ -480,7 +488,7 @@ void crashInit(void) SetErrorMode(SEM_FAILCRITICALERRORS); prevExFilter = SetUnhandledExceptionFilter(crashHandler); g_CrashEnabled = 1; -#elif defined(PLATFORM_LINUX) +#elif defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS) struct sigaction sigact = { 0 }; sigact.sa_flags = SA_SIGINFO | SA_ONSTACK; sigact.sa_sigaction = crashHandler; @@ -501,7 +509,7 @@ void crashShutdown(void) if (prevExFilter) { SetUnhandledExceptionFilter(prevExFilter); } -#elif defined(PLATFORM_LINUX) +#elif defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS) sigaction(SIGSEGV, &prevSigAction, NULL); sigaction(SIGABRT, &prevSigAction, NULL); sigaction(SIGBUS, &prevSigAction, NULL); diff --git a/port/src/dram.c b/port/src/dram.c index f0a5f4f4..d0896d67 100644 --- a/port/src/dram.c +++ b/port/src/dram.c @@ -53,7 +53,10 @@ #include #else #define _GNU_SOURCE /* memfd_create — must precede all system headers */ +#include +#include #include +#include #include #endif @@ -101,10 +104,20 @@ void *dramReserve(void) CloseHandle(hSec); return v1; #else - /* memfd + two MAP_SHARED mmaps = one backing store, two views. */ + /* One shared file descriptor + two MAP_SHARED mmaps = one backing store, + * two views. Linux can keep it fully anonymous with memfd; Darwin uses a + * POSIX shared-memory object and unlinks its name immediately. */ +#if defined(PLATFORM_MACOS) + char shmName[64]; + snprintf(shmName, sizeof(shmName), "/ge007_dram_%ld", (long)getpid()); + int fd = shm_open(shmName, O_RDWR | O_CREAT, 0600); + if (fd >= 0) + shm_unlink(shmName); +#else int fd = memfd_create("ge007_dram", 0); +#endif if (fd < 0) { - sysFatalError("dram: memfd_create failed"); + sysFatalError("dram: could not create shared backing store"); } if (ftruncate(fd, DRAM_SIZE) != 0) { sysFatalError("dram: ftruncate failed"); diff --git a/port/src/libultra.c b/port/src/libultra.c index 6c29fe5f..b4b38bc4 100644 --- a/port/src/libultra.c +++ b/port/src/libultra.c @@ -977,7 +977,7 @@ static void piServiceDma(s32 direction, u32 srcPA, void *dstVA, u32 size) char win[1200] = ""; char *wp = win; for (int i = 0; i < 32; i++) { - wp += snprintf(wp, win + sizeof(win) - (wp - win), + wp += snprintf(wp, sizeof(win) - (size_t)(wp - win), " %p", (void *)sp[i]); } #if defined(PLATFORM_WINDOWS) diff --git a/port/src/macho_weak_aliases.s b/port/src/macho_weak_aliases.s new file mode 100644 index 00000000..f68f862b --- /dev/null +++ b/port/src/macho_weak_aliases.s @@ -0,0 +1,25 @@ +/* Mach-O equivalents of the decomp's `#pragma weak alias = target`. */ + +.globl _g_DebugPortalsInputBufferSource1 +.set _g_DebugPortalsInputBufferSource1, _g_DebugPortalsInputBuffer1 +.globl _g_DebugPortalsInputBufferSource2 +.set _g_DebugPortalsInputBufferSource2, _g_DebugPortalsInputBuffer2 +.globl _g_DebugPortalsInputBufferSource3 +.set _g_DebugPortalsInputBufferSource3, _g_DebugPortalsInputBuffer3 +.globl _g_DebugPortalsInputBufferSource4 +.set _g_DebugPortalsInputBufferSource4, _g_DebugPortalsInputBuffer4 + +.globl _objectiveGetStatus_WEAK +.set _objectiveGetStatus_WEAK, _get_status_of_objective + +.globl _spec_keyboard_row_caps_z_x_c_v +.set _spec_keyboard_row_caps_z_x_c_v, _spec_keyboard_buffer + +.globl _fsin +.set _fsin, ___sinf +.globl _sinf +.set _sinf, ___sinf +.globl _fcos +.set _fcos, ___cosf +.globl _cosf +.set _cosf, ___cosf diff --git a/port/src/romdata.c b/port/src/romdata.c index d5012af6..785f2d35 100644 --- a/port/src/romdata.c +++ b/port/src/romdata.c @@ -36,6 +36,7 @@ extern int snprintf(char *str, size_t maxsize, const char *format, ...); /* POSIX: mmap the ROM at the fixed cart address (mirrors the VirtualAlloc * path). is a host header the decomp include path does not * shadow. */ + #include #include #endif @@ -382,9 +383,15 @@ int romdataInit(void) int flags = MAP_PRIVATE | MAP_ANONYMOUS; #ifdef MAP_FIXED_NOREPLACE flags |= MAP_FIXED_NOREPLACE; +#elif defined(PLATFORM_MACOS) + /* __PAGEZERO ends at CART_BASE in the macOS build, so this range + * is deliberately vacant. Darwin has no MAP_FIXED_NOREPLACE and + * otherwise treats CART_BASE only as a hint, relocating it. */ + flags |= MAP_FIXED; #endif void *at = mmap((void *)(uintptr_t)CART_BASE, maplen, PROT_READ | PROT_WRITE, flags, -1, 0); + int mapErrno = errno; if (at != MAP_FAILED && at == (void *)(uintptr_t)CART_BASE) { mappedLen = maplen; return romdataFinishCartMap(tok, img, sideTotal, cgTotal); @@ -392,8 +399,8 @@ int romdataInit(void) if (at != MAP_FAILED) munmap(at, maplen); sysLogPrintf(LOG_WARNING, "romdataInit: could not map 0x%08X " - "(ASLR/kernel refused the fixed address); using heap " - "copy — direct ROM reads will fail", CART_BASE); + "(mmap result=%p errno=%d); using heap copy — direct " + "ROM reads will fail", CART_BASE, at, mapErrno); #endif } diff --git a/port/src/video.c b/port/src/video.c index 1739110c..c8d48c64 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -13,6 +13,9 @@ #include #include +/* Prevent GCC's implicit declaration warning */ +extern void exit(int status); + #if defined(_WIN32) #include #define GE_MKDIR(p) _mkdir(p) @@ -794,8 +797,9 @@ void videoStartFrame(void) * CREATED the window pumps them — and every game thread can be blocked on a * message queue at any time. So the host main thread (which created the * window in videoInit) must keep pumping; otherwise the window goes - * "Not Responding" and ESC/close never arrive. fast3d's own handle_events - * (which runs during rendering) remains as a backstop. + * "Not Responding" and ESC/close never arrive. On macOS AppKit strictly + * requires this main-thread pump, so fast3d's render-thread event handler is + * disabled there; on other platforms it remains as a backstop. */ void videoPumpEvents(void) { diff --git a/scripts/gen_macho_syms.py b/scripts/gen_macho_syms.py new file mode 100644 index 00000000..c2e528e8 --- /dev/null +++ b/scripts/gen_macho_syms.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Convert ELF/PE GAS absolute symbols to Mach-O spelling.""" + +import pathlib +import re +import sys + + +def main() -> int: + if len(sys.argv) != 3: + print(f"usage: {sys.argv[0]} INPUT OUTPUT", file=sys.stderr) + return 2 + + source = pathlib.Path(sys.argv[1]) + output = pathlib.Path(sys.argv[2]) + lines = [] + + for line in source.read_text(encoding="utf-8").splitlines(): + if re.match(r"^\s*\.section\s+\.data\s*$", line): + continue + + match = re.match(r"^(\s*)\.global\s+([A-Za-z_]\w*)\s*$", line) + if match: + lines.append(f"{match.group(1)}.globl _{match.group(2)}") + continue + + match = re.match( + r"^(\s*)\.set\s+([A-Za-z_]\w*)(\s*,.*)$", line + ) + if match: + lines.append( + f"{match.group(1)}.set _{match.group(2)}{match.group(3)}" + ) + continue + + lines.append(line) + + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text("\n".join(lines) + "\n", encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/strip_weak_pragmas.py b/scripts/strip_weak_pragmas.py new file mode 100644 index 00000000..335a1631 --- /dev/null +++ b/scripts/strip_weak_pragmas.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Remove ELF-style weak aliases from a C source copied for Mach-O.""" + +import pathlib +import re +import sys + + +def main() -> int: + if len(sys.argv) != 3: + print(f"usage: {sys.argv[0]} INPUT OUTPUT", file=sys.stderr) + return 2 + + source = pathlib.Path(sys.argv[1]) + output = pathlib.Path(sys.argv[2]) + text = source.read_text(encoding="utf-8") + text = re.sub(r"^[ \t]*#pragma[ \t]+weak[^\n]*\n", "", text, + flags=re.MULTILINE) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(text, encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())