FABIEN SANGLARD'S WEBSITE

ABOUT  CONTACT  RSS   $$$


Sep 25, 2022
CCPS, a Capcom CPS-1 SDK

While I was studying Capcom's CPS-1, I found myself authoring many tools. I combined them together to make a SDK called ccps. The name is a portmanteau between cc, the C compiler and CP-System, the name of the machine before Capcom introduced the CPS-2 in 1993.

Why a SDK?

Building a game for a CPS-1 is a tedious process which requires to produce four sets of ROMs.

There are two CPUs (one running the game logic, and one taking care of the sound). Each CPU needs to be bootstrapped and their program compiled via different compilers. Both have a peculiar type of data interleaving across ROMs.

The game needs assets made of two sets of ROMs, one for the GFX and one for the SFX (sound samples). Both sets have their own type of interleaved. Additionally, music assets must be placed into the Z-80 ROM set.

And last but not least, each CPS-1 board is slightly different. Registers location can move and even register bit layout can change sometimes even between the same revision of a same game board.

The goal of ccps is to automate all of this. A game developer writes code for the game logic, produces GFX and SFX assets and then press "Enter" which results in a fully built game.

Design and Philosophy

The design of ccps is strongly inspired of git. It is a single binary working with sub-commands like ccps build or ccps install.

The philosophy of ccps is to strive for simplicity and keep the "genuine window of interest" of a potential game developer open as long as possible. The core idea is to allow a programmer to see and hear the machine perform as fast as possible with as little effort as possible.

For the languages, the SDK supports 68000 ASM, Z-80 ASM and C.

Installation

Ideally the setup process would feature a single step involving downloading a pre-built executable. But we live in an era where binaries should not be trusted. As a result, ccps is built from the source code.

The full setup takes four lines.

$ git clone git@github.com:fabiensanglard/ccps.git
$ cd ccps/
$ ./setup.sh
$ ./makeAndInstall.sh

Note that setup.sh is a small script which downloads the 68000 compiler (gcc) and the Z80 compiler (sdcc).

$ cat setup.sh  
sudo apt install  sdcc
sudo apt install gcc-m68k-linux-gnu 

At the time this article is published, only Linux and Windows (via WSL) are supported. If someone owns a Mac and wants to contribute a patch that uses brew or port, they are more than welcome to send a Pull Request.

Post

To start playing with the CPS-1, the first sub-command to run is not "hello world" but "post" (power-on self-test) which generates the smallest possible programs to ensures the toolchain is working as expected.

$ mkdir ~/post
$ cd ~/post  
$ ccps post -b sf2
$ find .
./cc/z80/crt0.s
./cc/z80/main.c
./cc/68000/crt0.s
./cc/68000/main.c

Only source code is generated. It ensures that CPUs will bootstrap, run, and talk to each other via the "latches".

Building

Now that we have something to compile, it is time to build it. Building is also done via a single command mysteriously called build. The compilers are driven and the resulting blob of instructions are split into files according to the board target (in this case "sf2").

$ ccps build -b sf2
$ ls out
sf2_29b.10e  sf2_9.12a    sf2e_30g.11e  sf2e_35g.9f   sf2e_38g.12f
sf2_36b.10f  sf2e_28g.9e  sf2e_31g.12e  sf2e_37g.11f

Each ROMs must be interleaved a certain way and ccps took care of it. As expected, no GFX and no SFX ROMs are generated.

Testing

To speed up the developement process, mame emulator is supported. The installation is done via a command audaciously named install.

Note that before we install, we need to have the board "sf2" in mame's roms folder. It is not provided here, just know that it is named sf2.zip and contains these files.

$ ccps install -d ~/mame/roms/sf2

Launching the game from mame GUI would trigger ROM SHA1 verification which would obviously fail. Using the command-line will result in a warning but works.

$ cd ~/mame
$ ./mame sf2  
sf2e_30g.11e WRONG CHECKSUMS:
    EXPECTED: CRC(fe39ee33) SHA1(22558eb15e035b09b80935a32b8425d91cd79669)
       FOUND: CRC(d5314bf9) SHA1(af11e43fcc0e41784301c7f10ebb2451ed419484)
    EXPECTED: ...     
       FOUND: ... 
     ...    

Because we indicated which board to target in the post command (ccps uses the same board names as found in video/ccps.cpp mame driver), the 68000 program automatically generated something interesting in the GFX ROMs (Ryu).

Sprite* s;
s = &sprites[0];
    
s->x = 220;
s->y = 100;
s->tile =   4;
s->attributes = 2 |  0x5 << 12 | 0x3 << 8;


sprites[1].attributes  = 0xFF00; // Last sprite marker

The tile value is 4 which matches the upper left corner of Ryu sprite. The value 0x5 is the height of the sprite -1. The value 0x3 is the sprite width -1. 2 is the palette ID. Notice that CPS-1 uses a marker to indicate the last sprite tile (0xFF00).

On the Z-80 side, the program generated simply requests the OKI to play a samples one after another every second. As a result, the speaker players "Japan", "USSR", and so on.

Already a developer can play with the programs by using different GFX tile coordinates or request the Z-80 to play different OKI samples.

