forked from Ports/goldeneye-pc-port
macos: packaging, sweep tooling, and two runtime fixes (D299, D300)
- tools_pc/bundle-mac.sh: a signed, double-clickable GoldenEye.app. Homebrew's libstdc++/libgcc_s and SDL2 are not system libraries, so they are bundled and rewritten to @executable_path/@loader_path. Note sdl2-compat (Homebrew's `sdl2`) finds its SDL3 backend through a RELATIVE rpath baked into the dylib, so SDL3 ships too and that rpath is repointed — copy it without that and sdl2-compat raises a modal error dialog from its initialiser and the app hangs before main(). prepare-assets/ goes in Contents/Resources/: codesign treats a directory under Contents/MacOS/ as nested code and fails the bundle. - tools_pc/level_sweep_mac.sh: per-level crash-log sweep, symbolicated with atos, with a rendered-pixel check so "runs but renders nothing" is caught. - D299: sndPlaySfx() rejected EVERY sound on arm64 — all in-level SFX were silent, while music played (separate player). Its guard predated the shifted window: `sp >= 0x400000000ULL` (16 GiB) is correct on x86_64 (DRAM at 0x70000000) and rejects everything on arm64 (DRAM at ~0x1000_707b_5740). Fixed with a portAddrIsInWindow() helper rather than absolute bounds. - D300: cleanupObjects() did `u32 *obj = (u32)g_CurrentSetup.propDefs`, truncating a host pointer to 32 bits. Crashed at stage unload — only reachable once a level actually ends. Verified: 21/21 on both the no-input and full-control sweeps; SFX voices allocate on every level.
This commit is contained in:
1 parent
d4cc71af0a
commit
498be832c2
18 files changed
+474
-43
No files matched your search
@@ -200,3 +200,10 @@ tools_pc/sweep-captures/capture-*/
|
||||
# referenced by no project tooling. Never commit.
|
||||
/Python/
|
||||
docs/media/*.mp4
|
||||
|
||||
# Debug traces and capture artifacts written by the engine at runtime
|
||||
# (GE_AUDIOTRACE, GE_MIXERTRACE, GE_AUDIODUMP, GE_PCDUMP, crash handler).
|
||||
# These are run output, never source -- see dev-process.md "keep PR noise low".
|
||||
*.log
|
||||
audiodump.raw
|
||||
.DS_Store
|
||||
+14
-3
@@ -63,6 +63,16 @@ endif()
|
||||
# (port/src/romassets_<region>.s, see scripts/gen_romassets.py).
|
||||
project(ge007 C CXX ASM)
|
||||
|
||||
# Host base of the N64 address window (see port/include/port_addr.h). Must be
|
||||
# set BEFORE add_definitions below. 0 on Windows/Linux; 16 TiB on arm64 macOS
|
||||
# (overridable with -DPORT_ADDR_BASE=... for tooling, e.g. an ASan build whose
|
||||
# shadow region overlaps 16 TiB).
|
||||
if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
|
||||
set(PORT_ADDR_BASE 0x100000000000 CACHE STRING "Host base of the N64 address window (arm64 macOS)")
|
||||
else()
|
||||
set(PORT_ADDR_BASE 0x0 CACHE STRING "Host base of the N64 address window")
|
||||
endif()
|
||||
|
||||
include(TargetArch)
|
||||
include(FindPkgConfig)
|
||||
|
||||
@@ -246,6 +256,7 @@ add_definitions(
|
||||
-D_LANGUAGE_C=1
|
||||
-DAVOID_UB=1
|
||||
-DPAL=${VERSION_IS_PAL}
|
||||
-DPORT_ADDR_BASE=${PORT_ADDR_BASE}
|
||||
${REGION_DEFS}
|
||||
)
|
||||
if(TARGET_IS_64BIT)
|
||||
@@ -271,7 +282,9 @@ add_compile_options(
|
||||
# runs the same way; the genuinely understood cases are still fixed properly
|
||||
# under #ifdef PORT (D189). A hardened Linux build would need the whole class
|
||||
# swept first — tracked in docs/dev/findings.md.
|
||||
add_compile_options(-fno-stack-protector)
|
||||
if(NOT GE_STACK_PROTECTOR_ALL)
|
||||
add_compile_options(-fno-stack-protector)
|
||||
endif()
|
||||
# Keep frame pointers: dbghelp's StackWalk64 (crash handler + thread dumps)
|
||||
# cannot read DWARF unwind info on MinGW, so the EBP chain is the only way to
|
||||
# get a real backtrace of a hung/crashed game thread.
|
||||
@@ -369,7 +382,6 @@ elseif(APPLE)
|
||||
# / 0x80000000) are impossible. The whole N64 address space is instead
|
||||
# shifted to PORT_ADDR_BASE and reserved PROT_NONE by portAddrInit()
|
||||
# (port/include/port_addr.h, docs/dev/MACOS-ARM64-PLAN.md).
|
||||
set(PORT_ADDR_BASE 0x100000000000)
|
||||
else()
|
||||
# Intel: Mach-O normally reserves [0, 4 GiB) as __PAGEZERO. The port needs
|
||||
# fixed mappings at 0x10000000 (ROM), 0x70000000 (s32-safe DRAM), and
|
||||
@@ -377,7 +389,6 @@ elseif(APPLE)
|
||||
# while leaving ample space before the first required mapping.
|
||||
set(CMAKE_EXE_LINKER_FLAGS
|
||||
"${CMAKE_EXE_LINKER_FLAGS} -Wl,-pagezero_size,0x10000")
|
||||
set(PORT_ADDR_BASE 0x0)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
+39
-2
@@ -28,8 +28,9 @@ for accepted versions and hashes.
|
||||
### Port build
|
||||
|
||||
> **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.
|
||||
> by CI and ships in the release bundle. macOS is supported natively on both
|
||||
> Intel and Apple Silicon (arm64); see the note below the table for the
|
||||
> arm64-specific points.
|
||||
|
||||
| Need | Windows (MSYS2 MINGW64) | Debian/Ubuntu | macOS (Homebrew) |
|
||||
|------|------------------------|---------------|------------------|
|
||||
@@ -50,6 +51,20 @@ 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.
|
||||
|
||||
**Apple Silicon (arm64).** Everything above applies as-is; `./build-pc.sh`
|
||||
detects the architecture and produces `build-pc/ge007.aarch64`. Two arm64
|
||||
specifics are handled automatically:
|
||||
|
||||
- Native arm64 cannot map the low 4 GiB (mandatory `__PAGEZERO`), so the N64
|
||||
address-space window is placed at a 16 TiB host base (`PORT_ADDR_BASE`,
|
||||
`port/include/port_addr.h`). It is a CMake cache var
|
||||
(`-DPORT_ADDR_BASE=...`) for tooling that needs it elsewhere; every
|
||||
game-visible 32-bit value is identical to the other platforms.
|
||||
- GCC's `libstdc++`/`libgcc_s` and SDL2 come from Homebrew and are **not**
|
||||
system libraries, so a redistributable build must bundle them — use
|
||||
`tools_pc/bundle-mac.sh` (below), which produces a signed, double-clickable
|
||||
`GoldenEye.app`.
|
||||
|
||||
### Asset extraction (decompilation toolchain)
|
||||
|
||||
The extraction scripts need `binutils-mips-linux-gnu` (or an equivalent MIPS
|
||||
@@ -169,6 +184,28 @@ deterministic function of the ROM. Re-run after any change to `d43_emit.py` /
|
||||
The ROM in `data/` (from step 4) and the sidecars are both required at
|
||||
runtime. `ge007.ini` is written under `data/` on first launch.
|
||||
|
||||
On macOS the binary is `./build-pc/ge007.aarch64` (Apple Silicon) or
|
||||
`./build-pc/ge007.x86_64` (Intel).
|
||||
|
||||
### Packaging a macOS app
|
||||
|
||||
```sh
|
||||
tools_pc/bundle-mac.sh # -> dist/GoldenEye.app + a .zip + sha256
|
||||
```
|
||||
|
||||
Builds a double-clickable, ad-hoc-signed `GoldenEye.app` (the engine plus
|
||||
bundled SDL2 / SDL3 / `libstdc++` / `libgcc_s`, README, licenses, and the
|
||||
`prepare-assets` tool). Note that Homebrew's `sdl2` is **sdl2-compat**, a
|
||||
shim over SDL3 that locates its backend through a *relative* rpath
|
||||
(`@loader_path/../../../../opt/sdl3/lib`): copy it without SDL3 and
|
||||
repointing that rpath, and it shows a modal error dialog from its
|
||||
initializer and the app hangs before `main()`. The script does both. It contains **no ROM and no game data**: the player
|
||||
drops their own ROM at
|
||||
`GoldenEye.app/Contents/MacOS/data/ge007.ntsc-final.z64` and the app finds it
|
||||
next to the executable. Because the app is ad-hoc signed rather than
|
||||
notarised, first launch needs right-click → *Open* (or
|
||||
`xattr -dr com.apple.quarantine GoldenEye.app`).
|
||||
|
||||
### Useful flags / env
|
||||
|
||||
| | |
|
||||
|
||||
@@ -579,9 +579,20 @@ Record as a `Dxx`.
|
||||
| Research | done | §1 F1–F18, measured 2026-09-18 |
|
||||
| M0 | **superseded by #88** | the clang build/toolchain work is replaced by PR #88's GCC-based macOS layer; only D296 + `gen_macho_syms --base` survive (§0b) |
|
||||
| M1 | **done** | shifted-window address model (D298); self-test ALL PASS on the #88 base |
|
||||
| M2 | **in progress** | game-code re-basing sweep; boot reaches the level stage load |
|
||||
| M3 | partly inherited | #88 supplies the AppKit main-thread guard; the two Core-profile GL fixes and the level sweep remain |
|
||||
| M4 | not started | packaging / CI / docs |
|
||||
| M2 | **done** | game-code re-basing sweep complete; **21/21 solo levels boot, render and run crash-free**, including with full controller input |
|
||||
| M3 | **done (core)** | #88's AppKit main-thread guard; `tools_pc/level_sweep_mac.sh` (crash + rendered-pixel check); the two Core-profile GL nits are cosmetic and parked in `docs/dev/GRAPHICS-BACKLOG.md` |
|
||||
| M4 | **done (core)** | `tools_pc/bundle-mac.sh` → signed, double-clickable `GoldenEye.app` (+ zip/sha256); `docs/building.md` macOS section + `tools_pc/README.md`. Outstanding: CI for macOS, and a live run of the `.app` from Finder |
|
||||
|
||||
**Verified state (2026-09-18).** `build-pc/ge007.aarch64` (Homebrew GCC 16.2)
|
||||
opens a window, renders, plays music **and sound effects**, and runs every solo
|
||||
level crash-free:
|
||||
|
||||
- `tools_pc/level_sweep_mac.sh` — 21/21, all with non-clear pixels
|
||||
(76–92%).
|
||||
- Full-control sweep (`GE_INPUTSCRIPT`: every button + 8-way stick, 26 s per
|
||||
level) — 21/21, with SFX voices allocating on every level.
|
||||
- Findings D294–D300; D299 (silent SFX) and D300 (stage-unload truncation)
|
||||
were both arm64-specific and are fixed.
|
||||
|
||||
### Prerequisites to run (easy to miss)
|
||||
|
||||
|
||||
@@ -36,16 +36,23 @@
|
||||
#include <stdint.h>
|
||||
#include "platform.h"
|
||||
|
||||
#ifndef PORT_ADDR_BASE
|
||||
#if defined(PLATFORM_MACOS) && defined(PLATFORM_ARM)
|
||||
/* 16 TiB: reserved cleanly on Apple Silicon, far above libmalloc's regions and
|
||||
* the mmap hint area, and well below fast3d_ptr_ok()'s 128 TiB bound. The
|
||||
* committed port/src/*.darwin.s files are generated with the SAME base
|
||||
* (scripts/gen_romassets.py --darwin --base 0x100000000000); portAddrInit()
|
||||
* checks the two agree at startup. */
|
||||
* generated Mach-O symbol files are produced with the SAME base (CMake's
|
||||
* PORT_ADDR_BASE cache var, fed to scripts/gen_macho_syms.py --base);
|
||||
* portAddrInit() checks the two agree at startup via cfb_16.
|
||||
*
|
||||
* Overridable from CMake (-DPORT_ADDR_BASE=...) for tooling that needs the
|
||||
* window somewhere else -- e.g. an AddressSanitizer build, whose shadow
|
||||
* region overlaps 16 TiB (it covers 0x027e00024000..0x10700001ffff, so a
|
||||
* sanitizer build uses a LowMem base like 0x4000000000). */
|
||||
#define PORT_ADDR_BASE 0x100000000000ULL
|
||||
#else
|
||||
#define PORT_ADDR_BASE 0x0ULL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Size of the N64 address space. */
|
||||
#define PORT_ADDR_WINDOW (1ULL << 32)
|
||||
@@ -97,6 +104,16 @@ static inline uint32_t portHostToN64(const void *p)
|
||||
return (uint32_t)v;
|
||||
}
|
||||
|
||||
/* Is a host pointer inside the N64 address window (DRAM / cart / stacks)?
|
||||
* Use this instead of hardcoded numeric bounds: the window is shifted by
|
||||
* PORT_ADDR_BASE on arm64, so an absolute bound that is correct on x86_64
|
||||
* silently rejects every real pointer there. */
|
||||
static inline int portAddrIsInWindow(const void *p)
|
||||
{
|
||||
uintptr_t v = (uintptr_t)p;
|
||||
return v - (uintptr_t)PORT_ADDR_BASE < (uintptr_t)PORT_ADDR_WINDOW;
|
||||
}
|
||||
|
||||
/* Cast-site helper for game code (the D3x ABI/layout class): reads a 32-bit
|
||||
* address out of a field/expression and yields a typed pointer. Identity on
|
||||
* Windows/Linux (PORT_ADDR_BASE == 0). */
|
||||
|
||||
@@ -173,6 +173,26 @@ int romConvertEnsureSidecars(const unsigned char *romImg, const char *romRelPath
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!convPath[0] && exedir[0]) {
|
||||
/* Two layouts the "$E/" forms above miss. (1) On POSIX "$E/" resolves
|
||||
* CWD-relative, not to the real exe dir, so a launch from another CWD
|
||||
* needs the absolute form. (2) A macOS .app keeps prepare-assets/ in
|
||||
* Contents/Resources/ — it cannot live in Contents/MacOS/, where
|
||||
* codesign treats the directory as nested code and refuses to sign. */
|
||||
static const char *candExe[3] = {
|
||||
"prepare-assets/ge007-convert",
|
||||
"../Resources/prepare-assets/ge007-convert",
|
||||
"ge007-convert",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(candExe) / sizeof(candExe[0]); i++) {
|
||||
char full[1024];
|
||||
snprintf(full, sizeof(full), "%s/%s", exedir, candExe[i]);
|
||||
if (fsExists(full)) {
|
||||
snprintf(convPath, sizeof(convPath), "%s", full);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!convPath[0]) {
|
||||
sysLogPrintf(LOG_ERROR,
|
||||
|
||||
+2
-2
@@ -3579,7 +3579,7 @@ bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomV
|
||||
|
||||
if (bgTestRayIntersectsBbox(from, dir, (s32 *) (&bboxMin), (s32 *) (&bboxMax)))
|
||||
{
|
||||
if (intersectRayTriangle((Vertex *)((s32)vtxbase - (0 - (idx[0] << 4))), (Vertex *)((s32)vtxbase - (0 - (idx[1] << 4))), (Vertex *)((s32)vtxbase - (0 - (idx[2] << 4))), (coord3d *) (((roomnum * 24) + ((uintptr_t) ptr_bgdata_room_fileposition_list)) + 12), from, to, dir, &hitbuf))
|
||||
if (intersectRayTriangle((Vertex *)((uintptr_t)vtxbase - (0 - (idx[0] << 4))), (Vertex *)((uintptr_t)vtxbase - (0 - (idx[1] << 4))), (Vertex *)((uintptr_t)vtxbase - (0 - (idx[2] << 4))), (coord3d *) (((roomnum * 24) + ((uintptr_t) ptr_bgdata_room_fileposition_list)) + 12), from, to, dir, &hitbuf))
|
||||
{
|
||||
tcmd = gdl;
|
||||
dx = ((s32) hitbuf.hitpos.x) - ((s32) from->x);
|
||||
@@ -3764,7 +3764,7 @@ bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomV
|
||||
|
||||
if (bgTestRayIntersectsBbox(from, dir, (s32 *) (&bboxMin2), (s32 *) (&bboxMax2)))
|
||||
{
|
||||
if (intersectRayTriangle((Vertex *)((s32)vtxbase - (0 - (idx2[0] << 4))), (Vertex *)((s32)vtxbase - (0 - (idx2[1] << 4))), (Vertex *)((s32)vtxbase - (0 - (idx2[2] << 4))), (coord3d *) (((roomnum * 24) + ((uintptr_t) ptr_bgdata_room_fileposition_list)) + 12), from, to, dir, &hitbuf))
|
||||
if (intersectRayTriangle((Vertex *)((uintptr_t)vtxbase - (0 - (idx2[0] << 4))), (Vertex *)((uintptr_t)vtxbase - (0 - (idx2[1] << 4))), (Vertex *)((uintptr_t)vtxbase - (0 - (idx2[2] << 4))), (coord3d *) (((roomnum * 24) + ((uintptr_t) ptr_bgdata_room_fileposition_list)) + 12), from, to, dir, &hitbuf))
|
||||
{
|
||||
tcmd = gdl;
|
||||
dx = ((s32) hitbuf.hitpos.x) - ((s32) from->x);
|
||||
|
||||
+6
-1
@@ -1,4 +1,9 @@
|
||||
#include <ultra64.h>
|
||||
#if defined(PORT)
|
||||
#include "port_addr.h"
|
||||
#else
|
||||
#define PORT_N64PTR(T, x) ((T *)(x))
|
||||
#endif
|
||||
#include <limits.h>
|
||||
#include <assets/animationtable_data.h>
|
||||
#include <random.h>
|
||||
@@ -438,7 +443,7 @@ void bheadAdjustAnimation(f32 speed)
|
||||
modelSetAnimation(
|
||||
&g_CurrentPlayer->model,
|
||||
// match hack: addu address backwards
|
||||
(struct ModelAnimation *) ((s32)g_BondMoveAnimationSetup[i].anim_id + (s32)&ptr_animation_table->data),
|
||||
PORT_N64PTR(struct ModelAnimation, (s32)g_BondMoveAnimationSetup[i].anim_id + (s32)&ptr_animation_table->data),
|
||||
(s32) BHEAD_FLIPFLAG,
|
||||
startframe,
|
||||
0.5f,
|
||||
|
||||
@@ -4592,7 +4592,7 @@ void bondviewMoveAnimationTick(f32 speed, f32 speedforwards, f32 speedsideways)
|
||||
// HACK: ptr_animation_table dereference addition is backwards.
|
||||
// this should be:
|
||||
// ptr_animation_table->data[g_bondviewBondDeathAnimations[((u32) randomGetNext() % (u32) g_bondviewBondDeathAnimationsCount)]]
|
||||
bheadStartDeathAnimation((struct ModelAnimation *) ((s32)g_bondviewBondDeathAnimations[((u32) randomGetNext() % (u32) g_bondviewBondDeathAnimationsCount)] + (s32)&ptr_animation_table->data[0]), randomGetNext() & 1, 0.0f, 1.0f);
|
||||
bheadStartDeathAnimation(PORT_N64PTR(struct ModelAnimation, (s32)g_bondviewBondDeathAnimations[((u32) randomGetNext() % (u32) g_bondviewBondDeathAnimationsCount)] + (s32)&ptr_animation_table->data[0]), randomGetNext() & 1, 0.0f, 1.0f);
|
||||
g_CurrentPlayer->startnewbonddie = FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ void bondviewLoadSetupIntroSection(void)
|
||||
intro_credits = (struct SetupIntroCredits*)intro_record;
|
||||
|
||||
// hack: bad address math
|
||||
credits = (CreditsEntry*)((s32)g_ptrStageSetupFile + (s32)intro_credits->unk04);
|
||||
credits = PORT_N64PTR(CreditsEntry, (s32)g_ptrStageSetupFile + (s32)intro_credits->unk04);
|
||||
credits_pointer = credits;
|
||||
|
||||
// what is the point of this?
|
||||
|
||||
@@ -896,6 +896,16 @@ struct anim_group_info *ptr_doubles_firing_animation_groups[] = {
|
||||
|
||||
struct weapon_firing_animation_table crouched_rifle_firing_animation_group1[] = {
|
||||
{ PTR_ANIM_fire_kneel_right_leg, 27.0, 0, 0, 0, -1.0, 35.0, 75.0, -1.0, -1.0, 31.0, 75.0, 0.87266463, -0.69813174, 0.90757126, -0.69813174, 1.5, 1.5 },
|
||||
#if defined(PORT)
|
||||
/* D298: every other *_firing_animation_groupN[] is zero-terminated;
|
||||
* initResolveAnimGroupTable() walks to that 0, so without one here it runs
|
||||
* into the next global (crouched_rifle_firing_animation_groupA) and
|
||||
* over-counts `len`. On N64 the 72-byte stride happens to stop it; on PC
|
||||
* the 80-byte stride runs several globals deep, so `len` allows a
|
||||
* `next_anim` past the array and the guard-attack path reads a bogus
|
||||
* animation. N64 build unchanged. */
|
||||
{0, 0.0, 0, 0, 0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct weapon_firing_animation_table crouched_rifle_firing_animation_groupA[] = {
|
||||
|
||||
@@ -23,7 +23,15 @@
|
||||
|
||||
void cleanupObjects(s32 stage)
|
||||
{
|
||||
u32 *obj = (u32)g_CurrentSetup.propDefs;
|
||||
/* D300: the decomp's `(u32)g_CurrentSetup.propDefs` converts the pointer
|
||||
* to a 32-bit integer and back into a `u32 *` -- a no-op where pointers
|
||||
* are 32-bit (N64), but on the 64-bit port it TRUNCATES the host pointer
|
||||
* to its low 32 bits, leaving an unbased N64 address (0x7017_c70f on
|
||||
* Cuba) that faults on the first CLEANUP_PDTYPE deref. propDefs is a real
|
||||
* host pointer (prop.c:1283 stores `local_stage + propDefs`), so cast
|
||||
* pointer-to-pointer and keep `u32 *` for sizepropdef()'s 4-byte-unit
|
||||
* increment. Same class as D3x; semantics unchanged. */
|
||||
u32 *obj = (u32 *)g_CurrentSetup.propDefs;
|
||||
|
||||
if (obj)
|
||||
{
|
||||
|
||||
@@ -462,8 +462,13 @@ u8 * langGet(s32 slotID)
|
||||
#endif
|
||||
u32 textslot_offset = textbank_ptr[slotID & 0x03FF]; /* load the textbank ptr table then get the slot's offset */
|
||||
|
||||
#if defined(PORT)
|
||||
/* D298/M2: keep the bank base at full pointer width. */
|
||||
uintptr_t output_slot = (uintptr_t)textbank_ptr + textslot_offset; /* base + slot offset */
|
||||
#else
|
||||
u32 output_slot = textslot_offset; /* add the text slot offset to the base ptr to get the ptr to text file's slot */
|
||||
output_slot += (u32)textbank_ptr;
|
||||
#endif
|
||||
#ifdef DEBUG
|
||||
return (textslot_offset != 0) ? (u8 *)output_slot : "Sorry, string not loaded.";
|
||||
#endif
|
||||
|
||||
+23
-23
@@ -7711,18 +7711,18 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
adjust_height = 1;
|
||||
}
|
||||
|
||||
newverts = vtxstore_allocate(rodata->numVertices, 0x0b0b, model->obj, objGetDestroyedLevel(obj));
|
||||
newverts = PORT_N64PTR(Vertex, vtxstore_allocate(rodata->numVertices, 0x0b0b, model->obj, objGetDestroyedLevel(obj)));
|
||||
|
||||
if (newverts != NULL)
|
||||
{
|
||||
if ((*vtxslot) != rodata->Vertices)
|
||||
if (PORT_N64PTR(Vertex, *vtxslot) != rodata->Vertices)
|
||||
{
|
||||
for (i = 0, offset = 0; i < rodata->numVertices; i++, offset += sizeof(Vertex))
|
||||
{
|
||||
*((Word4 *) (((u8 *) newverts) + offset)) = *((Word4 *) (((u8 *) (*vtxslot)) + offset));
|
||||
*((Word4 *) (((u8 *) newverts) + offset)) = *((Word4 *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset));
|
||||
}
|
||||
|
||||
sub_GAME_7F09C044(*vtxslot);
|
||||
sub_GAME_7F09C044(PORT_N64PTR(Vertex, *vtxslot));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -7736,9 +7736,9 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*vtxslot) != rodata->Vertices)
|
||||
if (PORT_N64PTR(Vertex, *vtxslot) != rodata->Vertices)
|
||||
{
|
||||
sub_GAME_7F09C044(*vtxslot);
|
||||
sub_GAME_7F09C044(PORT_N64PTR(Vertex, *vtxslot));
|
||||
*vtxslot = rodata->Vertices;
|
||||
obj->runtime_bitflags |= 4;
|
||||
}
|
||||
@@ -7778,14 +7778,14 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
|
||||
do
|
||||
{
|
||||
if (((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y < ymin)
|
||||
if (((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y < ymin)
|
||||
{
|
||||
ymin = ((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y;
|
||||
ymin = ((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y;
|
||||
}
|
||||
|
||||
if (ymax < ((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y)
|
||||
if (ymax < ((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y)
|
||||
{
|
||||
ymax = ((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y;
|
||||
ymax = ((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y;
|
||||
}
|
||||
|
||||
i++;
|
||||
@@ -7845,7 +7845,7 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
{
|
||||
if (obj->mtx.m[1][1] >= 0.0f)
|
||||
{
|
||||
if (((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y >= ymid)
|
||||
if (((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y >= ymid)
|
||||
{
|
||||
if (adjust_height)
|
||||
{
|
||||
@@ -7865,7 +7865,7 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
chance = 90;
|
||||
}
|
||||
}
|
||||
else if ((ymid ^ 0) >= ((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y)
|
||||
else if ((ymid ^ 0) >= ((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y)
|
||||
{
|
||||
if (adjust_height)
|
||||
{
|
||||
@@ -7894,10 +7894,10 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
{
|
||||
if (((s32) (chrObjRandomGetNext() % 100)) < chance)
|
||||
{
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->r = 0;
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->g = 0;
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->b = 0;
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->a = 255;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->r = 0;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->g = 0;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->b = 0;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->a = 255;
|
||||
}
|
||||
else
|
||||
#ifdef VERSION_EU
|
||||
@@ -7906,17 +7906,17 @@ void objDeform(ObjectRecord *obj, E_EXPLOSIONTYPE explosiontype)
|
||||
if ((explosiontype * 2) == EXPLOSION_MEDIUM)
|
||||
#endif
|
||||
{
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->a = 0;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->a = 0;
|
||||
}
|
||||
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y = (((f32) (((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y - ymin)) * yscale) + ((f32) ymin);
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.x += (chrObjRandomGetNext() % 80) - 40;
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y += (chrObjRandomGetNext() % 80) - 40;
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.z += (chrObjRandomGetNext() % 80) - 40;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y = (((f32) (((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y - ymin)) * yscale) + ((f32) ymin);
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.x += (chrObjRandomGetNext() % 80) - 40;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y += (chrObjRandomGetNext() % 80) - 40;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.z += (chrObjRandomGetNext() % 80) - 40;
|
||||
|
||||
if (((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y < ymin)
|
||||
if (((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y < ymin)
|
||||
{
|
||||
((Vertex *) (((u8 *) (*vtxslot)) + offset))->coord.y = ymin;
|
||||
((Vertex *) (((u8 *) PORT_N64PTR(Vertex, *vtxslot)) + offset))->coord.y = ymin;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <PR/libaudio.h>
|
||||
#include <os_extension.h>
|
||||
#include "music.h"
|
||||
#ifdef PORT
|
||||
#include "port_addr.h"
|
||||
#endif
|
||||
#include "snd.h"
|
||||
#ifdef PORT
|
||||
#include <stdio.h>
|
||||
@@ -1107,8 +1110,16 @@ ALSoundState *sndPlaySfx(struct ALBankAlt_s *soundBank, s16 soundIndex, ALSoundS
|
||||
* (0x1_4000_0000..); anything else (e.g. 0x0000_5622_0001_0001) is a
|
||||
* byte-scrambled / OOB read. */
|
||||
{
|
||||
uintptr_t sp = (uintptr_t)sound;
|
||||
if (sp < 0x10000 || sp >= 0x400000000ULL) {
|
||||
/* D299 (macOS arm64): this guard predates the shifted-window
|
||||
* address model (port/include/port_addr.h). With PORT_ADDR_BASE
|
||||
* != 0 a valid ALSound* is ~0x1000_707b_5740 -- far ABOVE the old
|
||||
* 16 GiB (0x400000000) ceiling -- so the hardcoded bound rejected
|
||||
* every real sound and SFX were silent. (Music is a separate
|
||||
* player, so only SFX were affected; on x86_64 the window starts
|
||||
* at 0 and DRAM at 0x7000_0000, below the ceiling, which is why
|
||||
* this never showed there.) Test the pointer against the actual
|
||||
* window instead. */
|
||||
if (!portAddrIsInWindow(sound)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@ investigation artifacts.
|
||||
| `playtest.sh` | Launch a level for `docs/dev/LEVEL-PLAYTEST.md` human validation (WS6). |
|
||||
| `audiodebug.ps1` | Audio counterpart to `debug.ps1` (that one is for crashes; this one is for audio behaviour). Drives the `GE_D204` health monitor + `GE_AUDIOTRACE`/`GE_AUDIODUMP` and prints a pass/fail verdict: real-time ratio, queue starvation, dropped blocks, oversized blocks, plus a soundIndex histogram. `-AB` runs the same binary twice (with and without `GE_D204_OLD`) and prints a before/after table — how D204 was measured. `-Play` for an instrumented interactive playtest (the D202 workflow), `-Soak` for a 5-min stability run, `-SyncData` to mirror `./data` first. |
|
||||
| `debug.ps1` / `repro_gdb.sh` / `attach_animgen.sh` | Launch (or attach to) the game under gdb so a crash always leaves a backtrace. |
|
||||
| `bundle-win.sh` / `bundle-linux.sh` | Package a built tree as a distributable archive (exe + licenses + `prepare-assets/`). |
|
||||
| `bundle-win.sh` / `bundle-linux.sh` / `bundle-mac.sh` | Package a built tree as a distributable archive (exe + licenses + `prepare-assets/`). `bundle-mac.sh` produces a signed, double-clickable `dist/GoldenEye.app` with SDL2 + libstdc++/libgcc_s bundled (Homebrew libs are not system libs) and `prepare-assets/` in `Contents/Resources/` — it cannot go in `Contents/MacOS/`, where codesign treats the directory as nested code and fails the bundle. |
|
||||
| `level_sweep_mac.sh` | macOS/arm64 sweep: bare `-level_XX` boot of all 21 solo levels, crash-log detection (symbolicated via `atos`), and a `GE_PCDUMP` frame that must be non-near-black (`PIX_MIN`, default 2%), so "runs but renders nothing" is caught too. `SWEEP_SECS` (default 18). |
|
||||
| `romverify.c` | One-shot `.z64` integrity check against the repo's ground truths. |
|
||||
| `dump_objectives.py` | Dump per-level objectives + win/fail criteria from the ROM (playtest validation; Step 10 / N8 acceptance oracle). |
|
||||
| `disasm.py` | Minimal MIPS disassembler for the BE ROM (RAM-addr → file offset). |
|
||||
|
||||
Executable
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Package a macOS (Apple Silicon / arm64) GoldenEye 007 PC port build as a
|
||||
# double-clickable .app for distribution.
|
||||
#
|
||||
# Run from anywhere, AFTER building (./build-pc.sh or a cmake --build):
|
||||
#
|
||||
# tools_pc/bundle-mac.sh [VERSION]
|
||||
#
|
||||
# Produces, under dist/ :
|
||||
# GoldenEye.app/ the app bundle (drop a ROM in to play)
|
||||
# goldeneye-pc-port-<VERSION>-macos-arm64.zip + .zip.sha256
|
||||
#
|
||||
# The bundle contains ONLY: the engine executable, its two Homebrew-provided
|
||||
# runtime libraries (SDL2, libstdc++/libgcc_s), a README, license texts, and
|
||||
# the prepare-assets/ tool. It contains NO ROM and NO game assets. zlib, Cocoa
|
||||
# and OpenGL come from macOS itself.
|
||||
#
|
||||
# The engine finds its data dir as `<exe dir>/data/` when there is no `data/`
|
||||
# in the CWD (port/src/system.c sysResolvePath), so the user's ROM + generated
|
||||
# sidecars live in GoldenEye.app/Contents/MacOS/data/.
|
||||
#
|
||||
# arm64 binaries must carry at least an ad-hoc signature to execute, and
|
||||
# install_name_tool invalidates whatever signature the linker applied — so the
|
||||
# script re-signs every binary (and the bundle) at the end.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
VERSION="${1:-$(git describe --tags --always --dirty 2>/dev/null || echo 0.0.0-dev)}"
|
||||
VERSION="${VERSION#v}" # strip a leading "v"
|
||||
NAME="goldeneye-pc-port-${VERSION}-macos-arm64"
|
||||
APP="dist/GoldenEye.app"
|
||||
MACOS="$APP/Contents/MacOS"
|
||||
RES="$APP/Contents/Resources"
|
||||
|
||||
# Accept a build from either a local build-pc/ tree or a CI macos tree.
|
||||
EXE=""
|
||||
for cand in build-macos/ge007.aarch64 build-pc/ge007.aarch64 build-pc/ge007; do
|
||||
if [ -f "$cand" ] && [ -x "$cand" ]; then EXE="$cand"; break; fi
|
||||
done
|
||||
[ -n "$EXE" ] || { echo "error: no arm64 ge007 binary found in build-macos/ or build-pc/ — build first" >&2; exit 1; }
|
||||
file "$EXE" | grep -q 'arm64' || { echo "error: $EXE is not an arm64 binary" >&2; exit 1; }
|
||||
|
||||
echo "==> Bundling $EXE -> $NAME"
|
||||
rm -rf "$APP" "dist/$NAME" "dist/${NAME}.zip" "dist/${NAME}.zip.sha256"
|
||||
mkdir -p "$MACOS" "$RES/licenses"
|
||||
|
||||
cp "$EXE" "$MACOS/ge007"
|
||||
chmod +x "$MACOS/ge007"
|
||||
EXE_NAME="ge007"
|
||||
|
||||
# --- bundle the Homebrew runtime deps --------------------------------
|
||||
# SDL2 and libstdc++ are the only non-system dynamic deps. Copy the exact
|
||||
# files this exe was linked against next to it and rewrite the load commands
|
||||
# to @executable_path so the bundled copies win, then make libstdc++ find its
|
||||
# own libgcc_s via @loader_path.
|
||||
copy_dep() { # <path-in-otool-output> -> echoes the bundled basename
|
||||
local src="$1" base
|
||||
base="$(basename "$src")"
|
||||
if [ ! -f "$src" ]; then
|
||||
echo "error: cannot locate $src (linked but not on disk)" >&2
|
||||
return 1
|
||||
fi
|
||||
cp -L "$src" "$MACOS/$base"
|
||||
chmod 644 "$MACOS/$base"
|
||||
echo "$base"
|
||||
}
|
||||
|
||||
SDL2_SRC="$(otool -L "$MACOS/$EXE_NAME" | awk '/libSDL2/ {print $1; exit}')"
|
||||
STDC_SRC="$(otool -L "$MACOS/$EXE_NAME" | awk '/libstdc\+\+/ {print $1; exit}')"
|
||||
[ -n "$SDL2_SRC" ] || { echo "error: no libSDL2 in $EXE_NAME's load commands" >&2; exit 1; }
|
||||
[ -n "$STDC_SRC" ] || { echo "error: no libstdc++ in $EXE_NAME's load commands" >&2; exit 1; }
|
||||
|
||||
SDL2_BASE="$(copy_dep "$SDL2_SRC")"
|
||||
STDC_BASE="$(copy_dep "$STDC_SRC")"
|
||||
install_name_tool -change "$SDL2_SRC" "@executable_path/$SDL2_BASE" "$MACOS/$EXE_NAME"
|
||||
install_name_tool -change "$STDC_SRC" "@executable_path/$STDC_BASE" "$MACOS/$EXE_NAME"
|
||||
|
||||
# Homebrew's `sdl2` is sdl2-compat, a thin shim over SDL3 that finds its
|
||||
# backend through a RELATIVE rpath baked into the dylib:
|
||||
# @loader_path/../../../../opt/sdl3/lib
|
||||
# That resolves correctly at /opt/homebrew/opt/sdl2-compat/lib/ and to
|
||||
# nothing once the dylib is copied into the bundle -- sdl2-compat then shows
|
||||
# a MODAL ERROR DIALOG from its initializer (dllinit -> error_dialog ->
|
||||
# NSAlert runModal) and the app hangs before main(). Bundle SDL3 and repoint
|
||||
# every Homebrew rpath at the bundle directory.
|
||||
SDL3_SRC="$(dirname "$(readlink -f /opt/homebrew/opt/sdl3/lib/libSDL3.dylib 2>/dev/null || echo /opt/homebrew/opt/sdl3/lib/libSDL3.0.dylib)")"
|
||||
if [ -f /opt/homebrew/opt/sdl3/lib/libSDL3.0.dylib ]; then
|
||||
cp -L /opt/homebrew/opt/sdl3/lib/libSDL3.0.dylib "$MACOS/libSDL3.0.dylib"
|
||||
chmod 644 "$MACOS/libSDL3.0.dylib"
|
||||
( cd "$MACOS" && ln -sf libSDL3.0.dylib libSDL3.dylib )
|
||||
echo " + libSDL3.0.dylib (+ libSDL3.dylib symlink; sdl2-compat backend)"
|
||||
else
|
||||
echo "error: SDL3 not found at /opt/homebrew/opt/sdl3/lib — required by sdl2-compat" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Repoint the bundled SDL2's Homebrew rpaths at @loader_path so the SDL3
|
||||
# dlopen finds the copy sitting next to it.
|
||||
while IFS= read -r rp; do
|
||||
case "$rp" in
|
||||
/opt/*|@loader_path/../../..*)
|
||||
install_name_tool -rpath "$rp" "@loader_path" "$MACOS/$SDL2_BASE" && \
|
||||
echo " ~ SDL2 rpath: $rp -> @loader_path" ;;
|
||||
esac
|
||||
done < <(otool -l "$MACOS/$SDL2_BASE" | awk '/LC_RPATH/{getline; getline; sub(/^ *path /,""); sub(/ \(offset.*/,""); print}')
|
||||
install_name_tool -id "@rpath/$SDL2_BASE" "$MACOS/$SDL2_BASE" 2>/dev/null || true
|
||||
|
||||
GCC_S_SRC="$(otool -L "$MACOS/$STDC_BASE" | awk '/libgcc_s/ {print $1; exit}')"
|
||||
if [ -n "$GCC_S_SRC" ]; then
|
||||
if [ ! -f "$GCC_S_SRC" ]; then
|
||||
# libstdc++ records it as @rpath/... — resolve against the libstdc++ dir.
|
||||
GCC_S_SRC="$(dirname "$STDC_SRC")/$(basename "$GCC_S_SRC")"
|
||||
fi
|
||||
GCC_S_BASE="$(copy_dep "$GCC_S_SRC")"
|
||||
install_name_tool -change "@rpath/$GCC_S_BASE" "@loader_path/$GCC_S_BASE" "$MACOS/$STDC_BASE" 2>/dev/null || \
|
||||
install_name_tool -change "$GCC_S_SRC" "@loader_path/$GCC_S_BASE" "$MACOS/$STDC_BASE"
|
||||
install_name_tool -id "@rpath/$STDC_BASE" "$MACOS/$STDC_BASE" 2>/dev/null || true
|
||||
echo " + $STDC_BASE + $GCC_S_BASE (bundled)"
|
||||
else
|
||||
echo " + $STDC_BASE (bundled)"
|
||||
fi
|
||||
echo " + $SDL2_BASE (bundled)"
|
||||
|
||||
# --- remaining deps should all be system ------------------------------
|
||||
echo " remaining dynamic dependencies (expected: libz + Apple frameworks):"
|
||||
otool -L "$MACOS/$EXE_NAME" | tail -n +2 | grep -vE "@executable_path|@loader_path" | sed 's/^/ /' || true
|
||||
|
||||
# --- Info.plist -------------------------------------------------------
|
||||
cat > "$APP/Contents/Info.plist" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key><string>GoldenEye 007</string>
|
||||
<key>CFBundleDisplayName</key><string>GoldenEye 007</string>
|
||||
<key>CFBundleIdentifier</key><string>org.goldeneye.pcport</string>
|
||||
<key>CFBundleExecutable</key><string>ge007</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key><string>${VERSION}</string>
|
||||
<key>CFBundleVersion</key><string>${VERSION}</string>
|
||||
<key>LSMinimumSystemVersion</key><string>11.0</string>
|
||||
<key>NSHighResolutionCapable</key><true/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
# --- docs + licenses --------------------------------------------------
|
||||
DEPS_BLOCK=$'## 1a. Runtime libraries\n\nNothing to install. SDL2 and the GCC C++ runtime (libstdc++, libgcc_s)\nare bundled inside the app; zlib, Cocoa and OpenGL come from macOS.\n\n**Gatekeeper:** the app is ad-hoc signed (not notarised). On first launch,\nright-click the app and choose *Open*, or run\n`xattr -dr com.apple.quarantine GoldenEye.app`. This is only needed once.\n'
|
||||
DECK_BLOCK=$'## 4a. Game controller (options overlay)\n\nThe options overlay is fully gamepad-driven: it opens with **Select**, the\nD-pad or left stick (up/down) moves between options, **A** steps the selected\noption forward, **B** steps it back, and **Start** (or Select again) closes.\nToggles flip, resolution / MSAA / filtering cycle, sliders step in\nincrements. With a keyboard attached the same overlay is `F10` + arrows/Enter.\n\n'
|
||||
# BSD sed/awk reject embedded newlines in -v, so the two multi-line blocks go
|
||||
# in via sed's `r` (read-file) command at their markers.
|
||||
DEPS_FILE="$(mktemp)"; DECK_FILE="$(mktemp)"
|
||||
printf '%s\n' "$DEPS_BLOCK" > "$DEPS_FILE"
|
||||
printf '%s\n' "$DECK_BLOCK" > "$DECK_FILE"
|
||||
sed -e "s|@VERSION@|${VERSION}|g" \
|
||||
-e "s|@PLATFORM@|macOS arm64 (Apple Silicon)|g" \
|
||||
-e "s|@EXE@|GoldenEye.app|g" \
|
||||
-e "s|@LICENSE_EXTRA@||g" \
|
||||
-e "/@DEPS@/r $DEPS_FILE" -e '/@DEPS@/d' \
|
||||
-e "/@DECK@/r $DECK_FILE" -e '/@DECK@/d' \
|
||||
tools_pc/dist/README.md.in > "$RES/README.md"
|
||||
rm -f "$DEPS_FILE" "$DECK_FILE"
|
||||
|
||||
cp NOTICE "$RES/licenses/NOTICE"
|
||||
cp LICENSE "$RES/licenses/LICENSE-port-MIT.txt"
|
||||
[ -f port/fast3d/LICENSE.txt ] && cp port/fast3d/LICENSE.txt "$RES/licenses/LICENSE-fast3d.txt"
|
||||
|
||||
# --- asset-prep tool (same assembly as the Linux/Windows bundles) -----
|
||||
# In Contents/Resources/, NOT Contents/MacOS/: codesign treats a directory
|
||||
# under MacOS/ as nested code and refuses to sign the bundle if it holds
|
||||
# non-Mach-O files (the vendored .inc.c headers). romconvert.c knows to look
|
||||
# in ../Resources/prepare-assets/ on macOS.
|
||||
PREP="$RES/prepare-assets"
|
||||
mkdir -p "$PREP/vendor/scripts" "$PREP/vendor/assets/obseg"
|
||||
cp tools_pc/dist/prepare-assets/prepare-assets.py "$PREP/"
|
||||
cp tools_pc/d43_emit.py tools_pc/d69_emit.py "$PREP/"
|
||||
cp tools_pc/d88_emit.py tools_pc/d88_propdefs.py "$PREP/"
|
||||
cp scripts/filelist.u.csv "$PREP/vendor/scripts/"
|
||||
cp assets/obseg/file_resource_table.inc.c "$PREP/vendor/assets/obseg/"
|
||||
# BSD cp has no --parents; recreate each header's tree under vendor/ by hand.
|
||||
find assets -iname 'modelfileheader.inc.c' -print | while IFS= read -r f; do
|
||||
d="$PREP/vendor/$(dirname "$f")"
|
||||
mkdir -p "$d"
|
||||
cp "$f" "$d/"
|
||||
done
|
||||
nmh="$(find "$PREP/vendor/assets" -iname 'modelfileheader.inc.c' | wc -l)"
|
||||
echo " + prepare-assets/ (emit scripts + $nmh model headers)"
|
||||
[ "$nmh" -gt 400 ] || { echo "error: prepare-assets vendored only $nmh model headers — expected ~512" >&2; exit 1; }
|
||||
|
||||
# Frozen first-run converter (drop-in ROM support). Same PyInstaller step as
|
||||
# Linux/Windows, but a macOS freeze only runs on macOS: if it is not present,
|
||||
# warn rather than fail — the app still runs with pre-generated sidecars.
|
||||
CV="$REPO_ROOT/tools_pc/dist/prepare-assets/ge007-convert"
|
||||
if [ -f "$CV" ]; then
|
||||
cp "$CV" "$PREP/"
|
||||
chmod 755 "$PREP/ge007-convert"
|
||||
echo " + prepare-assets/ge007-convert ($(du -h "$CV" | cut -f1))"
|
||||
else
|
||||
echo " ! prepare-assets/ge007-convert not found — the app will only run"
|
||||
echo " with pre-generated sidecars. Freeze it with:"
|
||||
echo " python3 -m PyInstaller --onefile --name ge007-convert \\"
|
||||
echo " --distpath tools_pc/dist/prepare-assets \\"
|
||||
echo " tools_pc/dist/prepare-assets/prepare-assets.py"
|
||||
fi
|
||||
|
||||
# --- guard: no ROM / game data snuck in ------------------------------
|
||||
if find "$APP" -type f \( -iname '*.z64' -o -iname '*.n64' -o -iname '*.v64' \) | grep -q .; then
|
||||
echo "error: bundle contains a ROM image — aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
BYTES="$(du -sk "$APP" | cut -f1)"
|
||||
LIMIT=$((120 * 1024)) # KiB
|
||||
if [ "$BYTES" -gt "$LIMIT" ]; then
|
||||
echo "error: bundle is ${BYTES} KiB (> 120 MB) — likely contains game data, aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- ad-hoc sign (arm64 requires a signature; install_name_tool voids it) --
|
||||
if command -v codesign >/dev/null 2>&1; then
|
||||
for lib in "$MACOS"/*.dylib; do
|
||||
[ -f "$lib" ] && codesign --force --sign - "$lib" 2>/dev/null || true
|
||||
done
|
||||
codesign --force --sign - "$MACOS/$EXE_NAME"
|
||||
# No --deep: it tries to sign the prepare-assets/ .c sources as nested code
|
||||
# and fails. The only Mach-O in the bundle is the exe + the dylibs above.
|
||||
codesign --force --sign - "$APP"
|
||||
echo " + ad-hoc signed ($(codesign -dv "$APP" 2>&1 | awk -F= '/Signature/ {print $2}'))"
|
||||
else
|
||||
echo " ! codesign not found — the app will not launch on arm64" >&2
|
||||
fi
|
||||
|
||||
# --- zip + checksum ---------------------------------------------------
|
||||
( cd dist && rm -f "${NAME}.zip" "${NAME}.zip.sha256" \
|
||||
&& zip -qry "${NAME}.zip" "GoldenEye.app" \
|
||||
&& shasum -a 256 "${NAME}.zip" > "${NAME}.zip.sha256" )
|
||||
|
||||
echo "==> dist/${NAME}.zip ($(du -h "dist/${NAME}.zip" | cut -f1))"
|
||||
cat "dist/${NAME}.zip.sha256"
|
||||
echo "==> dist/GoldenEye.app — drop your ROM at:"
|
||||
echo " GoldenEye.app/Contents/MacOS/data/ge007.ntsc-final.z64"
|
||||
find "$APP" -type f | sed "s#^dist/##" | sort
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# macOS/arm64 level sweep: bare -level_XX boot per level. PASS = ran the whole
|
||||
# window without a crash log AND a GE_PCDUMP frame that is not near-black
|
||||
# (catches "runs but renders nothing" that a crash-only check would miss).
|
||||
# Env: SWEEP_SECS (default 18), PIX_MIN (default 2, percent non-clear).
|
||||
cd "$(dirname "$0")/.."
|
||||
LV="Dam:33 Facility:34 Runway:35 Surface1:36 Bunker1:09 Silo:20 Frigate:26 Surface2:43 Bunker2:27 Statue:22 Archives:24 Streets:29 Depot:30 Train:25 Jungle:37 Control:23 Caverns:39 Cradle:41 Aztec:28 Egypt:32 Cuba:54"
|
||||
OUT=/tmp/mac_sweep_results; : > $OUT
|
||||
PIX_MIN=${PIX_MIN:-2}
|
||||
mkdir -p ppm
|
||||
for entry in $LV; do
|
||||
name=${entry%%:*}; num=${entry##*:}
|
||||
rm -f ge007.crash.log ppm/frame_000120.ppm
|
||||
GE_PCDUMP="120-120:1" ./build-pc/ge007.aarch64 -level_$num > /tmp/sweeplogs/$name.log 2>&1 &
|
||||
pid=$!
|
||||
sleep ${SWEEP_SECS:-18}
|
||||
kill $pid 2>/dev/null; sleep 1; kill -9 $pid 2>/dev/null; wait $pid 2>/dev/null
|
||||
sleep 1
|
||||
frames=$(grep -cE 'frame [0-9]+ rendered' /tmp/sweeplogs/$name.log 2>/dev/null)
|
||||
if [ -f ge007.crash.log ]; then
|
||||
M=$(grep -oE 'MODULE: 0x[0-9a-f]+' ge007.crash.log|head -1|awk '{print $2}')
|
||||
P=$(grep -oE '^PC: 0x[0-9a-f]+' ge007.crash.log|awk '{print $2}')
|
||||
sym=$(atos -o build-pc/ge007.aarch64 -l "$M" "$P" 2>/dev/null | head -1)
|
||||
cp ge007.crash.log /tmp/sweeplogs/$name.crash.log
|
||||
echo "$name ($num): CRASH @ $sym" | tee -a $OUT
|
||||
continue
|
||||
fi
|
||||
if [ -f ppm/frame_000120.ppm ]; then
|
||||
pc=$(python3 tools_pc/pixcount.py ppm/frame_000120.ppm 2>/dev/null | grep -oE '[0-9]+\.[0-9]+%' | head -1)
|
||||
pcn=${pc%\%}
|
||||
if [ -z "$pcn" ]; then
|
||||
echo "$name ($num): OK (frames=$frames, pixcount failed)" | tee -a $OUT
|
||||
elif awk "BEGIN{exit !($pcn < $PIX_MIN)}"; then
|
||||
echo "$name ($num): BLACK (frames=$frames, non-clear ${pc})" | tee -a $OUT
|
||||
else
|
||||
echo "$name ($num): OK (frames=$frames, non-clear ${pc})" | tee -a $OUT
|
||||
fi
|
||||
rm -f ppm/frame_000120.ppm
|
||||
else
|
||||
echo "$name ($num): OK (frames=$frames, no capture)" | tee -a $OUT
|
||||
fi
|
||||
done
|
||||
echo "SWEEP DONE" | tee -a $OUT
|
||||
Reference in new issue
Block a user