GeneralsX - Android Build Instructions (arm64-v8a)¶
Status: THE GAME RENDERS AND PLAYS. As of 2026-07-07 the full game runs on a Galaxy Tab S7+ (Adreno 650): main menu with animated shell map, skirmish lobby, and a live skirmish match at native 2800×1752, ~30-60 FPS — via DXVK (D3D8→Vulkan) on a bundled Mesa Turnip driver loaded rootlessly through libadrenotools. See Current Status, the rendering pipeline, and Known issues & remaining work.

Continuing this work? This document is the complete handover: the rendering pipeline is the mental model, and Development Loop, Debugging & Traps carries the hard-won lessons from the sessions that got the port here. Touch controls (gestures, state machine, design rationale, debugging): docs/port/TOUCH_CONTROLS.md. The story of how all this was discovered — walls, root causes, lessons: The Android Journey · Bugs & Lessons.
Prerequisites¶
System Requirements¶
- Ubuntu host (primary; this guide is Ubuntu-specific for paths/package names)
- Android device: arm64-v8a, API 29+ (tested on Galaxy S22 and Galaxy Tab S7+, see Devices Profiled)
- ~10 GB free disk space (NDK + SDK + vcpkg build artifacts)
1. Android NDK r27 LTS + SDK¶
Install the command-line tools and platform-tools under ~/Android/Sdk, then pull the pinned
NDK via sdkmanager:
sdkmanager --sdk_root=$HOME/Android/Sdk "platform-tools" "ndk;27.2.12479018"
Verify:
ls ~/Android/Sdk/ndk/27.2.12479018/build/cmake/android.toolchain.cmake
~/Android/Sdk/platform-tools/adb --version
2. Gradle 8.9¶
Download the Gradle 8.9 distribution and unpack it to ~/Android/gradle/gradle-8.9 (the
packaging step drives gradle assembleDebug directly, no wrapper is checked in).
ls ~/Android/gradle/gradle-8.9/bin/gradle
3. JDK 21¶
sudo apt install openjdk-21-jdk
java -version
4. vcpkg (full clone)¶
A full (non-shallow) vcpkg clone is required — the overlay triplet and ports registry lookups need history/manifest files a shallow clone omits.
git clone https://github.com/microsoft/vcpkg.git ~/vcpkg
~/vcpkg/bootstrap-vcpkg.sh
5. cmake, ninja¶
sudo apt install cmake ninja-build
6. Environment variables¶
Add to ~/.zshrc (or the equivalent shell profile):
# Android port toolchain
export ANDROID_SDK_ROOT=$HOME/Android/Sdk
export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/27.2.12479018
export VCPKG_ROOT=$HOME/vcpkg
export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$HOME/Android/gradle/gradle-8.9/bin:$PATH
7. Verify the environment¶
./scripts/build/android/check-android-env.sh
This checks ANDROID_NDK_HOME (and warns if the NDK isn't r27 LTS), ANDROID_SDK_ROOT/adb,
VCPKG_ROOT (full clone, not shallow), cmake/ninja/java, and warns (non-fatally) if no
device is attached.
Building¶
Clone the Repository¶
git clone https://github.com/ammaarreshi/Generals-Mac-iOS-iPad.git GeneralsX
cd GeneralsX
Submodules (required for the renderer)¶
git submodule update --init --recursive references/fadi-labib-dxvk references/libadrenotools
references/fadi-labib-dxvk is the DXVK fork that gets Patches/dxvk-android.patch applied at
build time (Turnip loader + Android WSI surface path); references/libadrenotools is the
rootless custom-Vulkan-driver loader.
Configure and Build¶
cmake --preset android-vulkan
cmake --build build/android-vulkan --target z_generals -j$(nproc --ignore=1)
The android-vulkan preset (CMakePresets.json) builds arm64-v8a, API 29+, against the
arm64-android overlay vcpkg triplet, with SDL3 + OpenAL + FFmpeg enabled. The DXVK
cross-build (meson, libdxvk_d3d8.so/libdxvk_d3d9.so) is driven from cmake/dx8.cmake as
part of this build; it applies Patches/dxvk-android.patch idempotently to the submodule.
Renderer support libraries (one-time per checkout)¶
./scripts/build/android/build-adrenotools.sh # libadrenotools + linker-namespace hooks
./scripts/build/android/fetch-turnip.sh # pinned Mesa Turnip ADPKG (Vulkan 1.3, MIT)
Both outputs are packaged into the APK by package-android-zh.sh; nothing binary is
committed to git.
The game builds as a shared library (SDL3's Android model runs the game inside
SDLActivity, not as a standalone executable):
build/android-vulkan/GeneralsMD/Code/Main/libmain.so
Packaging¶
./scripts/build/android/package-android-zh.sh [--install]
This script:
- Runs
check-android-env.sh - Verifies
libmain.sois a real arm64 build exportingSDL_main(readelf -h+nm -D, not just a nonzero exit code — a silent wrong-arch or missing-symbol build has bitten this port before) - Copies
libmain.so,libSDL3.so,libSDL3_image.so,libopenal.so(andlibgamespy.soif present) intoandroid/app/jniLibs/arm64-v8a/ - Copies the SDL3 Android Java glue into
android/app/sdl-java/ - Runs
gradle assembleDebug - With
--install, runsadb install -rand grants storage permissions
Output APK (~225 MB debug build):
android/app/build/outputs/apk/debug/app-debug.apk
App id: com.generalsx.generalszh. Main activity: .GeneralsXZHActivity.
Assets¶
The engine needs the retail Zero Hour .big archives plus the base-game (Generals) data
under ZH_Generals/, and staged fonts. Two ways to get there:
Option A — Steam (scripted)¶
./scripts/get-assets.sh
This is Steam-only — it expects a Steam library with Zero Hour installed and copies from there. If you don't have a Steam copy, use Option B.
Option B — Manual copy (retail disc/EA copy)¶
If you have a non-Steam retail install (e.g. an EA copy on USB media), lay it out manually:
~/GeneralsX/GeneralsZH/ # Zero Hour install: *.big archives + Data/
~/GeneralsX/GeneralsZH/ZH_Generals/ # base Generals game data (REQUIRED alongside ZH)
Copy the .big archives (generalszh.big, W3DZH.big, MapsZH.big, AudioZH.big, etc.)
and Data/ from the Zero Hour install into ~/GeneralsX/GeneralsZH/, and the base game's
files into ~/GeneralsX/GeneralsZH/ZH_Generals/. This is exactly the layout
push-assets-android.sh (below) expects, and matches the desktop-build asset layout
documented in MACOS.md.
Fonts¶
./scripts/build/ios/stage-fonts.sh
Stages Liberation fonts (renamed to match the retail font names, e.g. arial.ttf) to
~/GeneralsX/ios-staging/fonts — reused as-is for Android (same fontconfig-free
fonts/-directory font resolution as iOS).
Push to device¶
./scripts/build/android/push-assets-android.sh [ASSET_DIR]
- Defaults to
~/GeneralsX/GeneralsZH(override with the positional arg orGX_FONTSenv var for the fonts dir). - Requires a connected device (
adb get-state). - Stages a filtered copy (excludes Windows-only junk:
.dylib/.so/installers/redist/etc. — same exclusion list as the iOS packaging script) plus the staged fonts, thenadb push --syncto/sdcard/GeneralsZH. - Pushes ~2.9 GB in ~67 seconds over USB (observed).
- Verifies afterward by listing
.bigfiles,fonts/, and the requiredZH_Generals/dir on the device.
Device Setup¶
USB debugging¶
Enable Developer Options + USB debugging on the device, connect via USB, accept the RSA fingerprint prompt, and confirm:
adb devices
Samsung sideload gotcha¶
Samsung devices commonly reject a debug-signed sideload with:
INSTALL_FAILED_VERIFICATION_FAILURE
Fix (one-time, per device):
- Disable Play Protect's install-time verifier for adb installs:
adb shell settings put global verifier_verify_adb_installs 0 - Turn off Samsung Auto Blocker on-device (Settings -> Security and privacy -> Auto Blocker) — it independently blocks unauthorized app installs regardless of the verifier setting above.
Headless Verification (Phase 2 gate)¶
Two harnesses exist. Both prove the non-graphics engine (CompatLib, both filesystems, .big parsing, INI loading, deterministic sim loop) runs correctly on-device — no rendering required.
1. Replay playback harness¶
./scripts/build/android/run-headless-replay.sh <replay.rep> [timeout_s]
Pushes a .rep to the device, launches -headless -replay <path>, and polls for completion
via two independent signals: an exit-code file
(/sdcard/GeneralsZH/last-run-exitcode.txt, written by gxRedirectStdioToLogcat's caller in
GeneralsMD/Code/Main/SDL3Main.cpp after GameMain() returns) and a logcat completion
marker (tag GeneralsX). Reports PASS (exit 0) / FAIL / timeout, and dumps the sim-evidence
log lines (replay progress, frame/game-time, any CRC mismatch).
2. Self-recorded AI-vs-AI skirmish (the path that actually reaches exit 0)¶
A macOS-recorded replay played back on Android hits a genuine cross-platform determinism mismatch (bionic libm vs Apple libm transcendental-function drift accumulating over hundreds of frames — expected and documented, not an Android defect: see ANDROID_PORT_FINDINGS_2026-07-06.md §7). To get a same-platform, bit-exact replay, the engine can record its own AI-vs-AI skirmish headlessly and then play that recording back on the same device:
adb shell "am start -n com.generalsx.generalszh/.GeneralsXZHActivity \
--es args '-headless -skirmishReplay Maps/Whiteout.map -skirmishFrames 1500'"
This fires a 2-AI free-for-all skirmish on the named map, runs it to the given frame cap
(fixed seed, deterministic), and writes a .rep under the app's private storage. Pulling that
file and feeding it back into run-headless-replay.sh closes the loop: Android-recorded,
Android-played, no cross-platform libm involved.
3. One-command smoke test (record + replay + assert)¶
./scripts/build/android/smoke-test-android.sh [timeout_s]
Wraps both harnesses above into a single regression gate against the currently-installed
app (no build/install step): records a fresh skirmish, pulls the .rep, replays it through
run-headless-replay.sh, and asserts exit 0, no CRC-mismatch/desync in logcat, and a
non-trivial amount of game time simulated. Prints SMOKE TEST: PASS / SMOKE TEST: FAIL:
<reason>. Run it after any engine change as a quick "is the headless sim still correct?"
check.
Current Status¶
| Phase | Scope | Status |
|---|---|---|
| Phase 0 | Renderer route research + decision | ✅ Done — require-turnip (see renderer-route decision) |
| Phase 1 | Build system, Gradle shell app, packaging, app launches | ✅ Done — app runs SDL_main on-device (logcat: Running main function SDL_main from library .../libmain.so) |
| Phase 2 | Headless verification (non-graphics engine) | ✅ Done — Android-recorded AI-vs-AI skirmish replay (Maps/Whiteout.map, 1500 frames / 00:50 game time) plays back to exit 0, no CRC mismatch |
| Phase 3 | Renderer bring-up (DXVK cross-build, Turnip bundling, WSI, crash fixes) | ✅ Done — the game renders and plays (2026-07-07, Tab S7+/Adreno 650): main menu + shell map, skirmish lobby, live match, ~30-60 FPS at 2800×1752, driven end-to-end by touch |
| Phase 4+ | Touch controls polish, lifecycle (pause/resume), audio verification, perf, non-Adreno devices | ⏳ Next — see Known issues & remaining work |
Gate evidence¶
- P0: route decided —
require-turnip(native-arm64 dxvk-native + bundled Mesa Turnip via libadrenotools). See §5 of the renderer research doc. - P1: app launches and runs the engine's
SDL_mainentry point on-device (confirmed viaadb logcat). - P2: Android-recorded skirmish replay (
Maps/Whiteout.map,-skirmishFrames 1500) played back viarun-headless-replay.shon the Galaxy Tab S7+ → exit 0, 1500 frames simulated (00:50 game time), no CRC mismatch.
Devices profiled¶
| Device | SoC / GPU | Vulkan | textureCompressionBC |
Notes |
|---|---|---|---|---|
| Galaxy S22 (SM-S908B, Exynos) | Xclipse 920 | 1.3 | FALSE | No BCn, no Turnip (not Adreno) — asset-transcode fallback territory |
| Galaxy Tab S7+ (SM-T970) | Snapdragon 865 / Adreno 650, Android 13 | 1.3 | FALSE on stock driver | Empirically corrects the research doc's [CLAIM] that Adreno 650 stock exposes BCn — it does not. Makes require-turnip necessary, not optional, for this device. Primary Phase 3 target (Adreno 650 = Turnip's rock-solid 6xx tier). |
Rendering Pipeline (Phase 3)¶
Game (DirectX 8 calls)
→ libdxvk_d3d8.so / libdxvk_d3d9.so (DXVK 2.6 fork, meson cross-build,
Patches/dxvk-android.patch)
→ Vulkan 1.3 via Mesa Turnip (bundled libvulkan_freedreno.so,
loaded rootlessly by libadrenotools —
stock Adreno 650 driver only exposes 1.1)
→ VK_KHR_android_surface → ANativeWindow (SDL3 SurfaceView)
Key mechanics, each of which was a hard-won lesson:
- Turnip via adrenotools (
SDL3Main.cpp: gxSetupTurnipDriver+ the patch'svulkan_loader.cpp): the app stages the bundled Turnip .so into its private files dir and exportsGENERALSX_ADRENOTOOLS_*env vars; DXVK's Vulkan loader dlopensadrenotools_open_libvulkaninstead of the stocklibvulkan.so. - One Vulkan loader, not two (patch's
wsi_window_sdl3.cpp): SDL loads its own copy of the system Vulkan loader, soSDL_Vulkan_CreateSurfacewould hand DXVK's TurnipVkInstanceto a different loader. On Android the patched WSI fetches theANativeWindow*from SDL window properties and callsvkCreateAndroidSurfaceKHRresolved from DXVK's own loader. - Deferred surface creation (
SDL3Main.cppsetsDXVK_CONFIG=d3d9.deferSurfaceCreation = True): anANativeWindowaccepts exactly one producer connection. The engine's device-creation retry loop (W3DDisplay::init / dx8wrapper) can construct a doomed first device — its depth-format defaultD3DFMT_D32is rejected by the driver — whose implicit swapchain has already claimed the window; the failure path leaks the connection and every subsequent device then fails forever withVK_ERROR_NATIVE_WINDOW_IN_USE_KHR(black screen, game running headless behind it). With deferred creation only the device that actually presents ever touches the window. - Shared libc++ (
meson.buildpatch hunk +ANDROID_STL=c++_shared): DXVK must NOT statically link libstdc++ on Android, or aDxvkErrorthrown inside DXVK has its own RTTI and gets swallowed bycatch(...)in libmain.so instead of surfacing the real error. - Zero-initialized heap (
GameMemory.cpp): desktop builds route globalnewthrough the engine's memory pool, which memsets allocations to 0 — and the 2003 codebase silently relies on that. Bionicmallocreturns dirty memory; Android now usescallocso the hundreds of constructors that leave members uninitialized keep working.
Debugging tip: dxvk.conf without rebuilding¶
DXVK reads $PWD/dxvk.conf, and the game's working directory is /sdcard/GeneralsZH — so
you can A/B any DXVK option on-device with no rebuild:
printf 'd3d9.deferSurfaceCreation = True\n' > /tmp/dxvk.conf
adb push /tmp/dxvk.conf /sdcard/GeneralsZH/dxvk.conf
adb shell am force-stop com.generalsx.generalszh
adb shell am start -n com.generalsx.generalszh/.GeneralsXZHActivity
(Note: the DXVK_CONFIG env var set by SDL3Main.cpp wins over the file for the same key;
other keys merge. This is exactly how the deferSurfaceCreation root cause was confirmed
before baking the fix into code.)
Development Loop, Debugging and Traps¶
Iteration cycle (~2 min once the toolchain is set up)¶
cmake --build build/android-vulkan --target z_generals -j$(nproc --ignore=1)
./scripts/build/android/package-android-zh.sh --install
adb shell am force-stop com.generalsx.generalszh
adb logcat -c && adb shell am start -n com.generalsx.generalszh/.GeneralsXZHActivity
Key files (the Android-specific surface area)¶
| File | Role |
|---|---|
GeneralsMD/Code/Main/SDL3Main.cpp |
Entry point: Turnip staging, env setup (DXVK_WSI_DRIVER, DXVK_CONFIG, adrenotools vars), SDL init, window creation |
GeneralsMD/Code/GameEngineDevice/Source/SDL3GameEngine.cpp |
Event loop; GX_TOUCH_UI guards the touch→mouse gesture translator + background render-pause (shared with iOS). Full design/debugging doc: docs/port/TOUCH_CONTROLS.md |
Patches/dxvk-android.patch |
ALL DXVK changes (loader, WSI, meson, portability guards). Applied idempotently by cmake/dx8.cmake to the references/fadi-labib-dxvk submodule |
cmake/dx8.cmake |
Drives the DXVK meson cross-build inside the cmake build |
scripts/build/android/*.sh |
env check, adrenotools build, Turnip fetch, APK packaging, asset push, headless replay harness |
android/app/ |
Gradle shell app (SDLActivity subclass GeneralsXZHActivity) |
Rule: the DXVK submodule working tree must stay byte-identical to the patch. If you
edit DXVK code, regenerate: cd references/fadi-labib-dxvk && git diff >
../../Patches/dxvk-android.patch, and verify diff <(git diff)
../../Patches/dxvk-android.patch says identical.
Debugging toolbox (earned the hard way)¶
- Game log lines all come out under logcat tag
GeneralsX(stdio is redirected); DXVK's owninfo:/warn:/err:lines are inside that tag too. The pump filters[INI]/[SUBSYS]/[GX-ISSUE144]boot spam by default (~85% quieter); launch with theGENERALSX_VERBOSEenv set for the full firehose.err:/warn:/ERROR/FATALare never filtered. - Boot health check (after ~40 s):
adb logcat -d | grep -cE "Actual swapchain properties"→ must be ≥1;grep -c NATIVE_WINDOW_IN_USE→ must be 0;grep -c "beginning of crash"→ 0. - A/B any DXVK option with no rebuild — see the dxvk.conf tip above. This is how the black-screen root cause was confirmed in minutes.
- Screenshots work:
adb exec-out screencap -p > shot.pngcaptures the Vulkan SurfaceView fine. A ~20 KB PNG = black screen; multi-MB = real content. - Touch via adb:
input taphas a 0 ms down-up — the gesture translator's deferred click lands in the same frame as the hover and menus DON'T activate. Useadb shell input swipe X Y X Y 150to emulate a real finger. Menu buttons need ONE such tap (if two are needed again, the translator regressed). - Audio without ears:
adb shell dumpsys media.audio_flinger— look for the app's uid with tracks not in standby. The OpenAL backend list shows in logcat tagopenal("Supported backends: opensl, null, wave" — opensl must be chosen). - Two devices in DXVK logs (
grep -c "Device properties:"): two is currently NORMAL at boot and load-bearing — see #8 and Known issues below. Three+ means something new is wrong.
Traps that will bite again if forgotten¶
__ANDROID__also defines__linux__. Any#if defined(__linux__)desktop workaround silently applies to Android — that's exactly how audio got muted (a desktop-onlyALSOFT_DRIVERSoverride forced the null backend). When touching platform guards, always ask "and what does this do on Android?"- In release builds
WWASSERTis a no-op — engine invariants like "device must be null before Create_Device" silently don't hold. - DXVK can return
D3D_OKwith a null back buffer (_Get_DX8_Back_Buffer); guard every back-buffer consumer (seeW3DSmudge.cppfor the pattern: degrade the effect, don't crash). - The 2003 codebase assumes
newreturns zeroed memory (desktop routes through a memset-ing pool allocator; Android usescalloc). A nonsense-pointer SIGSEGV in a constructor-adjacent path usually means "uninitialized member desktop got away with" (see thePathfinder,W3DBridgeBuffer,W3DSmudgeManagerfixes). - Samsung sideload verification failures → see Device Setup.
Known Issues and Remaining Work¶
Resolved during the 2026-07-07 session (kept here so future readers know these were real and are fixed, not never-encountered):
Black screen /→ deferred surface creation (see rendering pipeline point 3).VK_ERROR_NATIVE_WINDOW_IN_USE_KHRloopTouch needed two taps per menu button→ the iOS touch→mouse gesture translator (tap-select, drag-box, long-press right-click, two-finger pan, pinch zoom) is now enabled on Android (GX_TOUCH_UIinSDL3GameEngine.cpp); a real finger activates in one tap. (adb shell input tapstill needs two — its 0 ms down-up delivers hover+click in one frame; useinput swipe x y x y 150to emulate a real tap.)First tap after boot swallowed / pan-zoom interference / jitter drag-boxes→ three gesture fixes (commitffa4645e9): the main menu auto-reveals on touch platforms (desktop hides it until the mouse moves, which cost touch users their entire first tap mid-reveal-transition); two-finger pan and pinch now mode-lock (whichever moves first wins — no more zoom ticks while panning or camera drift while pinching); and gesture thresholds are physical (~3 mm, DPI-scaled) instead of 8 px (~0.7 mm on this panel — fingertip jitter turned taps into accidental drag-boxes).Silent audio→ Android inherited the Linux desktopALSOFT_DRIVERS=pulse,alsa,...workaround (__ANDROID__also defines__linux__), forcing OpenAL to the null backend. Android now keeps default selection and picks OpenSL ES; audio_flinger shows live tracks.(~72k lines / 12 min) → N-patches are Windows-only.D3DRS_PATCHSEGMENTSwarn flood- Basic lifecycle: home → resume survives with the same process and rendering intact (the iOS background render-pause now also guards Android).
Known issues (2026-07-07):
MISSING: 'GUI:CustomMission'label in the Solo Play menu — upstream localization gap, cosmetic.- Two-device boot / 16-bit depth (#8):
the engine creates a doomed first device (its
D3DFMT_D32default is rejected) then a survivingD3DFMT_D16device. Investigation established the two-device dance is load-bearing — the 3D shell map renders black with a single device regardless of depth (D24S8/D16) or present mode; the first device's teardown primes Turnip's presenter. It is a DXVK/Turnip driver-lifecycle issue, not a depth/format one (an earlier "default D24S8" idea was disproven on-device). Cosmetic/boot-time only; the game renders fine. - Log spam — fixed (
9455765a2): the stderr→logcat pump now filters[INI]/[SUBSYS]/[GX-ISSUE144]boot traces by default (~85% quieter). Set theGENERALSX_VERBOSEenv to restore the full firehose;err:/warn:/ERROR/FATALare never filtered. - Audio quality/mix verified only as "tracks are playing" (audio_flinger); not yet listened to by a human.
Remaining work (Phase 4 candidates) — tracked as issues on fadi-labib/Generals-Android:
- Touch gesture tuning under real gameplay (drag-box vs pan feel, long-press timing) — #11.
- Campaign / Generals Challenge / video playback testing (untested on Android) — #12.
- Performance: FPS currently ~30-60 at native 2800×1752; render-scale option — #13.
- Non-Adreno devices: Xclipse 920 (Galaxy S22) has no BCn texture support and no Turnip — needs an asset-transcode fallback (Phase 0 research) — #9.
- Deeper lifecycle: rotation, split-screen, low-memory kills, save-on-pause.
Regression Checklist (definition of "still works")¶
After any change, verify on-device:
- Boot reaches the main menu with the animated shell map (screenshot is multi-MB).
Actual swapchain propertiesin logcat; zeroNATIVE_WINDOW_IN_USE; zerobeginning of crash.- One 150 ms swipe-tap on SOLO PLAY opens the submenu (single-tap works).
- Skirmish: map select → PLAY GAME → in-game HUD with FPS counter and running game clock.
dumpsys media.audio_flingershows the app's tracks active (not standby).- HOME → relaunch: same PID, rendering resumed.
Renderer-Route Decision¶
Full research and decision: docs/WORKDIR/planning/ANDROID_RENDERER_RESEARCH_2026-07.md,
decision in §5. Route: require-turnip — native-arm64 dxvk-native (mainline DXVK 2.x,
SDL3 WSI) with bundled Mesa Turnip loaded rootless via libadrenotools, targeting Qualcomm
Adreno flagships (Vulkan 1.3 floor). Codebase findings that fed this plan:
docs/WORKDIR/planning/ANDROID_PORT_FINDINGS_2026-07-06.md.
Related Scripts¶
| Script | Purpose |
|---|---|
scripts/build/android/check-android-env.sh |
Verify NDK/SDK/vcpkg/cmake/ninja/java + device state |
scripts/build/android/package-android-zh.sh |
Verify libmain.so artifact, embed .so's + SDL3 Java glue, gradle assembleDebug, optional install |
scripts/build/android/push-assets-android.sh |
Push filtered retail assets + staged fonts to /sdcard/GeneralsZH |
scripts/build/android/run-headless-replay.sh |
Push + launch -headless -replay, poll for completion, report PASS/FAIL |
scripts/build/android/smoke-test-android.sh |
One-command regression gate: record a fresh skirmish, replay it, assert exit 0 + CRC-clean |
scripts/get-assets.sh |
Steam-only asset fetch |
scripts/build/ios/stage-fonts.sh |
Stage Liberation fonts under retail font names (shared with iOS) |
CMakePresets.json (android-vulkan) |
Build preset (arm64-v8a, API 29+, SDL3 + OpenAL + FFmpeg) |
See the Dev Blog for detailed session-by-session technical notes.