Hello World

Once post is proven to work, we can use a second auto-generated project called hello world (sub-command hw).

$ mkdir ~/hw
$ cd ~/hw  
$ ccps hw
$ find .
./cc/z80/crt0.s
./cc/z80/main.c
./cc/68000/crt0.s
./cc/68000/main.c
./gfx/obj/hellworld.png
./sfx/hellworld.wav

This time, on top of programs for both CPUs we get two assets, an image hellworld.png and a sound sample hellworld.wav. Upon building, a full game is generated. The filenames are confusing (not by choice) but I swear it is all here.

$ ccps build -b sf2
$ ls out
sf2-11m.5d  sf2_19.12c   sf2_36b.10f  sf2-6m.4c  sf2-9m.3d     sf2e_35g.9f
sf2-13m.4d  sf2-1m.3a    sf2-3m.5a    sf2-7m.6a  sf2e_28g.9e   sf2e_37g.11f
sf2-15m.6d  sf2_29b.10e  sf2-4m.5c    sf2-8m.6c  sf2e_30g.11e  sf2e_38g.12f
sf2_18.11c  sf2-2m.3c    sf2-5m.4a    sf2_9.12a  sf2e_31g.12e

Testing the game is done the same as with the "post" example.

$ ccps install -d ~/mame/roms/sf2
$ cd ~/mame && ./mame sf2

And hello world!

GFX Debugging

To help troubleshoot problems related to GFX, ccps is able to dump the GFXROM in human visible form.

ccps dumpgfx -b sf2  

As discussed in a previous article[1], the GFX ROM is organized in "sheets". This is the format used when dumping.

In the dump above, the image helloworld.png from hw project was processed as a shape. It has been split in 16x16 tiles and each one of them was allocated on the sheet via a first-come first-serve allocator.

To draw a shape, the 68000 program must issue one draw command per tile.

void draw() {
  setPalette(0, 2, &phelloworld);
  int i = 0;
  for (; i < helloworld.numTiles; i++) {
     Sprite* s = &sprites[i];
     GFXShapeTile* t = &(helloworld.tiles[i]);
     s->x = 196 + t->x * 16;
     s->y = 100 + t->y * 16;
     s->tile = t->id;
     s->attributes = 2; // Palette 2
  }
  sprites[i].attributes = 0xFF00; // Last sprite marker
}

Notice the name of the variables helloworld wich provide the tile coordinates and phelloworld which provides the palette colors. A header and a C file were generated automatically so a developer only has to write a simple loop.

Sprite support

For shape that are rectangular, the CPS-1 support "sprite" draw commands where a single tile is provided but with specified dimensions (unfortunately reducing the number of draw calls does not allow to exceed the MAX 256 tiles limit of the machine). Check again the Ryu drawing call in project post where a single tile is specified (0x04) but twenty-four are drawn.

To use sprites drawing call, the tiles must be located contiguous in sheep space.

To visualize the difference, we can ask help from Honda and duplicate it. It is featured in the gfx/obj directory twice. Once with filename honda.png and then with filename Honda.png (notice the capital).

Capitalized images are treated as Sprite by ccps and allocated as such. Notice upon dumping the ROM how the sheet shows that Honda.png was allocated properly, using upper left tile coordinate 0x00 and spreading four tile wide and seven tile tall.

On the contrary, the shape honda.png was allocated "linearly". This illustrate the trade-off between Sprites and Shapes. A sprite is rectangular, is more demanding in terms of allocation, and waste tiles if they are fully transparent but it requires only one draw call.

Shapes on the other side have no allocation constraints and allow to discard transparent tiles but the mechanics of rendering them is slightly more difficult.

In practice, Capcom games used Sprites only for splash images or truly rectangular images. 90% of draw calls in Final Fight and Street Fighter II are Shapes.

SFX Debugging

ccps supports ADPCM compression to feed the OKI MSM6295. All .wav files found in the sfx directory are processed and placed in the OKI ROM database.

Like with the GFX ROM, ccps features a debugging tool which can be summoned via dumpsfx sub-command. Along with the ID of each samples, it allows to assess compression quality changes.

Original sound: 16-bit, 48,000 Hz, 1,584,244 bytes:


Prepared for compression: 16-bit, 7,575Hz, 250,140 bytes:


ADPCM Compressed: 4-bit, 7575Hz, 62,535 bytes:


Companion book

To help understanding the machine and the SDK better, I am releasing along with it a book named "The Book of CP-System"[2].

Contribute

ccps is far from being "done". It is an open source project with many "low-hanging fruits" ready to welcome contributors.

The big omission is the lack of music support. It is not very hard to do since one only has to write a VGM parser (preferably authored with the awesome DefleMask Tracker) to extract YM2151 instructions and put then into a .c file. Another thing I haven't had the time to add is support for background layers.

Most missing features are not a lot of code to write but someone has to do it. Check out the Github project page[3] to find a list of issues/bugs to work on if you are interested.

References

^ [1]Street Fighter II, paper trails
^ [2]The Book of CP-S
^ [3]Github: CCPS, a CPS-1 SDK


*