Skip to content

Script/Code mod support with TinyCC#156

Open
Mickemoose wants to merge 3 commits into
JRickey:mainfrom
Mickemoose:code-mod-support
Open

Script/Code mod support with TinyCC#156
Mickemoose wants to merge 3 commits into
JRickey:mainfrom
Mickemoose:code-mod-support

Conversation

@Mickemoose

Copy link
Copy Markdown

Add TCC mod loader and workspace template

What this adds

The runtime side of TCC source-compiled mods. Mods drop into mods/
next to the executable as folders or .o2r archives. At startup the
engine compiles each mod's .c source via libtcc, links it against
BattleShip.def, and calls ModInit. The Mods menu has a Hot Reload
button that unloads everything, recompiles from disk, and re-runs
ModInit.

Files

  • port/mods/ (new): HookManager (per-mod hook ownership + safe
    hot-reload), SymbolResolver (engine symbol lookup by name),
    mod_bridge (mod_install_hook + mod_resolve_symbol exposed via
    BattleShip.def for mods to call).
  • port/hooks/ (new): event firing system. Mods subscribe with
    REGISTER_LISTENER from MOD_INIT. Engine events fire from
    port.cpp and gameloop.cpp.
  • port/audio/mixer.{c,h} (new): PCM voice mixer hooked into
    AudioPlayerPlayFrame so mods can play custom audio.
  • port/gui/PortMenu.cpp: adds a Mods section with a Reload button.
  • port/port.cpp: brings up SymbolResolver / HookManager at startup,
    mounts mods/, runs ScriptLoader CompileAll + LoadAll. Tears
    down in reverse on shutdown.
  • port/gameloop.cpp: fires the per-frame engine event so mods can
    hook into the frame loop.
  • tools/merge_mod.py (new): the asset preprocessor and TU
    amalgamator every mod's CMakeLists invokes. Walks .c includes,
    decodes PNG/WAV/AIFC assets to N64-format inline bytes, and
    produces the single .c file libtcc compiles.
  • tools/{aifc_to_wav,wav_to_pcm_inc_c,png_to_rgba16_inc_c, png_to_ia8_inc_c}.py (new): asset converters for authoring.
  • workspace/template/ (new): starter scaffold for new mods with
    manifest.json, CMakeLists, mod.h, demo.c, README.
  • cmake/ModO2R.cmake (new): ssb64_add_mod_o2r_target() helper
    that wires cmake --build . --target mod_<NAME>_o2r to
    torch.exe pack.
  • CMakeLists.txt: vendors MinHook via FetchContent for the runtime
    detour primitive, copies libtcc headers and the runtime DLL into
    the build dir, generates BattleShip.def via tcc -impdef
    post-build.

What's not in this PR

  • No mods. workspace/ ships only the template.
  • No libultraship changes (separate PR). The submodule pointer
    bump in this PR depends on that PR landing first.

Dependencies

requires this LUS PR

How to test

  • Build clean and launch with no mods/ directory; engine should
    run normally with the loader idle.
  • Drop workspace/template/output/ into <install>/mods/ as a
    folder and relaunch. Engine logs Initializing script: template
    in ssb64.log; the demo mod's MOD_INIT runs.
  • Pack the same template as .o2r via
    cmake --build . --target mod_template_o2r, drop the .o2r in
    mods/, relaunch. Same behavior.
  • In the Mods menu, edit src/demo.c, run cmake --build . in the
    template, click Hot Reload. ssb64.log shows the unload +
    recompile + re-init cycle without restarting the engine.

Notes for review

  • HookManager uses MinHook. Mods' MOD_INIT runs inside a
    SetCurrentOwner / ClearCurrentOwner wrapper so every
    InstallHook call gets tagged. UnloadAll calls
    UninstallHooksForOwner per mod before the mod's PE image is
    freed. This is the part most likely to bite if mod lifecycle
    order changes.
  • Mod source in workspace/<NAME> isn't compiled by the engine
    build; merge_mod.py just preprocesses + copies. The runtime
    compile happens via libtcc when the mod loads. So mod build
    errors don't surface until launch + ssb64.log.
  • mod_bridge.cpp is anchored explicitly in port.cpp so the
    linker doesn't drop the bridge .obj at link time. If a future
    port.cpp refactor removes those anchor lines, mods stop being
    able to resolve mod_install_hook / mod_resolve_symbol.

Suggestions welcome

  • Where or even whether the workspace/ folder belongs in the repo or in the shipyard (unsure of its current status).
  • Whether the merge_mod.py tooling makes sense at tools/ root
    or under tools/mods/. or if any of the mod related tools belong in here or in the shipyard.

based on this Starship PR

Mickemoose added 3 commits May 8, 2026 21:18
Script/Code mods in C using Tiny C Compiler.
- Mods are compiled at runtime
- Mods are packaged into o2r files
- Hot Reload for mod development (can be a little finicky)
- some tools to convert pngs and wavs for mods
MSVC's WINDOWS_EXPORT_ALL_SYMBOLS exports functions but not non-trivial global data, preventing mods from resolving engine globals like gGCCommonLinks. Add a tiny accessor function gGCCommonLinks_Ref(int) that returns entries from the engine's gGCCommonLinks array, and expose its address via sModBridgeAnchorFighterListRef so TCC/mods can resolve it.

Note: no bounds checks are performed (callers are expected to use engine enums).
Add a wrapper gMPCollisionGroundData_Ref and anchor symbol sModBridgeAnchorGroundDataRef in port/port.cpp to expose the engine's MP ground collision data pointer to modules. This lets mods read map_bound_left/right/bottom (used for distributing spawn positions and stage geometry) via the same export hole used by gGCCommonLinks.
@the-outcaster the-outcaster mentioned this pull request May 10, 2026
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.

1 participant