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.hviaGX2HWFiltConv), not theGXTexFilterenum 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 reports0. The quotient reachedvkCreateImageas an overflowing extent andcomputePointerBounds()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 containedGameData.binand only checked that it was 512 bytes, so a torn write, a Dolphin byte-order save, or a leftover<slot>.bakcould 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()andcopyFileVerified()finished withrename(), 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, andport_save_mario_state(), now go throughrenameReplace(); 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.
SceneControlInforewritesmStagein 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, andsw16()/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 coverentries()andswap_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=1and-O3were force-defined for every target, so a Debug configure produced fully optimised, check-free, undebuggable code. Both now followCMAKE_BUILD_TYPE; the shipped Release / RelWithDebInfo bytes are unchanged.- Owned code compiles with
-Wall -Wextra: theplatform/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, incmake/PortDiagnostics.cmake. - The drawer prints the packaged version: the settings drawer showed a hand-written
v0.6.0while the APK was packaged as0.0.4a, so a bug report described a build that does not exist. The version now comes fromAndroidManifest.xmlat configure time asPORT_APP_VERSION, and a test refuses any version literal in owned sources.
6. Tooling and Supply Chain
build_installerchecks platform-tools member names as they resolve and raises on../, absolute entries or symlinks instead ofextractall()-ing them;tools/fetch_openxr.shpins version, byte size and SHA-256, verifies the archive before anything is unpacked and extracts member by member (that loader is compiled intolibgame.so).- The packager now refuses to sign when
libgame.so,libmain.so,libreserve.soorlibinstaller.sois 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.ymlruns the structure checks on the downloaded APK instead of only checking that it exists, andrelease.ymlno longer interpolates the dispatch tag or the NDK path into shell text; the tag is validated bytools/release_tag.pyagainst 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 thewrap.sh/libreserve.soaddress 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.soembeddingv0.0.4band 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-deltainstall: 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/GalaxyDatais shared storage and an uninstall leaves it alone, but anything under/sdcard/Android/data/com.openport.galaxyandroid/filesis 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
1a9b9a76dandb4b222b60. - 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.cppsequence-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.