Advertisement
Zoinkity

Flashback CT Codec

Dec 12th, 2015
222
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.40 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. ## Flashback CT codec.
  4. ## Written using Python 3.4.3 by butchering a different project.
  5. ## Not the best, but works.
  6.  
  7. def check(file):
  8.     """Returns 0 if CRC is correct.
  9.    Otherwise, returns difference in crc."""
  10.     from array import array
  11.     crc = int.from_bytes(file[-8:-4], 'little')
  12.     a = array("L", file[:-8])
  13.     for i in a:
  14.         crc ^= i
  15.     return crc
  16.  
  17. def dec(file):
  18.     def getBits(s):
  19.         from array import array
  20.         s = array("L", s)
  21.         s.byteswap()
  22.         c, bits, f = 0, 0, 0
  23.         for i in reversed(s):
  24.             i |= f
  25.             f = 0x100000000
  26.             while i != 1:
  27.                 while not bits:
  28.                     bits = yield c
  29.                     c = 0
  30.                 bits -= 1
  31.                 c <<= 1
  32.                 c |= i & 1
  33.                 i >>= 1
  34.         else:
  35.             # Dirty, dirty hack.
  36.             if not bits:
  37.                 yield c
  38.  
  39.     pos = int.from_bytes(file[-4:], 'big')
  40.     # crc = int.from_bytes(file[-8:-4], 'big')
  41.     out = bytearray(pos)
  42.     src = getBits(file[:-8])
  43.     next(src)
  44.     pos -= 1
  45.  
  46.     while pos >= 0:
  47.         if not src.send(1):
  48.             if not src.send(1):
  49.                 l = src.send(3) + 1
  50. ##                print("\t{:6X} at 0x{:<6X} - 0x{:<6X}".format(l, pos-l, pos))
  51.                 for j in range(l):
  52.                     out[pos] = src.send(8)
  53.                     pos -= 1
  54.                 continue
  55.             l, b = 2, src.send(8)
  56.         else:
  57.             b = src.send(2)
  58.             if b == 3:
  59.                 l = src.send(8) + 9
  60.                 for j in range(l):
  61.                     out[pos] = src.send(8)
  62.                     pos -= 1
  63.                 continue
  64.             if b == 2:
  65.                 l = src.send(8) + 1
  66.                 b = src.send(12)
  67.             else:
  68.                 l = 3 + b
  69.                 b = src.send(9 + b)
  70. ##        print("@ {:6X} from +{:06X} to 0x{:<6X} - 0x{:<6X}".format(l, b, pos-l, pos))
  71.         for j in range(l):
  72.             out[pos] = out[pos + b]
  73.             pos -= 1
  74.     return bytes(out)
  75.  
  76. def cmpBackward(data):
  77.     """Returns a [list] of LZ hits within data.
  78.    This can be converted into binary form via cmpMass().
  79.  
  80.    The data is searched back-to-front for duplicated segments.
  81.    These are encoded as tuples in one of these formats:
  82.        ('u', bytes, length)    uncompressed segment
  83.            bytes is bytearray of data to write
  84.            length should be len(bytes)
  85.  
  86.        ('c', back, length, cmdlen) compressed segment
  87.            back is #bytes from cur.position backward to hit (actual)
  88.            length is detected length of segment as to be written
  89.            cmdlen is a count of #cmds required for entry, used as correction to length
  90.  
  91.        ('e',)  end of list
  92.    """
  93.     data = bytes(reversed(data))
  94.     pos = len(data)
  95.     tbl = []
  96.     if not pos:
  97.         return tbl
  98.     u = 0
  99.     while pos>0:
  100.         o = 1
  101.         h = data.rfind(data[pos-o:pos], 0, max(0,pos-1)) + 1
  102.         if not h:
  103.             u+=1
  104.             pos-=1
  105.         else:
  106.             # b is last good hit, g is last good hit length
  107.             b, g, l, p = 0, -1, 0, 0
  108.             # I'll change this later.  Better method would be to test exponential growth, then test back exponentially until length.
  109.             while (l+o) < (pos-1):
  110.                 # e is minimum length for command based on hit
  111.                 e = 0
  112.                 i = l + o
  113.                 j = pos - l - o - h
  114.                 if i == 2 and j < 0x100:
  115.                     e = 1
  116.                 elif i == 3 and j < 0x200:
  117.                     e = 2
  118.                 elif i == 4 and j < 0x400:
  119.                     e = 2
  120.                 elif i < 0x101 and j < 0x1000:
  121.                     e = 3
  122.                 else:
  123.                     break
  124.                 # If l changed, you'll have to recalc
  125.                 if l > (l+o-e):
  126.                     l = l+o-e
  127.                     o = e
  128.                     continue
  129.                 # Only set when expressible!
  130.                 if l>=0:
  131.                     b, g = h, l
  132.                     p = o if o>e else e
  133.                 if e<=o:
  134.                     l+=1
  135.                 else:
  136.                     o = e
  137.  
  138.                 # Do the search
  139.                 h = data.rfind(data[pos-l-o:pos], 0, max(0,pos-1)) + 1
  140.                 if not h:
  141.                     break
  142.  
  143.             # pos - length is duplicated segment
  144.             l = g+p
  145.  
  146. ##                if g==0 and u:
  147. ##                    # If length of cmd same as uncompressed and already have uncompressed, extend it.
  148. ##                    u+=1
  149. ##                    pos-=1
  150. ##                elif l<2 or g<0:
  151.             if l<2 or g<0:
  152.                 # Catch unexpressable hits.
  153.                 u+=1
  154.                 pos-=1
  155.             else:
  156.                 if u:
  157.                     # Flush any uncompressed data first.
  158.                     tbl.append(('u', data[pos:pos+u], u))
  159.                     u = 0
  160.                 # Hit is start position of segment - hit location (relative)
  161.                 pos-=l
  162.                 tbl.append(('c', pos-b+1, l))
  163.     # Stream always has to start with uncompressed data.
  164.     u += pos
  165.     tbl.append(('u', data[0:u], u))
  166.     tbl.reverse()
  167.     tbl.append(('e',))
  168.     return tbl
  169.  
  170. def compilecmp(cmp, org_sz):
  171.     def bitPush(org_sz):
  172.         from array import array
  173.         a = array("L")
  174.         cur = 1
  175.         while True:
  176.             val, sz = yield()
  177.             if val is None or sz is None:
  178.                 break
  179.             for x in range(sz):
  180.                 cur <<= 1
  181.                 cur |= val & 1
  182.                 val >>= 1
  183.                 if cur & 0x100000000:
  184.                     a.append(cur & 0xFFFFFFFF)
  185.                     cur = 1
  186.         # Push final field and compute checksum.
  187.         a.append(cur)
  188.         crc = 0
  189.         # Could use itertools.accumulate to run this...
  190.         for x in a:
  191.             crc ^= x
  192.         a.append(crc)
  193.         a.append(org_sz)
  194.         a.byteswap()
  195.         yield(a.tobytes())
  196.  
  197.     push = bitPush(org_sz)
  198.     next(push)
  199.     count = 0
  200.     for i in reversed(cmp):
  201.         if i[0] is 'u':
  202.             s, l = i[1], i[2]
  203.             p = -1
  204.             count += i[2] << 3
  205.             while l > 0:
  206.                 if l<8: # 0.0.xxx       write 1 + getBits(3) bytes from source  [5 + 8c]
  207.                     for k in range(min(l, 8)):
  208.                         push.send((s[p], 8))
  209.                         p -= 1
  210.                     push.send((min(l, 8) - 1, 3))
  211.                     push.send((0, 2))
  212.                     count += 5
  213.                     l -= 8
  214.                 else:   # 1.11.xxxxxxxx write 9 + getBits(8) bytes from source  [11 + 8c]
  215.                     for k in range(min(l, 108)):
  216.                         push.send((s[p], 8))
  217.                         p -= 1
  218.                     push.send((min(l, 108) - 9, 8))
  219.                     push.send((7, 3))
  220.                     count += 11
  221.                     l -= 108
  222.         elif i[0] is 'c':
  223.             l, s = i[1], i[2]
  224.             if s == 2 and l < 0x100:    # 0.1.xxxxxxxx  copy 2 bytes from out+getBits(8)    [10]
  225.                 push.send((l, 8))
  226.                 push.send((1, 2))
  227.                 count += 10
  228.             elif s == 3 and l < 0x200:  # 1.00.xxxxxxxxx    copy 3 bytes from out+getBits(9)    [12]
  229.                 push.send((l, 9))
  230.                 push.send((4, 3))
  231.                 count += 12
  232.             elif s == 4 and l < 0x400:  # 1.01.xxxxxxxxxx   copy 4 bytes from out+getBits(10)   [13]
  233.                 push.send((l, 10))
  234.                 push.send((5, 3))
  235.                 count += 13
  236.             elif s <= 0x100 and l < 0x1000: # 1.10.xxxxxxxx.xxxxxxxxxxxx    copy getBits(8)+1 bytes from out+getBits(12)    [23]
  237.                 push.send((l, 12))
  238.                 push.send((s - 1, 8))
  239.                 push.send((6, 3))
  240.                 count += 23
  241.             else:
  242.                 raise ValueError("Unable to encode copy of 0x%X bytes from 0x%X." % (s, l))
  243.     print(count)
  244.     return push.send((None, None))
  245.  
  246. def main():
  247.     with open(r"LEVEL1.UCT", 'rb') as f:
  248.         org = f.read()
  249.     cmd = cmpBackward(org)
  250.     print(cmd)
  251.     out = compilecmp(cmd, len(org))
  252.     with open(r"test.CT", 'wb') as f:
  253.         f.write(out)
  254.  
  255.     test = dec(out)
  256.     print(dec(out) == org)
  257.     with open(r"test.UCT", 'wb') as f:
  258.         f.write(test)
  259.  
  260.  
  261. if __name__ == '__main__':
  262.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement