Add rle2_loader (pair runs), does save some bytes although it's not dramatic
authorNick Downing <nick@ndcode.org>
Mon, 13 Jun 2022 12:26:14 +0000 (22:26 +1000)
committerNick Downing <nick@ndcode.org>
Tue, 14 Jun 2022 07:18:56 +0000 (17:18 +1000)
loader/Makefile
loader/rle2_decode.py [new file with mode: 0755]
loader/rle2_encode.py [new file with mode: 0755]
loader/rle2_loader.asm [new file with mode: 0644]

index abca368..5846375 100755 (executable)
@@ -10,6 +10,7 @@ ASLINK=../asxv5pxx/asxmak/linux/exe/aslink
 HEX2BIN=hex2bin.py
 
 RLE_LOADER=0x800
+RLE2_LOADER=0x800
 HIRES_LOADER=0x2000
 
 .PHONY: all
@@ -17,6 +18,7 @@ all: \
 star_blazer.bin \
 star_blazer_dejunked0.bin \
 star_blazer_dejunked1.bin \
+star_blazer_rle2_loader.bin \
 star_blazer_rle_loader.bin
 
 star_blazer.bin: ../orig/Star_Blazer_1981_Star_Craft.do
@@ -28,6 +30,18 @@ star_blazer_dejunked0.bin: star_blazer.bin
 star_blazer_dejunked1.bin: star_blazer.bin
        ./dejunk.py $< $@ 0xff
 
+star_blazer_rle2_loader.bin: rle2_loader.bin star_blazer_hires_loader.bin
+       ./rle2_encode.py ${RLE2_LOADER} $^ $@
+
+rle2_loader.bin: rle2_loader.ihx
+       ${HEX2BIN} $< $@
+
+rle2_loader.ihx: rle2_loader.rel
+       ${ASLINK} -n -m -u -i -b text=${RLE2_LOADER} $@ $^
+
+rle2_loader.rel: rle2_loader.asm
+       ${AS6500} -l -o $<
+
 star_blazer_rle_loader.bin: rle_loader.bin star_blazer_hires_loader.bin
        ./rle_encode.py ${RLE_LOADER} $^ $@
 
