From 4b1eac2276dd26abacefa5d00afffb5cfb0b53af Mon Sep 17 00:00:00 2001 From: William Emerison Six Date: Fri, 31 Jul 2026 18:32:04 -0400 Subject: [PATCH 1/4] fix build dependencies in instructions for Fedora --- docs/BUILDING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 65d0ee921..afc63f9d2 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -103,7 +103,7 @@ pacman -S clang git cmake ninja lsb-release sdl2 libpng libzip nlohmann-json tin #### Fedora ```sh # using gcc -dnf install gcc gcc-c++ git cmake ninja-build lsb_release SDL2-devel libpng-devel libzip-devel libzip-tools nlohmann-json-devel tinyxml2-devel spdlog-devel boost-devel libogg-devel libvorbis-devel +dnf install gcc gcc-c++ git cmake ninja-build lsb_release SDL2-devel SDL2_net SDL2_net-devel libpng-devel libzip-devel libzip-tools nlohmann-json-devel tinyxml2-devel spdlog-devel boost-devel libogg-devel libvorbis-devel # or using clang dnf install clang git cmake ninja-build lsb_release SDL2-devel libpng-devel libzip-devel libzip-tools nlohmann-json-devel tinyxml2-devel spdlog-devel boost-devel libogg-devel libvorbis-devel From 25a462857780d98bc8ac57ece8612d7cae6e4fc6 Mon Sep 17 00:00:00 2001 From: William Emerison Six Date: Sat, 1 Aug 2026 03:04:35 +0000 Subject: [PATCH 2/4] The ExtractAssets target ran `torch o2r baserom.z64` without -u, so the generated bk.o2r carried no portVersion record. At runtime ReadPortVersionFromOTR() returns {0,0,0} for an archive with no portVersion, VerifyArchiveVersion() then compares that against the build version (1.0.0) and fails, so the game reports "Outdated/incompatible ROM archives" and drops into the re-extract flow on every launch. Pass `-u ${PROJECT_VERSION}` to the torch o2r command (mirroring how GeneratePortO2R already stamps lighthouse.o2r) so bk.o2r records a matching portVersion and the version check passes. --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba98a0bc2..26c4a099b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -750,7 +750,11 @@ add_custom_target( ExtractAssets DEPENDS TorchExternal WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMAND ${TORCH_EXECUTABLE} o2r baserom.z64 + # [port] Stamp bk.o2r with the project version so VerifyArchiveVersion() (Engine.cpp) + # accepts it. Without -u, Torch writes no portVersion record -> the archive reads as + # version {0,0,0}, never matches the build, and the runtime shows "incompatible ROM + # archives" and re-extracts on every launch. Mirrors GeneratePortO2R's stamping. + COMMAND ${TORCH_EXECUTABLE} o2r baserom.z64 -u ${PROJECT_VERSION} COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/bk.o2r" "${CMAKE_BINARY_DIR}/bk.o2r" ) From d11f951801a35d1a4e82584942531d5c3a1aed51 Mon Sep 17 00:00:00 2001 From: William Emerison Six Date: Sat, 1 Aug 2026 03:04:04 +0000 Subject: [PATCH 3/4] core1/graphics_thread: fix post-import freeze from OSMesg high-byte garbage thread5's event-vs-task discriminator read the full 8-byte OSMesg pointer (`(uintptr_t)msg.ptr < 100`). Hardware events are posted with OS_MESG_32(code), which sets .data32 but leaves the union's high 4 bytes uninitialized on a 64-bit host. When those garbage bytes came up non-zero (observed: the SP event arriving as 0x00007FCE_00000006), the event was mis-read as a task pointer and silently dropped. The dropped SP events were the graphics-completion SP and the yield-SP, so thread5_handleSPEvent never ran: a graphics task that had yielded (so an audio task could run) was never resumed, sUnkFlag1 stuck at TASK_YIELDED, the frame's DP -> sMesgQueue2 signal never fired, and the game hung right after ROM import. It was layout/timing-dependent because it rode on whatever stack garbage landed in those high bytes. Discriminate on msg.data32 (the event code, 3-13) instead of the full pointer. Task submissions carry a real pointer whose low 32 bits are always large, so they still route to the task branch. handleSPEvent now runs and the freeze is gone. Follow-up (libultraship): OS_MESG_8/16/32 should zero the whole 8-byte OSMesg union, not just the active member, so no other consumer can be bitten by the same uninitialized high bytes. This discriminator change is a defensive fix at the call site; fixing the macro removes the UB at its source. --- src/core1/graphics_thread.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core1/graphics_thread.c b/src/core1/graphics_thread.c index 4df502bf2..2f3fcf9f3 100644 --- a/src/core1/graphics_thread.c +++ b/src/core1/graphics_thread.c @@ -492,7 +492,17 @@ void thread5_entry(void *arg) { } ThreadWatchdog_Beat(WATCHDOG_THREAD5); // [port] one beat per serviced message thread5_checkAndExecutePreNMI(); - if ((uintptr_t)msg.ptr < 100) { + // [port] OSMesg is an 8-byte union on PC. Hardware events are posted with + // OS_MESG_32(code), which sets .data32 but leaves the union's high 4 bytes + // UNINITIALIZED (garbage). The original discrimination here was + // `(uintptr_t)msg.ptr < 100`, i.e. it read all 8 bytes — so an event whose garbage + // high bytes happened to be non-zero (observed: SP=6 arriving as 0x00007FCE_00000006) + // was mis-read as a task pointer and never dispatched. That silently dropped the + // gfx-completion/yield SP events, stranding the yield and hanging game-tick on + // sMesgQueue2 (the freeze). Task submissions carry a real pointer whose low 32 bits + // are always large, so discriminate on .data32 (the event code) instead, which is + // immune to the garbage high bytes. + if (msg.data32 < 100) { if (msg.data32 == THREAD5_MESSAGE_EVENT_SYNC) { thread5_handleSyncEvent(); } else if (msg.data32 == THREAD5_MESSAGE_EVENT_VI_RETRACE) { thread5_handleVIRetraceEvent(); } else if (msg.data32 == THREAD5_MESSAGE_EVENT_DP) { thread5_handleDPEvent(); } From a9fc96461277e9d5f04c427a7745c60cd156b783 Mon Sep 17 00:00:00 2001 From: William Emerison Six Date: Sat, 1 Aug 2026 07:25:04 -0400 Subject: [PATCH 4/4] shortened comments per request from JeodC --- CMakeLists.txt | 5 +---- src/core1/graphics_thread.c | 11 +---------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 26c4a099b..a69529235 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -750,10 +750,7 @@ add_custom_target( ExtractAssets DEPENDS TorchExternal WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - # [port] Stamp bk.o2r with the project version so VerifyArchiveVersion() (Engine.cpp) - # accepts it. Without -u, Torch writes no portVersion record -> the archive reads as - # version {0,0,0}, never matches the build, and the runtime shows "incompatible ROM - # archives" and re-extracts on every launch. Mirrors GeneratePortO2R's stamping. + # [port] Stamp bk.o2r with the project version so VerifyArchiveVersion() accepts it; without -u it reads as {0,0,0} and re-extracts every launch. COMMAND ${TORCH_EXECUTABLE} o2r baserom.z64 -u ${PROJECT_VERSION} COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/bk.o2r" "${CMAKE_BINARY_DIR}/bk.o2r" ) diff --git a/src/core1/graphics_thread.c b/src/core1/graphics_thread.c index 2f3fcf9f3..08809ae40 100644 --- a/src/core1/graphics_thread.c +++ b/src/core1/graphics_thread.c @@ -492,16 +492,7 @@ void thread5_entry(void *arg) { } ThreadWatchdog_Beat(WATCHDOG_THREAD5); // [port] one beat per serviced message thread5_checkAndExecutePreNMI(); - // [port] OSMesg is an 8-byte union on PC. Hardware events are posted with - // OS_MESG_32(code), which sets .data32 but leaves the union's high 4 bytes - // UNINITIALIZED (garbage). The original discrimination here was - // `(uintptr_t)msg.ptr < 100`, i.e. it read all 8 bytes — so an event whose garbage - // high bytes happened to be non-zero (observed: SP=6 arriving as 0x00007FCE_00000006) - // was mis-read as a task pointer and never dispatched. That silently dropped the - // gfx-completion/yield SP events, stranding the yield and hanging game-tick on - // sMesgQueue2 (the freeze). Task submissions carry a real pointer whose low 32 bits - // are always large, so discriminate on .data32 (the event code) instead, which is - // immune to the garbage high bytes. + // [port] OSMesg is an 8-byte union and OS_MESG_32 leaves its high bytes garbage, so discriminate on .data32 (the event code), not the full .ptr, which mis-read events as task pointers and dropped SP events (the post-import freeze). if (msg.data32 < 100) { if (msg.data32 == THREAD5_MESSAGE_EVENT_SYNC) { thread5_handleSyncEvent(); } else if (msg.data32 == THREAD5_MESSAGE_EVENT_VI_RETRACE) { thread5_handleVIRetraceEvent(); }