def __init__(self, stream): if stream.read_string() != b'UnityFS': raise ValueError('Not a UnityFS file!') version = stream.read_int() if version != 6: raise ValueError('Unsupported version: ' + str(version)) mpv = stream.read_string() fev = stream.read_string() total_file_size = stream.read_long() compressed_size = stream.read_int() decompressed_size = stream.read_int() flags = stream.read_int() if flags & 0x80: if total_file_size == 0: bundle_info_offset = -1 else: bundle_info_offset = total_file_size - compressed_size else: if flags & 0x100: bundle_info_offset = len(mpv) + len(fev) + 2 + 0x1A + 0x0A else: bundle_info_offset = len(mpv) + len(fev) + 0x1A + len( 'UnityFS') + 3 compression_mode = flags & 0x3F dir_info = flags & 0x40 bad_list = flags & 0x80 blocks = [] files = [] block = stream.read_bytes(compressed_size) if compression_mode in (self.COMP_LZ4, self.COMP_LZ4HC): data = lz4.block.decompress(block, uncompressed_size=decompressed_size) s2 = Stream(data) s2.skip(16) block_count = s2.read_int() for _ in range(block_count): decomp_size = s2.read_int() comp_size = s2.read_int() flag = s2.read_short() blocks.append((comp_size, decomp_size)) file_count = s2.read_int() for _ in range(file_count): f_offset = s2.read_long() decomp_size = s2.read_long() flag = s2.read_int() name = s2.read_string() files.append((f_offset, decomp_size, name)) else: raise ValueError('Unsupported compression mode') block_data = io.BytesIO() for i in blocks: bd = stream.read_bytes(i[0]) dc = lz4.block.decompress(bd, uncompressed_size=i[1]) block_data.write(dc) self.files = {} for i in files: block_data.seek(i[0]) self.files[i[2].decode()] = block_data.read(i[1])
def decode(self, obj, debug=False): object_id, data, type_tree = obj queue = list(type_tree[3]) if debug: print('-' * 50) stream = Stream(data) stream.enidan = self.stream.enidan structure = {} current, align = [structure], [False] prev = None while queue: i = queue.pop(0) while i[0] != 0 and i[0] < len(current): current.pop(-1) if align.pop(-1): t = (4 - (stream.tell() % 4)) % 4 assert stream.read_bytes(t) == b'\x00' * t multi = False if i[2] == b'int': d = stream.read_sint() elif i[2] == b'unsigned int': d = stream.read_int() elif i[2] == b'float': d = stream.read_float() elif i[2] == b'bool': d = stream.read_bool() elif i[2] == b'char': if isinstance(current[-1], list) and len(current[-1]) == 1: multi = True d = stream.read_bytes(current[-1][0]) else: d = stream.read_bytes(1) elif i[2] == b'UInt8': if isinstance(current[-1], list) and len(current[-1]) == 1: multi = True d = stream.read_bytes(current[-1][0]) else: d = stream.read_byte() elif i[1]: d = [] else: d = None if debug: print(len(current), str(stream.tell()).rjust(3), (' ' * i[0] + i[2].decode() + ': ').ljust(23) + i[3].decode().ljust(25), hex(i[-1]), str(d)[:30] + ('' if len(str(d)) <= 30 else '...')) if i[0] > len(current) and isinstance(current[-1], dict): current.append({}) current[-2][prev[3].decode()] = current[-1] align.append(i[-1] & 0x4000) elif i[-1] & 0x4000 and not i[1]: t = (4 - (stream.tell() % 4)) % 4 assert stream.read_bytes(t) == b'\x00' * t if isinstance(current[-1], list): if multi: current[-1].extend(d) else: current[-1].append(d) if current[-1] and len(current[-1]) == current[-1][0] + 1: if not current[-1].pop(0): queue.pop(0) current.pop(-1) if align.pop(-1): t = (4 - (stream.tell() % 4)) % 4 assert stream.read_bytes(t) == b'\x00' * t elif len(current[-1]) > 1: queue.insert(0, i) elif d is not None: current[-1][i[3].decode()] = d if isinstance(d, list): current.append(d) align.append(i[-1] & 0x4000) prev = i return structure