A commandline multitool for CHIP-8 variant development
- Chiplet
Chiplet is the result of my Work on Cadmium, my CHIP-8 emulator environment. It contains an execution path tracing disassembler, a preprocessor that is compatible with Octopus from Tim Franssen, both written by me, combined with an Octo Assembly Language compatible assembler now written in C++ but originally based on Code from C-Octo from John Earnest.
It combines these to allow using modular CHIP8 projects with multiple files, while retaining error message output that refers to those original files by translating C-Octo errors back to the original locations before the preprocessing step.
The disassembler also has options to search for specific opcode patterns and give opcode statistics or estimate the CHIP-8 variants that could execute the code.
USAGE: chiplet [options] ...
OPTIONS:
Assembler/Preprocessor:
--no-line-info
omit generation of line info comments in the preprocessed output
-I <arg>, --include-path <arg>
add directory to include search path
-P, --preprocess
only preprocess the file and output the result
-o <arg>, --output <arg>
name of output file, default stdout for preprocessor, a.out.ch8 for binary
Disassembler/Analyzer:
--list-duplicates
show found duplicates while scanning directories
--round-trip
decompile and assemble and compare the result
-d, --disassemble
dissassemble a given file
-f <arg>, --find <arg>
search for use of opcodes
-p, --full-path
print file names with path
-s, --scan
scan files or directories for chip roms and analyze them, giving some information
-u, --opcode-use
show usage of found opcodes
General:
--version
just shows version info and exits
-q, --quiet
suppress all output during operation
-v, --verbose
more verbose progress output
...
Files or directories to work on
Running a file that uses preprocessor directives through the preprocessor for later assembly by Octo:
chiplet -o output.8o -P octo-source-file.8o
If the output is not set, it will be written to stdout.
This runs the file through the preprocessor and the Octo assembler and generates a binary program.
chiplet -o output.ch8 octo-source-file.8o
If the output is not set, a file named a.out.ch8
is generated.
The Disassembler uses heuristic execution path tracing to detect data and code parts and automatically generate labels to create an Octo assembly language source that one can use to regenerate the binary.
Creating Octo source from a CHIP-8 variant binary:
chiplet -o output.8o -d some-program.ch8
If no output is set, the disassembly will be dumped to stdout.
Note: The as is the nature of heuristics, they are not perfect. In
rare cases the disassembler doesn't report an error but doesn't manage
to generate source that is compilable or is identical to the binary.
In case of doubt, using the call with the option --round-trip
will
disassemble into memory, run the result through the assembler and report
an error for any difference, so if that reports nothing, the source
generated by the disassembler is guaranteed to generate an identical
binary that was used as input, if assembled. A later version will make
this step implicit to ensure consistent disassembly. Starting with the
enhancements of v1.1.0 Chiplet is able to round-trip every program
file I could find that fits in 64k memory. (I currently only know of two
MegaChip examples that don't.)
Analyzing a single binary or a directory of binaries, suppressing unneeded output:
Example:
> chiplet -q -s IBM-Logo.ch8
IBM Logo.ch8, 6 opcodes used (0ms)
possible variants: chip-8, chip-10, chip-48, schip-1.0, schip-1.1, xo-chip
Used opcodes:
00E0: 1
1000: 1
6000: 2
7000: 5
A000: 6
D00F: 6
This can run over a directory of files, the opcode statistics are then for
all detected CHIP-8 files in the directory tree. Files scanned can have the
endings: .ch8
, .c8x
, .ch10
, .sc8
, .xo8
or .mc8
It will try to identify which CHIP-8 variants this binary is made for.
The estimation is far from perfect, and mainly excludes variants when
encountering opcode that are only supported by a specific version like
a long I
load from XO-CHIP.
Looking for binaries in a directory tree, that make use of an opcode
with a given pattern (e.g. Dx0F
looks for binaries that use
sprite * v0 0xF
):
> chiplet -q -f Dx0F my-chip-archive/
Dx0F: Flappy Pong (by cnelmortimer)(2017).ch8
Dx0F: Octo Bird (by Cody Hoover)(2016).ch8
Done scanning/decompiling 534 files, not counting 125 redundant copies, found opcodes in 2 files (50ms)
The pattern rule is case-insensitive and any character that is not a
hex digit will be seen as nibble sized wildcard. Multiple -f
options
can be used to look for multiple opcodes at one run.
For conditional assembly of parts of the source, the directives :if
,
:unless
, :else
and :end
can be used. The condition that defines
if the code is used or not is based on options given by -D
command-line
option or by :const
directives in the source.
A typical use case might be:
:if XOCHIP
i := long data
:else
i := data
:end
If compiling with -D XOCHIP
this sequence will emit the long
variant of the opcode (F000
) else the normal opcode will be used.
The :unless
directive has the inverted logic, emitting the following
code if the option is not set.
To organize a complex project it is often useful to split the code into multiple files, this can also be used to have some library of macros that can be used in multiple projects.
Including another source file:
:include "my-macros.8o"
The include-directive can also be used to import image files that are converted into 1-bit sprite data. The general syntax is:
:include "<path-to-image-file>" [<width>x<height>] [no-labels] [debug]
Where <path-to-image-file>
is the image file to import (valid image
formats/extension are .png
, .gif
, .bmp
, .jpg
, .jpeg
and .tga
),
valid sizes are 8x1
, 8x2
, 8x3
, 8x4
. 8x5
, 8x6
, 8x7
, 8x8
,
8x9
, 8x10
, 8x11
, 8x12
, 8x13
, 8x14
, 8x15
or 16x16
.
The generated sprite data blocks are predixed by a label of the form:
<file-basename>-<column>-<row>
If no labels should be generated, the optional argument no-labels
can
be used.
The optional debug
option can be used to let Chiplet dump the generated
sprite data on the console as annotated ASCII (actually Unicode) art.
Example:
:include "screen.png"
This includes the image, converts it into grayscale and every pixel brighter than 128 is interpreted as a 1, the others are 0. The image will be cut into sprites automatically, but with the additional sprite-size parameter a different splitting can be triggered. The sizes must be valid integer divisors of the image size or an error will be reported.
Chiplet is written in C++17 and uses CMake as a build solution. To build it, checkout or download the source.
Open a terminal, enter the directory where the code was extracted and run:
cmake -S . -B build
to configure the project, and
cmake --build build
to compile it.
Currently, Chiplet is not tested to compile with MSVC++ so the recommended toolchain is W64devkit. I might try to make it compile on MSVC++, but I am not working on that in the near future. That being said, I would not per-se reject PRs helping with this.
To build in the Bash from W64devkit:
export PATH="$PATH;C:/Program Files/CMake/bin"
cmake -G"Unix Makefiles" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -S . -B build-w64dev
cmake --build build-w64dev
- Octopus - This is the preprocessor that defined the syntax and feature set, we talked about this idea during it's creation time and I followed his specification to implement the preprocessor in Chiplet, and it should behave the same, with the only exception that Chiplet supports TGA but doesn't support TIFF.
- Octo assembler syntax - The preprocessor needs to make sure it doesn't break Octo, so it's lexer is implemented with respecto to the Octo specification by John Earnest.
- c-octo assembler - the octo
assembler for support of generating binaries from Octo assembler syntax
(containied in
external/c-octo
) - fmtlib - a modern formatting library in
the style of C++20 std::format (contained in
external/fmt
) - ghc::filesystem - An implementation of std::filesystem for C++11/14/17/20 that also works on MingW variants that don't have a working std::filesystem in C++17 and with better utf8 support (automatically fetched during configuration)
- stb_image - An image
format loader from Sean Barrets great
nothings
collection, used to support image import