
tuunanet/rea-ski-or-die-reconstruction

DOS版Ski or DieのSnowball WarミニゲームをRust + SDL2で独自に再構築したもので、REAを使って出荷されたバイナリを調査し、ゲームプレイルールを復元し、アセットを抽出して作成されました。

tuunanet/rea-ski-or-die-reconstruction

このプロジェクトは、16色EGAビルドSKIEGA上でREA(Reverse Engineer Anything)を実演し、EXEPACKを展開し、REA_GHIDRA_SEED_FILEを介してGhidra関数をシードし、rea inspect-native-load-image、rea trace、rea function、rea searchを使用してSnowball Warモジュールを復元します。スタンドアロンの抽出ツールがLZSSコンテナと画像フォーマットをデコードし、HUDフォントを復元する一方、Rustゲームは移動、スポーン、弾道の雪玉、ヒット判定、スコアリング、ラウンドルールを再実装します。
インストール方法は元のソースをご確認ください。
An independent, playable reconstruction of the Snowball War minigame ("snowball blast") from the DOS version of Ski or Die (Electronic Arts). The game is written in Rust and uses SDL2. The project demonstrates REA (Reverse Engineer Anything), an AI-assisted workflow for reverse engineering. The workflow analyzes the shipped binary, recovers the gameplay rules with evidence, records them, and rebuilds the minigame from that specification.
Only Snowball War is investigated and rebuilt. The other events and the original engine are out of scope.

