Folder structure

Trying to make it more package-y
This commit is contained in:
Mc-muffin
2023-05-19 08:07:41 -05:00
parent ded5772a25
commit 6e40568d9d
18 changed files with 92 additions and 42 deletions

0
pythonlib/__init__.py Normal file
View File

269
pythonlib/formats/FileIO.py Normal file
View 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)

View File

134
pythonlib/formats/fps4.py Normal file
View 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
View 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
View 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)

View 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)

View 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
)

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

View File

Binary file not shown.

View 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