- C++ 58.3%
- Python 21.1%
- Objective-C++ 14.5%
- CMake 2.8%
- Java 1%
- Other 2.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Textures are referenced only by hash: no tex_ name survives in the binary, and no resource hash appears as a code constant. The link lives in mesh material blocks, at +20 within each 160-byte material. That matters because meshes are genuinely multi-material. The surf shack uses eight textures, palms need separate frond and bark art, and both towel meshes share one texture. My hand-written mesh-to-texture table was guesswork and had real errors -- it pointed palms at tex_PalmTree_01, which does not exist, so those lookups failed silently and the props were skipped. That was a large part of why the beach looked empty. .dsmesh is now v2, carrying a texture hash per material. The renderer binds textures per part rather than per draw item, and uploads are cached by hash so shared art loads once. v1 is rejected outright rather than misread: without a materials block the part count would be read as a material count and every subsequent field would desynchronise. 99 tests, zero warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| client | ||
| DistantShore.app | ||
| re | ||
| server | ||
| .gitignore | ||
| README.md | ||
Distant Shore
A preservation project for Distant Shore (Blimp Pilots, 2009), a networked iPhone game whose servers have long since gone dark. The goal is a client that runs on current phones and a server you can host yourself.
What is here
| Path | What it is |
|---|---|
DistantShore.app/ |
The original 2009 bundle, the source of truth |
re/tools/ |
Format readers and the asset converter |
re/docs/ |
Protocol and file format specifications |
server/ |
The backend: legacy XML protocol + a modern JSON API |
.forgejo/workflows/ |
CI that publishes the server image |
This is a private, self-hosted repository. The original bundle is tracked because it is the irreplaceable input everything else derives from.
Generated directories
re/extracted/, re/assets/, re/decomp/, and re/ghidra/ are gitignored because they are
mechanically produced from the bundle and would churn on every tool change. Regenerate them:
python3 re/tools/wad.py DistantShore.app/resources.wad re/extracted
python3 re/tools/convert_assets.py DistantShore.app/resources.wad re/assets
The decompiler output comes from re/tools/DumpDecomp.java run
under Ghidra headless; it takes about 30 seconds for all 1,237 functions.
The client is written fresh against the specs in re/docs/ rather than translated from
decompiler output, because the original is armv6 with fixed-function OpenGL ES 1.1 — not a
usable porting base for arm64 and Metal.
Server
Serves two front ends over one shared data model, so both see the same ocean:
/bc/*.php— the original 2009 XML protocol, reproduced from the shipped binary. The original client can talk to this unmodified./api/v1/*— JSON for the new client.
cd server
docker compose up --build # http://localhost:8000, docs at /docs
Full detail, configuration, and deployment notes: server/README.md.
Deploying
Pushes to main that touch server/** run the test suite and, if it passes, publish a
multi-arch (amd64 + arm64) image to the container registry on git.casually.cat.
To deploy on the NAS, copy server/docker-compose.prod.yml
and server/.env.example (as .env) to the host — it pulls the
published image, so the host never needs this repo:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
The server is served at https://distantshore.casually.cat. Set HOST_PORT=127.0.0.1:8000 in
.env if a reverse proxy terminates TLS in front of it, so the container is not also exposed
directly on the LAN.
CI needs one repository secret, REGISTRY_TOKEN — a Forgejo access token with write:package.
The runs-on: docker label must match how your runner is registered.
Status
Done — reverse engineering, specs, asset pipeline, and the server (24 tests, image built and exercised end to end).
Next — the remaining gameplay reverse engineering (dsi_bounds, the tile/zone system, and the
shell-and-bottle economy), then the C++ engine core, the iOS shell, and the Android port.
Reverse engineering notes
Recovered with Ghidra from the shipped armv6 binary, which ships decrypted (cryptid 0) and
retains its symbol names, so the analysis reads in terms of real classes rather than addresses.
Findings are validated rather than assumed. All 56 meshes parse with every declared stride
equal to the sum of its components and zero of 19,947 indices out of range; all 108 textures
satisfy length == 60 + w*h*channels exactly, and decode to imagery with a mean
adjacent-pixel delta of 2.24 against roughly 85 for noise.
The sharp edge in the mesh format, documented in re/docs/formats.md:
BpMeshTemplate offsets are file-relative but BpPrimBuffer offsets are relative to the prim
buffer's own base. Mixing them yields plausible-looking garbage rather than an obvious failure.