Brings opponent_substitution.h and cop_traffic_disable.h - the
native32/launcher branch's proven, live-confirmed multiplayer-mod hooks
(opponent car/color substitution, cop dispatcher removal with a live
before/after roadblock proof, full traffic elimination) - into this repo
as source, plus DebugFeatures.kt/DebugMenuOverlay.kt (debug-only money/menu
scaffold) and this session's GameActivityMain.kt thread-safety fix
(BroadcastReceiver.onReceive wrapping native calls in
gameGLSurfaceView.queueEvent{} instead of calling straight into JNI from
the main thread - a real cross-thread race, independently useful on both
branches).
Two real conflicts, both resolved to preserve architecture rather than
naively keep both sides:
- main.cpp: kept this branch's own structure. The new .h files, like
lan_event_injection.h before them, are NOT #include'd or wired into
JNI_OnLoad - every hook in both files resolves and calls raw
"libapp_base + OFFSET" function pointers, which on a real dlopen'd
libapp.so is a valid jump target but here is only Unicorn-backed guest
memory - including them unmodified would crash immediately, not just
misbehave. Left as source for the same GuestFn/CallGuestFunction porting
session already planned for lan_event_injection.h.
- GameActivityMain.kt: trivial, a code comment disagreeing on this build's
own package id (nfs13_arm here vs nfs13_mod on native32) - merged to
document both.
Verified after resolving: compileTranslatedDebugKotlin,
compileNative32DebugKotlin, and externalNativeBuildDebug (arm64-v8a +
x86_64, confirmed via a fresh main.cpp.o/mpcore.so rebuild, not a cache
hit) all succeed - this merge compiles clean on both Kotlin flavors and
the native side, not just resolves textually.
Co-Authored-By: Claude <noreply@anthropic.com>
Subtask 2.1 - opponent_substitution.h. Hooks
OpponentCollection::PopulateFromProperties (sub_2B649C) and overwrites
Opponent.CarDescriptionName/ColourIndex at map-load time so a real lobby
player's car and colour take an AI opponent's slot. Confirmed live: a Ford
Focus RS500 was forced into a slot that held a different car and colour
before the hook, on a real (non-synthetic) replay. The starting-grid
placement code walks a different vector entirely, so there is an untraced
intermediate spawn/resolve step - it was not needed, because whatever it is,
it reads these same fields. The header records that honestly rather than
claiming the whole chain is understood.
Subtask 2.4 - cop_traffic_disable.h. Removes police from multiplayer races.
This corrects an earlier hook that only covered sub_F7E9C: tracing callers
showed sub_F5EA4 is the per-tick dispatcher and branches on a live flag into
two schedulers, both reaching the same SpawnCopCar. Hooking one leaf left the
other unblocked, which is why cops were still appearing while the hook logged
nothing. Hooking the dispatcher covers both leaves and skips only the
spawn decision, leaving the CopManager's other per-tick bookkeeping alone.
Civilian traffic and AI opponents are untouched - both are load-bearing for
multiplayer.
Debug menu scaffold: DebugFeatures.kt is the single switch deciding whether
debug UI is built at all; DebugMenuOverlay.kt is the Compose panel. Its Apply
deliberately touches no game memory yet - no balance getter/setter has been
located (DEBUG_MENU.md section 3) - so it edits local state only rather than
pretending to work.
Stays on this feature branch, not master: toggle-flag and experimental work
does not belong on a release-ready branch.
Co-Authored-By: Claude <noreply@anthropic.com>
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>