Fork of Marq’s PETSCII editor, the C64 cross-platform PETSCII art editor.
Installers are on the latest release page:
| Platform | Asset |
|---|---|
| macOS | .dmg, Apple silicon or Intel |
| Windows | .msi, 64-bit Intel/AMD |
| Linux | .deb / .rpm, 64-bit Intel/AMD or ARM |
| Linux, 32-bit ARM | .tar.gz |
32-bit ARM Linux .tar.gz ships without a runtime and needs Java 17 or later.
Note that macOS release is not signed.
The right-click → Open trick works on older macOS, but no longer on Sequoia and later.
If Open Anyway never appears, open Terminal and strip the quarantine flag by hand and then launch again:
xattr -d com.apple.quarantine /Applications/petscii.app
Depending on your distribution,
sudo dpkg -i petscii-0.3.1-linux-amd64.deb
or
sudo dnf install petscii-0.3.1-linux-amd64.rpm
Installs to /opt/petscii with a desktop entry. Linux distributions are untested.
Run the .msi.
Note: Release candidates with equal version number cannot be automatically upgraded. Uninstall the old one first manually.
See the CHANGELOG.md for the detailed list of changes per release.
On Linux, prefs.txt and plugin.js are searched in this order:
$HOME/.petscii/etc/petscii/$HOME (legacy)/usr/share/petscii/ (legacy)Experimental headless runner, for automating conversions and exports in a Linux CI. Processing cannot run headless natively, so it needs Xvfb and xdotool.
petscii_cli /path/to/petscii /path/to/image.c MACHINE "cmd;cmd;cmd"
MACHINE is any machine PETSCII supports. Commands are xdotool keystrokes, separated by semicolons. This loads /tmp/example.c, exports a .prg and a bordered .png:
./petscii_cli /opt/petscii/bin/petscii /tmp/example.c C64 "e;P"
Experimental. Ctrl-e runs plugin.js, found via the search order above, so each image folder can carry its own exporter. One plugin at a time — custom export code is usually specific to a single demo or game, and this keeps a zoo of exporters out of the editor.
TODO: invoke a file selector when plugin.js is missing.
Scripts get a deliberately small API:
| variable | type | purpose |
|---|---|---|
stdout |
PrintStream | exposes System.out |
outputs |
ArrayList | ArrayList of output writers, see below |
colors |
int[] | color array |
chars |
int[] | character array |
border |
int | border color |
bg |
int | background color |
filename |
String | path and file name of the current image |
fileprefix |
String | filename without .c suffix |
currentframe |
int | index of the current frame |
machine |
String | the target MACHINE in PETSCII editor |
An Output pairs a file name with a PrintWriter, and they are handled through the outputs list:
var fp = outputs.add_file(fileprefix + ".asm"); // file index
var asmfile = outputs.get(fp).pwriter;
asmfile.println("Hello world");
Repeat for more files. Adding the same file twice returns the existing index; outputs.get_file(name) looks one up, returning -1 if absent. Writers are flushed and closed after the script runs.
if (machine == "C64"){
// do stuff applicable for C64
}
else if (machine == "VIC20"){
// do stuff applicable for VIC20
}
Examples are in /extras/plugins. Copy one next to the executable as plugin.js, press Ctrl-e and see what happens.
Save .prg (e) writes a C-64 program that shows the picture. When the charset is not one of the built-in ones — you loaded a charset .png, traced an image, or opened a .c or .petmate carrying its own font — the character data goes into the .prg too, because it is not in ROM.
The file is one contiguous block with no padding: BASIC stub, 119 bytes of code, screen codes, colour RAM, and only those 256-byte charset pages the picture actually uses. A picture using all 256 characters is 4184 bytes; one that stays below character 128 is a kilobyte smaller. On start it copies the data to $0400, $d800 and $3800, points the VIC at the charset and stops.
The viewer source is /extras/asm/template-c64font.s. If you change it, rebuild data/template-c64font.prg with 64tass:
cd extras/asm && 64tass -o ../../data/template-c64font.prg template-c64font.s
Known issues: C-64 only. The C-64 flicker, VIC-20 and Plus/4 exporters still write ROM-charset .prg files, and say so in the status line. Load .prg does not read these files back.
Saving keeps the charset each frame is drawn with, so a picture with a charset of its own survives a save and load. Frames may differ: a .c can hold one charset per frame. Identical charsets are stored once and shared, and frames using the machine’s own font store nothing at all, so a picture that never left the ROM charset saves exactly as it always did.
Everything past the frames is written as C, not as comments. This is format 2:
unsigned char frame0000[]={ ... };
unsigned char frame0001[]={ ... };
static const int version=2;
static const unsigned char charset0000[]={// 256 characters, 8 bytes each
0,0,0,0,0,0,0,0,
...
};
static const int fonts[]={0,-1}; // charset per frame, -1 = the machine's own
static const int meta[]={40,25,1}; // width height case, 1 = upper
// META: 40 25 C64 upper
| Declaration | Holds |
|---|---|
version |
The format version, a running integer. Files without it are format 1: frames and the // META: comment, nothing else. |
charsetNNNN |
One charset, 256 characters of 8 bytes, bit 7 leftmost. Only written when a frame needs it. |
fonts |
The charset each frame is drawn with, in frame order. -1 is the machine’s own font. Only written when there are charsets. |
meta |
Width, height and case, 1 being upper. The machine is read from the // META: comment, which is written for older versions anyway. |
The file still compiles with cc65, which only warns about the declarations being unused.
The static const is deliberate. PETSCII stops reading frames at the first line that does not begin with unsigned char, so a version older than format 2 loads the picture, draws it with the ROM charset and ignores the rest of the file. Such a version reads the metadata from the // META: comment only, which is why that line is still written, and still written last. It cannot write any of the new data back, though: re-saving in an old version drops the charsets.
Loading a charset .png, tracing an image or toggling Case applies to the whole picture — every frame — as before. Only a file that carries per-frame charsets gets them, and switching frames then switches the characters with it.
/extras/charset_conv.sh converts a 128x128 image to a PETSCII charset and back.
Usage: ./extras/charset_conv.sh [options] <input_file>
Options:
-h, --help Show this help message and exit
-o, --output Output file
./extras/charset_conv.sh input.png -o=output.png # convert
./extras/charset_conv.sh input.png # print dimensions as (x,y)