Advertisement
thisismysignup

lzma based pico8 compression algorithm

Apr 9th, 2023 (edited)
912
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 4.83 KB | None | 0 0
  1. --[[
  2. lzma based pico8 compression algorithm
  3.  
  4. decompression in p8:
  5.  
  6. (the function as written copies the compressed data from the original cart to memory address 0, before decompressing it to a string. changing it to make more sense for your case is left as an exercise for the reader)
  7. ]]
  8.  
  9. function decompress(src, srclen)
  10.     reload(0, src, srclen)
  11.    
  12.     local str, range, size, code, addr, probs, divs = "", ~0, %0, $2, 6, {[0x101]=0x20,[0x121]=0x20}, {}
  13.  
  14.     local function bit(off)
  15.         if range^^0x8000 < 0x8100 then
  16.             range <<= 8; code <<= 8
  17.             code |= @addr >>> 16; addr += 1
  18.         end
  19.  
  20.         local prob = probs[off] or 0x400
  21.         local div = divs[off] or 3
  22.         local bound = (range >>> 11) * prob
  23.         local result = code^^0x8000 < bound^^0x8000
  24.         if result then
  25.             range = bound
  26.             prob += (0x800 - prob) \ div
  27.         else
  28.             range -= bound; code -= bound
  29.             prob -= prob \ div
  30.         end
  31.         if (off) probs[off] = prob; divs[off] = min(div + 1, 16)
  32.         return result
  33.     end
  34.  
  35.     local function val(limit, off)
  36.         local idx = 1
  37.         while (idx < limit) idx = idx * 2 + tonum(bit(off and off + idx))
  38.         return idx - limit
  39.     end
  40.  
  41.     local function xval(idx, shift)
  42.         local bits = idx \ shift - 1
  43.         return bits < 0 and idx or (shift + (idx & shift-1) << bits) + val(1 << bits)
  44.     end
  45.  
  46.     while #str < size do
  47.         if bit(0) then
  48.             str ..= chr(val(0x100, 0))
  49.         else
  50.             local offset = 1 + xval(val(32, 0x100), 2)
  51.             local count = 3 + xval(val(32, 0x120), 4)
  52.  
  53.             local pattern = sub(str, -offset, #str - offset + count)
  54.             while (#pattern < count) pattern ..= sub(pattern,1,count - #pattern)
  55.             str ..= pattern
  56.         end
  57.     end
  58.  
  59.     return str
  60. end
  61.  
  62. --[[
  63.   compression in python
  64.  
  65.   requires files from https://github.com/thisismypassport/shrinko8 in current directory
  66.  
  67.   (rewriting it in p8, if wanted, is left as - you guessed it)
  68. ]]
  69.  
  70. from utils import *
  71. from pico_compress import get_lz77, Lz77Tuple
  72.  
  73. def compress(code):
  74.     def split_count_val(val):
  75.         for shift in range(7):
  76.             if val < (8 << shift):
  77.                 return (4 * shift) + (val >> shift), shift, val & make_mask(0, shift)
  78.         raise Exception()
  79.  
  80.     def split_offset_val(val):
  81.         for shift in range(14):
  82.             if val < (4 << shift):
  83.                 return (2 * shift) + (val >> shift), shift, val & make_mask(0, shift)
  84.         raise Exception()
  85.    
  86.     low, rng, cache, cachesize = 0, 0xffffffff, 0, 1
  87.     probs = [0x400] * 0x140
  88.     probs[0x101] = probs[0x121] = 0x20
  89.     divs = [3] * 0x140
  90.  
  91.     f = BytesIO()
  92.     w = BinaryWriter(f)
  93.     w.u8(0) # will be overwritten below
  94.  
  95.     def range_norm():
  96.         nonlocal low, rng, cache, cachesize
  97.  
  98.         if low < 0xff000000 or low >= 0x100000000:
  99.             w.u8((cache + (low >> 32)) & 0xff)
  100.             for i in range(cachesize - 1):
  101.                 w.u8((0xff + (low >> 32)) & 0xff)
  102.             cache, cachesize = (low >> 24) & 0xff, 0
  103.            
  104.         cachesize += 1
  105.         low = (low & 0xffffff) << 8
  106.         rng <<= 8
  107.  
  108.     def range_bit(b, pri):
  109.         nonlocal low, rng
  110.         if rng < 0x1000000:
  111.             range_norm()
  112.  
  113.         prob = 0x400 if pri is None else probs[pri]
  114.         div = 1 if pri is None else divs[pri]
  115.         bound = (rng >> 11) * prob
  116.         if b:
  117.             rng = bound
  118.             prob += (0x800 - prob) // div
  119.         else:
  120.             rng -= bound
  121.             low += bound
  122.             prob -= prob // div
  123.  
  124.         if pri is not None:
  125.             probs[pri] = prob
  126.             divs[pri] = min(div + 1, 16)
  127.  
  128.     def range_val(val, limit, pri):
  129.         idx = 1
  130.         limbits = (limit - 1).bit_length()
  131.         while idx < limit:
  132.             limbits -= 1
  133.             b = (val >> limbits) & 1
  134.             range_bit(b, None if pri is None else pri + idx)
  135.             idx = idx * 2 + b
  136.    
  137.     for i, item in get_lz77(code, max_c=0x202):
  138.         islit = not isinstance(item, Lz77Tuple)
  139.         range_bit(islit, 0)
  140.  
  141.         if islit:
  142.             range_val(ord(item), 0x100, 0)
  143.  
  144.         else:
  145.             offval, countval = item.off, item.cnt
  146.            
  147.             oidx, obits, oval = split_offset_val(offval)
  148.             range_val(oidx, 0x20, 0x100)
  149.             range_val(oval, 1 << obits, None)
  150.  
  151.             cidx, cbits, cval = split_count_val(countval)
  152.             range_val(cidx, 0x20, 0x120)
  153.             range_val(cval, 1 << cbits, None)
  154.  
  155.     for i in range(5):
  156.         range_norm()
  157.  
  158.     r = BinaryReader(f)
  159.     r.setpos(1)
  160.     assert r.u8() == 0
  161.     a, b, c, d = r.u8(), r.u8(), r.u8(), r.u8()
  162.  
  163.     w.setpos(0)
  164.     w.u16(len(code))
  165.     w.u8(d); w.u8(c); w.u8(b); w.u8(a)
  166.  
  167.     return f.getvalue()
  168.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement