Ducks! — the game's own code, reconstructed
C reconstructed from the disassembly of Ducks! v1.2 (Tim Furnish / Hungry Software, 1998-2000), one file per unit of the original we can argue for. It builds and plays:
You need your own copy of the game, and make run will fetch it for you.
Nothing of Ducks! is distributed here — no executable, no .egg data, no artwork.
What make run does is download the archive from an archive page,
https://www.kieranmillar.com/ducks/, and keep the one file the port needs:
Eggs/Main.egg, which holds every picture, level, sound and line of text in the
game. That page is not the authors' — it is where a copy can still be found.
It needs curl and 7z, does nothing if Eggs/ is already there, and does not
touch the network at all if DUCKS_GAME_DIR points at a copy you already have.
make on its own just builds; make eggs just fetches; clean deliberately
leaves Eggs/ alone.
What you need
A C compiler, make, and SDL3 — not SDL2; the two are not interchangeable and
the build looks for pkgconfig(sdl3). curl and 7z are needed only by
make eggs, to fetch the game data.
| Fedora | sudo dnf install gcc make pkgconf-pkg-config SDL3-devel curl 7zip |
| Debian / Ubuntu | sudo apt install build-essential pkg-config libsdl3-dev curl p7zip-full |
| Arch | sudo pacman -S base-devel sdl3 curl p7zip |
| macOS | brew install sdl3 sevenzip (compiler from the Xcode command line tools) |
The Fedora line is the one this was built with; the others are the usual names
for the same four things. If a package has been renamed, what you want is the
SDL3 development package — the one that installs SDL3/SDL.h and an sdl3.pc.
Check it with:
pkg-config --modversion sdl3
Debian and Ubuntu only started carrying libsdl3-dev recently (trixie, 24.10).
On anything older, build SDL3 from https://github.com/libsdl-org/SDL. SDL2 is
not a fallback: the port uses SDL3's audio streams and its
SDL_GetKeyFromScancode with a modifier state.
What this is
The original sources are not available. This is what the code must have looked
like for the compiler to have emitted what it did: the game's own code segment,
0x04ca0-0x14620, read back out of the disassembly. Every routine in it is
transcribed and nothing stands in for one.
Every function carries the image offset it was read from, so any line can be checked against the binary, and where a name or a type is a guess it says so.
Two backends, one interface. game.h declares what the game needs from below
it; dos_io.c is the original reconstructed - VGA ports, Mode X planes, INT 33h -
and sdl_io.c is the same interface on SDL3. The game cannot tell which one it is
linked against, which is the test of whether the split was drawn in the right
place.
dos_io.c is a record rather than a second build: reimplementing the DOS side is
not the aim, so it holds what we know about the original's hardware layer - the
Mode X planes, the DAC, the CRTC, INT 33h - and the Makefile does not compile it.
Written as C99, not as period C. The aim is a port that builds and runs, so a construct is written the clearest modern way that matches the instructions. It is not an attempt to feed Turbo C++ 3.0 something byte-identical.
Files
| file | what is in it |
|---|---|
game.c |
the game: main and init, the resource and string loaders, the fonts, every menu and screen, the save format, the hall of fame, the level loader, the frame, every entity's behaviour, the tools, the particles, the endings and the cutscenes. In address order, which is the order the compiler emitted them |
game.h |
its types, its globals, and the interface both backends implement |
sdl_io.c |
that interface on SDL3: a linear framebuffer, an SDL palette, a 70 Hz deadline in place of the retrace spin, the mouse, the audio device |
dos_io.c |
that interface as the original: BIOS mode, Mode X planes, the DAC, the page flip, INT 33h, and every drawing primitive |
sound.c |
the sound module: the id-to-sample map, the eight voices, and the original's additive mixer |
egg.c |
the egg reader: the directory, the chunk decoder, the shifted-string reader |
It is the game. The intro, the menus, the levels, the tools, the endings and
the cutscenes: you can start a game, play it, lose your lives, watch the ending
and get your name on the hall of fame. That state is tagged no-stubs.
The menus first, since they came first: all fifteen are real - build_menus
assembles them out of the string tables at startup and run_screen runs whichever
one game_main points at - so PLAY DUCKS, OPTIONS, the settings screens, READ ME!
and QUIT DUCKS all navigate, the toggles toggle, and QUIT takes main's teardown
path and exits. That much was tagged menu-done.
The cheats work, and they are typed in capitals - the compare at 0:0x4c28 is
repe cmpsb, with no case folding anywhere in it. COLOURMAP then P during a
level draws the palette; THECROWDSAYBO gives you the level picker;
PLAYBACKTIME opens the demo picker. game.c lists all ten against the flags
they set, and one of them - KEYCODE - is dead in the shipped build: it toggles
a word nothing reads.
The window captures the mouse, because the game keeps the pointer position itself as a running total of INT 33h deltas and a pointer that stops at the edge of a window is one the game believes stopped moving. Ctrl+Alt lets go, and takes hold again; so does clicking in the window. Losing focus releases it without forgetting what you asked for.
READ ME! works end to end: build_episode_index (0x11657) reads the episode,
readme and demo indexes out of the egg at startup, so SELECT AN EPISODE and
READ ME! list what the file actually holds, and show_readme_section
(0x11efb) draws a section's pages and turns them.
LOAD / SAVE works too: five slots in GAME1.SG to GAME5.SG, with the name typed on a screen that is the menu itself with one line being edited. SAVE THIS GAME is only offered while a game is in progress, which is reachable now that the gameplay is.
Leave the menu alone for a while and the hall of fame comes up on its own, read
out of settings.dat at startup and written back on the way out.
The sliders, REGISTER DUCKS and the hall of fame all work, and every action code
game_main switches on reaches something real.
Sound works: 87 samples by id out of the egg's 0x58 blocks, eight voices, and
the original's additive mixer feeding an SDL device at the rate the game asks
for - including D mid-level, which doubles that rate exactly as the DSP time
constant did.
Where the rest of it is
This branch is the port. Everything that produced it is on develop: the
unpacker, the DOS and VGA emulator the port is compared against, the snapshots,
the verification harnesses, and the working notes that say how each routine was
read and what is still open.
Start with docs/notes/reconstruction.md
for how much is verified rather than merely written, the conventions the code
follows, and where the port knowingly differs from the original.
master is generated from develop with git subtree split, so it is never
committed to directly.
Licence
The reconstruction is MIT licensed; see LICENSE. That covers this
code only. Ducks! itself - the executable, the .egg data, the artwork -
belongs to Tim Furnish / Hungry Software and is not distributed here under any
licence.