github OpenPort-Labs/Galaxy-Android v0.0.4b
GalaxyAndroid v0.0.4b — Hotfix Release

5 hours ago

GalaxyAndroid v0.0.4b Hotfix Release

This hotfix closes the findings of a full-source audit of the port: texture filtering that did not follow the hardware filter code, save and checkpoint integrity, a netplay data race, damaged-archive handling in the cooker, unverified downloads in the tooling, and a settings drawer that printed a version no APK was ever built as.


Highlights & Fixes in v0.0.4b

1. Texture Filtering and Window-Size Correctness

  • Min-filter and mipmap decode corrected: MODE0 bits 5-7 carry the TX_MIN_* code the SDK writes (private/tx_reg.h via GX2HWFiltConv), not the GXTexFilter enum order, so the sampler cache was building the wrong minification filter and mip interpolation for most textured draws. resolveUnit() also mapped the same code twice and left both mipmap-linear cases unread.
  • Zero-size windows refused instead of overflowed: computeTargetSize() divided by the reported window height to match its aspect, and a window that is still being created or resized reports 0. The quotient reached vkCreateImage as an overflowing extent and computePointerBounds() then mapped touches into those bounds. An unmeasured window now keeps the fixed 16:9 target and an empty touch region until the next sized frame.

2. Save Integrity: Mirror Validation, Atomic Writes, Checkpoint Restore

  • validateSaveDir() reads the header: it accepted any regular file whose name merely contained GameData.bin and only checked that it was 512 bytes, so a torn write, a Dolphin byte-order save, or a leftover <slot>.bak could bless a tree the game cannot load and be promoted over live NAND. It now asks the Dolphin converter whether the blob is a native, checksum-valid save inside the real size band, and only a directory can satisfy a recursive step.
  • Replacing rename on every host: writeFileAtomic() and copyFileVerified() finished with rename(), which POSIX replaces through but the Windows C runtime refuses on an existing target, so the host tests were exercising a failure path the device never takes. Both, and port_save_mario_state(), now go through renameReplace(); the checkpoint writer no longer removes the live file before the rename or builds its strings on the JKR heap that scene teardown owns.
  • Stage names compared by contents: the checkpoint restore compared them by pointer. SceneControlInfo rewrites mStage in place, so the pointer never changed and the "new stage" edge fired on every frame; an empty stage name no longer matches a state file that stored none.

3. Netplay Peer Address Published Under Its Lock

mPeerIp, mPeerPort, mRoomName and mHostPort were written by the RX worker and read or cleared by the game thread with no synchronisation, while the class already owned a mutex used only for remote pad state. A std::string being reassigned concurrently is neither empty nor the value that was written, so a beacon reply could go to a half-updated address and a disconnect could clear a peer another thread was mid-send through. All four fields now move through peerSnapshot() / setPeer() / roomName() / hostPort() under that mutex, and call sites that read a member three times in a row take one snapshot instead.

4. Cooker Bounds Every Count an Archive Stores

  • Runaway counts stopped: entry counts, table offsets and string offsets all come out of the file, and the swappers iterated them as written - a damaged bank asking for a billion values ran for hours instead of ending with the buffer. _Span() now answers only positions wholly inside the buffer, the velocity regions stop at its end, and sw16() / sw32() refuse a negative offset.
  • A damaged RARC fails instead of shipping: a slice running off the end read short rather than failing, so the cooker rebuilt the data section around the truncated blob and wrote an archive that is self-consistent and missing its tail. _tables() now checks the header, the directory, file and string tables and the data section against the bytes actually present, before any of them is used; the same bounds cover entries() and swap_rarc(). The desktop cooker and the copy bundled into the app are held byte-for-byte identical by a test.

5. Build Correctness and the Version You Actually Report

  • assert() is compiled in again: NDEBUG=1 and -O3 were force-defined for every target, so a Debug configure produced fully optimised, check-free, undebuggable code. Both now follow CMAKE_BUILD_TYPE; the shipped Release / RelWithDebInfo bytes are unchanged.
  • Owned code compiles with -Wall -Wextra: the platform/ layer inherited the decompiled SDK's 33 -Wno-* options, which switched the umbrella warnings off for code that is ours, and the flags were ordered so a -Wno-* placed before the group was re-enabled by it. The SDK keeps its suppressions (it is translated 32-bit GameCube code), platform/ is warned on, and what the compiler then had to say is answered in the sources. One documented suppression survives, in cmake/PortDiagnostics.cmake.
  • The drawer prints the packaged version: the settings drawer showed a hand-written v0.6.0 while the APK was packaged as 0.0.4a, so a bug report described a build that does not exist. The version now comes from AndroidManifest.xml at configure time as PORT_APP_VERSION, and a test refuses any version literal in owned sources.

6. Tooling and Supply Chain

  • build_installer checks platform-tools member names as they resolve and raises on ../, absolute entries or symlinks instead of extractall()-ing them; tools/fetch_openxr.sh pins version, byte size and SHA-256, verifies the archive before anything is unpacked and extracts member by member (that loader is compiled into libgame.so).
  • The packager now refuses to sign when libgame.so, libmain.so, libreserve.so or libinstaller.so is missing, where it used to warn and carry on - a partial build tree produced a signable, uploadable APK with no game in it.
  • desktop-installers.yml runs the structure checks on the downloaded APK instead of only checking that it exists, and release.yml no longer interpolates the dispatch tag or the NDK path into shell text; the tag is validated by tools/release_tag.py against an anchored pattern.

7. Test Coverage

New viewport_bounds_test and mario_state_test exercise the resize guard and the checkpoint writer, test_app_version.py pins the manifest as the only source of the version, and test_ci_targets.py now refuses a workflow that builds a target no add_test() registers or omits one that does. Six files that had outgrown the repository's own 300-line gate were split along conventions the tree already uses; ctest names, CI target lists and every assertion count are unchanged.


Package Details & Verification

  • Package: com.openport.galaxyandroid
  • Version: 0.0.4b (VersionCode: 18)
  • Target Backend: Pure Vulkan 1.0 (No OpenGL fallback)
  • APK SHA-256: F686210334BEFBDC453E5F2912F5BD03EAF009413F9A1DAB59119B6E6935E49A
  • APK Size: 18,081,963 bytes
  • Signer SHA-256: 78AED23B0C12EB62D8E47C4330D334F12CE7BB69456DFE58CD941555D43D9E8F (single signer)
  • Channel: tester sideload build, android:debuggable="true" so the wrap.sh / libreserve.so address reservation runs. Not a store upload.
  • Verified on this machine: mobile ARM64 build exit 0 with 0 warnings, 16/16 ctest cases, 92 in-tree unittest cases, 144 tooling unittest cases (1 skipped), LOC gate 443/443 files compliant, libgame.so embedding v0.0.4b and no stale version literal.

Update Compatibility - please read

The published v0.0.4 and v0.0.4a APKs were signed with the local Android debug certificate (CB60845D8E122C98A992D8617B7A43D31D6DBC76E2157CA75916FD76F160BC8A). This build is signed with the tester release certificate 78AED23B…, the identity the tester-key 0.0.4-delta builds carry.

  • Updating a tester-key 0.0.4-delta install: works in place, data preserved.
  • Updating a GitHub-installed v0.0.4 / v0.0.4a APK: fails with a signature mismatch. Uninstall first. Whether your saves survive depends on where the data lives: /sdcard/GalaxyData is shared storage and an uninstall leaves it alone, but anything under /sdcard/Android/data/com.openport.galaxyandroid/files is removed with the app. Back your saves up first either way.
  • The compromised historical key 1883CD60… is refused by the packaging gate and was not used here.

Still Open (owner / hardware actions)

  • Production signing key rotation: the historical key is still reachable in git history at 1a9b9a76d and b4b222b60.
  • On-device verification of these fixes: no headset or phone was attached for this release, so the rendering and save fixes above are proven by host tests, the mobile build and code review only.
  • Netplay pairing authentication and the NetplayPackets.cpp sequence-number reset.
  • CI still pins NDK r25c while the local build uses 28.2.13676358; no Linux/TSan/UBSan legs.

Legal Notice: You must provide your own legally dumped optical disc image. No proprietary assets or Nintendo code/binaries are included in this package.

Don't miss a new Galaxy-Android release

NewReleases is sending notifications on new releases.