Contents
- Quick start
- Modes
- Algorithms
- sfx — self-extracting PRG
- chain — multi-stage SFX
- mem — compressed payload only
- level — indexed multi-file archive
- source — complete 6asm source output
- asm — standalone decompressor source
- info — inspect a PRG file
- bench — compare compression ratios
- unpack — decompress a packed PRG
- Addresses
- Algorithm details
6pack
Compressor and linker for the Commodore 64. Takes .prg files and produces smaller .prg files using a pluggable set of compression algorithms (ZX0, LZSA2, BB2, RLE).
Quick start
# Pack with default ZX0 — produces game.sfx.prg that RUNs directly
6pack sfx game.prg
# Pick a different algorithm
6pack sfx -a lzsa2 game.prg
# Compare compression across all algorithms
6pack bench game.prg
# Inspect a packed file
6pack info game.sfx.prg
# Unpack back to the original
6pack unpack game.sfx.prg
Modes
| mode | what it makes |
|---|---|
sfx |
Self-extracting PRG — RUN it and it decompresses itself |
chain |
Multi-stage SFX — decompresses multiple payloads in sequence |
mem |
Compressed payload only — for linking with your own decompressor |
level |
Indexed multi-file archive — for streaming level data at runtime |
source |
Complete 6asm source file — decompressor + compressed data as .byte directives |
asm |
Standalone decompressor source — configurable 6asm include file |
info |
Inspect a PRG file — shows layout, algorithm, compression stats |
bench |
Compare compression ratios across all algorithms (and external tools) |
unpack |
Decompress a packed PRG back to the original |
Algorithms
All four algorithms are fully supported across every mode (sfx, mem, level, source, asm, info, unpack, bench).
| Algorithm | Typical ratio | 6502 core | Best for |
|---|---|---|---|
zx0 (default) |
best | 210 bytes | general-purpose |
lzsa2 |
good | 249 bytes | fast decompression |
bb2 |
good | 215 bytes | ByteBoozer2 compatibility |
rle |
basic | 74 bytes | repetitive data, tiny core |
Select an algorithm with -a <name> / --algorithm <name> on any mode that produces compressed output.
sfx — self-extracting PRG
Wraps the input in a BASIC stub and a 6502 decompressor. The result loads and runs like any other program: LOAD it, type RUN, and it decompresses itself to the original address and jumps to the entry point.
6pack sfx <input.prg[@addr]...> [options]
Multiple input files are merged into a single flat memory image before compression. Append @0xAAAA to a filename to override its load address.
Options
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
zx0 |
Compression algorithm: zx0, lzsa2, bb2, rle. |
-e, --effort 1-9 |
9 |
Compression effort. 9 = optimal (slow), 1 = fast. |
-s, --sys addr |
load address | Address to JMP to after decompression. |
-o, --output file |
stem.sfx.prg |
Output path. |
-b, --backward |
off | Use backward (in-place) ZX0 decompressor. |
-d, --dest addr |
$0801 |
SFX load address. When set, omits the BASIC stub — invoke via JMP/SYS. |
-x, --effect name |
none |
Visual effect during decompression: none, border-cycle, bg-cycle, border-data, d418-data. |
-i, --init spec |
none | One-time setup before decompression. Repeatable. Presets: border=N, bg=N, fill-screen=N, fill-color=N, jsr=$XXXX. Raw hex also accepted. |
--blank-screen |
off | Blank the VIC-II screen ($D011=0) during decompression. Hides visual garbage and avoids badline cycle stealing. Screen restores automatically after decompression via the KERNAL IRQ. |
--bank value |
$37 |
Processor port ($01) value after decompression. Common: $37 (normal), $35 (no KERNAL/BASIC), $34 (all RAM). |
--fill byte |
$00 |
Fill byte for gaps when merging non-contiguous PRGs. |
--classic |
off | Use classic (non-inverted) Elias gamma (ZX0 only). |
-v, --verbose |
off | Print compression statistics and memory layout. |
--run emu |
off | Launch emulator after packing. auto tries x64sc/x64/vice. |
--watch |
off | Recompress whenever an input file changes. Ctrl+C to stop. |
SYS sniffing
When -s/--sys is not specified, 6pack automatically extracts the SYS target address from the input PRG's BASIC stub (e.g., 10 SYS 2061). Falls back to the load address if no BASIC SYS line is found.
Custom effects
Use --effect custom=file.asm to provide your own 6502 effect routine. The .asm file is assembled on the fly via 6asm (org $0200). Raw binary files also accepted: --effect custom=file.bin.
Add ,preserve-a to make the literal byte value available in A: --effect custom=file.asm,preserve-a
Examples
# Basic usage — produces a RUN-able SFX
6pack sfx game.prg
# Specify entry point
6pack sfx game.prg -s 0x0810
# Use LZSA2 (larger ratio, faster decompression)
6pack sfx game.prg -a lzsa2
# Use BB2
6pack sfx game.prg -a bb2
# Blank screen during depack, exit with all-RAM banking
6pack sfx game.prg --blank-screen --bank 0x34
# Load SFX at custom address (no BASIC stub, invoke via SYS 16384)
6pack sfx game.prg -d 0x4000
# With border colour cycling effect
6pack sfx game.prg -x border-cycle
# Merge two PRGs and pack
6pack sfx main.prg music.prg@0xA000 -s 0x0810
# Verbose output showing full layout
6pack sfx game.prg -v
# Pack and immediately test in VICE
6pack sfx game.prg --run auto
# Watch for changes and auto-rebuild
6pack sfx game.prg --watch
# Both: rebuild on change, launch in VICE each time
6pack sfx game.prg --watch --run auto
# Custom decompression effect from assembly source
6pack sfx game.prg -x custom=myeffect.asm,preserve-a
Auto-mode selection
When the default $0801 layout doesn't fit (large programs that overlap the top-of-RAM staging area), the ZX0 builder automatically falls back to forward in-place decompression with safety padding. No manual flags needed — 6pack sfx bigfile.prg just works.
SFX binary layout
$0801 BASIC stub (12 bytes): line 6 SYS 2061
$080D sfx_init: resets stack, SEI, banks ALL-RAM, copies core to $0100,
sets ZP pointers, JMP $0100
$08xx Decompressor core (algorithm-dependent size, runs at $0100)
$09xx [optional safety padding] + compressed payload
Zero-page usage during decompression
| address | role |
|---|---|
$F8/$F9 |
src — pointer into compressed stream |
$FA/$FB |
dst — pointer into decompressed output |
$FC/$FD |
working variables (varies by algorithm) |
$FE/$FF |
working variables (varies by algorithm) |
chain — multi-stage SFX
Builds a single PRG that decompresses multiple payloads in sequence. Each stage's decompressor exits by jumping to the next stage's init stub. The final stage jumps to the sys address.
Useful for large productions: intro → main part, or loader → game.
6pack chain <stage0.prg[@addr]> <stage1.prg[@addr]> [stageN.prg...] [options]
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
zx0 |
Compression algorithm for all stages. |
-e, --effort 1-9 |
9 |
Compression effort. |
-s, --sys addr |
last stage load addr | Final JMP target after all stages decompress. |
-o, --output file |
stem.chain.prg |
Output path. |
--blank-screen |
off | Blank VIC-II during decompression. |
--bank value |
$37 |
Exit banking value. |
-v, --verbose |
off | Show per-stage layout. |
Examples
# Two-stage chain: intro decompresses first, then main
6pack chain intro.prg main.prg -s 0x0810
# Three stages with verbose layout
6pack chain loader.prg intro.prg game.prg -v
# Use BB2 compression
6pack chain -a bb2 stage1.prg stage2.prg
Layout
$0801 BASIC stub (SYS 2061)
$080D Stage 0: sfx_init + core + compressed payload
(exit JMP → stage 1 address)
$xxxx Stage 1: sfx_init + core + compressed payload
(exit JMP → sys address)
Each stage's SYS target is auto-detected from its BASIC SYS line (if present), or defaults to its load address. The explicit --sys flag only affects the final stage.
mem — compressed payload only
Compresses the input and writes just the compressed bytes as a .prg. Use this when you supply your own decompressor in your project.
6pack mem <input.prg> [options]
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
zx0 |
Compression algorithm. |
-e, --effort 1-9 |
9 |
Compression effort. |
--classic |
off | Classic Elias gamma (ZX0 only). |
-o, --output file |
stem.mem.prg |
Output path. |
-p, --pack-address addr |
source load address | Load address for the .prg header. |
-v, --verbose |
off | Print compression stats. |
level — indexed multi-file archive
Compresses multiple PRGs into one blob with an index table at the front. Useful for games that stream level data at runtime.
6pack level <file1.prg> [file2.prg ...] [options]
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
zx0 |
Compression algorithm. |
-e, --effort 1-9 |
9 |
Compression effort applied to all files. |
--classic |
off | Classic Elias gamma (ZX0 only). |
-o, --output file |
stem.lvl.prg |
Output path. |
--archive-address addr |
$0801 |
Load address in the .prg header. |
-v, --verbose |
off | Print compression stats. |
Archive format
Offset Size Field
0 2 N — number of entries (little-endian)
2 N×4 Index table: load_address (2) + compressed_size (2) per entry
2+N×4 ... Compressed payloads concatenated in order
To decompress entry i at runtime:
1. Read N from offset 0.
2. Read load_address[i] and compressed_size[i] from 2 + i*4.
3. Skip sum(compressed_size[0..i-1]) bytes past the end of the index (2 + N*4) to find the payload.
4. Call your decompressor with src = payload start, dst = load_address[i].
source — complete 6asm source output
Outputs a self-contained .asm file with the decompressor and compressed data as .byte directives. Assemble directly with 6asm.
6pack source <input.prg> [options]
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
zx0 |
Compression algorithm. |
-e, --effort 1-9 |
9 |
Compression effort. |
-s, --sys addr |
load address | JMP target after decompression. |
-o, --output file |
stdout | Output .asm file. |
--blank-screen |
off | Add screen blank/restore to the setup code. |
--bank value |
$37 |
Exit banking value. |
Example
6pack source game.prg -a lzsa2 -s 0x0810 -o depack.asm
6asm -o depack.prg depack.asm
The output contains:
- depack label: setup code that loads ZP pointers, calls the decompressor, restores banking, and jumps to sys
- dzx0 / rle_decomp / bb2_decomp / lzsa2_decomp label: the decompressor subroutine (RTS-based, callable via JSR)
- zx0_data / rle_data / bb2_data / lzsa2_data label: compressed data as .byte directives
asm — standalone decompressor source
Outputs just the decompressor as a 6asm source file.
6pack asm [options]
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
zx0 |
Which decompressor to output: zx0, lzsa2, bb2, rle. |
--zp addr |
$F8 |
Zero-page base address (8 bytes used). |
-o, --output file |
stdout | Output .asm file. |
Example
# ZX0 decompressor (default)
6pack asm -o zx0_decomp.asm
# RLE decompressor with ZP at $02
6pack asm -a rle --zp 0x02 -o rle_decomp.asm
# LZSA2 decompressor
6pack asm -a lzsa2 -o lzsa2_decomp.asm
info — inspect a PRG file
Shows file metadata, detects SFX files, identifies the compression algorithm, and prints stream statistics (for ZX0).
6pack info <file.prg>
Example output
$ 6pack info game.sfx.prg
file game.sfx.prg (7597 bytes)
load addr $0801
end addr $25AB
payload 7595 bytes
type SFX (BASIC stub: SYS 2061)
algorithm ZX0
payload at +297 (7298 bytes compressed):
compressed 7298 bytes → 7328 bytes (99.6%)
commands 37 total (17 literal runs, 18 new-match, 2 rep-match)
literals 17 runs · 7242 raw bytes (98.8% of output)
new-match 18 matches · offset $0001–$1C5F · length 2–12 avg 4
rep-match 2 matches · length 2–3 avg 2
max surplus 34 bytes
Detects:
- SFX with BASIC stub at $0801
- SFX without BASIC stub (custom --dest address)
- Algorithm: ZX0, LZSA2, BB2, or RLE (by 6502 core binary signature)
bench — compare compression ratios
Compresses with all built-in algorithms and shows a comparison table. Also probes for external tools (exomizer, pucrunch) if installed, for competitive comparison.
6pack bench <file.prg> [-e effort] [--speed]
Pass --speed to also measure 6502 decompression cycles for each algorithm (runs the actual 6502 decompressor in the built-in emulator).
Example output
$ 6pack bench mandel.prg
bench: mandel.prg (10001 bytes, load $6000)
algorithm compressed ratio surplus core
------------ ---------- -------- -------- -----
zx0 3254 32.5% 6749 210
lzsa2 3544 35.4% 6460 249
bb2 3390 33.9% 6613 215
rle 4475 44.7% 5527 74
unpack — decompress a packed PRG
Decompresses a packed PRG back to the original. Auto-detects the algorithm for SFX files; for raw mem output, specify the algorithm manually.
6pack unpack <packed.prg> [options]
| flag | default | meaning |
|---|---|---|
-a, --algorithm |
auto-detect | Algorithm: zx0, lzsa2, bb2, rle. Required for raw mem files. |
--classic |
on | Use classic Elias gamma (off = inverted). Only affects ZX0 mem files. |
-o, --output file |
stem.unpacked.prg |
Output path. |
--load-address addr |
from SFX / same as input | Override the output PRG load address. |
Examples
# Unpack an SFX (algorithm auto-detected)
6pack unpack game.sfx.prg
# Unpack a raw mem file (must specify algorithm)
6pack unpack game.mem.prg -a zx0
# Unpack a BB2 file
6pack unpack game.bb2.prg -a bb2
Addresses
All address arguments accept these formats:
| format | example |
|---|---|
Hex with $ |
$0801 |
Hex with 0x |
0x0801 |
| Decimal | 2049 |
Algorithm details
ZX0 (default)
Einar Saukas's LZ+Elias-gamma format. Best compression ratio for general C64 data. Classic (non-inverted) gamma is used for the SFX 6502 decompressor.
- 6502 core size: 210 bytes (forward), 239 bytes (backward)
- Effort 1–9: controls the number of optimal parser slots (4–72)
- Best for: general data, code, graphics, large files
LZSA2
Emmanuel Marty's LZSA2 format. LZ77 with variable-width offsets and nibble-encoded lengths. Competitive compression, faster decompression than ZX0.
- 6502 core size: 249 bytes
- Stream format: XYZ|LL|MMM token byte + nibble-encoded lengths + variable-width offsets (5/9/13/16-bit + rep-match)
- Best for: when decompression speed matters more than pure ratio
- License: zlib (Emmanuel Marty)
BB2 (ByteBoozer2)
David Malmborg's (HCL) ByteBoozer2 format. LZ77 with interleaved-gamma lengths and two offset tables (short for 2-byte matches, long for 3+ byte matches).
- 6502 core size: 215 bytes
- Stream format: MSB-first bitstream; offset tables of 3/6/8/10 bits (short) and 4/7/10/13 bits (long); length 255 = EOF
- Best for: competitive with ZX0 on many files, smaller core than LZSA2
- Source: adapted from the ByteBoozer2 project by David Malmborg, maintained by Luigi Di Fraia
RLE
Simple byte-oriented run-length encoding. Faster decompression, smaller core, but worse compression ratio than LZ algorithms.
- 6502 core size: 74 bytes
- Stream format:
$00=EOF,$01–$7F=literal run,$80–$FF=byte repeat (2–129 times) - Best for: screen fills, color memory, repetitive sprite data, cases where decompressor size matters