forked from Ports/goldeneye-pc-port
Implement macOS (Intel) support
This commit is contained in:
1 parent
768ac8d837
commit
62b2105458
16 files changed
+376
-35
No files matched your search
+114
-9
@@ -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 <thread>/gthr
|
||||
# headers in the fast3d C++ TUs need the real <sched.h> (cpu_set_t,
|
||||
# sched_yield). Both MinGW and Linux keep <sched.h> 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
|
||||
# <stdlib.h>`, 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}
|
||||
)
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ listed plainly there.
|
||||
**v0.2.2 - playable, with known rough edges.** The full single-player
|
||||
campaign is completable end to end (all 21 missions, Agent difficulty,
|
||||
playtested), at a steady 60 fps; all 21 solo missions load, render and run
|
||||
crash-free, verified on Windows, Linux and real Steam Deck hardware. Feedback
|
||||
is very welcome.
|
||||
crash-free, verified on Windows, Linux and real Steam Deck hardware. Native
|
||||
Intel macOS builds are supported. Feedback is very welcome.
|
||||
|
||||
**Working:** boot sequence and front end (menu → mission select → briefing →
|
||||
start), front-end menu navigation on the left stick to match the F10 overlay
|
||||
@@ -87,7 +87,7 @@ dual-stick controller layout; file-backed saves; faithful N64 progression by def
|
||||
(F10 → *All unlocked* opens every level, 007 mode and the full cheat menu); F10 in-game
|
||||
options overlay (resolution, frame cap, MSAA, filtering, FOV, sensitivity,
|
||||
quit to desktop);
|
||||
Windows and Linux.
|
||||
Windows, Linux, and Intel macOS.
|
||||
|
||||
**Known issues:**
|
||||
|
||||
@@ -130,7 +130,7 @@ Windows and Linux.
|
||||
capture full faulting registers in `ge007.crash.log`.
|
||||
- Assorted further cosmetic defects are tracked in
|
||||
[`docs/dev/GRAPHICS-BACKLOG.md`](docs/dev/GRAPHICS-BACKLOG.md).
|
||||
- No macOS or ARM support; no controller rebinding UI.
|
||||
- No ARM support; no controller rebinding UI.
|
||||
|
||||
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).
|
||||
@@ -173,7 +173,7 @@ directionally, on the way to v1.0:
|
||||
- **PAL and JP ROM support** ([issue #85](https://github.com/jkdansereau/goldeneye-pc-port/issues/85)); NTSC-U is the only supported region today.
|
||||
- **Real widescreen** (properly expanding the field of view at 16:9, rather
|
||||
than today's 4:3-stretch).
|
||||
- **Controller rebinding UI**, and macOS/ARM builds.
|
||||
- **Controller rebinding UI**, and ARM builds.
|
||||
- **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.
|
||||
|
||||
+15
-7
@@ -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
|
||||
|
||||
@@ -561,6 +561,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D258 | **PAL/JP asset conversion is broken at the source-data level — `scripts/filelist.e.csv` and `filelist.j.csv` carry naming defects, so `gen_romassets.py e/j` (and…** — full `## D258` entry at file tail | OPEN — repair path verified, not applied (out of v0.2.0 scope). |
|
||||
| D285 | **Steam Deck SIGSEGV during D255 live-testing = SEPARATE bug: the `sndHandleEvent` preempt-scan walks the active-sound list unlocked (M-140; fixed M-141).** — full `## D285` entry at file tail | **FIXED (M-141, `src/snd.c`)** — `osSetIntMask` lock added around the preempt-scan, matching `sndSetupSound`'s existing unconditional (no `#ifdef PORT`) pattern in the same file; build green, `-level_09`/`-level_20` unregressed (1400+ frames, no crash, no D152 lock-stuck warnings). Not yet gdb/live-confirmed against a real reproduced crash — see M-141 body. Do not conflate with D255 — different thread, different subsystem. |
|
||||
| D293 | **QoL ask: no discoverable way to quit the game from the UI (M-141, Steam Deck user playtest — "couldn't see a way to exit the game. Maybe build this into the…** — full `## D293` entry at file tail | FIXED (M-141) — "Quit to desktop" row added to the F10 overlay; build-verified Win+Linux, not yet live-confirmed.… |
|
||||
| D294 | **Native Intel macOS build support.** Apple Clang lacks the required Plan 9 struct extensions; Darwin also needs SDK header discovery, Mach-O absolute/weak-alias symbol spelling, POSIX shared-memory backing instead of Linux `memfd_create`, and Darwin signal-context handling. | **FIXED** — CMake selects versioned Homebrew GNU GCC, generates Mach-O assembly/source adapters in the build tree, and the port layer now handles Darwin libc macros, shared memory, and crash diagnostics. Fresh `build-pc.sh ntsc-final` build and `--version` smoke test pass on x86_64 macOS 26.6.2. |
|
||||
|
||||
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
||||
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
|
||||
@@ -10149,3 +10150,50 @@ do {
|
||||
## D293 — QoL ask (M-141, Steam Deck user playtest): no discoverable way to quit the game from the UI on a controller-only setup
|
||||
**Context.** Deck/controller-only setups had only window-close/Alt+F4 to quit; no in-UI affordance.
|
||||
**FIXED.** Added a `ROW_ACTION` row kind (not config-backed, same pattern as the existing `__Resolution` row) to the F10 options overlay ("Quit to desktop") that triggers the identical `configSave()`+`exit(0)` path `SDL_QUIT` already uses. Build-verified on Windows and Linux; `verify.sh dam` PASS, no regression.
|
||||
|
||||
## D294 — Native Intel macOS build support
|
||||
**Context.** The CMake file contained nominal Apple branches, but had never
|
||||
completed a Darwin build. `/usr/bin/gcc` is Apple Clang and rejects the
|
||||
decomp's required `-fplan9-extensions`; host-header paths assumed MinGW/Linux;
|
||||
ELF-style absolute symbols and weak aliases are invalid in Mach-O; the DRAM
|
||||
mirror used Linux-only `memfd_create`; and crash diagnostics only implemented
|
||||
Windows/Linux.
|
||||
|
||||
**FIXED.** CMake now selects Homebrew's versioned GNU `gcc-N`/`g++-N`, locates
|
||||
libc headers through the macOS SDK, omits Linux-only `-ldl`, and generates
|
||||
Mach-O-spelled copies of the ROM/DRAM absolute symbols. Five sources containing
|
||||
ELF weak-alias pragmas are copied into the build tree with only those pragmas
|
||||
removed; a Mach-O assembly file supplies equivalent indirect symbols. Darwin
|
||||
fortify/`errno` macros are suppressed while legacy declarations parse.
|
||||
`dramReserve` uses an immediately-unlinked POSIX shared-memory object, and the
|
||||
POSIX crash handler supports Darwin's x86-64 signal context.
|
||||
|
||||
**Verified.** On x86_64 macOS 26.6.2 with Homebrew GCC 16.2.0, both an
|
||||
incremental native build and a fresh build through `build-pc.sh` completed at
|
||||
100%. `file` identifies the output as a Mach-O 64-bit x86_64 executable,
|
||||
`otool -L` resolves SDL2, zlib, libstdc++, Cocoa, and OpenGL, and
|
||||
`ge007.x86_64 --version` reports `x86_64-osx`. Runtime gameplay was not
|
||||
exercised because ROM-derived user data is not part of build verification.
|
||||
Apple Silicon remains unsupported/untested.
|
||||
|
||||
**Runtime follow-up.** The first real launch exposed a link-time/runtime gap:
|
||||
Darwin's default Mach-O `__PAGEZERO` segment reserved `[0, 0x100000000)`.
|
||||
Consequently both the cartridge mapping at `0x10000000` and the mirrored DRAM
|
||||
mapping at `0x70000000`/`0x80000000` failed despite a successful build.
|
||||
The Apple link now sets `-pagezero_size 0x10000`, preserving null-page
|
||||
protection while leaving all three N64 address windows available. Darwin also
|
||||
lacks Linux's `MAP_FIXED_NOREPLACE`; the ROM mapping
|
||||
therefore uses `MAP_FIXED` after the linker has guaranteed the cartridge range
|
||||
is vacant. Without it, `mmap` relocated the cartridge despite the now-free
|
||||
range, boot fell back to a heap copy, and direct absolute ROM-symbol reads
|
||||
crashed during level initialization. With fixed ROM mapping working, the next
|
||||
launch reached rendering and exposed AppKit's main-thread-only event rule:
|
||||
fast3d polled SDL from the scheduler/render thread even though the port's host
|
||||
main loop already owns a complete event pump. The fast3d pump is now disabled
|
||||
only on macOS; `videoPumpEvents()` continues to process input/window events.
|
||||
After generating all three required sidecar sets from the user's NTSC ROM,
|
||||
`-level_09` ran for more than 1,500 rendered frames with the ROM fixed at
|
||||
`0x10000000`, both DRAM views active, all 512 model and 73 stage sidecars
|
||||
patched, and no crash. The missing packed-vertex OpenGL entry-point messages
|
||||
are non-fatal capability noise from the GL loader; rendering proceeds on the
|
||||
created macOS OpenGL 4.1 core context.
|
||||
@@ -485,6 +485,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 <PR/os.h>` in a port `.c`/`.h` that also sees `<errno.h>`
|
||||
breaks: `OSContStatus`/`OSContPad` have a `u8 errno;` field vs errno.h's
|
||||
macro. libultra.c wraps the include in `#pragma push_macro("errno")` /
|
||||
|
||||
@@ -309,6 +309,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) {
|
||||
@@ -357,6 +363,7 @@ static void gfx_sdl_handle_events(void) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The window/context are created on the host main thread, but the game's
|
||||
|
||||
@@ -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 <PR/ultratypes.h> /* u8..s32, f32, size_t (host on PC) */
|
||||
|
||||
@@ -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))
|
||||
|
||||
+15
-7
@@ -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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@@ -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 <ucontext.h>
|
||||
#include <signal.h>
|
||||
@@ -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);
|
||||
|
||||
+15
-2
@@ -53,7 +53,10 @@
|
||||
#include <windows.h>
|
||||
#else
|
||||
#define _GNU_SOURCE /* memfd_create — must precede all system headers */
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#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");
|
||||
|
||||
+1
-1
@@ -972,7 +972,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)
|
||||
|
||||
@@ -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
|
||||
+9
-2
@@ -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). <sys/mman.h> is a host header the decomp include path does not
|
||||
* shadow. */
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#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
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -12,6 +12,9 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Prevent GCC's implicit declaration warning */
|
||||
extern void exit(int status);
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#define GE_MKDIR(p) _mkdir(p)
|
||||
@@ -496,8 +499,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)
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user