From 7e4d9f9062abdb02494c628804537c892c5b71e1 Mon Sep 17 00:00:00 2001 From: Mc-muffin <8714476+Mc-muffin@users.noreply.github.com> Date: Fri, 6 Jan 2023 22:45:40 -0500 Subject: [PATCH] Update dump process with speaker nodes --- ToolsTOR.py | 253 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 220 insertions(+), 33 deletions(-) diff --git a/ToolsTOR.py b/ToolsTOR.py index 1bcfafa..6dcb895 100644 --- a/ToolsTOR.py +++ b/ToolsTOR.py @@ -1,3 +1,5 @@ +from dataclasses import dataclass +from itertools import tee from ToolsTales import ToolsTales import subprocess from dicttoxml import dicttoxml @@ -16,7 +18,21 @@ import string import io import pak2 as pak2lib from theirsce import Theirsce -from theirsce_instructions import InstructionType, TheirsceStringInstruction +from theirsce_instructions import AluOperation, InstructionType, ReferenceScope, TheirsceBaseInstruction, TheirsceReferenceInstruction, TheirsceStringInstruction + +@dataclass +class LineEntry: + names: list[str] + text: str + offset: int + +@dataclass +class NameEntry: + index: int + offsets: list[int] + + +VARIABLE_NAME = "[VARIABLE]" class ToolsTOR(ToolsTales): @@ -187,7 +203,7 @@ class ToolsTOR(ToolsTales): print("Writing file %05d..." % (i-1)) # Extract all the skits files - def extract_All_Skits(self, replace): + def extract_All_Skits(self, replace=False): i = 1 os.makedirs( self.skit_XML_patch + "XML", exist_ok=True) list_pak2_files = [ self.dat_archive_extract + "PAK2/" + ele for ele in os.listdir(self.dat_archive_extract + "PAK2")] @@ -241,23 +257,33 @@ class ToolsTOR(ToolsTales): def extract_TheirSce_XML(self, theirsce, file_name, destination, section, replace): #Create the XML file - root = etree.Element('SceneText') - etree.SubElement(root, "OriginalName").text = file_name - stringsNode = etree.SubElement(root, "Strings") + # root = etree.Element('SceneText') + # etree.SubElement(root, "OriginalName").text = file_name rsce = Theirsce(path=theirsce) - pointers_offset, texts_offset = self.extract_Story_Pointers(rsce) - - text_list = [self.bytes_to_text(theirsce, ele)[0] for ele in texts_offset] + #pointers_offset, texts_offset = self.extract_Story_Pointers(rsce) + names, lines = self.extract_lines_with_speaker(rsce) + + for i, (k, v) in enumerate(names.items(), -1): + names[k] = NameEntry(i, v) #Remove duplicates #list_informations = self.remove_duplicates(["Story"] * len(pointers_offset), pointers_offset, text_list) - list_informations = ( ['Story', pointers_offset[i], text_list[i]] for i in range(len(text_list))) + # list_lines = ( ['Story', line.offset, line.text] for line in lines) + # list_names = ( ['Story', line.offset, line.text] for i, (k, v) in enumerate(found_names.items())) #Build the XML Structure with the information file_path = destination +"XML/"+ self.get_file_name(file_name) - root = self.create_Node_XML(file_path, list_informations, section, "SceneText") + + root = etree.Element("SceneText") + speakers_node = etree.SubElement(root, 'Speakers') + etree.SubElement(speakers_node, 'Section').text = "Speakers" + strings_node = etree.SubElement(root, 'Strings') + etree.SubElement(strings_node, 'Section').text = section + + self.make_speakers_section(speakers_node, names) + self.make_strings_section(strings_node, lines, names) #Write the XML file txt=etree.tostring(root, encoding="UTF-8", pretty_print=True) @@ -270,34 +296,194 @@ class ToolsTOR(ToolsTales): '../{}/Data/{}/{}/XML/{}.xml'.format(self.repo_name, self.gameName, section, self.get_file_name(file_name)), '../Data/{}/{}/XML/{}.xml'.format(self.repo_name, section, self.get_file_name(file_name)) ) + + def make_strings_section(self, root, lines: list[LineEntry], names: dict[str, NameEntry]): + pass + for line in lines: + entry_node = etree.SubElement(root, "Entry") + etree.SubElement(entry_node,"PointerOffset").text = str(line.offset) + text_split = list(filter(None, re.split(self.COMMON_TAG, line.text))) + + if len(text_split) > 1 and text_split[0].startswith("= 1: + name = [self.bytes_to_text(theirsce, p.offset + theirsce.strings_offset) for p in params] + [names.setdefault(n, []).append(p.position + 1) for n, p in zip(name, params)] + elif len(params) == 0: + name = [] + text = self.bytes_to_text(theirsce, op2.offset + theirsce.strings_offset) + lines.append(LineEntry(name, text, op2.position + 1)) + #print(f"{params}: {text}") + used = True + skip() + continue + + # This sequence represents the textbox call with + # the text being a variable (Notice boxes do this) + if (op1.type is InstructionType.STRING + and op2.type is InstructionType.REFERENCE + and op3.type is InstructionType.SYSCALL + and op3.function_index == 0x45 + ): + name = [self.bytes_to_text(theirsce, op1.offset + theirsce.strings_offset)] + names.setdefault(name[0], []).append(op1.position + 1) + for param in params: + text = self.bytes_to_text(theirsce, param.offset + theirsce.strings_offset) + lines.append(LineEntry(name, text, param.position + 1)) + #print(f"{text}: {name}") + used = True + params.clear() + skip() + continue + + # This sequence represents a regular textbox call + # where both fields are an string (everything else, save for skits) + if (op1.type is InstructionType.STRING + and op2.type is InstructionType.STRING + and op3.type is InstructionType.SYSCALL + and op3.function_index == 0x45 + ): + name = [self.bytes_to_text(theirsce, op1.offset + theirsce.strings_offset)] + names.setdefault(name[0], []).append(op1.position + 1) + text = self.bytes_to_text(theirsce, op2.offset + theirsce.strings_offset) + lines.append(LineEntry(name, text, op2.position + 1)) + #print(f"{name}: {text}") + skip() + continue + + # Any other string in assorted code calls + if op1.type is InstructionType.STRING: + #print(theirsce.read_string_at(op1.offset + theirsce.strings_offset)) + text = self.bytes_to_text(theirsce, op1.offset + theirsce.strings_offset) + lines.append(LineEntry([], text, op1.position + 1)) + continue + + return names, lines + + + def extract_story_pointers_plain(self, theirsce: Theirsce): pointers_offset = []; texts_offset = [] for opcode in theirsce.walk_code(): - if opcode == self.string_opcode: + if opcode.type == self.string_opcode: pointers_offset.append(theirsce.tell() - 2) # Maybe check this later texts_offset.append(opcode.offset + theirsce.strings_offset) return pointers_offset, texts_offset #Convert a bytes object to text using TAGS and TBL in the json file - def bytes_to_text(self, fileRead, offset=-1, end_strings = b"\x00"): + def bytes_to_text(self, theirsce: Theirsce, offset=-1, end_strings = b"\x00"): finalText = '' TAGS = self.jsonTblTags['TAGS'] if (offset > 0): - fileRead.seek(offset, 0) + theirsce.seek(offset, 0) - pos = fileRead.tell() - b = fileRead.read(1) + pos = theirsce.tell() + b = theirsce.read(1) while b != end_strings: #print(hex(fileRead.tell())) b = ord(b) if (b >= 0x99 and b <= 0x9F) or (b >= 0xE0 and b <= 0xEB): - c = (b << 8) + ord(fileRead.read(1)) + c = (b << 8) + ord(theirsce.read(1)) # if str(c) not in json_data.keys(): # json_data[str(c)] = char_index[decode(c)] @@ -311,7 +497,7 @@ class ToolsTOR(ToolsTales): elif b == 0x1: finalText += ("\n") elif b in (0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xB, 0xC, 0xD, 0xE, 0xF): - b2 = struct.unpack("