Download the original DOS release that you own:
https://www.abandonwaredos.com/abandonware-game.php?gid=1074
Note: The copy used for the analysis matches this listing.
Extract the archive into Ski_Or_Die/:
Ski_Or_Die/ <- unzip the release here (any sub-directory layout works)
Build the runtime assets (Python 3, standard library only, nothing from the game is committed):
python3 tools/extract/build_assets.py Ski_Or_Die assets --preview
Build and play:
cargo run --release
You need these tools: Rust stable with Cargo, the SDL2 development library
(libsdl2-dev, pkg-config name sdl2), and Python 3. See Build
for details.
The extractor works by itself. It does these tasks:
The extractor writes assets/snowball.pack and the byte-exact audio data
in assets/audio/. The tool prints the SHA-256 of the pack. For the
release used here the value is 1a45a848…100a and the size is
1 079 163 bytes. The --preview option writes PNG/PPM preview images to
assets/preview/.
Ski or Die ships several events inside one shared executable. One event is enough to demonstrate the workflow. Snowball War is the best target because it gives the most gameplay for the least reverse-engineering effort:
snowfight competition, snowballs kids, and
bonus for clearing level. The analysis starts from these strings.
They find the module and its data before any code is understood.| Item | Value |
|---|---|
| Directory | Ski_Or_Die/ (kept in this working copy, listed in .gitignore) |
| Analyzed file | SKIEGA — the 16-colour EGA build, plain MS-DOS executable, MZ |
| SHA-256 | 6638bd4d4e6e43136d1fcf69b800baa4ad5a1c7850b2f0eed4607d46fdcead70 |
| Compression | EXEPACK (stub at CS:0x0CA6:0x0012, message !Packed file is corrupt) |
| Unpacked image | 0xF7C0 bytes, SHA-256 79ee524c51ebf424c8f94acbd48396159b77c4b87fed637b710f8a3a6f3e87d3 |
The executable is compressed with EXEPACK, so the analysis needed a faithful unpack step first. The shipped file is never modified. The original game, its data files, its art, and its audio stay out of the repository.
rea doctor showed that DOS MZ targets need the REA Ghidra path, the
"Ghidra 16-bit real-mode adapter". Hopper is the other configured
provider, and it rejects DOS formats.rea doctor then
reported all Ghidra checks healthy: ghidra-version,
ghidra-headless, and ghidra-java.rea inspect-native-load-image --provider ghidra and reported
status: verified, entry 0x100C5, language x86:LE:16:Real Mode,
load segment 0x1000.REA_GHIDRA_SEED_FILE). Event modules are reachable only through
indirect dispatch, so auto-analysis cannot find them. REA reported
559 seeded functions created, 4494 code seeds decoded, 0 failures.
REA flags these results as caller assertions.rea trace, rea function,
and rea search. The commands ran over the Snowball War module (entry
0x3B0E, frame loop 0x3B53, HUD 0x3DF0, movement 0x3FDF, spawner
0x427D, script engine 0x4338, snowball creation 0x448F, throw and
hit 0x47C1/0x48B0, time tick 0x4A49, object step 0x64EC, input
0x6C78/0x6D0C).The opencode MCP server for this session started before GHIDRA_INSTALL_DIR
existed. Every analysis call therefore ran through the REA CLI, which
uses the same package and providers. The opencode config now carries the
Ghidra environment for future sessions. See
docs/reverse-engineering-notes.md
for the full details, commands, and evidence excerpts.
tools/extract/build_assets.py extracts the graphics and audio from the
shipped data files. It decodes the LZSS containers and the three recovered
image formats. The reconstruction uses the results:
| Asset | Source | Notes |
|---|---|---|
| assets/snowball.pack | written by the extractor | everything the Rust game loads at runtime |
| assets/audio/* | tunes + *.SMB banks | byte-exact audio data (playback not implemented yet) |
| assets/preview/arena.png (--preview) | SNOWFORT.LZW | 320x200 Snowball War arena |
| assets/preview/sprites.png (--preview) | SFFBM.LZW | 165 sprites: kids, snowballs, cursor, specials |
| assets/preview/overlay.png (--preview) | SFSHP.LZW | per-team scenery bands and compass wedges |
| font (inside the pack) | executable DS:0x6728 | the game's 8x8 HUD font |
docs/assets.md has the full findings. The short version:
0x177E7, flag from [SI+0xE]) when to apply
the mirror.CPSHP.LZW,
"please enter number in hole"). It is not asset encryption.Render one frame without a window to compare it with the original screen:
cargo run --example frame -- 300 frame.ppm # headless render for comparison
The following rules are confirmed by code. See the behavior spec for the addresses:
x∈[0,299] and y∈[53,199] and starts at (150,150). Objects clamp at
x≤319 and y≤199.unlimited is active. The hit test uses the
point (x+9, y−8) against the queued objects. Each type has its own
score and death effects.+25
bonus and score += 1000 + snowballs. The timer has 60 units and ticks
every 41 frames. A time-out converts the remaining snowballs into score.
Practice mode ends at meter 0x0F.unlimited for 400 frames. The game gives +10 ammo every 50 frames
while the player has no snowballs.The following items are uncertain or approximated:
[0x75e3] gate is implemented exactly. Only the selected team's
kids are visible and hittable, so the player switches teams to reach the
level target (see the spec, §9).You need these tools on Linux:
libsdl2-dev (pkg-config name sdl2)cargo build --release
cargo test # 17 gameplay tests
cargo fmt --check
cargo clippy --all-targets --all-features
cargo run --release
The window is 960×600 pixels. The game uses a 320×200 logical screen with integer scaling.
| Key | Action | |---|---| | Arrow keys / keypad (8-way) | Move the throwing cursor (accelerates while held) | | Enter (or Space) | Start · throw a snowball at the cursor (repeat while held) | | Right Shift + ←/→/↑/↓ | Switch kid team (0…3). The compass shows each team's count | | M | Toggle competition / practice (menu) | | P | Pause | | Esc | End the round | | Window close | Quit |
src/assets.rs loads assets/snowball.pack (arena, sprites, font)
src/game.rs simulation: states, frames, throw/hit, round rules
src/participant.rs participant teams, kid scripts, spawn logic
src/snowball.rs snowball lifecycle + scripted arc step
src/roam.rs roaming special object
src/arena.rs arena geometry and hit boxes
src/input.rs recovered input semantics
src/render.rs software rasteriser: original arena/sprites/font
src/main.rs window, key events, fixed 35 Hz timestep
src/data.rs gameplay tables recovered from the binary (+ provenance)
examples/frame.rs headless frame renderer (comparison with the original)
tests/gameplay.rs 17 automated gameplay tests
tools/extract/ standalone asset extractor (run before the game)
docs/ reverse-engineering notes, behavior spec, asset notes
assets/audio/. The original player is a driver inside the
executable (init 0x6105, entry [0x6A20], tune loader 0x1814D).
Playback needs an FM (OPL2) core or a port of the driver. See
docs/assets.md §4.docs/behavior-spec.md §10.1). The menu and the
prompts get a one-pixel black outline, so the light colours stay
readable over the arena.assets/ from your own copy
of the release with tools/extract/build_assets.py (see
Quick start).MIT for the reconstruction. The original game remains copyrighted by its owners. Nothing proprietary is included in this repository.