You've already forked PythonLib
mirror of
https://github.com/lifebottle/PythonLib.git
synced 2026-02-13 15:25:50 -08:00
Folder structure
Trying to make it more package-y
This commit is contained in:
0
pythonlib/__init__.py
Normal file
0
pythonlib/__init__.py
Normal file
269
pythonlib/formats/FileIO.py
Normal file
269
pythonlib/formats/FileIO.py
Normal file
@@ -0,0 +1,269 @@
|
||||
from io import BytesIO
|
||||
import struct
|
||||
|
||||
class FileIO(object):
|
||||
def __init__(self, path, mode="r+b", endian="little"):
|
||||
self.mode = mode
|
||||
self._isBitesIO = False
|
||||
if type(path) is bytes:
|
||||
self.path = None
|
||||
self.f = path
|
||||
self.is_memory_file = True
|
||||
elif type(path) is BytesIO:
|
||||
self.path = None
|
||||
self.f = path
|
||||
self._isBitesIO = True
|
||||
self.is_memory_file = True
|
||||
else:
|
||||
self.path = path
|
||||
self.is_memory_file = False
|
||||
self.endian = "<" if endian == "little" or endian == "<" else ">"
|
||||
|
||||
def __enter__(self):
|
||||
if self.is_memory_file:
|
||||
self.f = self.f if self._isBitesIO else BytesIO(self.f)
|
||||
else:
|
||||
self.f = open(self.path, self.mode)
|
||||
self.f.seek(0)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.f.close()
|
||||
|
||||
def close(self):
|
||||
self.f.close()
|
||||
|
||||
def tell(self):
|
||||
return self.f.tell()
|
||||
|
||||
def seek(self, pos, whence=0):
|
||||
self.f.seek(pos, whence)
|
||||
|
||||
def read(self, n=-1):
|
||||
return self.f.read(n)
|
||||
|
||||
def read_at(self, pos, n=-1):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read(n)
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def write(self, data):
|
||||
self.f.write(data)
|
||||
|
||||
def write_at(self, pos, data):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write(data)
|
||||
self.seek(current)
|
||||
|
||||
def peek(self, n):
|
||||
pos = self.tell()
|
||||
ret = self.read(n)
|
||||
self.seek(pos)
|
||||
return ret
|
||||
|
||||
def write_line(self, data):
|
||||
self.f.write(data + "\n")
|
||||
|
||||
def set_endian(self, endian):
|
||||
self.endian = "<" if endian == "little" or endian == "<" else ">"
|
||||
|
||||
def read_int8(self):
|
||||
return struct.unpack("b", self.read(1))[0]
|
||||
|
||||
def read_int8_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_int8()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_uint8(self):
|
||||
return struct.unpack("B", self.read(1))[0]
|
||||
|
||||
def read_uint8_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_uint8()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_int16(self):
|
||||
return struct.unpack(self.endian + "h", self.read(2))[0]
|
||||
|
||||
def read_int16_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_int16()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_uint16(self):
|
||||
return struct.unpack(self.endian + "H", self.read(2))[0]
|
||||
|
||||
def read_uint16_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_uint16()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_int32(self):
|
||||
return struct.unpack(self.endian + "i", self.read(4))[0]
|
||||
|
||||
def read_int32_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_int32()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_uint32(self):
|
||||
return struct.unpack(self.endian + "I", self.read(4))[0]
|
||||
|
||||
def read_uint32_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_uint32()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_int64(self):
|
||||
return struct.unpack(self.endian + "q", self.read(8))[0]
|
||||
|
||||
def read_int64_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_int64()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_uint64(self):
|
||||
return struct.unpack(self.endian + "Q", self.read(8))[0]
|
||||
|
||||
def read_uint64_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_uint64()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_single(self):
|
||||
return struct.unpack(self.endian + "f", self.read(4))[0]
|
||||
|
||||
def read_single_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_single()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def read_double(self):
|
||||
return struct.unpack(self.endian + "d", self.read(8))[0]
|
||||
|
||||
def read_double_at(self, pos):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
ret = self.read_double()
|
||||
self.seek(current)
|
||||
return ret
|
||||
|
||||
def skip_padding(self, alignment):
|
||||
while self.tell() % alignment != 0:
|
||||
self.read_uint8()
|
||||
|
||||
def write_int8(self, num):
|
||||
self.f.write(struct.pack("b", num))
|
||||
|
||||
def write_int8_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_int8(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_uint8(self, num):
|
||||
self.f.write(struct.pack("B", num))
|
||||
|
||||
def write_uint8_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_uint8(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_int16(self, num):
|
||||
self.f.write(struct.pack(self.endian + "h", num))
|
||||
|
||||
def write_int16_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_int16(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_uint16(self, num):
|
||||
self.f.write(struct.pack(self.endian + "H", num))
|
||||
|
||||
def write_uint16_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_uint16(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_int32(self, num):
|
||||
self.f.write(struct.pack(self.endian + "i", num))
|
||||
|
||||
def write_int32_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_int32(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_uint32(self, num):
|
||||
self.f.write(struct.pack(self.endian + "I", num))
|
||||
|
||||
def write_uint32_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_uint32(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_int64(self, num):
|
||||
self.f.write(struct.pack(self.endian + "q", num))
|
||||
|
||||
def write_int64_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_int64(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_uint64(self, num):
|
||||
self.f.write(struct.pack(self.endian + "Q", num))
|
||||
|
||||
def write_uint64_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_uint64(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_single(self, num):
|
||||
self.f.write(struct.pack(self.endian + "f", num))
|
||||
|
||||
def write_single_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_single(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_double(self, num):
|
||||
self.f.write(struct.pack(self.endian + "d", num))
|
||||
|
||||
def write_double_at(self, pos, num):
|
||||
current = self.tell()
|
||||
self.seek(pos)
|
||||
self.write_double(num)
|
||||
self.seek(current)
|
||||
|
||||
def write_padding(self, alignment, pad_byte=0x00):
|
||||
while self.tell() % alignment != 0:
|
||||
self.write_uint8(pad_byte)
|
||||
0
pythonlib/formats/__init__.py
Normal file
0
pythonlib/formats/__init__.py
Normal file
134
pythonlib/formats/fps4.py
Normal file
134
pythonlib/formats/fps4.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import os, struct, sys
|
||||
|
||||
def dump_fps4(name, name2, base_path):
|
||||
|
||||
with open(name, 'rb') as f:
|
||||
|
||||
head, tail = os.path.split(name)
|
||||
file_name = tail.split(".")[0]
|
||||
|
||||
fps4 = f.read(4)
|
||||
if fps4 != b'FPS4':
|
||||
#print("Wrong file.")
|
||||
return
|
||||
|
||||
file_number = struct.unpack('<L', f.read(4))[0]
|
||||
header_size = struct.unpack('<L', f.read(4))[0]
|
||||
offset = struct.unpack('<L', f.read(4))[0]
|
||||
block_size = struct.unpack('<H', f.read(2))[0]
|
||||
|
||||
if block_size == 0x2C:
|
||||
f.seek(header_size, 0)
|
||||
files_offsets = []
|
||||
files_sizes = []
|
||||
files_names = []
|
||||
|
||||
for i in range(file_number-1):
|
||||
files_offsets.append(struct.unpack('<L', f.read(4))[0])
|
||||
files_sizes.append(struct.unpack('<L', f.read(4))[0])
|
||||
f.read(4) # File size
|
||||
fname = f.read(block_size-0xC).decode("ASCII").strip('\x00')
|
||||
files_names.append(fname)
|
||||
|
||||
try:
|
||||
|
||||
os.mkdir(os.path.join(base_path,file_name.upper()))
|
||||
except:
|
||||
pass
|
||||
|
||||
if offset != 0x00:
|
||||
for i in range(file_number-1):
|
||||
o = open(os.path.join(base_path, file_name.upper(),files_names[i]), 'wb')
|
||||
f.seek(files_offsets[i], 0)
|
||||
o.write(f.read(files_sizes[i]))
|
||||
o.close()
|
||||
else:
|
||||
with open(name2, 'rb') as f2:
|
||||
for i in range(file_number-1):
|
||||
o = open(os.path.join(base_path, file_name.upper(), files_names[i]), 'wb')
|
||||
f2.seek(files_offsets[i], 0)
|
||||
o.write(f2.read(files_sizes[i]))
|
||||
o.close()
|
||||
|
||||
|
||||
def dump_folder(folder):
|
||||
|
||||
os.chdir(folder)
|
||||
|
||||
# Only for m.b & m.dat
|
||||
for f in os.listdir(os.getcwd()):
|
||||
if '.B' in f:
|
||||
dump_fps4(f, f.split('.')[0]+'.MAPBIN')
|
||||
print ("Extracting %s" % f)
|
||||
|
||||
def pack_folder(folder, dat='.dat'):
|
||||
|
||||
buffer = 0
|
||||
files_sizes = []
|
||||
files_names = []
|
||||
b_file = open(folder + '.b', 'wb')
|
||||
dat_file = open(folder + dat, 'wb')
|
||||
files = [ele for ele in os.listdir(folder) if os.path.isfile(folder + '/' + ele)]
|
||||
|
||||
for n in files:
|
||||
f = open(os.path.join(folder, n), 'rb')
|
||||
files_names.append(n)
|
||||
data = f.read()
|
||||
dat_file.write(data)
|
||||
files_sizes.append(len(data))
|
||||
f.close()
|
||||
|
||||
dat_file.close()
|
||||
|
||||
b_file.write(b'\x46\x50\x53\x34') # FPS4
|
||||
b_file.write(struct.pack('<L', len(files) + 1))
|
||||
b_file.write(struct.pack('<L', 0x1C))
|
||||
b_file.write(b'\x00' * 4 + b'\x2C\x00\x0F\x00\x01\x01\x00\x00' + b'\x00' * 4)
|
||||
|
||||
for i in range(len(files)):
|
||||
b_file.write(struct.pack('<L', buffer))
|
||||
b_file.write(struct.pack('<L', files_sizes[i]))
|
||||
b_file.write(struct.pack('<L', files_sizes[i]))
|
||||
b_file.write(files_names[i].encode())
|
||||
b_file.write(b'\x00' * (32 - (len(files_names[i]) % 32)))
|
||||
buffer += files_sizes[i]
|
||||
|
||||
b_file.write(struct.pack('<L', buffer) + b'\x00'*12)
|
||||
b_file.close()
|
||||
|
||||
def pack_all(folder):
|
||||
|
||||
os.chdir(folder)
|
||||
|
||||
for d in os.listdir(os.getcwd()):
|
||||
if os.path.isdir(d):
|
||||
pack_folder(d)
|
||||
print ("Packing %s" % d)
|
||||
|
||||
def pack_m(folder):
|
||||
|
||||
os.chdir(folder)
|
||||
|
||||
for d in os.listdir(os.getcwd()):
|
||||
if os.path.isdir(d):
|
||||
pack_folder(d, '.MAPBIN')
|
||||
print ("Packing %s" % d)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("allo")
|
||||
if sys.argv[1] == '-d':
|
||||
dump_fps4(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
elif sys.argv[1] == '-dm':
|
||||
dump_folder(sys.argv[2])
|
||||
elif sys.argv[1] == '-i':
|
||||
pack_folder(sys.argv[2])
|
||||
elif sys.argv[1] == '-ia':
|
||||
pack_all(sys.argv[2])
|
||||
elif sys.argv[1] == '-im':
|
||||
pack_m(sys.argv[2])
|
||||
print ("Done!")
|
||||
|
||||
|
||||
sys.exit(1)
|
||||
162
pythonlib/formats/pak2.py
Normal file
162
pythonlib/formats/pak2.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import struct, sys, os, io
|
||||
from dataclasses import dataclass, field
|
||||
@dataclass
|
||||
class pak2_chunks:
|
||||
theirsce: bytes = b""
|
||||
lipsync: bytes = b""
|
||||
unused: bytes = b""
|
||||
image_unk1: bytes = b""
|
||||
image_unk2: bytes = b""
|
||||
image_blobs: list = field(default_factory=list)
|
||||
|
||||
@dataclass
|
||||
class pak2_file:
|
||||
#offsets: list
|
||||
char_count: int = 0
|
||||
slot_count: int = 0
|
||||
image_count: int = 0
|
||||
chunks: pak2_chunks = field(default_factory=pak2_chunks)
|
||||
|
||||
def get_file_name_noext(path):
|
||||
return os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
|
||||
def get_parent_folder(path):
|
||||
return os.path.normpath(os.path.join(path, os.pardir))
|
||||
|
||||
|
||||
def insert_padded_chunk(file: bytes, chunk: bytes, alignment: int = 4):
|
||||
file.write(chunk)
|
||||
pad = (alignment - (file.tell() % alignment)) % alignment
|
||||
file.write(b"\x00" * pad)
|
||||
return file.tell()
|
||||
|
||||
|
||||
def get_theirsce_from_pak2(file: bytes)->bytes:
|
||||
offsets = struct.unpack("<3I", file[:12])
|
||||
|
||||
# Handle null 2nd offset because of course that's a thing
|
||||
if offsets[1] == 0:
|
||||
return file[offsets[0] : offsets[2]]
|
||||
else:
|
||||
return file[offsets[0] : offsets[1]]
|
||||
|
||||
|
||||
def get_data(file: bytes)->pak2_file:
|
||||
offsets = struct.unpack("<6I", file[:24])
|
||||
data = pak2_file()
|
||||
data.char_count = struct.unpack("<H", file[0x18:0x1A])[0]
|
||||
data.slot_count = struct.unpack("<H", file[0x1A:0x1C])[0] # 0x20 always
|
||||
data.image_count = struct.unpack("<H", file[0x1C:0x1E])[0]
|
||||
|
||||
# Handle null 2nd offset because of course that's a thing
|
||||
if offsets[1] == 0:
|
||||
data.chunks.theirsce = file[offsets[0] : offsets[2]]
|
||||
else:
|
||||
data.chunks.theirsce = file[offsets[0] : offsets[1]]
|
||||
size = struct.unpack("<I", file[offsets[1] : offsets[1] + 4])[0] + 0x10
|
||||
data.chunks.lipsync = file[offsets[1] : offsets[1] + size]
|
||||
|
||||
data.chunks.unused = file[offsets[2] : offsets[2] + (data.char_count * 4)]
|
||||
data.chunks.image_unk1 = file[offsets[3] : offsets[3] + (data.slot_count * 4)]
|
||||
data.chunks.image_unk2 = file[offsets[4] : offsets[4] + (data.image_count * 2)]
|
||||
# image_data = bytearray(file[offsets[5]:len(file)])
|
||||
|
||||
blob_offsets = list(
|
||||
struct.unpack(
|
||||
"<%dI" % (data.image_count * 2), file[offsets[5] : offsets[5] + data.image_count * 8]
|
||||
)
|
||||
)
|
||||
del blob_offsets[1::2] #Yeet all the odd offset, they be useless
|
||||
data.chunks.image_blobs = []
|
||||
for blob in blob_offsets:
|
||||
blob_size = struct.unpack("<I", file[blob : blob + 4])[0]
|
||||
if blob_size == 0:
|
||||
blob_size = 0x400
|
||||
data.chunks.image_blobs.append(file[blob : blob + blob_size])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def create_pak2(data: pak2_file)->bytes:
|
||||
output = io.BytesIO()
|
||||
output.seek(0)
|
||||
output.write(b"\x00" * 0x20)
|
||||
offsets_new = []
|
||||
offsets_new.append(output.tell())
|
||||
|
||||
# theirsce
|
||||
offsets_new.append(insert_padded_chunk(output, data.chunks.theirsce))
|
||||
|
||||
# lipsync
|
||||
offsets_new.append(insert_padded_chunk(output, data.chunks.lipsync))
|
||||
|
||||
# unused
|
||||
offsets_new.append(insert_padded_chunk(output, data.chunks.unused))
|
||||
|
||||
# unk1
|
||||
offsets_new.append(insert_padded_chunk(output, data.chunks.image_unk1))
|
||||
|
||||
# unk2
|
||||
offsets_new.append(insert_padded_chunk(output, data.chunks.image_unk2))
|
||||
|
||||
# images
|
||||
# Create image chunk
|
||||
image_chunk = b"\x00" * (data.image_count * 8) # minimum size
|
||||
insert_padded_chunk(output, image_chunk, 128)
|
||||
image_offsets = []
|
||||
|
||||
image_offsets.append(output.tell())
|
||||
|
||||
for blob in data.chunks.image_blobs:
|
||||
image_offsets.append(insert_padded_chunk(output, blob, 128))
|
||||
|
||||
image_offsets = image_offsets[:-1]
|
||||
image_offsets = [
|
||||
val for val in image_offsets for _ in (0, 1)
|
||||
] # image data offsets are duplicated
|
||||
|
||||
# Write image data offsets
|
||||
output.seek(offsets_new[5])
|
||||
output.write(struct.pack("<%dI" % len(image_offsets), *image_offsets))
|
||||
|
||||
# Write chunk offsets
|
||||
output.seek(0)
|
||||
output.write(struct.pack("<%dI" % len(offsets_new), *offsets_new))
|
||||
|
||||
# Write metadata
|
||||
output.write(struct.pack("<H", data.char_count))
|
||||
output.write(struct.pack("<H", data.slot_count))
|
||||
output.write(struct.pack("<H", data.image_count))
|
||||
|
||||
end = output.getvalue()
|
||||
|
||||
return end
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:")
|
||||
print("pak2.py [pak2_path] [theirsce_path]")
|
||||
sys.exit()
|
||||
|
||||
with open(sys.argv[1], "rb") as input:
|
||||
file = input.read()
|
||||
|
||||
pak2 = get_data(file)
|
||||
|
||||
print("file size: %d" % len(file))
|
||||
print("char_count: %d" % pak2.char_count)
|
||||
print("slot_count: %d" % pak2.slot_count)
|
||||
print("image_count: %d" % pak2.image_count)
|
||||
print()
|
||||
|
||||
# Get new Theirsce, if there's no second arg just reinsert original
|
||||
if len(sys.argv) > 2:
|
||||
with open(sys.argv[2], "rb+") as f:
|
||||
theirsce = f.read()
|
||||
|
||||
with open(sys.argv[2] + ".new", "wb+") as output:
|
||||
output.write(create_pak2(pak2))
|
||||
|
||||
print("Done!")
|
||||
49
pythonlib/formats/scpk.py
Normal file
49
pythonlib/formats/scpk.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from dataclasses import dataclass
|
||||
from ..formats.FileIO import FileIO
|
||||
from ..utils import comptolib
|
||||
|
||||
MAGIC = b"SCPK"
|
||||
|
||||
|
||||
@dataclass
|
||||
class scpk_file():
|
||||
is_compressed: bool
|
||||
type: int
|
||||
data: bytes
|
||||
|
||||
|
||||
class Scpk(FileIO):
|
||||
def __init__(self, path="") -> None:
|
||||
super().__init__(path, "r+b", "<")
|
||||
super().__enter__()
|
||||
if self.read(4) != MAGIC:
|
||||
raise ValueError("Not an SCPK file!")
|
||||
|
||||
self.unk1 = self.read_uint16()
|
||||
self.unk2 = self.read_uint16()
|
||||
file_amount = self.read_uint32()
|
||||
self.read_uint32() # padding?
|
||||
self.files = []
|
||||
|
||||
sizes = []
|
||||
for _ in range(file_amount):
|
||||
sizes.append(self.read_uint32())
|
||||
|
||||
for size in sizes:
|
||||
data = self.read(size)
|
||||
is_compressed = comptolib.is_compressed(data)
|
||||
c_type = 0
|
||||
if is_compressed:
|
||||
c_type = data[0]
|
||||
data = comptolib.decompress_data(data)
|
||||
|
||||
if len(data) > 8 and data[0:8] == b"THEIRSCE":
|
||||
self.rsce = data
|
||||
|
||||
self.files.append(scpk_file(is_compressed, c_type, data))
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.files[item]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.files)
|
||||
232
pythonlib/formats/theirsce.py
Normal file
232
pythonlib/formats/theirsce.py
Normal file
@@ -0,0 +1,232 @@
|
||||
from typing import Generator
|
||||
|
||||
from .FileIO import FileIO
|
||||
from .theirsce_funcs import *
|
||||
from .theirsce_instructions import *
|
||||
|
||||
# I followed other project when making this class
|
||||
# not sure how if it's the best approach
|
||||
|
||||
SECTION_AMOUNT = 6
|
||||
|
||||
@dataclass
|
||||
class subsection:
|
||||
unk1: int
|
||||
unk2: int
|
||||
off: int
|
||||
|
||||
|
||||
class Theirsce(FileIO):
|
||||
def __init__(self, path=""):
|
||||
super().__init__(path, "r+b", "<")
|
||||
super().__enter__()
|
||||
self.magic = self.read(8)
|
||||
|
||||
if self.magic != b"THEIRSCE":
|
||||
raise ValueError("Not a THEIRSCE file!")
|
||||
|
||||
self.code_offset = self.read_uint32()
|
||||
self.strings_offset = self.read_uint32()
|
||||
self.unk_field = self.read_uint32()
|
||||
|
||||
self.frame_offset = self.read_uint16()
|
||||
self.entry_offset = self.read_uint16()
|
||||
|
||||
self.sections: list[subsection] = []
|
||||
# section_stop = self.readUShort(); self.seek(-2, 1)
|
||||
#section_amount = (section_stop - 0x18) // 2
|
||||
|
||||
for _ in range(SECTION_AMOUNT):
|
||||
pos = self.tell() + 2
|
||||
self.seek(self.read_uint16())
|
||||
|
||||
subsections = []
|
||||
for _ in range(self.read_uint16()):
|
||||
sub = subsection(self.read_uint16(), self.read_uint16(), self.read_uint16() + self.code_offset)
|
||||
subsections.append(sub)
|
||||
self.sections.append(subsections)
|
||||
self.seek(pos)
|
||||
|
||||
def __enter__(self):
|
||||
return super().__enter__()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
return super().__exit__(exc_type, exc_value, traceback)
|
||||
|
||||
def walk_code(self, start=None, end=None) -> Generator[None, TheirsceBaseInstruction, None]:
|
||||
start = self.code_offset if start is None else start
|
||||
end = self.strings_offset if end is None else end
|
||||
|
||||
self.seek(start)
|
||||
|
||||
while self.tell() < end:
|
||||
opcode = self.read_opcode()
|
||||
pos = self.tell()
|
||||
yield opcode
|
||||
self.seek(pos)
|
||||
|
||||
def read_tag_bytes(self):
|
||||
data = b""
|
||||
|
||||
while self.read_uint8_at(self.tell() + 1) != 0x80:
|
||||
data += self.read(1)
|
||||
opcode = data[-1]
|
||||
|
||||
if opcode < 0x80:
|
||||
if opcode & 8 != 0:
|
||||
data += self.read(2)
|
||||
else:
|
||||
data += self.read(1)
|
||||
|
||||
elif opcode < 0xE0:
|
||||
size_mask = (opcode >> 3) & 3
|
||||
|
||||
if size_mask == 1: data += self.read(1)
|
||||
elif size_mask == 2: data += self.read(2)
|
||||
elif size_mask == 3: data += self.read(4)
|
||||
|
||||
elif opcode < 0xF0:
|
||||
data += self.read(1)
|
||||
|
||||
elif opcode < 0xF8:
|
||||
if (0xF2 <= opcode < 0xF5) or opcode == 0xF7:
|
||||
data += self.read(2)
|
||||
elif opcode == 0xF5:
|
||||
data += self.read(4)
|
||||
elif opcode == 0xF6:
|
||||
data += self.read(1)
|
||||
for _ in range(data[-1]):
|
||||
if ord(data[-1]) & 8 != 0:
|
||||
data += self.read(2)
|
||||
else:
|
||||
data += self.read(3)
|
||||
|
||||
elif opcode < 0xFC:
|
||||
data += self.read(2)
|
||||
|
||||
self.read(1)
|
||||
return data
|
||||
|
||||
def read_opcode(self):
|
||||
pos = self.tell()
|
||||
opcode = self.read_uint8()
|
||||
|
||||
# Reference Block
|
||||
if opcode < 0x80:
|
||||
var_type = (opcode >> 4) & 7
|
||||
shift = 0
|
||||
|
||||
#id = mask
|
||||
if var_type == 0: # bitfields
|
||||
value = self.read_uint8()
|
||||
if opcode & 8 == 0:
|
||||
next_byte = opcode & 3
|
||||
pass
|
||||
else:
|
||||
next_byte = self.read_uint8()
|
||||
pass
|
||||
shift = (value & 7) #<< 3
|
||||
value = ((( value | (next_byte << 8)) >> 3) & 0xFF)
|
||||
else:
|
||||
value = self.read_uint8()
|
||||
top = 1
|
||||
if opcode & 8 != 0:
|
||||
top = 2
|
||||
next_byte = self.read_uint8()
|
||||
value = ( ((next_byte & 0xFF) << 8) | (value & 0xFF))
|
||||
value = ((opcode & 3) << (8 * top)) | value
|
||||
|
||||
# scope
|
||||
if opcode & 4 == 0:
|
||||
if value < 0x400:
|
||||
scope = ReferenceScope.GLOBAL
|
||||
else:
|
||||
scope = ReferenceScope.FILE
|
||||
value -= 0x400
|
||||
else:
|
||||
scope = ReferenceScope.LOCAL
|
||||
|
||||
return TheirsceReferenceInstruction(ref_type=VariableType(var_type), scope=scope, offset=value, shift=shift, position=pos)
|
||||
|
||||
# ALU operations
|
||||
elif opcode < 0xC0:
|
||||
return TheirsceAluInstruction(operation = AluOperation(opcode & 0x3F), position=pos)
|
||||
|
||||
# PUSH block, the amount of bytes depend on encoding
|
||||
elif opcode < 0xE0:
|
||||
size_mask = (opcode >> 3) & 3
|
||||
signed = opcode & 4 != 0
|
||||
top = opcode & 7
|
||||
|
||||
if size_mask == 0:
|
||||
value = 0xFFFFFF00 | (top | 0xF8) if signed else top
|
||||
if size_mask == 1:
|
||||
value = top << 8 | self.read_uint8()
|
||||
value = value | 0xFFFF0000 | 0xF800 if signed else value
|
||||
elif size_mask == 2:
|
||||
value = top << 16 | self.read_uint16()
|
||||
value = value | 0xFF000000 | 0xF80000 if signed else value
|
||||
elif size_mask == 3:
|
||||
value = self.read_uint32()
|
||||
|
||||
# to signed
|
||||
value = value | (-(value & 0x80000000))
|
||||
|
||||
return TheirscePushInstruction(value=value, position=pos)
|
||||
|
||||
# CALL block, available commands are at 0x1e5300
|
||||
# first entry is number of parameters then function
|
||||
elif opcode < 0xF0:
|
||||
index = ((opcode & 0xF) << 8) | self.read_uint8()
|
||||
return TheirsceSyscallInstruction(function_index=index, function_name=SYSCALL_NAMES[index], position=pos)
|
||||
|
||||
# Flow related block
|
||||
elif opcode < 0xF8:
|
||||
if opcode == 0xF0:
|
||||
return TheirsceReturnInstruction(is_void=True, position=pos)
|
||||
elif opcode == 0xF1:
|
||||
return TheirsceReturnInstruction(is_void=False, position=pos)
|
||||
|
||||
# Need to be offsetted to start of code
|
||||
elif opcode >= 0xF2 and opcode < 0xF5:
|
||||
target = self.code_offset + self.read_uint16()
|
||||
return TheirsceBranchInstruction(branch_type=BranchType(opcode - 0xF2), destination=target, position=pos)
|
||||
elif opcode == 0xF5:
|
||||
target = self.code_offset + self.read_uint16()
|
||||
reserve = self.read_uint16()
|
||||
return TheirsceLocalCallInstruction(destination=target, reserve=reserve, position=pos)
|
||||
|
||||
# ?
|
||||
elif opcode == 0xF6:
|
||||
variables = self.read_uint8()
|
||||
params = []
|
||||
for _ in range(variables):
|
||||
if self.read_uint8() & 8 != 0:
|
||||
params.append(self.read_uint16())
|
||||
else:
|
||||
params.append(self.read_uint8())
|
||||
|
||||
return TheirsceAcquireInstruction(params=params,variables=variables, position=pos)
|
||||
|
||||
elif opcode == 0xF7:
|
||||
param = self.read_uint16() # Type?
|
||||
return TheirsceBreakInstruction(param=param, position=pos)
|
||||
|
||||
# Get string
|
||||
elif opcode < 0xFC:
|
||||
value = ((opcode & 3) << 16) | self.read_uint16()
|
||||
return TheirsceStringInstruction(offset=value,text="", position=pos)
|
||||
|
||||
# ?
|
||||
elif opcode == 0xFE:
|
||||
return TheirsceSpecialReferenceInstruction(position=pos)
|
||||
|
||||
# Impossible
|
||||
else:
|
||||
raise ValueError(f"INVALID OPCODE 0x{opcode:2X}")
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# with Theirsce("./10233d.theirsce") as f:
|
||||
# for op in f.walk_code():
|
||||
# if op.type == InstructionType.ACQUIRE:
|
||||
# print(op)
|
||||
371
pythonlib/formats/theirsce_funcs.py
Normal file
371
pythonlib/formats/theirsce_funcs.py
Normal file
@@ -0,0 +1,371 @@
|
||||
SYSCALL_NAMES = (
|
||||
"printf", # 0x0
|
||||
None, # 0x1
|
||||
None, # 0x2
|
||||
None, # 0x3
|
||||
None, # 0x4
|
||||
"set_person", # 0x5
|
||||
"set_person_3d", # 0x6
|
||||
"set_position", # 0x7
|
||||
"set_position_3d", # 0x8
|
||||
"delete_person", # 0x9
|
||||
"animate_person", # 0xA
|
||||
"set_balloon", # 0xB
|
||||
"get_param", # 0xC
|
||||
"set_param", # 0xD
|
||||
"move_position", # 0xE
|
||||
"move_position_3d", # 0xF
|
||||
"na_move_position", # 0x10
|
||||
"na_move_position_3d", # 0x11
|
||||
"move_check", # 0x12
|
||||
"sky_init", # 0x13
|
||||
"cloud_init", # 0x14
|
||||
"cloud_inc_alpha", # 0x15
|
||||
"cloud_dec_alpha", # 0x16
|
||||
"delete_cloud_dec_alpha", # 0x17
|
||||
"trap_line", # 0x18
|
||||
"trap_box", # 0x19
|
||||
"trap_box_stoptimer", # 0x1A
|
||||
"trap_box_stoptimer_pop", # 0x1B
|
||||
"trap_poly4", # 0x1C
|
||||
"trap_poly4_stoptimer", # 0x1D
|
||||
"trap_poly4_stoptimer_pop", # 0x1E
|
||||
"trap_line_3d", # 0x1F
|
||||
"trap_box_3d", # 0x20
|
||||
"trap_box_stoptimer_3d", # 0x21
|
||||
"trap_box_stoptimer_pop_3d", # 0x22
|
||||
"trap_poly4_3d", # 0x23
|
||||
"trap_poly4_stoptimer_3d", # 0x24
|
||||
"trap_poly4_stoptimer_pop_3d",# 0x25
|
||||
"trap_contact_chr", # 0x26
|
||||
"delete_trap", # 0x27
|
||||
"is_trap", # 0x28
|
||||
"event_line", # 0x29
|
||||
"event_box", # 0x2A
|
||||
"event_poly4", # 0x2B
|
||||
"event_line_3d", # 0x2C
|
||||
"event_box_3d", # 0x2D
|
||||
"event_poly4_3d", # 0x2E
|
||||
"delete_event", # 0x2F
|
||||
"is_event", # 0x30
|
||||
"line_hit", # 0x31
|
||||
"line_hit_ply", # 0x32
|
||||
"line_hit_npc", # 0x33
|
||||
"line_hit_3d", # 0x34
|
||||
"line_hit_ply_3d", # 0x35
|
||||
"line_hit_npc_3d", # 0x36
|
||||
"delete_line_hit", # 0x37
|
||||
"is_line_hit", # 0x38
|
||||
"scope", # 0x39
|
||||
"scope_3d", # 0x3A
|
||||
"delete_scope", # 0x3B
|
||||
"is_scope", # 0x3C
|
||||
"scroll_direct", # 0x3D
|
||||
"scroll", # 0x3E
|
||||
"zoom_scroll", # 0x3F
|
||||
"is_scroll", # 0x40
|
||||
"scroll_four", # 0x41
|
||||
None, # 0x42
|
||||
"fst_load", # 0x43
|
||||
"gradation_palet", # 0x44
|
||||
None, # 0x45
|
||||
None, # 0x46
|
||||
None, # 0x47
|
||||
None, # 0x48
|
||||
None, # 0x49
|
||||
None, # 0x4A
|
||||
None, # 0x4B
|
||||
None, # 0x4C
|
||||
None, # 0x4D
|
||||
None, # 0x4E
|
||||
None, # 0x4F
|
||||
None, # 0x50
|
||||
None, # 0x51
|
||||
None, # 0x52
|
||||
None, # 0x53
|
||||
None, # 0x54
|
||||
None, # 0x55
|
||||
None, # 0x56
|
||||
None, # 0x57
|
||||
None, # 0x58
|
||||
None, # 0x59
|
||||
None, # 0x5A
|
||||
None, # 0x5B
|
||||
None, # 0x5C
|
||||
None, # 0x5D
|
||||
None, # 0x5E
|
||||
None, # 0x5F
|
||||
None, # 0x60
|
||||
None, # 0x61
|
||||
None, # 0x62
|
||||
None, # 0x63
|
||||
None, # 0x64
|
||||
"event_line_inf", # 0x65
|
||||
"event_box_inf", # 0x66
|
||||
"event_poly4_inf", # 0x67
|
||||
"event_line_3d_inf", # 0x68
|
||||
"event_box_3d_inf", # 0x69
|
||||
"event_poly4_3d_inf", # 0x6A
|
||||
None, # 0x6B
|
||||
None, # 0x6C
|
||||
None, # 0x6D
|
||||
None, # 0x6E
|
||||
None, # 0x6F
|
||||
"scroll_cnt", # 0x70
|
||||
"zoom_scroll_cnt", # 0x71
|
||||
"map_bright", # 0x72
|
||||
"change_bg_anime", # 0x73
|
||||
"get_bg_anime_param", # 0x74
|
||||
"scope_msg", # 0x75
|
||||
"scope_msg_3d", # 0x76
|
||||
None, # 0x77
|
||||
None, # 0x78
|
||||
None, # 0x79
|
||||
None, # 0x7A
|
||||
None, # 0x7B
|
||||
None, # 0x7C
|
||||
"is_sideview", # 0x7D
|
||||
"get_map_no", # 0x7E
|
||||
"bg_alpha", # 0x7F
|
||||
None, # 0x80
|
||||
None, # 0x81
|
||||
None, # 0x82
|
||||
None, # 0x83
|
||||
None, # 0x84
|
||||
None, # 0x85
|
||||
None, # 0x86
|
||||
None, # 0x87
|
||||
None, # 0x88
|
||||
None, # 0x89
|
||||
None, # 0x8A
|
||||
None, # 0x8B
|
||||
None, # 0x8C
|
||||
None, # 0x8D
|
||||
None, # 0x8E
|
||||
"get_sys_map_rate", # 0x8F
|
||||
None, # 0x90
|
||||
None, # 0x91
|
||||
None, # 0x92
|
||||
None, # 0x93
|
||||
None, # 0x94
|
||||
None, # 0x95
|
||||
None, # 0x96
|
||||
None, # 0x97
|
||||
None, # 0x98
|
||||
None, # 0x99
|
||||
"get_int_no", # 0x9A
|
||||
None, # 0x9B
|
||||
None, # 0x9C
|
||||
None, # 0x9D
|
||||
"special_person", # 0x9E
|
||||
"special_person_3d", # 0x9F
|
||||
None, # 0xA0
|
||||
None, # 0xA1
|
||||
None, # 0xA2
|
||||
None, # 0xA3
|
||||
"walk_se", # 0xA4
|
||||
"get_mapsize_x", # 0xA5
|
||||
"get_mapsize_y", # 0xA6
|
||||
"set_3d_zoom_rate", # 0xA7
|
||||
None, # 0xA8
|
||||
None, # 0xA9
|
||||
"set_cloud_h", # 0xAA
|
||||
"move_stop", # 0xAB
|
||||
None, # 0xAC
|
||||
"set_child_chr", # 0xAD
|
||||
"del_child_chr", # 0xAE
|
||||
"get_child_pos", # 0xAF
|
||||
"get_parent_chr", # 0xB0
|
||||
"is_bg_atari", # 0xB1
|
||||
None, # 0xB2
|
||||
"set_gradation_chr", # 0xB3
|
||||
"set_rot_chr_color", # 0xB4
|
||||
"scroll_offset", # 0xB5
|
||||
None, # 0xB6
|
||||
None, # 0xB7
|
||||
"set_bg_pal_anime", # 0xB8
|
||||
None, # 0xB9
|
||||
"get_pl_move_spd", # 0xBA
|
||||
"get_pl_move_dir", # 0xBB
|
||||
None, # 0xBC
|
||||
None, # 0xBD
|
||||
None, # 0xBE
|
||||
None, # 0xBF
|
||||
None, # 0xC0
|
||||
None, # 0xC1
|
||||
None, # 0xC2
|
||||
None, # 0xC3
|
||||
None, # 0xC4
|
||||
None, # 0xC5
|
||||
"set_force_mode", # 0xC6
|
||||
"trap_force_line", # 0xC7
|
||||
"trap_force_box", # 0xC8
|
||||
"trap_force_poly4", # 0xC9
|
||||
"trap_force_line_3d", # 0xCA
|
||||
"trap_force_box_3d", # 0xCB
|
||||
"trap_force_poly4_3d", # 0xCC
|
||||
"trap_force_rain_point", # 0xCD
|
||||
"trap_force_chr", # 0xCE
|
||||
"line_hit_force", # 0xCF
|
||||
"line_hit_force_3d", # 0xD0
|
||||
"get_force_pow", # 0xD1
|
||||
"get_force_lever", # 0xD2
|
||||
"set_csp_param", # 0xD3
|
||||
"get_csp_param", # 0xD4
|
||||
"calc_csp_param", # 0xD5
|
||||
None, # 0xD6
|
||||
None, # 0xD7
|
||||
None, # 0xD8
|
||||
None, # 0xD9
|
||||
"set_move_pass", # 0xDA
|
||||
"move_pass", # 0xDB
|
||||
None, # 0xDC
|
||||
"scroll_limit", # 0xDD
|
||||
None, # 0xDE
|
||||
None, # 0xDF
|
||||
None, # 0xE0
|
||||
None, # 0xE1
|
||||
"demo_stop_move_pass", # 0xE2
|
||||
None, # 0xE3
|
||||
None, # 0xE4
|
||||
"trap_force_rain_point_3d", # 0xE5
|
||||
"trap_force_rain_chr", # 0xE6
|
||||
"get_force_rain_trap_count", # 0xE7
|
||||
"get_force_rain_trap_no", # 0xE8
|
||||
None, # 0xE9
|
||||
"get_prev_crate", # 0xEA
|
||||
None, # 0xEB
|
||||
None, # 0xEC
|
||||
None, # 0xED
|
||||
None, # 0xEE
|
||||
None, # 0xEF
|
||||
None, # 0xF0
|
||||
"force_obj_delete", # 0xF1
|
||||
"get_force_action", # 0xF2
|
||||
None, # 0xF3
|
||||
None, # 0xF4
|
||||
None, # 0xF5
|
||||
"set_chr_bright", # 0xF6
|
||||
None, # 0xF7
|
||||
"set_fade_chr_color", # 0xF8
|
||||
None, # 0xF9
|
||||
None, # 0xFA
|
||||
"set_line_hit_mode", # 0xFB
|
||||
None, # 0xFC
|
||||
None, # 0xFD
|
||||
None, # 0xFE
|
||||
"trap_force_box_ivy_up", # 0xFF
|
||||
"trap_force_box_ivy_dn", # 0x100
|
||||
"trap_force_poly4_ivy_up", # 0x101
|
||||
"trap_force_poly4_ivy_dn", # 0x102
|
||||
None, # 0x103
|
||||
"set_ladder", # 0x104
|
||||
None, # 0x105
|
||||
None, # 0x106
|
||||
None, # 0x107
|
||||
"print_screen", # 0x108
|
||||
"cross_fade", # 0x109
|
||||
None, # 0x10A
|
||||
None, # 0x10B
|
||||
None, # 0x10C
|
||||
None, # 0x10D
|
||||
None, # 0x10E
|
||||
None, # 0x10F
|
||||
"define_texture", # 0x110
|
||||
"get_ce_arg", # 0x111
|
||||
"set_ce_arg", # 0x112
|
||||
"set4_ce_arg", # 0x113
|
||||
None, # 0x114
|
||||
None, # 0x115
|
||||
None, # 0x116
|
||||
None, # 0x117
|
||||
None, # 0x118
|
||||
None, # 0x119
|
||||
None, # 0x11A
|
||||
None, # 0x11B
|
||||
"set_weather_disp", # 0x11C
|
||||
None, # 0x11D
|
||||
None, # 0x11E
|
||||
"set_keyframe", # 0x11F
|
||||
"set_keyframe_arg", # 0x120
|
||||
"delete_keyframe", # 0x121
|
||||
None, # 0x122
|
||||
None, # 0x123
|
||||
None, # 0x124
|
||||
None, # 0x125
|
||||
None, # 0x126
|
||||
"debug_bp", # 0x127
|
||||
None, # 0x128
|
||||
None, # 0x129
|
||||
None, # 0x12A
|
||||
None, # 0x12B
|
||||
None, # 0x12C
|
||||
None, # 0x12D
|
||||
None, # 0x12E
|
||||
None, # 0x12F
|
||||
None, # 0x130
|
||||
None, # 0x131
|
||||
None, # 0x132
|
||||
None, # 0x133
|
||||
None, # 0x134
|
||||
None, # 0x135
|
||||
None, # 0x136
|
||||
None, # 0x137
|
||||
None, # 0x138
|
||||
None, # 0x139
|
||||
None, # 0x13A
|
||||
None, # 0x13B
|
||||
None, # 0x13C
|
||||
None, # 0x13D
|
||||
None, # 0x13E
|
||||
None, # 0x13F
|
||||
None, # 0x140
|
||||
None, # 0x141
|
||||
# PSP only
|
||||
None, # 0x142
|
||||
None, # 0x143
|
||||
None, # 0x144
|
||||
None, # 0x145
|
||||
None, # 0x146
|
||||
None, # 0x147
|
||||
None, # 0x148
|
||||
None, # 0x149
|
||||
None, # 0x14A
|
||||
None, # 0x14B
|
||||
None, # 0x14C
|
||||
None, # 0x14D
|
||||
None, # 0x14E
|
||||
None, # 0x14F
|
||||
None, # 0x150
|
||||
None, # 0x151
|
||||
None, # 0x152
|
||||
None, # 0x153
|
||||
)
|
||||
|
||||
SYSCALL_ARGUMENT_COUNT = (
|
||||
0x02,0x02,0x01,0x01,0x00,0x06,0x07,0x03,0x04,0x01,0x03,0x05,0x02,0x03,0x04,
|
||||
0x05,0x04,0x05,0x01,0x02,0x06,0x00,0x00,0x00,0x06,0x06,0x07,0x08,0x0A,0x0B,
|
||||
0x0C,0x08,0x08,0x09,0x0A,0x0E,0x0F,0x10,0x02,0x01,0x01,0x06,0x06,0x0A,0x08,
|
||||
0x08,0x0E,0x01,0x01,0x06,0x06,0x06,0x08,0x08,0x08,0x01,0x01,0x05,0x06,0x01,
|
||||
0x01,0x02,0x03,0x04,0x00,0x03,0x06,0x02,0x03,0x08,0x01,0x04,0x02,0x01,0x01,
|
||||
0x01,0x00,0x01,0x01,0x07,0x05,0x01,0x02,0x00,0x00,0x00,0x00,0x02,0x03,0x01,
|
||||
0x01,0x01,0x04,0x00,0x02,0x01,0x02,0x01,0x02,0x01,0x01,0x07,0x07,0x0B,0x09,
|
||||
0x09,0x0F,0x01,0x01,0x01,0x01,0x01,0x03,0x04,0x03,0x05,0x03,0x07,0x08,0x03,
|
||||
0x04,0x07,0x01,0x03,0x02,0x00,0x00,0x02,0x02,0x04,0x09,0x04,0x01,0x01,0x02,
|
||||
0x02,0x05,0x01,0x02,0x02,0x03,0x03,0x02,0x00,0x02,0x01,0x01,0x03,0x03,0x02,
|
||||
0x01,0x01,0x01,0x09,0x00,0x01,0x00,0x01,0x0F,0x10,0x04,0x01,0x04,0x02,0x02,
|
||||
0x01,0x01,0x01,0x01,0x08,0x01,0x01,0x02,0x03,0x01,0x01,0x01,0x04,0x01,0x06,
|
||||
0x08,0x03,0x03,0x00,0x04,0x02,0x00,0x00,0x03,0x00,0x02,0x04,0x03,0x02,0x02,
|
||||
0x01,0x02,0x03,0x01,0x06,0x06,0x0A,0x08,0x08,0x0E,0x04,0x02,0x06,0x08,0x00,
|
||||
0x00,0x05,0x03,0x09,0x00,0x03,0x02,0x04,0x06,0x03,0x02,0x04,0x00,0x01,0x04,
|
||||
0x01,0x03,0x01,0x02,0x05,0x02,0x00,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,
|
||||
0x01,0x00,0x00,0x08,0x01,0x00,0x04,0x04,0x07,0x08,0x02,0x01,0x00,0x00,0x02,
|
||||
0x06,0x06,0x0A,0x0A,0x01,0x09,0x01,0x04,0x02,0x00,0x01,0x00,0x01,0x07,0x00,
|
||||
0x01,0x02,0x09,0x04,0x05,0x08,0x01,0x00,0x01,0x03,0x03,0x0A,0x03,0x01,0x01,
|
||||
0x02,0x01,0x09,0x02,0x03,0x01,0x02,0x01,0x06,0x03,0x01,0x00,0x05,0x01,0x00,
|
||||
0x01,0x01,0x02,0x00,0x01,0x04,0x01,0x01,0x01,0x08,0x01,0x01,0x02,0x00,0x04,
|
||||
0x06,0x00,0x00,0x05,0x02,0x02,0x01,
|
||||
# PSP
|
||||
0x00,0x01,0x01,0x00,0x00,0x01,0x01,0x01,0x01,0x02,0x01,0x02,0x02,0x01,0x01,
|
||||
0x01,0x01,0x04
|
||||
)
|
||||
193
pythonlib/formats/theirsce_instructions.py
Normal file
193
pythonlib/formats/theirsce_instructions.py
Normal file
@@ -0,0 +1,193 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
class VariableType(Enum):
|
||||
BIT = 0
|
||||
BYTE = 1
|
||||
SHORT = 2
|
||||
INT = 3
|
||||
PTR = 4
|
||||
|
||||
|
||||
class ReferenceScope(Enum):
|
||||
LOCAL = 0
|
||||
FILE = 1
|
||||
GLOBAL = 2
|
||||
|
||||
|
||||
class BranchType(Enum):
|
||||
UNCONDITIONAL = 0
|
||||
NOT_EQUALS = 1
|
||||
EQUALS = 2
|
||||
|
||||
|
||||
class InstructionType(Enum):
|
||||
INVALID = -1
|
||||
ALU = 0
|
||||
PUSH = 1
|
||||
SYSCALL = 2
|
||||
BRANCH = 3
|
||||
LOCAL_CALL = 4
|
||||
RETURN = 5
|
||||
ACQUIRE = 6
|
||||
BREAK = 7
|
||||
STRING = 8
|
||||
REFERENCE = 9
|
||||
SP_REF = 10
|
||||
|
||||
|
||||
class AluOperation(Enum):
|
||||
# UNARY
|
||||
SWITCH_POP = 0
|
||||
POST_INCREMENT = 1
|
||||
POST_DECREMENT = 2
|
||||
PRE_INCREMENT = 3
|
||||
PRE_DECREMENT = 4
|
||||
ARITHMETIC_NEGATION = 5
|
||||
BITWISE_NOT = 6
|
||||
LOGICAL_NOT = 7
|
||||
|
||||
# BINARY
|
||||
INDEX_ARRAY = 8
|
||||
SUBSCRIPT = 8
|
||||
MULTIPLICATION = 9
|
||||
DIVISION = 10
|
||||
MODULO = 11
|
||||
ADDITION = 12
|
||||
SUBTRACTION = 13
|
||||
BITWISE_LEFT_SHIFT = 14
|
||||
BITWISE_RIGHT_SHIFT = 15
|
||||
GREATER_THAN = 16
|
||||
LESS_THAN = 17
|
||||
GREATER_THAN_OR_EQUALS = 18
|
||||
LESS_THAN_OR_EQUALS = 19
|
||||
EQUALS = 20
|
||||
NOT_EQUALS = 21
|
||||
BITWISE_AND = 22
|
||||
BITWISE_XOR = 23
|
||||
BITWISE_OR = 24
|
||||
LOGICAL_AND = 25
|
||||
LOGICAL_OR = 26
|
||||
ASSIGNMENT = 27
|
||||
MULTIPLICATION_ASSIGNMENT = 28
|
||||
DIVISION_ASSIGNMENT = 29
|
||||
MODULO_ASSIGNMENT = 30
|
||||
ADDITION_ASSIGNMENT = 31
|
||||
SUBTRACTION_ASSIGNMENT = 32
|
||||
BITWISE_LEFT_SHIFT_ASSIGNMENT = 33
|
||||
BITWISE_RIGHT_SHIFT_ASSIGNMENT = 34
|
||||
BITWISE_AND_ASSIGNMENT = 35
|
||||
BITWISE_XOR_ASSIGNMENT = 36
|
||||
BITWISE_OR_ASSIGNMENT = 37
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceBaseInstruction:
|
||||
mnemonic: str = field(default=False, init=False)
|
||||
type: InstructionType = field(default=False, init=False)
|
||||
#size: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceAluInstruction(TheirsceBaseInstruction):
|
||||
operation: AluOperation
|
||||
position: int
|
||||
mnemonic = "ALU"
|
||||
type = InstructionType.ALU
|
||||
|
||||
def __post_init__(self):
|
||||
self.mnemonic = self.operation.name
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirscePushInstruction(TheirsceBaseInstruction):
|
||||
value: int
|
||||
position: int
|
||||
mnemonic = "PUSH"
|
||||
type = InstructionType.PUSH
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceSyscallInstruction(TheirsceBaseInstruction):
|
||||
function_index: int
|
||||
function_name: str
|
||||
position: int
|
||||
mnemonic = "SYSCALL"
|
||||
type = InstructionType.SYSCALL
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceLocalCallInstruction(TheirsceBaseInstruction):
|
||||
destination: int
|
||||
reserve: int
|
||||
position: int
|
||||
mnemonic = "CALL"
|
||||
type = InstructionType.LOCAL_CALL
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceAcquireInstruction(TheirsceBaseInstruction):
|
||||
params: list[int]
|
||||
variables: int
|
||||
position: int
|
||||
mnemonic = "ACQUIRE"
|
||||
type = InstructionType.ACQUIRE
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceBreakInstruction(TheirsceBaseInstruction):
|
||||
param: int
|
||||
position: int
|
||||
mnemonic = "BREAK"
|
||||
type = InstructionType.BREAK
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceBranchInstruction(TheirsceBaseInstruction):
|
||||
destination: int
|
||||
branch_type: BranchType
|
||||
position: int
|
||||
mnemonic = ""
|
||||
type = InstructionType.BRANCH
|
||||
|
||||
def __post_init__(self):
|
||||
self.mnemonic = self.branch_type.name
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceReturnInstruction(TheirsceBaseInstruction):
|
||||
is_void: bool
|
||||
position: int
|
||||
mnemonic = "RETURN"
|
||||
type = InstructionType.RETURN
|
||||
|
||||
def __post_init__(self):
|
||||
if self.is_void:
|
||||
self.mnemonic += "_VOID"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceStringInstruction(TheirsceBaseInstruction):
|
||||
text: str
|
||||
offset: int
|
||||
position: int
|
||||
mnemonic = "STRING"
|
||||
type = InstructionType.STRING
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceReferenceInstruction(TheirsceBaseInstruction):
|
||||
ref_type: VariableType
|
||||
scope: ReferenceScope
|
||||
offset: int
|
||||
shift: int
|
||||
position: int
|
||||
mnemonic = "REF"
|
||||
type = InstructionType.REFERENCE
|
||||
|
||||
|
||||
@dataclass
|
||||
class TheirsceSpecialReferenceInstruction(TheirsceBaseInstruction):
|
||||
position: int
|
||||
mnemonic = "SP_REF"
|
||||
type = InstructionType.SP_REF
|
||||
1111
pythonlib/games/ToolsNDX.py
Normal file
1111
pythonlib/games/ToolsNDX.py
Normal file
File diff suppressed because it is too large
Load Diff
10
pythonlib/games/ToolsTODDC.py
Normal file
10
pythonlib/games/ToolsTODDC.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from .ToolsTales import ToolsTales
|
||||
|
||||
class ToolsTODDC(ToolsTales):
|
||||
|
||||
|
||||
def __init__(self, tbl):
|
||||
|
||||
super().__init__("TODDC", tbl, "Tales-of-Destiny-DC")
|
||||
|
||||
|
||||
839
pythonlib/games/ToolsTOR.py
Normal file
839
pythonlib/games/ToolsTOR.py
Normal file
File diff suppressed because it is too large
Load Diff
1114
pythonlib/games/ToolsTales.py
Normal file
1114
pythonlib/games/ToolsTales.py
Normal file
File diff suppressed because it is too large
Load Diff
0
pythonlib/games/__init__.py
Normal file
0
pythonlib/games/__init__.py
Normal file
0
pythonlib/utils/__init__.py
Normal file
0
pythonlib/utils/__init__.py
Normal file
BIN
pythonlib/utils/comptolib.dll
Normal file
BIN
pythonlib/utils/comptolib.dll
Normal file
Binary file not shown.
168
pythonlib/utils/comptolib.py
Normal file
168
pythonlib/utils/comptolib.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import ctypes
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
# Error codes
|
||||
SUCCESS = 0
|
||||
ERROR_FILE_IN = -1
|
||||
ERROR_FILE_OUT = -2
|
||||
ERROR_MALLOC = -3
|
||||
ERROR_BAD_INPUT = -4
|
||||
ERROR_UNKNOWN_VERSION = -5
|
||||
ERROR_FILES_MISMATCH = -6
|
||||
|
||||
|
||||
class ComptoFileInputError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ComptoFileOutputError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ComptoMemoryAllocationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ComptoBadInputError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ComptoUnknownVersionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ComptoMismatchedFilesError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ComptoUnknownError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def RaiseError(error: int):
|
||||
if error == SUCCESS:
|
||||
return
|
||||
elif error == ERROR_FILE_IN:
|
||||
raise ComptoFileInputError("Error with input file")
|
||||
elif error == ERROR_FILE_OUT:
|
||||
raise ComptoFileOutputError("Error with output file")
|
||||
elif error == ERROR_MALLOC:
|
||||
raise ComptoMemoryAllocationError("Malloc failure")
|
||||
elif error == ERROR_BAD_INPUT:
|
||||
raise ComptoBadInputError("Bad Input")
|
||||
elif error == ERROR_UNKNOWN_VERSION:
|
||||
raise ComptoUnknownVersionError("Unknown version")
|
||||
elif error == ERROR_FILES_MISMATCH:
|
||||
raise ComptoMismatchedFilesError("Mismatch")
|
||||
else:
|
||||
raise ComptoUnknownError("Unknown error")
|
||||
|
||||
|
||||
comptolib_path = Path(__file__).parent / "comptolib.dll"
|
||||
|
||||
comptolib = ctypes.cdll.LoadLibrary(str(comptolib_path))
|
||||
compto_decode = comptolib.Decode
|
||||
compto_decode.argtypes = (
|
||||
ctypes.c_int,
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(ctypes.c_uint),
|
||||
)
|
||||
compto_decode.restype = ctypes.c_int
|
||||
|
||||
compto_encode = comptolib.Encode
|
||||
compto_encode.argtypes = (
|
||||
ctypes.c_int,
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(ctypes.c_uint),
|
||||
)
|
||||
compto_encode.restype = ctypes.c_int
|
||||
|
||||
compto_fdecode = comptolib.DecodeFile
|
||||
compto_fdecode.argtypes = ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int
|
||||
compto_fdecode.restype = ctypes.c_int
|
||||
|
||||
compto_fencode = comptolib.EncodeFile
|
||||
compto_fencode.argtypes = ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int
|
||||
compto_fencode.restype = ctypes.c_int
|
||||
|
||||
|
||||
class ComptoFile:
|
||||
def __init__(self, type: int, data: bytes) -> None:
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.data[item]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
|
||||
def compress_data(input: bytes, raw: bool = False, version: int = 3) -> bytes:
|
||||
input_size = len(input)
|
||||
output_size = ((input_size * 9) // 8) + 10
|
||||
output = b"\x00" * output_size
|
||||
output_size = ctypes.c_uint(output_size)
|
||||
error = compto_encode(version, input, input_size, output, ctypes.byref(output_size))
|
||||
RaiseError(error)
|
||||
|
||||
if not raw:
|
||||
output = (
|
||||
struct.pack("<b", version)
|
||||
+ struct.pack("<L", output_size.value)
|
||||
+ struct.pack("<L", input_size)
|
||||
+ output[: output_size.value]
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def decompress_data(input: bytes, raw: bool = False, version: int = 3) -> bytes:
|
||||
if raw:
|
||||
input_size = len(input)
|
||||
output_size = input_size * 10
|
||||
else:
|
||||
version = struct.unpack("<b", input[:1])[0]
|
||||
input_size, output_size = struct.unpack("<2L", input[1:9])
|
||||
|
||||
output = b"\x00" * output_size
|
||||
input = input[9:]
|
||||
|
||||
error = compto_decode(
|
||||
version, input, input_size, output, ctypes.byref(ctypes.c_uint(output_size))
|
||||
)
|
||||
RaiseError(error)
|
||||
return output
|
||||
|
||||
|
||||
def compress_file(input: str, output: str, raw: bool = False, version: int = 3) -> None:
|
||||
error = compto_fencode(input.encode("utf-8"), output.encode("utf-8"), raw, version)
|
||||
RaiseError(error)
|
||||
|
||||
|
||||
def decompress_file(
|
||||
input: str, output: str, raw: bool = False, version: int = 3
|
||||
) -> None:
|
||||
error = compto_fdecode(input.encode("utf-8"), output.encode("utf-8"), raw, version)
|
||||
RaiseError(error)
|
||||
|
||||
|
||||
def is_compressed(data: bytes) -> bool:
|
||||
if len(data) < 0x09:
|
||||
return False
|
||||
|
||||
expected_size = struct.unpack("<L", data[1:5])[0]
|
||||
tail_data = abs(len(data) - (expected_size + 9))
|
||||
|
||||
if expected_size == len(data) - 9:
|
||||
return True
|
||||
|
||||
if tail_data <= 0x10 and data[expected_size + 9 :] == b"#" * tail_data:
|
||||
return True # SCPK files have these trailing "#" bytes :(
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user