diff --git a/.github/workflows/amiga_demo.yml b/.github/workflows/amiga_demo.yml
index 4658deb..763e096 100644
--- a/.github/workflows/amiga_demo.yml
+++ b/.github/workflows/amiga_demo.yml
@@ -32,9 +32,15 @@ jobs:
cd ipng2iff
cargo build -r
sudo cp target/release/ipng2iff /usr/local/bin/
+ - name: Install salvador (zx0 packer)
+ run: |
+ git clone https://github.com/emmanuel-marty/salvador.git
+ cd salvador
+ make -j$(nproc)
+ sudo cp salvador /usr/local/bin/
- name: Build ADF using make
run: |
- make adf XDF_TOOL=venv/bin/xdftool -j$(nproc)
+ make adf XDF_TOOL=venv/bin/xdftool
- name: Upload ADF artifact(s)
uses: actions/upload-artifact@v4
with:
diff --git a/.gitignore b/.gitignore
index de1a45f..09c9a99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ uae/dh0/main
assets/*.png
assets/*.iff
assets/*.raw
+assets/*.zx0
# src that we don't care about
include/*_palette.i
# compiled tools
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index f0c1a9a..29ffde8 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -21,6 +21,7 @@
"exefilename": "../uae/dh0/main",
"entrypoint": "main.s",
"args": [
+ "-mrel",
"-bamigahunk",
"-Bstatic"
]
diff --git a/Makefile b/Makefile
index 594287b..36d1282 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,9 @@ ifneq ($(DEBUG),1)
LINK_ARGS += -s
endif
+# ZX0 compressor
+ZX0 := salvador -v
+
# Source files
SRC_DIR := ./src
MAIN_SRC := $(SRC_DIR)/main.s
@@ -57,6 +60,8 @@ OBJS := $(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(ASM_SRCS:.s=.o))
ASSETS_DIR := ./assets
GIMP_ASSETS := $(wildcard $(ASSETS_DIR)/*.xcf)
RAW_ASSETS := $(GIMP_ASSETS:.xcf=.raw)
+# ZX0 Assets are RAW_ASSETS compressed with ZX0 (salvador)
+ZX0_ASSETS := $(RAW_ASSETS:.raw=_raw.zx0)
PALETTE_DIR := ./include
# The target ADF dir
@@ -64,13 +69,21 @@ ADF_DIR := ./uae/dh0
# The Target Binary Program
TARGET := $(ADF_DIR)/main
+# Generic rule to create a RAW asset from XCF
+$(ASSETS_DIR)/%.raw: $(ASSETS_DIR)/%.xcf
+ @./scripts/convert_assets_to_raw.sh -x -p -r -s -i $(PALETTE_DIR) "$<"
+
+# Generic rule to compress a RAW asset using zx0
+$(ASSETS_DIR)/%_raw.zx0: $(ASSETS_DIR)/%.raw
+ $(ZX0) "$<" "$@"
+
# Generic rule to assemble a 68k asm source file (../src/*.cpp) into an object file (*.o)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.s $(ASM)
# @echo '(${ASM}) Assembling source file: $<'
$(ASM) $(ASM_ARGS) -o "$@" "$<"
# Link executable
-$(TARGET): $(LD) $(RAW_ASSETS) $(BUILD_DIR) $(MAIN_OBJ) $(OBJS)
+$(TARGET): $(LD) $(RAW_ASSETS) $(ZX0_ASSETS) $(BUILD_DIR) $(MAIN_OBJ) $(OBJS)
# @echo '(${LD}) Linking target: $@'
$(LD) $(LINK_ARGS) -o "$@" $(MAIN_OBJ) $(OBJS)
@echo 'Finished linking target: $@'
@@ -79,10 +92,6 @@ $(TARGET): $(LD) $(RAW_ASSETS) $(BUILD_DIR) $(MAIN_OBJ) $(OBJS)
$(BUILD_DIR):
@mkdir -p $(BUILD_DIR)
-# RAW assets
-$(RAW_ASSETS):
- @./scripts/convert_assets_to_raw.sh -x -p -r -s -i $(PALETTE_DIR)
-
# TOOLS - vasm
$(ASM): $(VASM_DIR)
@echo 'Building vasmm68k_mot in $(VASM_DIR)...'
@@ -105,7 +114,7 @@ all: $(TARGET)
tools: $(ASM) $(LD)
-assets: $(RAW_ASSETS)
+assets: $(RAW_ASSETS) $(ZX0_ASSETS)
ADF_FILE := amiga_demo.adf
ADF_VOLUME_NAME := 'Amiga Demo'
@@ -138,6 +147,7 @@ clean_assets:
@rm -f $(ASSETS_DIR)/*.png
@rm -f $(ASSETS_DIR)/*.iff
@rm -f $(ASSETS_DIR)/*.raw
+ @rm -f $(ASSETS_DIR)/*.zx0
# clean EVERYTHING
clean_all: clean clean_tools clean_assets clean_adf
diff --git a/README.md b/README.md
index c9e6867..c41e30a 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ under [their own licenses](./tools/LICENSE.md).
The following features are provided and demonstrated:
* Conversion of [GIMP](https://www.gimp.org/) authored image assets (*`.xcf`) into `.png`, `.iff` and `.raw` (interleaved) formats
+* Compression ('packing') or raw assets using the '`zx0`' format
* Generation of palette (`COLORxx` register) data for image assets in copper list format
* Host compilation of assembler (`vasm`) and linker (`vlink`) tools included.
* Building to a [UAE](https://en.wikipedia.org/wiki/UAE_(emulator)) emulated hard drive (`dh0`) folder
@@ -24,7 +25,7 @@ for CI/CD automated building in the the cloud.
* Only RAW files with 'interleaved' bitplanes data are generated (no 'back-to-back' support)
* Only images for low resolution (non-EHB) mode apps are supported.
-* No compression/packing support.
+* Only `zx0` compression/packing support.
* No support for attached sprites palette generation
* No special treatment for AGA
* Bare bones 'no frills' bootable AmigaDOS ADFs. i.e. no loading messages etc.
@@ -32,10 +33,13 @@ for CI/CD automated building in the the cloud.
## Demo App ##
This repo contains source code for a simple Amiga demo using Bitplanes (playfield), Blitter objects (BOBs) and Sprites.
+
This demo was made using samples of [example code](https://www.edsa.uk/blog/downloads) from the excellent book
['Bare-Metal Amiga Programming'](https://www.edsa.uk/blog/bare-metal-amiga-programming)
by E. Th. van den Oosterkamp, and used under his permissive license terms.
+This demo also includes m68k asm `zx0` decompression code from [`salvador`](https://github.com/emmanuel-marty/salvador) by Emmanuel Marty, also used under permissive license terms.
+
The app was also developed in the equally excellent
[Amiga Assembly](https://marketplace.visualstudio.com/items?itemName=prb28.amiga-assembly)
extension for [Visual Studio Code](https://code.visualstudio.com/) (VSCode).
@@ -98,6 +102,22 @@ It is part of the [`netpbm`](https://netpbm.sourceforge.net/) toolkit.
sudo apt install netpbm
```
+### salvador ###
+[salvador](https://github.com/emmanuel-marty/salvador) is used to compress `*.raw` images into `*_raw.zx0` packed files in the `zx0` format to help save space.
+
+`salvador` has to be built from source.
+```sh
+git clone https://github.com/emmanuel-marty/salvador
+cd salvador
+make
+```
+
+Once `salvador` has built, you need to move it to somewhere where it can be found in your `$PATH`. e.g.
+
+```sh
+sudo cp salvador /usr/local/bin
+```
+
### amitools ###
[amitools](https://pypi.org/project/amitools/) is used to create floppy disk (`ADF`) images using
@@ -212,7 +232,7 @@ Passing `-h` (or `--help`) to the script shows usage information:
```none
$ ./scripts/convert_assets_to_raw.sh -h
-usage: convert_assets_to_raw.sh [options]
+usage: convert_assets_to_raw.sh [options] [input file]
Options:
@@ -236,6 +256,8 @@ Options:
Include generation of PNG files from XCFs.
```
+If no `input_file` is specified, the script will works as a wildcard selecting all applicable files in the specified (or default) 'assets' directory.
+
## GitHub Actions Workflow ##
This repository includes a [GitHub Actions](https://docs.github.com/en/actions) (GHA)
diff --git a/scripts/convert_assets_to_raw.sh b/scripts/convert_assets_to_raw.sh
index e143386..db420bc 100755
--- a/scripts/convert_assets_to_raw.sh
+++ b/scripts/convert_assets_to_raw.sh
@@ -26,13 +26,13 @@ script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
# Get script name
script_name="$(basename "$0")"
-# Set derault input/output dirs
+# Set default input/output dirs
asset_dir=$(realpath "$script_path/../assets")
inc_dir="$asset_dir"
-# Function to show ussage information and exit
+# Function to show usage information and exit
show_usage() {
- echo "usage: ${script_name} [options]"
+ echo "usage: ${script_name} [options] [input file]"
echo -e "\nOptions:\n" \
"\n -a,--asset-dir
\n" \
" Use dir to find source assets and place output assets.\n" \
@@ -102,7 +102,7 @@ while [[ $# > 0 ]]; do
-r | --iff-raw) skip_raw=false ;;
-s | --inc-pal) skip_palette=false ;;
-x | --xcf-png) skip_png=false ;;
- *) echo "Unrecognized option ${1} ignored" ;;
+ *) input_file="${1}" ;;
esac
shift
done
@@ -112,6 +112,15 @@ if $show_usage; then
exit 0
fi
+
+if [[ -z "$input_file" ]]; then
+ input_file="${asset_dir}/*"
+else
+ input_file="$(echo ${input_file%.*})"
+fi
+
+echo "INPUT FILE(S): ${input_file}"
+
# Check to make sure we have the tools we need available in $PATH
rgb2iff_cmd="ipng2iff"
ilbm_cmd="ilbmtoppm"
@@ -154,12 +163,12 @@ if [[ ! -d "$inc_dir" ]]; then
fi
inc_dir=$(realpath "$inc_dir")
-# Convert all *.xcf files in assets/GIMP into PNG files in assets/PNG
+# Convert *.xcf files in assets/GIMP into PNG files in assets/PNG
if $skip_png; then
echo "XCF->PNG generation skipped. Specify --xcf-png option to include"
else
- for xcf_file in "$asset_dir"/*.xcf; do
- png_file="$asset_dir/$(basename $xcf_file .xcf).png"
+ for xcf_file in ${input_file}.xcf; do
+ png_file="$(dirname $xcf_file)/$(basename $xcf_file .xcf).png"
echo -e "\nConverting: XCF --> PNG\n<-- $xcf_file\n--> $png_file"
if ! $dry_run; then
# Write PNG file
@@ -168,13 +177,13 @@ else
done
fi
-# Convert all *.png files in assets/PNG into IFF/ILBM *.iff files in assets/IFF
+# Convert *.png files in assets/PNG into IFF/ILBM *.iff files in assets/IFF
if $skip_iff; then
echo "PNG->IFF generation skipped. Specify --png-iff option to include"
else
- for png_file in "$asset_dir"/*.png; do
+ for png_file in ${input_file}.png; do
png_res=$(file "$png_file" | grep -oP '([[:digit:]]+[[:blank:]]*x[[:blank:]]*[[:digit:]]+)' | sed 's/ //g')
- iff_file="$asset_dir/$(basename $png_file .png).iff"
+ iff_file="$(dirname $png_file)/$(basename $png_file .png).iff"
echo -e "\nConverting: PNG --> IFF\n<-- $png_file ($png_res)\n--> $iff_file"
if ! $dry_run; then
# Write IFF file
@@ -223,7 +232,7 @@ get_ilbm_info() {
done <<<$(echo "$ilbm_cmap")
}
-# Convert all *.iff files in assets/IFF to raw (interleaved) plane data in RAW
+# Convert *.iff files in assets/IFF to raw (interleaved) plane data in RAW
process_ilbm=false
if $skip_raw; then
echo "IFF->RAW conversion skipped. Specify --iff-raw option to include"
@@ -236,10 +245,10 @@ else
process_ilbm=true
fi
if $process_ilbm; then
- for iff_file in "$asset_dir"/*.iff; do
+ for iff_file in ${input_file}.iff; do
# grab some metadata on the IFF file
get_ilbm_info
- raw_file="$asset_dir/$(basename $iff_file .iff).raw"
+ raw_file="$(dirname $iff_file)/$(basename $iff_file .iff).raw"
# Only write one palette file for each sprite pair
case "{$iff_file,,}" in
*spr0* | *spr1*) palette_file="$inc_dir/sprites_01_palette.i" ;; # Sprites 0 & 1 palette file
@@ -278,13 +287,13 @@ fi
# Delete intermediary files if requested:
if $delete_int_files; then
- for png_file in "$asset_dir"/*.png; do
+ for png_file in ${input_file}.png; do
echo "Removing $png_file"
if ! $dry_run; then
rm "$png_file"
fi
done
- for iff_file in "$asset_dir"/*.iff; do
+ for iff_file in ${input_file}.iff; do
echo "Removing $iff_file"
if ! $dry_run; then
rm "$iff_file"
diff --git a/src/bobs.s b/src/bobs.s
index 7e69c8b..df7c711 100644
--- a/src/bobs.s
+++ b/src/bobs.s
@@ -72,7 +72,7 @@ MoveBOB::
; D1.W - Y pos (vert)
PrepBOB::
- LEA.L Background(PC),a1 ; APTR interleaved playfield
+ LEA.L Bitplanes(PC),a1 ; APTR interleaved playfield
MULU #80,d1 ; Convert Y pos into offset
ADD.L d1,a1 ; Add offset to destination
AND.W #$FFF0,d0 ; Position without shift
@@ -104,7 +104,7 @@ PrepBOB::
; D1.W - Y pos (vert)
PlaceBOB:
- LEA.L Background(PC),a2 ; APTR interleaved playfield
+ LEA.L Bitplanes(PC),a2 ; APTR interleaved playfield
MULU #80,d1 ; Convert Y pos into offset
ADD.L d1,a2 ; Add offset to destination
EXT.L d0 ; Clear top bits of D0
@@ -120,8 +120,8 @@ PlaceBOB:
MOVE.L a1,BLTAPT(a5) ; Source A = Mask
MOVE.L a0,BLTBPT(a5) ; Source B = Object
- MOVE.L a2,BLTCPT(a5) ; Source C = Background
- MOVE.L a2,BLTDPT(a5) ; Destination = Background
+ MOVE.L a2,BLTCPT(a5) ; Source C = Bitplanes
+ MOVE.L a2,BLTDPT(a5) ; Destination = Bitplanes
MOVE.W #$FFFF,BLTAFWM(a5) ; No first word masking
MOVE.W #$FFFF,BLTALWM(a5) ; No last word masking
MOVE.W d0,BLTCON1(a5) ; Use shift for source B
@@ -142,7 +142,7 @@ PlaceBOB:
; D1.W - Y pos (vert)
ClearBOB:
- LEA.L Background(PC),a1 ; APTR interleaved playfield
+ LEA.L Bitplanes(PC),a1 ; APTR interleaved playfield
MULU #80,d1 ; Convert Y pos into offset
ADD.L d1,a1 ; Add offset to destination
AND.W #$FFF0,d0 ; Position without shift
diff --git a/src/main.s b/src/main.s
index 747c8f0..4d02a4e 100644
--- a/src/main.s
+++ b/src/main.s
@@ -20,102 +20,110 @@
; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- INCLUDE "BareMetal.i"
+ INCLUDE "BareMetal.i"
;-----------------------------------------------------------
- SECTION Code,CODE_C
+ SECTION Code,CODE_C
- INCLUDE "SafeStart.i"
+ INCLUDE "SafeStart.i"
Main:
- LEA.L Coplist(PC),a0 ;
- MOVE.L a0,COP1LC(a5) ; Set start address for coplist
+ LEA.L Coplist(PC),a0 ;
+ MOVE.L a0,COP1LC(a5) ; Set start address for coplist
; Setup the bitplane pointers in the coplist
- LEA.L CopBPL(PC),a0
- LEA.L Background(PC),a1 ; APTR Start of bitplane 1
- MOVE.L a1,d0
- MOVE.W d0,6(a0) ; Place low word into coplist
- SWAP d0
- MOVE.W d0,2(a0) ; Place high word into coplist
- ADDA.W #40,a1 ; Bitplane 2 starts one line later
- MOVE.L a1,d0
- MOVE.W d0,14(a0) ; Place low word into coplist
- SWAP d0
- MOVE.W d0,10(a0) ; Place high word into coplist
+ LEA.L CopBPL(PC),a0
+ LEA.L Bitplanes(PC),a1 ; APTR Start of bitplane 1
+ MOVE.L a1,d0
+ MOVE.W d0,6(a0) ; Place low word into coplist
+ SWAP d0
+ MOVE.W d0,2(a0) ; Place high word into coplist
+ ADDA.W #40,a1 ; Bitplane 2 starts one line later
+ MOVE.L a1,d0
+ MOVE.W d0,14(a0) ; Place low word into coplist
+ SWAP d0
+ MOVE.W d0,10(a0) ; Place high word into coplist
+
+; Decompress background image data into Bitplanes
+ LEA.L Background_ZX0,a0 ; ZX0 compressed SRC
+ LEA.L Bitplanes,a1 ; Bitplanes DEST
+ BSR zx0_decompress
; Setup the sprite pointers in the coplist
- LEA.L CopSprite(PC),a0 ; APTR Sprite pointers in coplist
- LEA.L SpriteList(PC),a1 ; APTR List of sprite pointers
- MOVEQ #8-1,d7 ; Process 8 sprite pointers
-.NextSprite MOVE.L (a1)+,d0
- MOVE.W d0,6(a0) ; Place low word into coplist
- SWAP d0
- MOVE.W d0,2(a0) ; Place high word into coplist
- ADDQ.L #8,a0 ; Move to next pointer in coplist
- DBF d7,.NextSprite
+ LEA.L CopSprite(PC),a0 ; APTR Sprite pointers in coplist
+ LEA.L SpriteList(PC),a1 ; APTR List of sprite pointers
+ MOVEQ #8-1,d7 ; Process 8 sprite pointers
+.NextSprite MOVE.L (a1)+,d0
+ MOVE.W d0,6(a0) ; Place low word into coplist
+ SWAP d0
+ MOVE.W d0,2(a0) ; Place high word into coplist
+ ADDQ.L #8,a0 ; Move to next pointer in coplist
+ DBF d7,.NextSprite
; Prepare the playfield. Note COLOURxx registers set by Copper palette include
- MOVE.W #40,BPL1MOD(a5) ; All bitplanes need to skip one line
- MOVE.W #40,BPL2MOD(a5) ; at the end of each line
+ MOVE.W #40,BPL1MOD(a5) ; All bitplanes need to skip one line
+ MOVE.W #40,BPL2MOD(a5) ; at the end of each line
- MOVE.W #$2200,BPLCON0(a5) ; 2 bitplanes, enable colour on composite
- MOVE.W #0,BPLCON1(a5) ; No delay/shift on odd or even bitplane
- MOVE.W #0,BPLCON2(a5) ; Functionality not required
- MOVE.W #$24,BPLCON2(a5) ; All sprites above pf1
+ MOVE.W #$2200,BPLCON0(a5) ; 2 bitplanes, enable colour on composite
+ MOVE.W #0,BPLCON1(a5) ; No delay/shift on odd or even bitplane
+ MOVE.W #0,BPLCON2(a5) ; Functionality not required
+ MOVE.W #$24,BPLCON2(a5) ; All sprites above pf1
- MOVE.W #0,FMODE(a5) ; AGA: Use 16 bit DMA transfers
- MOVE.W #$2C81,DIWSTRT(a5) ; Left/top corner of display window
- MOVE.W #$2CC1,DIWSTOP(a5) ; Right/bottom corner of display window
- MOVE.W #$38,DDFSTRT(a5) ; Location of first DMA fetch each line
- MOVE.W #$D0,DDFSTOP(a5) ; Location of last DMA fetch each line
+ MOVE.W #0,FMODE(a5) ; AGA: Use 16 bit DMA transfers
+ MOVE.W #$2C81,DIWSTRT(a5) ; Left/top corner of display window
+ MOVE.W #$2CC1,DIWSTOP(a5) ; Right/bottom corner of display window
+ MOVE.W #$38,DDFSTRT(a5) ; Location of first DMA fetch each line
+ MOVE.W #$D0,DDFSTOP(a5) ; Location of last DMA fetch each line
- MOVE.W #$81E0,DMACON(a5) ; Enable bitplane, Copper, Sprite and Blitter DMA
+ MOVE.W #$81E0,DMACON(a5) ; Enable bitplane, Copper, Sprite and Blitter DMA
- MOVE.W PrevPos(PC),d0
- MOVE.W PrevPos+2(PC),d1
- LEA.L ObjectStore,a0
- BSR PrepBOB
+ MOVE.W PrevPos(PC),d0
+ MOVE.W PrevPos+2(PC),d1
+ LEA.L ObjectStore,a0
+ BSR PrepBOB
; Wait for the user to click the mouse
.WaitLoop
- MOVE.L VPOSR(a5),d0 ; Get VPOSR and VHPOSR
- LSR.L #8,d0 ; Shift vertical pos to lowest 9 bits
- AND.W #$01FF,d0 ; Remove unwanted bits
- CMP.W #$0001,d0 ; On line 1?
- BNE.B .Skip ; No? Do nothing
-
- BSR.W MoveBOB
- BSR.W MoveSprites
+ MOVE.L VPOSR(a5),d0 ; Get VPOSR and VHPOSR
+ LSR.L #8,d0 ; Shift vertical pos to lowest 9 bits
+ AND.W #$01FF,d0 ; Remove unwanted bits
+ CMP.W #$0001,d0 ; On line 1?
+ BNE.B .Skip ; No? Do nothing
+
+ BSR.W MoveBOB
+ BSR.W MoveSprites
.Wait
- MOVE.L VPOSR(a5),d0 ; Get vertical an horizontal position
- LSR.L #8,d0 ; Shift vertical pos to lowest 9 bits
- AND.W #$01FF,d0 ; Remove unwanted bits
- CMP.W #$0001,d0 ; On line 1?
- BEQ.B .Wait ; Wait until no longer on line 1
+ MOVE.L VPOSR(a5),d0 ; Get vertical an horizontal position
+ LSR.L #8,d0 ; Shift vertical pos to lowest 9 bits
+ AND.W #$01FF,d0 ; Remove unwanted bits
+ CMP.W #$0001,d0 ; On line 1?
+ BEQ.B .Wait ; Wait until no longer on line 1
.Skip
- BTST #6,CIAAPRA ; Check for left mouse click
- BNE.B .WaitLoop ; No click, keep testing
- RTS
+ BTST #6,CIAAPRA ; Check for left mouse click
+ BNE.B .WaitLoop ; No click, keep testing
+ RTS
;-----------------------------------------------------------
-PrevPos:: DC.W 0,0
+PrevPos:: DC.W 0,0
-Background:: INCBIN "assets/Background.raw"
+Background_ZX0:: INCBIN "assets/Background_raw.zx0"
+ EVEN
;-----------------------------------------------------------
- SECTION BitPlane,BSS_C
+ SECTION BitPlane,BSS_C
-ObjectStore:: DS.B (80*64*2)/8
+Bitplanes:: DS.B 320*256*2/8 ; 320x256 screen, 2 (interleaved) bitplanes
+
+ObjectStore:: DS.B (80*64*2)/8
;-----------------------------------------------------------
diff --git a/src/unzx0.s b/src/unzx0.s
new file mode 100644
index 0000000..be7a639
--- /dev/null
+++ b/src/unzx0.s
@@ -0,0 +1,76 @@
+; unzx0_68000.s - ZX0 decompressor for 68000 - 88 bytes
+;
+; in: a0 = start of compressed data
+; a1 = start of decompression buffer
+;
+; Copyright (C) 2021 Emmanuel Marty
+; ZX0 compression (c) 2021 Einar Saukas, https://github.com/einar-saukas/ZX0
+;
+; This software is provided 'as-is', without any express or implied
+; warranty. In no event will the authors be held liable for any damages
+; arising from the use of this software.
+;
+; Permission is granted to anyone to use this software for any purpose,
+; including commercial applications, and to alter it and redistribute it
+; freely, subject to the following restrictions:
+;
+; 1. The origin of this software must not be misrepresented; you must not
+; claim that you wrote the original software. If you use this software
+; in a product, an acknowledgment in the product documentation would be
+; appreciated but is not required.
+; 2. Altered source versions must be plainly marked as such, and must not be
+; misrepresented as being the original software.
+; 3. This notice may not be removed or altered from any source distribution.
+
+zx0_decompress::
+ movem.l a2/d2,-(sp) ; preserve registers
+ moveq #-128,d1 ; initialize empty bit queue
+ ; plus bit to roll into carry
+ moveq #-1,d2 ; initialize rep-offset to 1
+
+.literals: bsr.s .get_elias ; read number of literals to copy
+ subq.l #1,d0 ; dbf will loop until d0 is -1, not 0
+.copy_lits: move.b (a0)+,(a1)+ ; copy literal byte
+ dbf d0,.copy_lits ; loop for all literal bytes
+
+ add.b d1,d1 ; read 'match or rep-match' bit
+ bcs.s .get_offset ; if 1: read offset, if 0: rep-match
+
+.rep_match: bsr.s .get_elias ; read match length (starts at 1)
+.do_copy: subq.l #1,d0 ; dbf will loop until d0 is -1, not 0
+.do_copy_offs: move.l a1,a2 ; calculate backreference address
+ add.l d2,a2 ; (dest + negative match offset)
+.copy_match: move.b (a2)+,(a1)+ ; copy matched byte
+ dbf d0,.copy_match ; loop for all matched bytes
+
+ add.b d1,d1 ; read 'literal or match' bit
+ bcc.s .literals ; if 0: go copy literals
+
+.get_offset: moveq #-2,d0 ; initialize value to $fe
+ bsr.s .elias_loop ; read high byte of match offset
+ addq.b #1,d0 ; obtain negative offset high byte
+ beq.s .done ; exit if EOD marker
+ move.w d0,d2 ; transfer negative high byte into d2
+ lsl.w #8,d2 ; shift it to make room for low byte
+
+ moveq #1,d0 ; initialize length value to 1
+ move.b (a0)+,d2 ; read low byte of offset + 1 bit of len
+ asr.l #1,d2 ; shift len bit into carry/offset in place
+ bcs.s .do_copy_offs ; if len bit is set, no need for more
+ bsr.s .elias_bt ; read rest of elias-encoded match length
+ bra.s .do_copy_offs ; go copy match
+
+.get_elias: moveq #1,d0 ; initialize value to 1
+.elias_loop: add.b d1,d1 ; shift bit queue, high bit into carry
+ bne.s .got_bit ; queue not empty, bits remain
+ move.b (a0)+,d1 ; read 8 new bits
+ addx.b d1,d1 ; shift bit queue, high bit into carry
+ ; and shift 1 from carry into bit queue
+
+.got_bit: bcs.s .got_elias ; done if control bit is 1
+.elias_bt: add.b d1,d1 ; read data bit
+ addx.l d0,d0 ; shift data bit into value in d0
+ bra.s .elias_loop ; keep reading
+
+.done: movem.l (sp)+,a2/d2 ; restore preserved registers
+.got_elias: rts