Advertisement
Zoinkity

F-Zero X course class and checksum algo

Jan 5th, 2016
291
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.04 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # Written with python 3.4.
  4. # TODO: format the various strings properly to account for used encodings and char sets.
  5. #   Fair to note that isn't standardized now that the translation screwed it up...
  6.  
  7. class course:
  8.     def __init__(self, **kwargs):
  9.         from collections import namedtuple
  10.         self.Node = namedtuple("Node", ['xpos', 'ypos', 'zpos', 'width_l', 'width_r', 'track_attr', 'bank', 'pit', 'boost', 'dirt', 'ice', 'ramp', 'mine', 'gate', 'bg', 'sign'])
  11.         nodes = kwargs.get('nodes', [])
  12.         self.nodes = [self.Node(*i) for i in nodes]
  13.         self.title = kwargs.get('title', '')
  14.         self.scene = kwargs.get('scene', 0)
  15.         self.sky = kwargs.get('sky', 0)
  16.         self.music = kwargs.get('music', 0)
  17.         # These are extensions found in fzep files.
  18.         self.author = kwargs.get('author', '')
  19.         self.flavor_e = kwargs.get('description_en', '')
  20.         self.flavor_j = kwargs.get('description_jp', '')
  21.         self.cartmusic = kwargs.get('cartmusic', 0)
  22.         self.horizons = kwargs.get('horizons', [])
  23.  
  24.     def __bytes__(self):
  25.         """Generates a CRSD from current data."""
  26.         import struct
  27.         pat = struct.Struct(">3f2hL")
  28.         strips = ('both', 'left', 'right', 'center')
  29.         others = ('center', 'left', 'right')
  30.         out = bytearray()
  31.         out.append(4)
  32.         out.append(len(self.nodes))
  33.         out.append(self.scene)
  34.         out.append(self.sky)
  35.         # Placeholder the checksum.
  36.         out.extend(bytes(4))
  37.         out.append(1)
  38.         out.extend(struct.pack(">22s", self.title.encode()))
  39.         out.append(self.music)
  40.         bnks = list(bytes(64))
  41.         pits = bytearray(64)
  42.         bsts = bytearray(64)
  43.         dirt = bytearray(64)
  44.         slip = bytearray(64)
  45.         jump = bytearray(64)
  46.         mine = bytearray(64)
  47.         gate = bytearray(64)
  48.         objs = bytearray(64)
  49.         sign = bytearray(64)
  50.         for i,n in enumerate(self.nodes):
  51.             out.extend(pat.pack(*n[0:6]))
  52.             bnks[i] = n.bank
  53.             pits[i] = 0xFF if n.pit is None else strips.index(n.pit)
  54.             bsts[i] = 0xFF if n.boost is None else others.index(n.boost)
  55.             dirt[i] = 0xFF if n.dirt is None else strips.index(n.dirt)
  56.             slip[i] = 0xFF if n.ice is None else strips.index(n.ice)
  57.             jump[i] = 0xFF if n.ramp is None else others.index(n.ramp)
  58.             mine[i] = 0xFF if n.mine is None else others.index(n.mine)
  59.             gate[i] = 0xFF if n.gate is None else n.gate
  60.             objs[i] = 0xFF if n.bg is None else n.bg
  61.             sign[i] = 0xFF if n.sign is None else n.sign
  62.         # Pad for remaining unused nodes, then append the banks.
  63.         out.extend(bytes(0x520 - len(out)))
  64.         out.extend(struct.pack(">64h", *bnks))
  65.         out.extend(pits)
  66.         out.extend(bsts)
  67.         out.extend(dirt)
  68.         out.extend(slip)
  69.         out.extend(jump)
  70.         out.extend(mine)
  71.         out.extend(gate)
  72.         out.extend(objs)
  73.         out.extend(sign)
  74.         # Compute and set the checksum.
  75.         out[4:8] = struct.pack(">L", checksum(out) & 0xFFFFFFFF)
  76.         return bytes(out)
  77.  
  78.     @classmethod
  79.     def from_crsd(cls, data, name=''):
  80.         """Parses a CRSD file or original course data, returning a course instance.
  81.        <data> should be either a bytes or bytearray object."""
  82.         from struct import Struct
  83.         node = Struct(">3f2hL")
  84.         attr = Struct(">b63xb63xb63xb63xb63xb63xb63xb63xb")
  85.         strips = ('both', 'left', 'right', 'center', None)
  86.         others = ('center', 'left', 'right', None)
  87.         n = []
  88.         for i in range(data[1]):
  89.             n.append(list(node.unpack_from(data, 32 + i * 20)))
  90.             v = i << 1
  91.             n[i].append(int.from_bytes(data[0x520 + v:0x522 + v], 'big', signed=True))
  92.             v = attr.unpack_from(data, 0x5A0 + i)
  93.             n[i].append(strips[v[0]])
  94.             n[i].append(others[v[1]])
  95.             n[i].append(strips[v[2]])
  96.             n[i].append(strips[v[3]])
  97.             n[i].append(others[v[4]])
  98.             n[i].append(others[v[5]])
  99.             n[i].append(None if v[6]<0 else v[6])
  100.             n[i].append(None if v[7]<0 else v[7])
  101.             n[i].append(None if v[8]<0 else v[8])
  102.         if not name:
  103.             name = data[9:data.find(b'\x00', 9, 0x1F)].decode()
  104.         return cls(nodes=n, scene=data[2], sky=data[3], music=data[0x1F], title=name)
  105.  
  106.     @classmethod
  107.     def from_fzep(cls, data):
  108.         """Parses an fzep binary returning a course instance.
  109.        <data> should be either a bytes or bytearray object."""
  110.         import struct
  111.         node = struct.Struct(">3f3h3B")
  112.         hdr = struct.unpack_from(">6sH4x10B", data)
  113.         others = ('center', 'left', 'right', None)
  114.         tracktypes = (0x100001BF, 0x18000000, 0x08000040, 0x20000080,
  115.                       0x000000C0, 0x00000100, 0x28000140, 0x080001C0)
  116.         if hdr[0] != b"FZEPXT" or hdr[1] != 0x10A:
  117.             raise TypeError("Not an FZEP v.1.10 file!")
  118.         v = 22 + hdr[3]
  119.         i = v + hdr[4]
  120.         author = data[22:v].decode()
  121.         # Not safe due to unused J font extension, but whatever.
  122.         title = data[v:i].decode()
  123.         v = i + hdr[5]
  124.         dscr_e = data[i:v].decode()
  125.         i = v + hdr[6]
  126.         # Pretty sure you need to stick the leading half of each EUC JP wchar back on.
  127.         dscr_j = data[v:i].decode()
  128.         base = i + hdr[10] * 3
  129.         h = []
  130.         for j in range(i, base, 3):
  131.             h.append([data[j], struct.unpack(">f", data[j+1:j+3]+b'\x00\x00')[0]])
  132.         n = []
  133.         for i in range(hdr[11]):
  134.             v = node.unpack_from(data, base + i * 21)
  135.             n.append([v[0], v[1], v[2], v[3], v[4], 0, v[5] & 0x1FF, None, None, None, None, None, None, None, None, None])
  136.             j = (v[5] >> 9) & 7
  137.             n[i][5] = tracktypes[j] | (v[6] >> 4)
  138.             j = v[7] >> 2
  139.             n[i][8] = others[v[7] & 3]
  140.             n[i][11] = others[j & 3]
  141.             j = v[7] >> 4
  142.             n[i][12] = others[j & 3]
  143.             j = v[7] >> 6
  144.             n[i][13] = None if j==3 else j
  145.             j = (v[5] >> 12) & 0xF
  146.             n[i][14] = None if j==15 else j
  147.             j = v[6] & 0xF
  148.             n[i][15] = None if j==5 else j
  149.             # This can probably be simplified.
  150.             j = v[8] & 3
  151.             if j == 1:
  152.                 n[i][7] = "right"
  153.             elif j == 2:
  154.                 n[i][9] = "right"
  155.             elif j == 3:
  156.                 n[i][10] = "right"
  157.             j = (v[8] >> 2) & 3
  158.             if j == 1:
  159.                 n[i][7] = "center"
  160.             elif j == 2:
  161.                 n[i][9] = "center"
  162.             elif j == 3:
  163.                 n[i][10] = "center"
  164.             j = (v[8] >> 4) & 3
  165.             if j == 1:
  166.                 n[i][7] = "left"
  167.             elif j == 2:
  168.                 n[i][9] = "left"
  169.             elif j == 3:
  170.                 n[i][10] = "left"
  171.             j = v[8] & 0x33
  172.             if j==0x11:
  173.                 n[i][7] = 'both'
  174.             elif j==0x22:
  175.                 n[i][9] = 'both'
  176.             elif j==0x33:
  177.                 n[i][10] = 'both'
  178.         return cls(nodes=n, title=title, scene=hdr[9] & 0xF, sky=hdr[9]>>4, music=hdr[8],
  179.                    cartmusic=hdr[7], author=author, description_en=dscr_e,
  180.                    description_jp=dscr_j, horizons=h)
  181.  
  182.     def to_crsd(self):
  183.         return bytes(self)
  184.  
  185.     def to_fzep(self):
  186.         import struct
  187.         tracks = (0x400006, 0x600000, 0x200001, 0x800002, 3, 4, 0xA00005, 0x200007)
  188.         strips = ('both', 'left', 'right', 'center')
  189.         others = ('center', 'left', 'right', None)
  190.         pat = struct.Struct(">3f2h")
  191.  
  192.         out = bytearray(b'FZEPXT')
  193.         out.append(1)
  194.         out.append(10)
  195.         out.extend(bytes(4))
  196.         out.append(4)
  197.         out.append(len(self.author))
  198.         out.append(len(self.title))
  199.         out.append(len(self.flavor_e))
  200.         out.append(len(self.flavor_j))
  201.         out.append(self.cartmusic)
  202.         out.append(self.music)
  203.         v = self.sky << 4
  204.         v |= self.scene
  205.         out.append(v)
  206.         out.append(len(self.horizons))
  207.         out.append(len(self.nodes))
  208.         out.extend(self.author.encode())
  209.         out.extend(self.title.encode())
  210.         out.extend(self.flavor_e.encode())
  211.         out.extend(self.flavor_j.encode())
  212.         for n in self.horizons:
  213.             out.append(n[0])
  214.             out.extend(struct.pack(">f", n[1])[0:2])
  215.         for n in self.nodes:
  216.             out.extend(pat.pack(n.xpos, n.ypos, n.zpos, n.width_l, n.width_r))
  217.             v = 0xF if n.bg is None else n.bg
  218.             v <<= 3
  219.             v |= tracks.index(n.track_attr >> 6)
  220.             v <<= 1
  221.             v |= n.bank >> 8
  222.             out.append(v)
  223.             out.append(n.bank & 0xFF)
  224.             v = n.track_attr & 0xF
  225.             v <<= 4
  226.             v |= 5 if n.sign is None else n.sign
  227.             out.append(v)
  228.             v = 3 if n.gate is None else n.gate
  229.             v <<= 2
  230.             v |= others.index(n.mine)
  231.             v <<= 2
  232.             v |= others.index(n.ramp)
  233.             v <<= 2
  234.             v |= others.index(n.boost)
  235.             out.append(v)
  236.             v = 0
  237.             if n.pit in ('left', 'both'):
  238.                 v |= 0x10
  239.             elif n.dirt in ('left', 'both'):
  240.                 v |= 0x20
  241.             elif n.ice in ('left', 'both'):
  242.                 v |= 0x30
  243.             if n.pit == 'center':
  244.                 v |= 4
  245.             elif n.dirt == 'center':
  246.                 v |= 8
  247.             elif n.ice == 'center':
  248.                 v |= 0xC
  249.             if n.pit in ('right', 'both'):
  250.                 v |= 1
  251.             elif n.dirt in ('right', 'both'):
  252.                 v |= 2
  253.             elif n.ice in ('right', 'both'):
  254.                 v |= 3
  255.             out.append(v)
  256.         return bytes(out)
  257.  
  258. # Don't know for certain if these are static.
  259. # Values found before track info, at 800C3200.
  260. ekv = (0.699999988079071,
  261.        1.100000023841858,
  262.        5000.0,
  263.        15000.0,
  264.        -15000.0,
  265.        2.200000047683716,
  266.        1.2000000476837158,
  267.        4.400000095367432,
  268.        0.8999999761581421,
  269.        0.800000011920929,
  270.        4.800000190734863)
  271.  
  272. def checksum(crs, f=ekv):
  273.     """Computes the checksum for CRSD <crs> using table of floats <f>."""
  274.     from struct import Struct
  275.     from array import array
  276.     a = array("f", (0, 0, 0, 0))
  277.     node = Struct(">3f2hL")
  278.     attr = Struct(">b63xb63xb63xb63xb63xb63xb63xb63xb")
  279.     crc = crs[1]
  280.     y, u, l = int(f[2]) + 1, int(f[3]) + 1, int(f[4])
  281.     if crs[0] != 4 or crs[31] > 14:
  282.         return -1
  283.     for i in range(crs[1]):
  284.         xpos, ypos, zpos, width_l, width_r, track_attr = node.unpack_from(crs, 32 + 20*i)
  285.         # Tests if all vertices in valid range and if not returns -1 as checksum.
  286.         if xpos.__trunc__() not in range(l, u):
  287.             return -1
  288.         if ypos.__trunc__() not in range(-250, y):
  289.             return -1
  290.         if zpos.__trunc__() not in range(l, u):
  291.             return -1
  292.         a[0] = xpos
  293.         a[1] = ypos
  294.         a[2] = zpos
  295.         track_attr &= ~0x4003E600
  296.         track_attr *= 254 - i
  297.         a[3] = i * f[6]
  298.         a[3] += f[5]
  299.         a[2] *= a[3]
  300.         a[3] = i * f[8]
  301.         a[3] += f[7]
  302.         a[2] *= a[3]
  303.         a[2] += width_l
  304.         a[3] = i * f[0]
  305.         a[3] += f[1]
  306.         a[1] *= a[3]
  307.         a[3] = i * f[9]
  308.         a[3] += 5.5
  309.         a[3] *= width_r
  310.         a[3] *= f[10]
  311.         a[0] += a[1]
  312.         a[0] += a[2]
  313.         a[0] += a[3]
  314.         crc += a[0].__trunc__()
  315.         v = i << 1
  316.         crc += track_attr
  317.         crc += int.from_bytes(crs[0x520 + v:0x522 + v], 'big', signed=True) * (0x93DE - v)
  318.         v = attr.unpack_from(crs, 0x5A0 + i)
  319.         crc += v[0] * i
  320.         crc += v[1] * (i + 0x10)
  321.         crc += v[2] * (i + 0x80)
  322.         crc += v[3] * (i + 0x100)
  323.         crc += v[4] * (i + 0x800)
  324.         crc += v[5] * (i + 0x1000)
  325.         crc += v[6] * (i + 0x8000)
  326.         crc += v[7] * (i + 0x10000)
  327.         crc += v[8] * (i + 0x80000)
  328.     return int(crc) & 0xFFFFFFFF
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement