mirror of https://github.com/zeldaret/tp.git
187 lines
4.4 KiB
Python
187 lines
4.4 KiB
Python
"""
|
|
|
|
Simple library for reading and paring rarc files.
|
|
|
|
"""
|
|
|
|
import struct
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Dict
|
|
|
|
#
|
|
# source:
|
|
# http://wiki.tockdom.com/wiki/RARC_(File_Format)
|
|
#
|
|
|
|
NODE_SIZE = 0x10
|
|
DIRECTORY_SIZE = 0x14
|
|
ROOT = struct.unpack('>I', "ROOT".encode('ascii'))[0]
|
|
|
|
def chunks(lst, n):
|
|
for i in range(0, len(lst), n):
|
|
yield lst[i:i + n]
|
|
|
|
@dataclass
|
|
class StringTable:
|
|
""" RARC String Table """
|
|
|
|
strings: Dict[int, str] = field(default_factory=dict)
|
|
|
|
def get(self, offset):
|
|
return self.strings[offset]
|
|
|
|
|
|
@dataclass
|
|
class Directory:
|
|
""" RARC Directory """
|
|
|
|
index: int
|
|
name_hash: int
|
|
type: int
|
|
name_offset: int
|
|
data_offset: int
|
|
data_length: int
|
|
unknown0: int
|
|
|
|
name: str = None
|
|
rarc: "RARC" = field(default=None, repr=False)
|
|
|
|
|
|
@dataclass
|
|
class File(Directory):
|
|
""" RARC File """
|
|
|
|
|
|
@dataclass
|
|
class Folder(Directory):
|
|
""" RARC Folder """
|
|
|
|
|
|
@dataclass
|
|
class Node:
|
|
""" RARC Node """
|
|
|
|
identifier: int
|
|
name_offset: int
|
|
name_hash: int
|
|
directory_count: int
|
|
directory_index: int
|
|
|
|
name: str = None
|
|
rarc: "RARC" = field(default=None, repr=False)
|
|
|
|
def files_and_folders(self, depth):
|
|
""" Generator for eacg file and directory of this node """
|
|
for directory in self.rarc._directories[self.directory_index:][:self.directory_count]:
|
|
yield depth, directory
|
|
if isinstance(directory, Folder):
|
|
if directory.data_offset < len(self.rarc._nodes):
|
|
node = self.rarc._nodes[directory.data_offset]
|
|
if directory.name == "." or directory.name == "..":
|
|
continue
|
|
yield from node.files_and_folders(depth + 1)
|
|
|
|
|
|
@dataclass
|
|
class RARC:
|
|
"""
|
|
RARC - Archive of files and folder
|
|
"""
|
|
|
|
# header
|
|
magic: int # 'RARC'
|
|
file_length: int
|
|
header_length: int
|
|
file_offset: int
|
|
file_data_length: int
|
|
file_data_length2: int
|
|
unknown0: int
|
|
unknown1: int
|
|
|
|
# info block
|
|
node_count: int
|
|
node_offset: int
|
|
directory_count: int
|
|
directory_offset: int
|
|
string_table_length: int
|
|
string_table_offset: int
|
|
file_count: int
|
|
unknown2: int
|
|
unknown3: int
|
|
|
|
string_table: StringTable = None
|
|
_nodes: List[Node] = field(default_factory=list)
|
|
_directories: List[Node] = field(default_factory=list)
|
|
_root: Node = None
|
|
|
|
@property
|
|
def files_and_folders(self):
|
|
""" Generator for each file and directory """
|
|
yield from self._root.files_and_folders(0)
|
|
|
|
|
|
def read_string_table(rarc, data):
|
|
buffer = data[rarc.string_table_offset:][:rarc.string_table_length]
|
|
rarc.string_table = StringTable()
|
|
|
|
offset = 0
|
|
for string in buffer.decode('ascii').split('\0'):
|
|
rarc.string_table.strings[offset] = string
|
|
offset += len(string) + 1
|
|
|
|
|
|
def read_node(rarc, buffer):
|
|
node = Node(*struct.unpack('>IIHHI', buffer))
|
|
node.name = rarc.string_table.get(node.name_offset)
|
|
node.rarc = rarc
|
|
return node
|
|
|
|
|
|
def read_nodes(rarc, data):
|
|
buffer = data[rarc.node_offset:][:rarc.node_count * NODE_SIZE]
|
|
rarc._nodes = []
|
|
for node_buffer in chunks(buffer, NODE_SIZE):
|
|
node = read_node(rarc, node_buffer)
|
|
if node.identifier == ROOT:
|
|
rarc._root = node
|
|
rarc._nodes.append(node)
|
|
|
|
|
|
def read_directory(rarc, buffer, file_data):
|
|
header = struct.unpack('>HHHHIII', buffer)
|
|
if header[0] == 0xFFFF:
|
|
directory = Folder(*header)
|
|
else:
|
|
directory = File(*header)
|
|
directory.data = file_data[directory.data_offset:][:directory.data_length]
|
|
directory.name = rarc.string_table.get(directory.name_offset)
|
|
directory.rarc = rarc
|
|
return directory
|
|
|
|
|
|
def read_directories(rarc, data, file_data):
|
|
buffer = data[rarc.directory_offset:][:rarc.directory_count * DIRECTORY_SIZE]
|
|
rarc._directories = []
|
|
for directory_buffer in chunks(buffer, DIRECTORY_SIZE):
|
|
rarc._directories.append(read_directory(
|
|
rarc, directory_buffer, file_data))
|
|
|
|
|
|
def read(buffer) -> RARC:
|
|
""" Read and parse RARC from buffer. """
|
|
|
|
# TODO: Add error checking
|
|
header = struct.unpack('>IIIIIIII', buffer[:32])
|
|
info = struct.unpack('>IIIIIIHHI', buffer[32:][:32])
|
|
rarc = RARC(*header, *info)
|
|
|
|
data = buffer[32:]
|
|
file_data = data[rarc.file_offset:][:rarc.file_length]
|
|
|
|
read_string_table(rarc, data)
|
|
read_nodes(rarc, data)
|
|
read_directories(rarc, data, file_data)
|
|
|
|
return rarc
|