Building TH-Platform, a Multiplayer Platform for Touhou STG
TH-Platform is a third-party desktop client for the Touhou danmaku STGs, bolting lobby, matchmaking, and room features onto TH06, TH07, TH08, and TH09. The official games don’t do multiplayer, so this fills that gap.
Multiplayer splits into two halves: how people get grouped together, and how the two sides’ state stays in sync inside the game process. The second half is the dirty work — injecting a DLL, hooking game logic, and wrestling with the original exe’s memory layout. That lives in a companion repo, TH08-Platform, chipped away at separately. This repo only handles the first half: the client itself, with a full set of pages for the lobby, rooms, groups, DMs, profiles, and settings. Tangling UI up with reverse engineering is miserable, so splitting them apart and shipping each on its own keeps things clean.
The frontend is Vite 6 + React 18 + TypeScript, styled with Tailwind, components built on Radix primitives, icons from lucide, wrapped in a Tauri 2 native shell — the goal is a borderless, fixed 1440x900 desktop window, not the browser thing. No routing library; I wrote a hash router myself. #/lobby/th08 and #/room/4912 are enough, and it gets browser back/forward for free with zero dependencies. There’s no backend yet, so the data layer starts as a mock. The key piece is the types.ts type contract: the mock and the real facade share one set of types, so when the real backend comes online, not a single call site changes — you just swap out the mock. The theme defaults to dark with three levels, stored in localStorage under th-platform-theme, and index.html carries a small pre-render script to stop the app from flashing white on entry before it goes dark.
The hardest piece is the Windows-only game loader, written in Rust on the Tauri side and exposed as launch_game / terminate_game. It’s the old-school injection routine: start the target process suspended, inject the companion DLL, CreateRemoteThread paired with LoadLibraryA, then resume execution. Multiplayer parameters don’t go through the command line — they’re passed to the child process via environment variables:
| Variable | Purpose |
|---|---|
TH08_PLATFORM_PEER |
Peer address ip:port |
TH08_PLATFORM_HOST |
Marks this side as the host |
TH08_PLATFORM_LISTEN |
Listen port, host defaults to 7480, peer 7481 |
TH08_PLATFORM_DISABLE_MULTIPLAYER |
Master switch for multiplayer behavior |
On the TS side, src/lib/tauri/loader.ts wraps a launchGame that assembles the game path, DLL path, and host mode and hands it off to Rust. This thing only lives inside the Tauri shell; running plain pnpm dev where window.__TAURI__ doesn’t exist throws outright — intentionally, to avoid triggering it by accident during development.
Right now v0.1.0 is still in progress, essentially UI scaffolding plus mock data. The real backend isn’t wired up, and the Rust loader is built but Windows-only. Next up: fill in the theme presets, channels, and full profile pages, get Tauri packaging sorted, then hook the mock up to a real backend and genuinely wire it together with the DLL injector over in TH08-Platform.