
CarnEvil
Port nativo de CarnEvil para PC.
CarnEvil
Port nativo basado en su ficha de origen.
README del repositorio
carnevil-decomp
Matching decompilation of Midway's CarnEvil (1998, Seattle hardware: MIPS R5000, 3dfx
Voodoo, DCS2 sound) and a native PC port built from the same source. The goal is a 1:1
port: every byte of the original GAME.EXE v1.0.3 is rebuilt and compared on each build,
and the port runs that code on a thin hardware layer. See docs/PLAN.md for the plan and
docs/ for the notes of each phase.
Where it stands
- Byte-exact rebuild:
makereproduces GAME.EXE fromasm/+src/and fails on any difference. The GOOSE operating-system library and the DCS sound library (27 modules) and the first game modules are matched C; the rest is still assembly. - The port (
make port) boots the game's real main process: hardware info screen, CMOS, audits and adjustments, high scores, pricing and credits, the sound board bring-up, then the attract mode with its movies, text, music and sound effects. Coins, start buttons, service credit and the volume buttons work. A start runs the real game flow: the level-select gallery, Haunted House, Rickety Town and Freak Show, the Big Top, the end credits and back to attract, for one or two players. What the game code still lacks would be stubbed, and every stub announces itself (below); a full game reaches none. - Port additions (none change the game): mouse and gamepad aiming for both players with
MAME-style crosshairs, fullscreen and borderless windows, a smooth
--hirespicture. - macOS (Apple Silicon and Intel), Linux (x86_64) and Windows (32-bit, cross-compiled from Linux) build today; GitHub Actions builds all three on every push.
Layout
-
docs/plan and notes;config/symbols and module ranges;include/shared headers;test/oracle data. -
asm/the disassembly (generated bymake, git-ignored). -
src/the C source.goose/andsound/are the SDK's library sources, generated bymakefrom your SDK checkout pluspatches/, so nothing of Midway's is committed.game/is the game, by subsystem:core/boot, attract, game flow, waves, players, the gun, credits, audits, the select screenrender/camera and vector maths, the model renderer, clipper, quads, text, sprites, textures and lightsmovie/the movie player, its scripts and the screen shakesound/the level sound helpers and the sound board I/Oenemies/the spawn tables, the shared enemy code, each enemy, the weapons and effects
Generated tables (
*_data.c,movie_scripts.c) sit next to the code that uses them. -
port/the host layer:runtime/HAL, scheduler, memory, disk;video/the OpenGL renderer and window;audio/audio output and the virtual DCS sound board;stubs/the stand-ins for game code not decompiled yet;tests/the self-test;third_party/the vendored DCS decoder. -
tools/:build/the compiler wrapper, assembler fix-ups, disassembler, EXE packer andcheck.sh;match/the function matching tools;extract/the table extractors that write the*_data.cfiles;rom/the disk image and file system readers (and the original hand extractors inrom/legacy/);analysis/disassembly viewers and the Binary Ninja client. -
original/(git-ignored) holds your game data and SDK checkout viasetup.sh.
Getting started
The repository contains no game data and no SDK. Three things come from elsewhere, and
setup.sh only links to them (the links live in the git-ignored original/):
- Your own dump of the CarnEvil v1.0.3 hard disk (MAME set
carnevil), as a raw image (chdman extractraw). The port needs both the game's files and the raw image (the movies are raw partitions outside the filesystem);setup.shextracts the files for you. - The Midway Seattle SDK:
git clone https://github.com/historicalsource/midwayvideo(18 MB). The port only needs its headers; the byte-exact rebuild also runs its original DOS compiler. - This repository.
Play the port
Needs python3, clang and make. On Linux (x86_64) also SDL2 and OpenGL headers, e.g. on
Ubuntu sudo apt install clang make python3 pkg-config libsdl2-dev libgl-dev.
git clone https://github.com/historicalsource/midwayvideo ../midwayvideo
./setup.sh --raw /path/to/carnevil.raw --sdk ../midwayvideo
make port
build/host/carnevil --partition original/partition --raw original/carn.raw # macOS
build/linux/carnevil --partition original/partition --raw original/carn.raw # Linux
Windows: cross-compile from Linux with MinGW-w64 (sudo apt install mingw-w64) and SDL2's
MinGW development package (SDL2-devel-2.30.9-mingw from libsdl.org, unpacked under
~/win-deps, or pass SDL2_MINGW=.../i686-w64-mingw32):
make port WINDOWS=1 # build/win/carnevil.exe and SDL2.dll
carnevil.exe --raw C:\path\to\carnevil.raw
It is a 32-bit program (the game's own 32-bit layout) and runs on 64-bit Windows; Wine runs it
too. macOS and Linux keep separate outputs (build/host + build/port on macOS, build/linux on
Linux; the MIPS binutils in tools/mipsel-elf-binutils and tools/mipsel-elf-binutils-linux),
so one checkout shared with a VM builds on both without one clobbering the other.
Without setup.sh the port only needs the disk image: build/host/carnevil --raw /path/to/carnevil.raw unpacks the game's files from the image's file system into
state/partition on the first run (no Python needed) and plays from there.
./setup.sh --partition DIR works instead of --raw if you extracted the files yourself
(tools/rom/legacy/ has the stand-alone extractors); add --raw as well for the movies.
Verify the byte-exact rebuild (for working on the decompilation)
Additionally needs python3's capstone (pip install capstone), DOSBox-X on the PATH (it
runs the SDK's DOS compiler for the C modules) and the MIPS binutils, built once:
tools/build/get-binutils.sh # mipsel-elf as/ld into tools/ (git-ignored)
make # rebuild GAME.EXE from asm/ + src/ and check it is byte-identical
make host # native compile of every C module + self-test
make and make port stop with a message naming the missing piece if a link is absent.
Running the port
build/host/carnevil --raw original/carn.raw
The release packages start it through run.sh (Linux, macOS) or play.bat (Windows): give
the image's path once (run.sh remembers it), then any of the options below.
Picture and window
| Option | Effect |
|---|---|
| (none) | Cabinet mode: the game's 400x256 16-bit picture (with the Voodoo's dithering where the game enables it), scaled up without smoothing (see docs/port.md) |
--hires | Draw at the window's resolution in 24-bit colour instead |
--fullscreen | Start fullscreen at the desktop's own resolution (no display mode change; the picture keeps its shape with black bars) |
--borderless | A window without a title bar |
--crosshair 0 | No port crosshairs (see Controls) |
Windows can be resized, and the picture always scales to fit.
Game settings
| Option | Effect |
|---|---|
--violence 0..3 | The operator's VIOLENCE adjustment: 0 none (smoke instead of blood, no acid or flamethrower pickups), 1 green blood, 2 red blood, 3 full gore (the factory default) |
--adjust N=V | Any adjustment by its number in the game's ADJUST.FMT (e.g. 58=0 turns BIG BABY off) |
--load-time X | Scale the level-load time (default 2.8, a test calibration explained in docs/port.md; 1 is the port's instant loading) |
--violence and --adjust are written into the CMOS state as the operator's test menu
would, so they persist.
Files
| Option | Effect |
|---|---|
--raw IMAGE | The hard disk image (default original/carn.raw) |
--partition DIR | Already-unpacked game files (default original/partition if present; otherwise partition 1 of the image is unpacked into <state>/partition on the first run) |
--state DIR | Where the game's CMOS image and the files it writes are kept (default state/; delete it for a factory-fresh boot) |
CARNEVIL_RAW, CARNEVIL_PARTITION and CARNEVIL_STATE are the environment equivalents.
Testing and debugging: --headless (no window), --fast (no frame pacing),
--frames N (stop after N frames), --press FRAME:SWITCH,... (script cabinet switches),
--dump frame.ppm or --dump-every PREFIX N (screenshots), --wav FILE (capture the
soundtrack), -v (traces; twice for every ioctl), --debug FLAGS (the game's own debug
displays, below).
The game's debug displays. The game keeps a word of debug flags (debug_mode). The shipped
game clears it at boot, and only a player 3/4 switch toggles one bit, so the displays were
reached from a debugger on the development systems. --debug FLAGS holds the given bits on,
for example --debug 0x8504:
| Flag | Display |
|---|---|
| 0x4 | Triangle and DMA counters, frames per second (the hardware counters read 0 in the port) |
| 0x20 | Each enemy and light as a dot on a top-down map |
| 0x40 | A marker on every point light, and the number of directional lights |
| 0x100 | Collision spheres: each body part's (green) and each enemy's (red) |
| 0x400 | Enemy numbers |
| 0x1000 | Each enemy's path |
| 0x2000 | The movie frame |
| 0x8000 | Heap use and the object count |
Others change behaviour rather than draw: 0x1 is the developers' mode (free play, the level's 3D in the attract), 0x10 freezes the camera, 0x10000 follows one enemy and holds the movie.
Mods
A mods/ folder (--mods DIR for another, --no-mods for none) holds files that replace the
game's own while it runs; the extracted game files are never changed. It is a port addition and
changes nothing in the decompiled game. See MODS.md for how to use it.
In mods/ | Replaces |
|---|---|
FILE.EXT | the game file of that name (any case): a texture .WMS, a sound bank .BNK... (not models, animations, paths or cameras: the game checksums those) |
PACK/... | the same for every file in the pack folder (and one level of subfolders) |
PACK/NAME.png | the texture NAME.WMS, at any size with the same proportions |
order.txt | (optional) pack names, one per line, first wins; the rest follow alphabetically; loose files win over packs |
Movies and music are streamed from raw partitions of the disk image, not files, so they can't be replaced. Models, animations, paths and cameras fail the checksum the game checks them against, so the game loads their original backups instead; the game's saved files (CMOS, high scores) are never overridden.
--export-textures DIR writes all 1148 textures as DIR/NAME.png (the largest mip level, the
orientation the mods folder reads back) to start from. A replacement picture keeps its texture's
proportions: the game's texture coordinates are fractions of the picture, so a bigger one is just
sharper (most visibly with --hires). Its pixels and the driver's mip levels go to the GPU as they
are; pixels in the colour the game keys out stay keyed out. Each replacement is printed once,
when first used. Replacement data is kept outside the 32-bit arena the game allocates from, so a
texture pack does not change the free memory the game measures.
Controls
The cabinet's switches are mapped to the keyboard, and the mouse is player 1's light gun: move it over the picture to aim (the game's own gun code converts the position through its calibration) and click to shoot. Pointing off the picture counts as pointing off the screen, which is how the original reloads.
| Key | Cabinet switch |
|---|---|
5 or C | Coin (left slot) |
1 | Player 1 start |
2 | Player 2 start |
9 | Service credit |
- / = | Volume down / up (hold: the volume bar appears) |
Space | Player 1 pump |
Left mouse button or Left Ctrl | Player 1 trigger |
| Mouse position | Player 1 aim |
F2 | Test switch (diagnostics not implemented yet) |
| Key | Port |
|---|---|
F11 or Alt+Enter (macOS: Option+Return or the green window button) | Fullscreen on/off |
Gamepads
Both players can use a gamepad: SDL game controllers on Linux and Windows (Xbox, PlayStation, Switch Pro and most others), Apple's GameController framework on macOS. The first pad connected plays player 1 and the second player 2; pads can be plugged in and out while the game runs. Moving the mouse gives player 1 back to the mouse.
| Pad | Cabinet |
|---|---|
| Left stick or d-pad | Aim (the stick moves the crosshair; the further, the faster) |
| A or right trigger | Trigger |
| B or left trigger | Pump |
| Y or right shoulder | Reload (points the gun off the screen and fires, as on the cabinet) |
| Start / Menu | That player's start |
| Back / Options | Coin (player 1 the left slot, player 2 the right) |
Two mice and light guns
The system merges every mouse into one pointer, so normally only player 1 has a mouse.
--mice reads each mouse and light gun on its own instead (Raw Input on Windows, XInput2 on
Linux under X11, IOHIDManager on macOS): the first device to click is player 1's gun and the
next player 2's. Ordinary mice move their player's crosshair (--mouse-speed X, game pixels
per count, default 0.25); devices that report absolute positions (DolphinBar, Sinden, GUN4IR,
AimTrak, tablets) aim where they point, and pointing one off the picture reloads as on the
cabinet.
| Device button | Cabinet |
|---|---|
| Left | Trigger |
| Right | Reload (points the gun off the screen and fires) |
| Middle | Pump |
In this mode the system pointer no longer aims for player 1, and gamepads still work for
either player. Under Wayland (no X11), --mice reports that it is unavailable.
Crosshairs
The cabinet's light guns need no on-screen sight, so the original draws none during play
(only on the high-score initials board). Like MAME, the port draws one over the finished
picture for each aiming player, mouse or pad (player 1 red, player 2 blue), and hides the
system pointer over the picture. It is not part of the game's picture: --crosshair 0
turns it off.
Scripted input
--press takes these switch names: coin, coin2, coin3, coin4, bill, start1,
start2, service, test, slam, voldown, volup, p1a..p1d, p2a, p2b,
trigger, pump, trigger2, pump2 (each press is held for 6 frames). Headless shots go
to the screen centre, or to CARNEVIL_AIM=x,y (x 0..400, y 0..256 from the bottom) or a
schedule CARNEVIL_AIM="FRAME:x,y;...". CARNEVIL_PADSIM="FRAME:PAD:LX,LY,BUTTONS;..."
stands in for a gamepad (the stick and the PAD_* button bits from port/runtime/hal.h,
from that frame on). CARNEVIL_MICESIM="FRAME:DEVICE:rel:DX,DY|down:BUTTON|up:BUTTON;..."
stands in for separate mice (--mice), and CARNEVIL_DUMP_FROM=N skips --dump-every
screenshots before frame N. Coins are not counted while the boot load runs (until about frame 960
with the default --load-time), so scripts press later:
build/host/carnevil --partition original/partition --raw original/carn.raw \
--headless --fast --frames 1150 --press 1000:coin,1010:coin,1050:start1
Documentation
docs/PLAN.mdthe phased plan.docs/build.mdthe byte-exact build and how C modules replace assembly.docs/toolchain.mdthe original compiler, its flags and what we learned matching it.docs/library.mdthe GOOSE and sound library modules and their revisions.docs/port.mdhow the original code runs on a PC: memory, scheduler, HAL, renderer, movies, text, sound board, inputs and credits, the boot sequence.port/third_party/dcsdecoder/README.mdthe vendored DCS audio decoder and its origin.
Faithfulness
Game and library code is transcribed from the disassembly and checked byte-exact where it
has been matched; modules not yet matched are marked in docs/port.md. The same C source
goes into the byte-identical GAME.EXE (make, where the untranscribed functions are still
the original assembly) and into the port (make port, which links only C). The hardware is
the only thing replaced: port/ stands in for the disk, I/O ASIC, CMOS, security PIC,
Voodoo and the DCS2 board, following the SDK sources and MAME as the reference. Files the
game writes go to the state directory so an extracted partition is never modified.
Game code the port does not have in C yet is a stub, never a silent one: each stub prints
[stub] name reached at vblank N the first time it runs and the run ends with the list of
every stub it touched. That list is the work queue; a stub is retired by transcribing its
function and matching it with tools/match/trymatch.sh. Host-only code paths in game sources
are #ifdef PORT_HOST blocks, so the matching build never sees them.
Comentarios (0)
Inicia sesión para dejar un comentario.
Sé el primero en comentar sobre este juego.
