diff --git a/Tales_Exe.py b/Tales_Exe.py index 93416cb..7c9f056 100644 --- a/Tales_Exe.py +++ b/Tales_Exe.py @@ -41,10 +41,10 @@ def get_arguments(argv=None): sp_extract.add_argument( "-ft", "--file_type", - choices=["Iso", "Main", "Menu", "Story", "Skits"], + choices=["Iso", "Main", "Menu", "Story", "Minigame", "Skits"], required=True, metavar="file_type", - help="(Required) - Options: Iso, Init, Main, Menu, Story, Skits", + help="(Required) - Options: Iso, Init, Main, Menu, Story, Minigame, Skits", ) sp_extract.add_argument( @@ -80,10 +80,10 @@ def get_arguments(argv=None): sp_insert.add_argument( "-ft", "--file_type", - choices=["Iso", "Main", "Menu", "Story", "Skits", "All", "Asm"], + choices=["Iso", "Main", "Menu", "Story", "Skits", "Minigame", "All", "Asm"], required=True, metavar="file_type", - help="(Required) - Options: Iso, Init, Main, Elf, Story, Skits, All, Asm", + help="(Required) - Options: Iso, Init, Main, Elf, Story, Skits, Minigame, All, Asm", ) sp_insert.add_argument( @@ -169,6 +169,9 @@ if __name__ == "__main__": elif args.file_type == "Story": tales_instance.pack_all_story() + + elif args.file_type == "Minigame": + tales_instance.pack_all_minigame() elif args.file_type == "Iso": tales_instance.make_iso() @@ -186,6 +189,7 @@ if __name__ == "__main__": tales_instance.pack_all_story() tales_instance.pack_all_skits() tales_instance.pack_all_menu() + tales_instance.pack_all_minigame() tales_instance.patch_binaries() tales_instance.make_iso() @@ -204,5 +208,8 @@ if __name__ == "__main__": if args.file_type == "Story": tales_instance.extract_all_story() + if args.file_type == "Minigame": + tales_instance.extract_all_minigame() + if args.file_type == "Skits": tales_instance.extract_all_skits(args.replace) diff --git a/pythonlib/games/ToolsTOR.py b/pythonlib/games/ToolsTOR.py index a88bcb1..3ca1809 100644 --- a/pythonlib/games/ToolsTOR.py +++ b/pythonlib/games/ToolsTOR.py @@ -15,6 +15,7 @@ import pycdlib import pyjson5 as json from dulwich import line_ending, porcelain from tqdm import tqdm +import io import pythonlib.formats.pak2 as pak2lib import pythonlib.utils.comptolib as comptolib @@ -362,6 +363,7 @@ class ToolsTOR(ToolsTales): while True: b = src.read(1) if b == b"\x00": break + if b == b"": break b = ord(b) # Custom Encoded Text @@ -1102,7 +1104,163 @@ class ToolsTOR(ToolsTales): i += 1 yield data + (b"\x00" * remainder), 1 + def get_xml_from_list(self, lines: list[tuple[int, str]], section: str) -> bytes: + + root = etree.Element("SceneText") + strings_node = etree.SubElement(root, 'Strings') + etree.SubElement(strings_node, 'Section').text = section + for line in lines: + entry_node = etree.SubElement(strings_node, "Entry") + etree.SubElement(entry_node,"PointerOffset").text = str(line[0]) + + text_split = list(filter(None, re.split(self.COMMON_TAG, line[1]))) + + if len(text_split) > 1 and text_split[0].startswith(" None: + print("Extracting Minigame files...") + + valid_sfm2 = [ + 1, 17, 30, 31, 34, 36, 37, 38, + 39, 40, 41, 42, 43, 45, 48, 53, 61 + ] + + folder_path = self.paths["translated_files"] / "minigame" + folder_path.mkdir(exist_ok=True) + pak3_path = self.paths["extracted_files"] / "DAT" / "PAK3" / "00023.pak3" + + minigame_pak = Pak.from_path(pak3_path, 3) + for index in tqdm(valid_sfm2): + self.id = 0 + sfm = minigame_pak.files[index].data + + lines = [] + + with FileIO(sfm, "rb") as f: + # special case + if index == 45: + tbl_off = f.read_int32_at(0x20) + f.seek(tbl_off) + offsets = struct.unpack("<14I", f.read(0x38))[1::2] + + for off in offsets: + lines.append((off, self.bytes_to_text(f, tbl_off + off))) # fake offset + + code_off = f.read_int32_at(0x18) + code_end = code_off + f.read_int32_at(0xC) + text_off = f.read_int32_at(0x1C) + + f.seek(code_off) + while f.tell() < code_end: + b = f.read_int8() + len = (b >> 4) & 0xF + opcode = b & 0xF + + # is it the load string opcode? + if opcode == 7: + if f.read_int8() == 2: + off = f.tell() + if len == 3: + str_off = f.read_uint8() + elif len == 4: + str_off = f.read_uint16() + else: + raise ValueError + + save = f.tell() + lines.append((off, self.bytes_to_text(f, str_off + text_off))) + f.seek(save) + else: + f.read(len - 2) + else: + f.read(len - 1) + + + xml_text = self.get_xml_from_list(lines, "Minigame") + + xml_name = f"{index:04d}.xml" + with open(folder_path / xml_name, "wb") as xml: + xml.write(xml_text.replace(b"\n", b"\r\n")) + + + def pack_all_minigame(self): + print("Recreating Minigame files...") + + valid_sfm2 = [ + 1, 17, 30, 31, 34, 36, 37, 38, + 39, 40, 41, 42, 43, 45, 48, 53, 61 + ] + + pak3_path = self.paths["extracted_files"] / "DAT" / "PAK3" / "00023.pak3" + out_path = self.paths["temp_files"] / "DAT" / "SCPK" / "00023" + out_path.mkdir(parents=True, exist_ok=True) + + minigame_pak = Pak.from_path(pak3_path, 3) + for index in tqdm(valid_sfm2): + + sfm = minigame_pak.files[index].data + + with FileIO(sfm, "rb") as f: + new_text_offsets = dict() + root = self.read_xml(folder_path / f"{index:04d}.xml") + text_off = f.read_int32_at(0x1C) + tbl_off = f.read_int32_at(0x20) + 4 + f.seek(text_off) + f.truncate() + + nodes = [ele for ele in root.iter('Entry')] + nodes = [ele for ele in nodes if ele.find('PointerOffset').text != "-1"] + + for entry_node in nodes: + new_text_offsets[entry_node.find("PointerOffset").text] = f.tell() + bytes_entry = self.get_node_bytes(entry_node) + f.write(bytes_entry + b'\x00') + + for i, (pointer, text_offset) in enumerate(new_text_offsets.items()): + new_value = text_offset - text_off + + if index == 45 and i < 7: + f.write_uint32_at(tbl_off + (i * 8), new_value + 0x54) + else: + f.seek(int(pointer) - 2) + len = (f.read_uint8() >> 4) & 0xF + if len == 3: + f.write(struct.pack("