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