arm64: flat guest mapping, audio, crash reporting, self-extracting data
The run of work that took the translated build from "boots" to "playable". Engine: - Flat guest mapping replaces the software MMU on aarch64 hosts. A 4 GiB PROT_NONE reservation lets a 32-bit guest address zero-extend safely, so tcg_out_qemu_ld/st short-circuit before tcg_out_tlb_read and the prologue materialises the base into X28. Measured 1.24x (51 vs 41 fps, interleaved A/B). Note the base must be set BEFORE UC_INIT - Unicorn inits lazily, and setting it after uc_open silently falls back to softmmu. - num_get<char> facet implemented, which was the real cause of the crash after the prologue race; a full race is now playable end to end. - Thread-stack free list + ReleaseThreadEngine, fixing the arena leak that showed up as a black screen when entering a race. kMaxGuestThreads 16 -> 64. - Real ARM32 FMOD now runs in-engine via the Java FMODAudioDevice bridge, with a per-thread JNIEnv. Two of the three blockers were our own single-image-era guards. Host/app: - Native crash handler: async-signal-safe, decodes the host fault back to a guest address, writes a report file and nothing else. CrashReportActivity picks it up on the NEXT launch, zips it, and offers to share. No backend, no automatic upload. - Game data ships inside the APK and self-extracts on first launch, so a tester installs one file and plays. Copy-to-.part-then-rename, with a free-space check up front. - EGL context preserved across pause, fixing black textures on resume. - Navigation bar hidden and re-hidden on focus gain; volume keys reported as system keys, checked before the loading-state gate. - x86_64 added to abiFilters: the ARM32 guest runs under tcg/i386 with no houdini in the path. The flat mapping is aarch64-only, so that host falls back to the software MMU - commented at the abiFilters line. Ignore rules added for app/translated/ (611 MB of signed release APK, which also carries the bundled OBB) and ostream_repro/build/. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -566,6 +566,53 @@ void glLinkProgram(GLuint program) {
|
||||
}
|
||||
|
||||
|
||||
// ---- FMOD bring-up trace (2026-09-21, task #67) ----
|
||||
//
|
||||
// The emulated engine now loads the game's REAL libfmodex/libfmodevent and
|
||||
// runs them, but FMOD never reaches output initialisation - Shim_dlopen logs
|
||||
// every call and there is not one, so `libOpenSLES.so` is never opened. The
|
||||
// question that cannot be answered by staring at our side: what does this
|
||||
// sequence look like on real ARM32 hardware, where sound works?
|
||||
//
|
||||
// The user's observation that audio starts right after the EA logo says the
|
||||
// chain runs early, so these four points should all appear near the start of a
|
||||
// native trace. Each logs its FMOD_RESULT (0 == FMOD_OK), which names the
|
||||
// failure directly if one of them is where the two runs diverge.
|
||||
//
|
||||
// Per-call logging is fine here, unlike __dynamic_cast below: these are
|
||||
// one-shot initialisation calls, not a quarter million per second.
|
||||
// NOTE: do NOT interpose dlopen here. The first attempt did, and it killed the
|
||||
// process before the game even started: the Android runtime dlopen()s
|
||||
// libart.so during startup, our wrapper could not resolve the real symbol that
|
||||
// early (RealSym goes through dlsym, which is not usable from a dlopen
|
||||
// interposer at that point), so it returned NULL and the runtime died on the
|
||||
// null handle - "Failed to dlopen libart.so", then SIGSEGV at address 0,
|
||||
// "wrap.sh terminated by signal 11". Exactly the hazard this file already
|
||||
// documents for pthread_once.
|
||||
//
|
||||
// It is also unnecessary: our own side already logs every dlopen through
|
||||
// Shim_dlopen. What the native run has to answer is where FMOD's INIT chain
|
||||
// goes, and the three interposers below do that without touching the loader.
|
||||
|
||||
// FMOD interposition was tried here on 2026-09-21 and REMOVED. Two failures,
|
||||
// both worth keeping as a warning:
|
||||
//
|
||||
// 1. Interposing dlopen killed the process at startup - the runtime's own
|
||||
// dlopen("libart.so") got our wrapper before it could resolve the real
|
||||
// symbol, returned NULL, and the app died on a null handle.
|
||||
// 2. libfmodex/libfmodevent live in the APP's linker namespace, which an
|
||||
// LD_PRELOAD'd agent cannot reach - neither dlsym(RTLD_NEXT) nor
|
||||
// dlopen(RTLD_NOLOAD) found a single FMOD symbol. The wrapper therefore
|
||||
// always took its fallback path, and that fallback REPLACED FMOD's
|
||||
// initialisation with a stub - silencing audio on the very device that
|
||||
// was supposed to serve as the working reference.
|
||||
//
|
||||
// The lesson is the measurement one: an instrument that cannot do the real
|
||||
// work must not stand in for it. Whether the native game reaches OpenSL is
|
||||
// answerable from OUTSIDE the process entirely, by looking for libOpenSLES.so
|
||||
// in /proc/<pid>/maps - no injection, no interference, no way to break what is
|
||||
// being measured.
|
||||
|
||||
// ---- __dynamic_cast rate (2026-09-19, task #58) ----
|
||||
// The emulated engine's own per-shim counter found __dynamic_cast making up
|
||||
// 49% of ALL shim crossings during a prologue load - 284,986 calls/sec. The
|
||||
|
||||
Reference in New Issue
Block a user