diff --git a/loader/rle2_decode.py b/loader/rle2_decode.py
new file mode 100755 (executable)
index 0000000..31fdd87
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+
+import sys
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+# key byte is partitioned as follows
+KEY_EOF = 0
+KEY_LIT = 1 # literal lengths 01..a7
+KEY_RUN = 0xa8 # run lengths 02..49
+KEY_PAIR_RUN = 0xf0 # pair run lengths 03..12
+N_KEYS = 0x100
+
+if len(sys.argv) < 3:
+  print(f'usage: {sys.argv[0]:s} in.bin out.bin')
+  sys.exit(EXIT_FAILURE)
+in_bin = sys.argv[1]
+out_bin = sys.argv[2]
+
+with open(in_bin, 'rb') as fin:
+  data = list(fin.read())
+  hdr = data[:4]
+  rle2 = data[4:]
+load_addr = hdr[0] | (hdr[1] << 8)
+assert hdr[2] == len(rle2) & 0xff
+assert hdr[3] == len(rle2) >> 8
+
+assert rle2[0] == 0xa9 # lda #NN
+assert rle2[4] == 0xa9 # lda #NN
+src = rle2[1] | (rle2[5] << 8)
+assert src == load_addr + len(rle2) - 1
+
+assert rle2[8] == 0xa9 # lda #NN
+assert rle2[0xc] == 0xa9 # lda #NN
+dest = rle2[9] + (rle2[0xd] << 8)
+
+assert rle2[0x6b] == 0x4c # jmp NNNN
+start = rle2[0x6c] | (rle2[0x6d] << 8)
+
+bin = [0] * (dest + 1 - start)
+print(f'rle2 {len(rle2):04x} bin {len(bin):04x}')
+
+i = len(rle2)
+j = len(bin)
+while True:
+  i -= 1
+  assert i >= 0xa1
+  count = rle2[i]
+  if count == KEY_EOF:
+    break
+  if count < KEY_RUN:
+    i -= count
+    assert i >= 0xa1
+
+    j -= count
+    assert j >= 0
+
+    bin[j:j + count] = rle2[i:i + count]
+  elif count < KEY_PAIR_RUN:
+    count -= KEY_RUN - 2
+
+    i -= 1
+    assert i >= 0
+
+    j -= count
+    assert j >= 0
+
+    bin[j:j + count] = rle2[i:i + 1] * count
+  else:
+    count -= KEY_PAIR_RUN - 3
+
+    i -= 2
+    assert i >= 0
+
+    j -= count
+    assert j >= 0
+
+    bin[j:j + count] = (rle2[i:i + 2] * ((count + 1) >> 1))[-count:]
+assert i == 0xa1
+assert j == 0
+
+load_addr = start
+load_size = len(bin)
+hdr = [load_addr & 0xff, load_addr >> 8, load_size & 0xff, load_size >> 8]
+
+with open(out_bin, 'wb') as fout:
+  fout.write(bytes(hdr + bin))
diff --git a/loader/rle2_encode.py b/loader/rle2_encode.py
new file mode 100755 (executable)
index 0000000..39f0d7a
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+
+import sys
+
+EXIT_SUCCESS = 0
+EXIT_FAILURE = 1
+
+# key byte is partitioned as follows
+KEY_EOF = 0
+KEY_LIT = 1 # literal lengths 01..a7
+KEY_RUN = 0xa8 # run lengths 02..49
+KEY_PAIR_RUN = 0xf0 # pair run lengths 03..12
+N_KEYS = 0x100
+
+MAX_LIT = KEY_RUN - KEY_LIT # a7
+MAX_RUN = KEY_PAIR_RUN - KEY_RUN + 1 # 49
+MAX_PAIR_RUN = N_KEYS - KEY_PAIR_RUN + 2 # 12
+
+if len(sys.argv) < 5:
+  print(f'usage: {sys.argv[0]:s} load_addr rle2_loader.bin in.bin out.bin')
+  sys.exit(EXIT_FAILURE)
+load_addr = int(sys.argv[1], 0)
+rle2_loader_bin = sys.argv[2]
+in_bin = sys.argv[3]
+out_bin = sys.argv[4]
+
+with open(rle2_loader_bin, 'rb') as fin:
+  rle2_loader = list(fin.read())
+assert len(rle2_loader) == 0xa1
+
+with open(in_bin, 'rb') as fin:
+  data = list(fin.read())
+  hdr = data[:4]
+  bin = data[4:]
+start = hdr[0] | (hdr[1] << 8)
+assert hdr[2] == len(bin) & 0xff
+assert hdr[3] == len(bin) >> 8
+
+rle2 = [0] * (len(bin) + (len(bin) >> 7) + 2)
+
+i = len(bin)
+j = len(rle2)
+while i:
+  # look for a run
+  count = 1
+  while (
+    count < MAX_RUN and
+      i - count - 1 >= 0 and
+      bin[i - count - 1] == bin[i - 1]
+  ):
+    count += 1
+
+  # since we are in key mode we can output runs of at least 2
+  if count >= 2:
+    i -= count
+    assert i >= 0
+
+    j -= 1
+    assert j >= 0
+    rle2[j] = KEY_RUN + count - 2
+
+    j -= 1
+    assert j >= 0
+    rle2[j] = bin[i]
+  else:
+    # look for a run of pairs
+    count = 2
+    while (
+      count < MAX_PAIR_RUN and
+        i - count - 1 >= 0 and
+        bin[i - count - 1] == bin[i - 1 - (count & 1)]
+    ):
+      count += 1
+
+    # since we are in key mode we can output pair runs of at least 2
+    if count >= 3:
+      i -= count
+      assert i >= 0
+
+      j -= 1
+      assert j >= 0
+      rle2[j] = KEY_PAIR_RUN + count - 3
+
+      j -= 2
+      assert j >= 0
+      rle2[j:j + 2] = bin[i + count - 2:i + count]
+    else:
+      # we will have to start literal mode
+      # stop with a run of a least 3 or pair run of at least 4
+      count = 1
+      while (
+        count < MAX_LIT and
+          count < i and
+          (
+            i - count - 3 < 0 or
+              bin[i - count - 2] != bin[i - count - 1] or
+              bin[i - count - 3] != bin[i - count - 1]
+          )
+          and
+          (
+            i - count - 4 < 0 or
+              bin[i - count - 3] != bin[i - count - 1] or
+              bin[i - count - 4] != bin[i - count - 2]
+          )
+      ):
+        count += 1
+
+      # but do not stop in the middle of a pair
+      if (
+        count == MAX_LIT and
+          count + 1 <= i and
+          bin[i - count - 1] == bin[i - count]
+      ):
+        count -= 1
+
+      i -= count
+      assert i >= 0
+
+      j -= 1
+      assert j >= 0
+      rle2[j] = KEY_LIT + count - 1
+
+      j -= count
+      assert j >= 0
+      rle2[j:j + count] = bin[i:i + count]
+j -= 1
+assert j >= 0
+rle2[j] = KEY_EOF
+
+rle2 = rle2_loader + rle2[j:]
+print(f'bin {len(bin):04x} rle2 {len(rle2):04x}')
+
+src = load_addr + len(rle2) - 1
+assert rle2[0] == 0xa9 # lda #NN
+rle2[1] = src & 0xff
+assert rle2[4] == 0xa9 # lda #NN
+rle2[5] = src >> 8
+
+dest = start + len(bin) - 1
+assert rle2[8] == 0xa9 # lda #NN
+rle2[9] = dest & 0xff
+assert rle2[0xc] == 0xa9 # lda #NN
+rle2[0xd] = dest >> 8
+
+assert rle2[0x6b] == 0x4c # jmp NNNN
+rle2[0x6c] = start & 0xff
+rle2[0x6d] = start >> 8
+
+load_size = len(rle2)
+hdr = [load_addr & 0xff, load_addr >> 8, load_size & 0xff, load_size >> 8]
+
+with open(out_bin, 'wb') as fout:
+  fout.write(bytes(hdr + rle2))
diff --git a/loader/rle2_loader.asm b/loader/rle2_loader.asm
new file mode 100644 (file)
index 0000000..25d2d5e
--- /dev/null
@@ -0,0 +1,136 @@
+; key byte is partitioned as follows
+KEY_EOF = 0
+KEY_LIT = 1 ; literal lengths 01..a7
+KEY_RUN = 0xa8 ; run lengths 02..49
+KEY_PAIR_RUN = 0xf0 ; pair run lengths 03..12
+N_KEYS = 0x100
+
+       .r65c02
+
+       .area   zpage
+       .setdp
+
+       .ds     80
+src:   .ds     2                       ; address of next byte to read
+dest:  .ds     2                       ; address of next byte to write
+count: .ds     1                       ; scratch for calculating decrement
+swap:  .ds     1                       ; value to xor onto data each byte
+
+       .area   text
+
+       lda     #0
+       sta     src
+       lda     #0
+       sta     src + 1
+
+       lda     #0
+       sta     dest
+       lda     #0
+       sta     dest + 1
+
+       ldy     #0
+loop:  lda     [src],y
+       beq     done
+       cmp     #KEY_RUN
+       bcs     run
+
+       ; lit
+       sta     count
+       tay
+
+       ; src -= count + 1
+       ;clc
+       lda     src
+       sbc     count
+       sta     src
+       lda     src + 1
+       sbc     #0
+       sta     src + 1
+
+       ; dest -= count
+       ;sec
+       lda     dest
+       sbc     count
+       sta     dest
+       lda     dest + 1
+       sbc     #0
+       sta     dest + 1
+
+0$:    lda     [src],y
+       sta     [dest],y
+       dey
+       bne     0$
+       beq     loop
+
+run:   cmp     #KEY_PAIR_RUN
+       bcs     pair_run
+
+       sec
+       sbc     #KEY_RUN - 2
+       sta     count
+
+       ; src -= 2
+       ;sec
+       lda     src
+       sbc     #2
+       sta     src
+       lda     src + 1
+       sbc     #0
+       sta     src + 1
+
+       ; dest -= count
+       ;sec
+       lda     dest
+       sbc     count
+       sta     dest
+       lda     dest + 1
+       sbc     #0
+       sta     dest + 1
+
+       iny
+       lda     [src],y
+
+       ldy     count
+0$:    sta     [dest],y
+       dey
+       bne     0$
+       beq     loop
+
+done:  jmp     0
+
+pair_run:
+       sec
+       sbc     #KEY_PAIR_RUN - 3
+       sta     count
+
+       ; src -= 3
+       ;sec
+       lda     src
+       sbc     #3
+       sta     src
+       lda     src + 1
+       sbc     #0
+       sta     src + 1
+
+       ; dest -= count
+       ;sec
+       lda     dest
+       sbc     count
+       sta     dest
+       lda     dest + 1
+       sbc     #0
+       sta     dest + 1
+
+       iny
+       lda     [src],y
+       iny
+       eor     [src],y
+       sta     swap
+       lda     [src],y
+
+       ldy     count
+0$:    sta     [dest],y
+       eor     swap
+       dey
+       bne     0$
+       jmp     loop