PORTCLUB
Cabecera de CarnEvil
Port nativo

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: make reproduces GAME.EXE from asm/ + 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 --hires picture.
  • 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 by make, git-ignored).

  • src/ the C source. goose/ and sound/ are the SDK's library sources, generated by make from your SDK checkout plus patches/, 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 screen
    • render/ camera and vector maths, the model renderer, clipper, quads, text, sprites, textures and lights
    • movie/ the movie player, its scripts and the screen shake
    • sound/ the level sound helpers and the sound board I/O
    • enemies/ 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 and check.sh; match/ the function matching tools; extract/ the table extractors that write the *_data.c files; rom/ the disk image and file system readers (and the original hand extractors in rom/legacy/); analysis/ disassembly viewers and the Binary Ninja client.

  • original/ (git-ignored) holds your game data and SDK checkout via setup.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/):

  1. 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.sh extracts the files for you.
  2. 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.
  3. 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

OptionEffect
(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)
--hiresDraw at the window's resolution in 24-bit colour instead
--fullscreenStart fullscreen at the desktop's own resolution (no display mode change; the picture keeps its shape with black bars)
--borderlessA window without a title bar
--crosshair 0No port crosshairs (see Controls)

Windows can be resized, and the picture always scales to fit.

Game settings

OptionEffect
--violence 0..3The 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=VAny adjustment by its number in the game's ADJUST.FMT (e.g. 58=0 turns BIG BABY off)
--load-time XScale 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

OptionEffect
--raw IMAGEThe hard disk image (default original/carn.raw)
--partition DIRAlready-unpacked game files (default original/partition if present; otherwise partition 1 of the image is unpacked into <state>/partition on the first run)
--state DIRWhere 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:

FlagDisplay
0x4Triangle and DMA counters, frames per second (the hardware counters read 0 in the port)
0x20Each enemy and light as a dot on a top-down map
0x40A marker on every point light, and the number of directional lights
0x100Collision spheres: each body part's (green) and each enemy's (red)
0x400Enemy numbers
0x1000Each enemy's path
0x2000The movie frame
0x8000Heap 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.EXTthe 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.pngthe 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.

KeyCabinet switch
5 or CCoin (left slot)
1Player 1 start
2Player 2 start
9Service credit
- / =Volume down / up (hold: the volume bar appears)
SpacePlayer 1 pump
Left mouse button or Left CtrlPlayer 1 trigger
Mouse positionPlayer 1 aim
F2Test switch (diagnostics not implemented yet)
KeyPort
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.

PadCabinet
Left stick or d-padAim (the stick moves the crosshair; the further, the faster)
A or right triggerTrigger
B or left triggerPump
Y or right shoulderReload (points the gun off the screen and fires, as on the cabinet)
Start / MenuThat player's start
Back / OptionsCoin (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 buttonCabinet
LeftTrigger
RightReload (points the gun off the screen and fires)
MiddlePump

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.md the phased plan.
  • docs/build.md the byte-exact build and how C modules replace assembly.
  • docs/toolchain.md the original compiler, its flags and what we learned matching it.
  • docs/library.md the GOOSE and sound library modules and their revisions.
  • docs/port.md how 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.md the 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.