Crates.io | c8 |
lib.rs | c8 |
version | 1.0.1 |
source | src |
created_at | 2024-03-24 08:12:26.192535 |
updated_at | 2024-03-26 20:14:35.718406 |
description | 🎮 CHIP-8 / S-CHIP / XO-CHIP tui toolkit featuring a virtual machine, debugger, and disassembler |
homepage | |
repository | https://github.com/tochiu/c8 |
max_upload_size | |
id | 1184197 |
size | 842,369 |
About • Features • Installation • Usage • Motivation
C8 is a terminal user interface tooklit made to run, debug, and disassemble CHIP-8, S-CHIP, and XO-CHIP games. At its core you can:
c8 run [ROM_PATH]
--debug
to enable debug mode--kind
followed by classic
, chip8
, schip
, or xochip
to force other CHIP-8 variants if auto-select fails--hz
followed by your target instructions per second if neededc8 dasm [ROM_PATH] > [OUTPUT_FILE_PATH]
c8 check [ROM_PATH]
Feature | C8 |
---|---|
Full Chip-8 + Classic, S-CHIP, and XO-Chip Support | ✔ |
Full Sound Support | ✔ |
Full 4-bit Color Support | ✔ |
debug Undo, Redo, and Step through Execution |
✔ |
debug Virtual Machine State Inspection |
✔ |
debug Register and Address Watchpoints |
✔ |
debug Instruction Breakpoints |
✔ |
debug Program Execution History |
✔ |
debug Keyboard State Modification |
✔ |
debug Dump Current Program Memory State |
✔ |
Static Tracing Disassembler | ✔ |
Configurable Execution Speed | ✔ |
Compatibility Profiles | ✔ |
Pre-defined and Custom Color Palettes | 🚧 |
Individual Configurable Quirks | 🚧 |
debug
are only available in debug modeC8 is tested mostly on Windows but should work on Mac and Linux. If on Linux, see below for required system packages before you continue.
At this moment C8 can only be installed from cargo or built from source.
C8 is published on crates.io and can be installed with cargo. Rust 1.70.0 or greater is required.
cargo install c8
C8 can be built from source with cargo. Rust 1.70.0 or greater is required.
git clone https://github.com/tochiu/c8.git
cd c8
cargo install --path ./
To be sure C8 is installed, run the classic IBM Logo ROM from the repository directory
c8 run roms/c8/ibm_logo.ch8
On Linux, the X11 development libraries are required to query keyboard state from the OS since terminals generally do not support key up events. In addition, the Advanced Linux Sound Architecture (ALSA) development libraries are required on the system.
On Ubuntu/Debian:
sudo apt install libx11-dev
sudo apt install librust-alsa-sys-dev
On Fedora/RHEL/CentOS:
sudo dnf install xorg-x11-server-devel
sudo dnf install alsa-lib-devel
On newer versions of MacOS, you may run into issues where you only see meta keys such as shift, backspace, et cetera. This is due to a permission issue. To work around this:
To run a CHIP-8 program, use the c8 run
command followed by the path to the program.
--hz
flag followed by a target instructions per second (IPS) value--kind
flag followed by either chip8
, classic
, schip
, or xochip
--kind
is not specified, c8 will make a best guess of the CHIP-8 variant--debug
flag[!IMPORTANT] The
classic
variant is not a full COSMAC VIP emulator but instead just the quirk settings from CHIP-8 on a VIP.
For example:
c8 run roms/xo/super_neatboy.ch8 --hz 50000 --kind xochip
will run the Super Neatboy rom at 50000 IPS on the XO-CHIP variant. In the above example, the --kind
flag is not necessary since C8 will auto-select the XO-CHIP variant.
The C8 disassembler is a static tracing disassembler. It will not execute the program to disassemble it but will instead trace the program from the starting address through all possible branches to determine what regions of memory are code and what regions are data. From there, it will output a view of program memory with the disassembled instructions alongside the raw memory data. Because this is a static analysis of the program, self-modifying code will not dissassemble quite well. The dissassembler will not always be certain whether a given address is an instruction or not (see: The Halting Problem). Each address is annotated with a label indicating the confidence level of that address being an instruction. The labels are as follows:
Symbol | Label | Description |
---|---|---|
' ' |
NOT |
The address is not an instruction |
'?' |
PARSABLE |
The address can be parsed as a valid instruction |
'*' |
REACHABLE |
The address can be parsed as a valid instruction and can theoretically be executed by the program |
'O' |
VALID |
The address can be parsed as a valid instruction and can theoretically be executed by the program, and can be part of at least one static execution path |
'X' |
PROVEN |
The address is an instruction that is part of at least one static execution path |
A static execution path is a sequence of instructions beginning at the starting instruction address (0x200
) that can be executed by the program without a dependence on the state of the virtual machine.
For example, an instruction that jumps to a specific location in memory from the starting address will create a new static (X
label) execution path starting from that location. The disassembler will follow all possible static execution paths to determine the confidence level of each address.
But if an instruction jumps to a location in memory that is determined by something like a value in a register, the disassembler will create a new reachable (*
label) execution path starting from all possible jump locations. This is because the disassembler cannot determine the value of the register at the time of disassembly. From there, the disassembler will follow all possible execution paths to determine the confidence level of each address. Some addresses will be promoted to valid (O
label) if they are lead back to at least one static execution path.
To disassemble a CHIP-8 program, use the c8 dasm
command followed by the path to the program. This will print the disassembled program to the standard output. Add a --kind
flag to specify the CHIP-8 variant. For example:
c8 dasm roms/ch8/ibm_logo.ch8
will dissasemble the ibm_logo rom and output the following:
0x200 cls # X 00E0 clear
0x202 ld i 0x22A # X A22A i = 0x22A
0x204 ld v0 12 # X 600C v0 = 12
0x206 ld v1 8 # X 6108 v1 = 8
0x208 drw v0 v1 15 # X D01F draw 8x15 @ v0,v1
0x20A add v0 9 # X 7009 v0 += 9
0x20C ld i 0x239 # X A239 i = 0x239
0x20E drw v0 v1 15 # X D01F draw 8x15 @ v0,v1
0x210 ld i 0x248 # X A248 i = 0x248
0x212 add v0 8 # X 7008 v0 += 8
0x214 drw v0 v1 15 # X D01F draw 8x15 @ v0,v1
0x216 add v0 4 # X 7004 v0 += 4
0x218 ld i 0x257 # X A257 i = 0x257
0x21A drw v0 v1 15 # X D01F draw 8x15 @ v0,v1
0x21C add v0 8 # X 7008 v0 += 8
0x21E ld i 0x266 # X A266 i = 0x266
0x220 drw v0 v1 15 # X D01F draw 8x15 @ v0,v1
0x222 add v0 8 # X 7008 v0 += 8
0x224 ld i 0x275 # X A275 i = 0x275
0x226 drw v0 v1 15 # X D01F draw 8x15 @ v0,v1
0x228 jp 0x228 # X 1228
0x22A # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x22B # 00 2X GRAPHIC ................
0x22C # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x22D # 00 2X GRAPHIC ................
0x22E # ? 3C00 2X GRAPHIC ....@@@@@@@@....
0x22F # 00 2X GRAPHIC ................
0x230 # ? 3C00 2X GRAPHIC ....@@@@@@@@....
0x231 # 00 2X GRAPHIC ................
0x232 # ? 3C00 2X GRAPHIC ....@@@@@@@@....
0x233 # 00 2X GRAPHIC ................
0x234 # ? 3C00 2X GRAPHIC ....@@@@@@@@....
0x235 # 00 2X GRAPHIC ................
0x236 # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x237 # 00 2X GRAPHIC ................
0x238 # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x239 # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x23A # 00 2X GRAPHIC ................
0x23B # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x23C # 00 2X GRAPHIC ................
0x23D # ? 3800 2X GRAPHIC ....@@@@@@......
0x23E # 00 2X GRAPHIC ................
0x23F # ? 3F00 2X GRAPHIC ....@@@@@@@@@@@@
0x240 # 00 2X GRAPHIC ................
0x241 # ? 3F00 2X GRAPHIC ....@@@@@@@@@@@@
0x242 # 00 2X GRAPHIC ................
0x243 # ? 3800 2X GRAPHIC ....@@@@@@......
0x244 # 00 2X GRAPHIC ................
0x245 # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x246 # 00 2X GRAPHIC ................
0x247 # FF 2X GRAPHIC @@@@@@@@@@@@@@@@
0x248 # ? 8000 2X GRAPHIC @@..............
0x249 # ? 00E0 2X GRAPHIC ................
0x24A # E0 2X GRAPHIC @@@@@@..........
0x24B # ? 00E0 2X GRAPHIC ................
0x24C # E0 2X GRAPHIC @@@@@@..........
0x24D # 00 2X GRAPHIC ................
0x24E # ? 8000 2X GRAPHIC @@..............
0x24F # 00 2X GRAPHIC ................
0x250 # ? 8000 2X GRAPHIC @@..............
0x251 # ? 00E0 2X GRAPHIC ................
0x252 # E0 2X GRAPHIC @@@@@@..........
0x253 # ? 00E0 2X GRAPHIC ................
0x254 # E0 2X GRAPHIC @@@@@@..........
0x255 # 00 2X GRAPHIC ................
0x256 # 80 2X GRAPHIC @@..............
0x257 # F8 2X GRAPHIC @@@@@@@@@@......
0x258 # 00 2X GRAPHIC ................
0x259 # FC 2X GRAPHIC @@@@@@@@@@@@....
0x25A # 00 2X GRAPHIC ................
0x25B # ? 3E00 2X GRAPHIC ....@@@@@@@@@@..
0x25C # 00 2X GRAPHIC ................
0x25D # ? 3F00 2X GRAPHIC ....@@@@@@@@@@@@
0x25E # 00 2X GRAPHIC ................
0x25F # ? 3B00 2X GRAPHIC ....@@@@@@..@@@@
0x260 # 00 2X GRAPHIC ................
0x261 # ? 3900 2X GRAPHIC ....@@@@@@....@@
0x262 # 00 2X GRAPHIC ................
0x263 # F8 2X GRAPHIC @@@@@@@@@@......
0x264 # 00 2X GRAPHIC ................
0x265 # F8 2X GRAPHIC @@@@@@@@@@......
0x266 # 03 2X GRAPHIC ............@@@@
0x267 # 00 2X GRAPHIC ................
0x268 # 07 2X GRAPHIC ..........@@@@@@
0x269 # 00 2X GRAPHIC ................
0x26A # 0F 2X GRAPHIC ........@@@@@@@@
0x26B # 00 2X GRAPHIC ................
0x26C # ? BF00 2X GRAPHIC @@..@@@@@@@@@@@@
0x26D # 00 2X GRAPHIC ................
0x26E # FB 2X GRAPHIC @@@@@@@@@@..@@@@
0x26F # 00 2X GRAPHIC ................
0x270 # F3 2X GRAPHIC @@@@@@@@....@@@@
0x271 # 00 2X GRAPHIC ................
0x272 # E3 2X GRAPHIC @@@@@@......@@@@
0x273 # 00 2X GRAPHIC ................
0x274 # ? 43E0 2X GRAPHIC ..@@........@@@@
0x275 # E0 2X GRAPHIC @@@@@@..........
0x276 # ? 00E0 2X GRAPHIC ................
0x277 # E0 2X GRAPHIC @@@@@@..........
0x278 # 00 2X GRAPHIC ................
0x279 # ? 8000 2X GRAPHIC @@..............
0x27A # 00 2X GRAPHIC ................
0x27B # ? 8000 2X GRAPHIC @@..............
0x27C # 00 2X GRAPHIC ................
0x27D # ? 8000 2X GRAPHIC @@..............
0x27E # 00 2X GRAPHIC ................
0x27F # ? 8000 2X GRAPHIC @@..............
0x280 # ? 00E0 2X GRAPHIC ................
0x281 # E0 2X GRAPHIC @@@@@@..........
0x282 # ? 00E0 2X GRAPHIC ................
0x283 # E0 2X GRAPHIC @@@@@@..........
Each relevant address is printed with the instruction if needed.
Past the #
symbol is the label of that address. Because ibm_logo is a simple program, every address is labelled either a proven instruction (X
label) or data with the occasional ?
label if it happens to be parsable as an instruction.
Following the label is the value at that address in hexadecimal:
Last is a description of the instruction or data:
In this case, the visual representation of the data region showcases the IBM graphic that is saved in the rom.
[!NOTE] This dissassembler doubles as a memory viewer. The memory panel in the debugger is simply an up-to-date dissasembly of program memory.
c8 check
is a tool built on top of the disassembler that checks a rom for bad execution branches. It accomplishes this by running the disassembler on the program and logging areas where proven (X
label) or valid (O
label) instructions can lead to executing an invalid instruction.
In order to debug a CHIP-8 program, run
c8 run [PATH_TO_PROGRAM] --debug
This will start the debugger with the program loaded in a paused state right before the first instruction is executed. Use the help
command to see the full list of commands. At any point, press Ctrl+C
to exit.
Below is what the debugger looks like when it first starts:
[!TIP] Make sure the terminal window is as big as possible to ensure every panel is visible.
At a glance there will be:
output
commandshow display
or hide display
commandmemory
commandshow memory -v
or hide memory -v
commandgoto
command followed by pc
, i
, or a specific addressfollow
command followed by a pointer (e.g. pc
or i
)unfollow
commanddump memory
command followed by a file pathhistory
command[!TIP] When focused on a panel (e.g. memory), You can seek to the start or the end using the
Home
andEnd
keys. Use theEsc
key to return to the command line interface.
On SCHIP and XOCHIP programs, the high resolution graphics mode alters the UI layout to look like the following:
Run the program:
Start the execution of your program with the continue
command:
(c8db) continue
This will minimize the debugger and run the program until a debug event is triggered or execution is paused. Press Esc
to pause execution and return to the debugger.
[!IMPORTANT] A debug event is a trigger that interrupts program execution and drops into the debugger window. The features in C8DB that trigger debug events are watchpoints and breakpoints.
- A breakpoint is set to trigger right before an instruction at a specified address is executed
- A watchpoint is set to trigger right after a specified register or address is modified
Step through the program:
Use step
to execute the next instruction. Follow it with an integer n
to execute the next n
instructions. This will be interrupted if a debug event is triggered. For example:
(c8db) step 50
will execute the next 50 instructions.
[!NOTE] If you use
step
orcontinue
with a past program state (reachable usingundo
), all future program states are cleared and execution will advance. If instead you would like to replay those future states, useredo
instead.
[!TIP] If you are stuck on an instruction because it is polling for a key event, use the
key
command to simulate key events. Typekey --help
for more information.
Seek through execution history:
Use undo
and redo
to seek through the program execution history. Follow it with an integer n
to rewind or fast-forward through the last n
program states.
Alternatively, use the history
command to focus onto the program history panel. Use the W/S or Up/Down keys to seek through program execution. This is just a graphical layer over the undo
and redo
commands.
redo
is a particularly special command. Technically, it doesn't simply execute the next instruction, since the execution of some instructions are non-deterministic with respect to the program state, e.g. user input or RNG. If necessary, certain properties are stored between executing instructions in order to properly replay it. That is what redo
utilizes.
If you are in a specific program state and instead of replaying, you want to execute the program from that point, use step
or continue
instead.
Set execution speed:
Use hertz
followed by n
, where n
is the target speed in instructions per second, to set the program execution speed. For example:
(c8db) hertz 60
will execute the program at a rate of 60 instructions per second.
[!IMPORTANT] C8 runs at a fixed frame-rate of 60hz. If your target execution speed is expressed in cycles per frame, multiply it by 60 to get the equivalent instructions per second.
Sometimes it is useful to pause execution when a certain condition is met. This is where breakpoints and watchpoints come in. A breakpoint is set to trigger right before an instruction at a specified address is executed. A watchpoint is set to trigger right after a specified register or address is modified.
Set a breakpoint:
Use break
followed by an address to set a breakpoint. For example:
(c8db) break 0x200
will set a breakpoint at address 0x200
. Once the program reaches this address, execution will pause and drop into the debugger. To list all breakpoints, type info break
. To remove a breakpoint, use the clear
command. In this example:
(c8db) clear break 0x200
will remove the breakpoint at address 0x200
. To clear all breakpoints, type clear all break
.
Set a watchpoint:
Use watch
followed by a register or address to set a watchpoint. For example:
(c8db) watch i
will set a watchpoint on register i
. Once this register is modified, execution will pause and drop into the debugger. If we set a watchpoint on an adresss instead, execution will pause when that address is written to. To list all watchpoints, type info watch
. To remove a watchpoint, use the clear
command. In this example:
(c8db) clear watch i
will remove the watchpoint on register i
. To clear all watchpoints, type clear all watch
.
This is my first completed rust project (haha). A friend of mine sent me an article on how to get started with writing emulators with CHIP-8. It was a super interesting read and a good excuse to learn Rust! After I finished the emulator, I thought I could go further. So here we are. If you're thinking about writing your own CHIP-8 emulator, you should! It's a great start to emulation development and building on top of it with other CHIP-8 variants is an excellent exercise in writing extensible software.