Skip to content

Sync with upstream and store Android saves in Android/media - #1

Open
outusuke wants to merge 78 commits into
izzy2lost:mainfrom
outusuke:main
Open

outusuke wants to merge 78 commits into
izzy2lost:mainfrom
outusuke:main

Conversation

@outusuke

Copy link
Copy Markdown
  • Merges HarbourMasters/Starship main (up to 3322643)
  • Moves saves and game files from Android/data to Android/media so
    Syncthing and other file syncers can reach them. MainActivity overrides
    getExternalFilesDir(null) so the engine's SDL path lookup matches the
    launcher's game directory.
  • CI: grant contents write for the release step, and skip the removed
    'tools' SDK package (use sdkmanager from PATH)

Existing installs are not migrated: saves, the ROM and sf64.o2r stay in
the old Android/data folder.

garrettjoecox and others added 30 commits March 20, 2026 23:53
Restores libultraship as a submodule at upstream's pin (eaaf9d0) instead of
the ~60k-line vendored copy this fork carried. Every fork patch in that copy
was audited and found superseded, dead, or an artifact of the old Android
flow:

  - os_eeprom.cpp's Android_GetSaveDir() hook is replaced by upstream's
    Ship::Context::GetPathRelativeToAppDirectory("default.sav")
  - interpreter.cpp's upfront 64MB posix_memalign buffer is replaced by
    upstream's lazy allocation at the real max texture size
  - MobileImpl's touch-area/camera additions and the Gui.cpp calls into them
    are unused by Starship; the on-screen controller becomes a virtual SDL
    joystick instead
  - gfxbridge.h differed only in line endings, OTR_ARCHIVE_INCLUDED was never
    referenced, and CrashHandler.h was already identical

Engine.cpp takes upstream's constructor ordering, which drops the Android
waitForSetupFromNative() polling: the launcher now generates sf64.o2r before
the game starts, so there is nothing to wait for. Android keeps only the
fullscreen default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The app used to require an sf64.o2r the user had produced on a PC with
Torch, then hand it over through a SAF folder grant backed by
MANAGE_EXTERNAL_STORAGE. Now the launcher takes the ROM itself, verifies
it, and runs Torch — already linked into the game library — to build the
archive on the device. No storage permission, no desktop step.

The app is rebuilt in Kotlin around that flow, matching Ghostship and
SpaghettiKart:

  LauncherActivity  unpack bundled files, take a ROM, extract, hand off
  GameAssets        where everything lives, plus the JNI extraction call
  MainActivity      the game and the on-screen controller overlay
  Mods/SavesActivity  managers reachable from the in-game menu

The overlay drives a virtual SDL joystick (src/port/android/AndroidBridge.cpp)
instead of synthesising input, so it goes through libultraship's normal
mapping and is remappable in game. That also replaces the look-around
area's old route into a fork-only libultraship camera API, which went
away with the vendored copy: it now drives the right stick.

Build side:

  - Groovy -> Kotlin DSL, AGP 8.13.2, Kotlin 2.2.10, NDK 30.0.15729638
  - config.yml, assets/yaml/ and starship.o2r are bundled as one
    reproducible gamedata.zip with a digest beside it, wired through the
    variant API so a missing engine archive fails the build loudly rather
    than shipping an APK with nothing in it
  - cmake/dependencies/android.cmake supplies what the NDK build has no
    package manager for. libultraship's own Android cmake only fetches
    SDL2 and leaves libzip, nlohmann_json, tinyxml2 and spdlog to one.
    It also pins SDL 2.32.10, because the 2.28.1 libultraship asks for
    calls ALooper_pollAll, which NDK 27 removed from the headers; the
    Java glue's SDL_MICRO_VERSION moves with it or SDLActivity refuses
    to start. Ogg and Vorbis are fetched too — the Android branch of the
    platform switch links them but never had a find_package.
  - the host-only Torch ExternalProject, the asset copy next to the
    binary and CPack are all skipped on Android
  - .gitignore's "build*" was swallowing build.gradle.kts

Verified: libStarship.so builds clean for arm64-v8a from an empty build
directory, and exports all six JNI entry points the Kotlin side declares.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The debug variant compiled 590 native files, Torch's Companion.cpp among
them, with no -O flag at all: the root CMakeLists sets CMAKE_C_FLAGS_RELEASE
but nothing for Debug, so Debug fell through to the compiler default of -O0.

That was survivable while Torch ran on a PC. It is not now that it runs on
the phone. Extraction is STL-heavy work over a 12MB ROM, and unoptimised it
made no visible progress — a profile of the stuck thread showed a bounded
~11-level recursion through Companion::ParseNode into the audio factories,
burning effectively all of its time in std::vector<uint8_t> copy and destroy.
Not a hang; just far too slow to ever finish.

Forcing Release for every variant resolves to -O2 -DNDEBUG on Android.
Extraction then completes in 74s on a Pixel 10a and the game boots.

isJniDebuggable goes with it: the native side is Release regardless now, so
there is nothing useful left to attach to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three fixes found by running the game on a device.

The overlay's Menu button did nothing. libultraship drives two separate
things (Gui::DrawMenu): Escape toggles a "Menu", F1 toggles a "MenuBar".
SpaghettiKart installs a Menu, so its overlay sends Escape, and that is
what this button was copied from. Starship installs a menu bar instead —
ImguiUI.cpp calls SetMenuBar() and never SetMenu() — so GetMenu() was null
and Escape was a no-op. F1 is also what the game's own hint tells players
to press. isMenuOpen() already read GetMenuOrMenubarVisible(), which covers
both, so the Mods button appearing with the menu kept working throughout.

add_subdirectory(libultraship) passed libultraship's own source directory
as its binary directory, which put libImGui.a and libstb.a in the source
tree. With one architecture that is untidy; Android builds every ABI from
the same tree, so the two raced over those archives and the arm64 link
failed on armeabi-v7a objects. It only linked at all the first time by
winning that race. Given a binary directory of its own, scoped to Android
so nothing else changes.

The mods and saves screens never applied window insets — the title sat
under the status bar and "Done" was half behind the navigation bar. They
came from SpaghettiKart, which has no padForSystemBars(); Ghostship calls
it in all three activities, and now so does this. The saves screen also
still described Controller Pak data, which Star Fox 64 does not have.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updated contact information from Discord to official website.
Move the game directory from Android/data to Android/media so Syncthing
and other file syncers can reach saves without special access.

MainActivity overrides getExternalFilesDir(null) so the engine's SDL
path lookup lands in the same directory the launcher uses.
Android port from izzy2lost/Starship, merged with HarbourMasters/Starship
main (up to 3322643), plus saves and game files moved from Android/data
to Android/media so Syncthing can reach them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.