/si78c

si78c is a memory accurate reimplementation of Space Invaders in C.

Primary LanguageC

si78c

si78c is a memory accurate reimplementation of the 1978 arcade game Space Invaders in C.

It requires the original arcade ROM to function to load various sprites and other data, but does not use the original game code.

It is not an emulation, but rather a restoration.

It is however, accurate enough that it can be used to understand the inner workings of the original system, in a more accessible manner.

Many thanks to Christopher Cantrell at computerarcheology.com. Without his painstaking work and excellent notes, this project would have taken a lot longer.

Project Scale

This was a reasonably large undertaking, requiring many iterations over several months, and I would conservatively estimate that around 200 hours of work have been put into the project so far.

The original ROM is around 2000 lines of 8080 assembler, all of it game code. The final published version of si78c is around 1500 lines of game code, 500 lines of support code, and around 800 lines of comments.

There are about twenty thousand more lines of unpublished code in the background consisting of the previous iterations and other support scripts and tools that needed to be written to get the job done.

Accuracy

When running, si78c produces identical memory states (apart from the stack) to the original version. As a natural side effect, it produces pixel accurate frames compared to the original.

Cycle timing is not particularly accurate, but the game code is not particularly sensitive to this, as it uses interrupts for timing most things, instead of clock cycles.

Audience

The intended audience is hackers, enthusiasts, scholars, students, historians, and anyone else engaged in digital archaeology.

The code is intended to be used as a more accessible guide to studying the original game, and learning about its inner workings.

Conventions

Where practical, I have used the same or similar function and variable names as Cantrell, to more easily aid people studying both versions. The code is also laid out in a similar order to the original.

Every function with a matching analog in the original system is signposted with a comment like xref 028e, which gives the address of the matching routine in the original ROM.

A few other places in the code like loops and branches are annotated similarly.

To find more detail on code near an xref, you can use Cantrell's excellent notes here.

Threading

The original code is interrupt driven, and partially co-operatively multitasked. The game spends about a third of the time running the main thread, which gets pre-empted by the midscreen and vblank interrupts. The other two thirds of the time is split between those interrupt contexts, which are not pre-empted, but decide when to return to main.

It also contains some interesting parts like this:

02D0: 31 00 24        LD      SP,$2400         // wipe the stack
02D3: FB              EI                       // drop isr context
02D4: CD D7 19        CALL    DsableGameTasks  // keep going from this point

That code (running in an interrupt context) after detecting the player's death, essentially wipes out all thread contexts (including itself), and then becomes the new main context.

To handle things like this in the this in si78c, I decided to use ucontext (user level threading) to give me more fine grained control over thread switching, creation and destruction.

The equivalent piece of code in C to the above is a bit messier, and involves using swapcontext to swap to a scheduler context, which then resets all the contexts, and then swaps back and re-enters at the desired point.

Limitations

There is no sound, as the sound hardware is not emulated.

Cycle timing is not particularly accurate, as mentioned, but its not very important in this case.

The code will currently only work on little endian systems, as the original system (8080) was little endian, and we use the ROM data as is.

Porting

The game is known to build and run on Ubuntu 18.04 and MacOSX El Capitan, and will likely run on other x86 Unix systems, as long as they support ucontext and SDL2. Unfortunately, ucontext is deprecated on MacOSX, so it may not run on later versions.

The game is written in the subset of C99 that is compatible with C++, and uses no compiler extensions apart from attribute packed. It will build fine on GCC 3 and above, and most likely any Unix C or C++ compiler newer than that.

Porting to a non unix system like Windows would require changing out ucontext to use threads or fibers. Porting to a simpler system like DOS, or something bare metal will require writing some code to blit to the framebuffer, and adapting to whatever native interrupt facilities are available.

In terms of CPU grunt required, the code isn't particularly optimized, and currently requires a 32-bit processor capable of at least 10 Mips. It could be made to fit on a smaller system with a bit of work.

The code assumes little endian. To port to a big endian system would require further dissection of the ROM, to identify and swizzle any little endian data when it is loaded.

Porting the code to another language would not be a small task, and would most likely require switching out the threading system, and converting the code to not use pointers.

Building and running

SDL2 is required as a dependency, to install on Ubuntu, do this:

$ sudo apt-get install libsdl2-dev

To grab the code and build the binary, do this:

$ git clone https://github.com/loadzero/si78c.git && cd si78c
$ make

As mentioned, the original arcade ROM is required, invaders.zip from the MAME_078 set is known to work.

The constituent parts must be placed in a folder called inv1, and have these checksums:

$ md5sum inv1/*

7d3b201f3e84af3b4fcb8ce8619ec9c6  inv1/invaders.e
7709a2576adb6fedcdfe175759e5c17a  inv1/invaders.f
9ec2dc89315a0d50c5e166f664f64a48  inv1/invaders.g
e87815985f5208bfa25d567c3fb52418  inv1/invaders.h

To run it:

$ ./bin/si78c

The keyboard controls are:

a   LEFT
d   RIGHT
1   1P
2   2P
j   FIRE
5   COIN
t   TILT