from .ToolsTales import ToolsTales import subprocess # from dicttoxml import dicttoxml import json import struct import shutil import os import re import io import pandas as pd import xml.etree.ElementTree as ET import glob import lxml.etree as etree from xml.dom import minidom from pathlib import Path class ToolsNDX(ToolsTales): def __init__(self, tbl): super().__init__("NDX", tbl, "Narikiri-Dungeon-X") with open("../{}/Data/{}/Misc/{}".format(self.repo_name, self.gameName, self.tblFile), encoding="utf-8") as f: self.jsonTblTags = json.load(f) self.jsonTblTags["TBL"] = { int(k):v for k,v in self.jsonTblTags["TBL"].items()} self.jsonTblTags["COLOR"] = { int(k):v for k,v in self.jsonTblTags["COLOR"].items()} keys = [int(ele, 16) for ele in self.jsonTblTags["TAGS"].keys()] self.jsonTblTags["TAGS"] = dict(zip(keys, list(self.jsonTblTags["TAGS"].values()))) self.itable = dict([[i, struct.pack(">H", int(j))] for j, i in self.jsonTblTags['TBL'].items()]) self.itags = dict([[i, j] for j, i in self.jsonTblTags['TAGS'].items()]) if "COLOR" in self.jsonTblTags.keys(): self.icolors = dict([[i, j] for j, i in self.jsonTblTags['COLOR'].items()]) self.id = 1 self.struct_id = 1 self.eboot_name = 'EBOOT.BIN' #Load the hash table for the files json_file = open('../{}/Data/{}/Misc/hashes.json'.format(self.repo_name, self.gameName), 'r') self.hashes = json.load(json_file) json_file.close() self.repo_name = 'Narikiri-Dungeon-X' self.misc = '../Data/{}/Misc'.format(self.repo_name) self.disc_path = '../Data/{}/Disc'.format(self.repo_name) self.story_XML_extract = '../Data/{}/Story/'.format(self.repo_name) #Files are the result of PAKCOMPOSER + Comptoe here self.story_XML_new = '../{}/Data/NDX/Story/XML'.format(self.repo_name) self.skit_extract = '../Data/{}/Skit/'.format(self.repo_name) #Files are the result of PAKCOMPOSER + Comptoe here self.elf_new = '../Data/{}/Disc/New/PSP_GAME/SYSDIR/EBOOT.bin'.format(self.repo_name) self.all_extract = '../Data/{}/All/'.format(self.repo_name) self.all_original = '../Data/{}/Disc/Original/PSP_GAME/USRDIR/all.dat'.format(self.repo_name) self.all_new = '../Data/{}/Disc/New/PSP_GAME/USRDIR/all.dat'.format(self.repo_name) #File is all.dat self.story_struct_byte_code = b'\x18\x00\x0C\x04' self.story_string_byte_code = b'\x00\x00\x82\x02' self.make_dirs() ############################# # # Extraction of files and unpacking # ############################# # Make the basic directories for extracting all.dat def make_dirs(self): self.mkdir('../Data') self.mkdir('../Data/{}'.format(self.repo_name)) self.mkdir('../Data/{}/Disc'.format(self.repo_name)) self.mkdir('../Data/{}/Disc/Original'.format(self.repo_name)) self.mkdir('../Data/{}/Disc/New'.format(self.repo_name)) self.mkdir('../Data/{}/Misc'.format(self.repo_name)) self.mkdir('../Data/{}/All'.format(self.repo_name)) self.mkdir('../Data/{}/Story'.format(self.repo_name)) self.mkdir('../Data/{}/Story/New'.format(self.repo_name)) self.mkdir('../Data/{}/Story/XML'.format(self.repo_name)) self.mkdir('../Data/{}/Events'.format(self.repo_name)) self.mkdir('../Data/{}/Events/New'.format(self.repo_name)) self.mkdir('../Data/{}/Events/XML'.format(self.repo_name)) self.mkdir('../Data/{}/Menu'.format(self.repo_name)) self.mkdir('../Data/{}/Menu/New'.format(self.repo_name)) self.mkdir('../Data/{}/Menu/XML'.format(self.repo_name)) self.mkdir('../Data/{}/Skits'.format(self.repo_name)) self.mkdir('../Data/{}/Skits/New'.format(self.repo_name)) self.mkdir('../Data/{}/Skits/XML'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/character'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/charsnd'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/data'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/effect'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/event'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/gui'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/map'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/resident'.format(self.repo_name)) self.mkdir('../Data/{}/All/battle/tutorial'.format(self.repo_name)) self.mkdir('../Data/{}/All/chat'.format(self.repo_name)) self.mkdir('../Data/{}/All/gim'.format(self.repo_name)) self.mkdir('../Data/{}/All/map'.format(self.repo_name)) self.mkdir('../Data/{}/All/map/data'.format(self.repo_name)) self.mkdir('../Data/{}/All/map/pack'.format(self.repo_name)) self.mkdir('../Data/{}/All/movie'.format(self.repo_name)) self.mkdir('../Data/{}/All/snd'.format(self.repo_name)) self.mkdir('../Data/{}/All/snd/init'.format(self.repo_name)) self.mkdir('../Data/{}/All/snd/se3'.format(self.repo_name)) self.mkdir('../Data/{}/All/snd/se3/map_mus'.format(self.repo_name)) self.mkdir('../Data/{}/All/snd/strpck'.format(self.repo_name)) self.mkdir('../Data/{}/All/sysdata'.format(self.repo_name)) # Extract the story files def extract_All_Story(self, replace=False, extract_XML = True): print("Extracting Story") path = os.path.join( self.all_extract, 'map/pack/') for f in os.listdir( path ): if os.path.isfile( path+f) and '.cab' in f: if extract_XML: self.extract_CAB_File(path,f, '../Data/{}/Story'.format(self.repo_name)) else: self.extract_CAB_File(path,f) def extract_CAB_File(self, path, f, xml_path=None): #Unpack the CAB into PAK3 file path_new = '{}/New'.format(xml_path) shutil.copy( path+f,os.path.join(path_new, f)) self.extract_Cab(f, f.replace(".cab", ".pak3"), path_new) #Decompress using PAKCOMPOSER + Comptoe self.pakComposer_Comptoe(f.replace(".cab", ".dat"), "-d", "-3",0, os.path.join( path_new, f.replace(".cab","")) ) if xml_path != None: #Extract from XML self.extract_TSS_File(os.path.join(path_new, f.replace(".cab", ""), f.replace(".cab", "")), xml_path) # Extract one single CAB file to the XML format def extract_TSS_File(self, pak3_folder, xml_path): self.id = 1 self.speaker_id = 1 self.struct_id = 1 if os.path.exists(pak3_folder): #1) Grab TSS file from PAK3 folder tss, file_tss = self.get_tss_from_pak3( pak3_folder) #2) Extract TSS to XML self.extract_tss_XML(tss, pak3_folder, xml_path) def get_tss_from_pak3(self, pak3_folder): if os.path.isdir(pak3_folder): folder_name = os.path.basename(pak3_folder) file_list = [os.path.dirname(pak3_folder) + "/" + folder_name + "/" + ele for ele in os.listdir(pak3_folder)] for file in file_list: with open(file, "rb") as f: data = f.read() if data[0:3] == b'TSS': print("... Extract TSS for file {} of size: {}".format(folder_name, len(data))) return io.BytesIO(data), file def extract_All_Event(self): print("Extracting Events") events_files = [file for file in os.listdir("../Data/{}/All/map".format(self.repo_name)) if file.endswith(".bin")] for event_file in events_files: self.extract_Event_File(event_file) #Extract Field file def extract_Field(self): with open('../Data/{}/Events/New/map/field/field.dat'.format(self.repo_name), "rb") as f: data = f.read() tss_offset = struct.unpack("") [self.create_Entry(root.find("Strings"), pointer_offset, jap,1, "Struct", struct_speaker_id, unknown_pointer) for jap in jap_split_bubble] self.struct_id += 1 return speaker_offset def add_Speaker_Entry(self, root, pointer_offset, japText): speaker_entries = [entry for entry in root.iter("Entry") if entry != None and entry.find("JapaneseText").text == japText] struct_speaker_id = 0 if len(speaker_entries) > 0: #Speaker already exist speaker_entries[0].find("PointerOffset").text = speaker_entries[0].find("PointerOffset").text + ",{}".format(pointer_offset) struct_speaker_id = speaker_entries[0].find("Id").text else: #Need to create that new speaker entry_node = etree.SubElement(root, "Entry") etree.SubElement(entry_node,"PointerOffset").text = str(pointer_offset) etree.SubElement(entry_node,"JapaneseText").text = str(japText) etree.SubElement(entry_node,"EnglishText") etree.SubElement(entry_node,"Notes") etree.SubElement(entry_node,"Id").text = str(self.speaker_id) etree.SubElement(entry_node,"Status").text = "To Do" struct_speaker_id = self.speaker_id self.speaker_id += 1 return struct_speaker_id def extract_From_String(self, f, strings_offset, pointer_offset, text_offset, root): f.seek(text_offset, 0) japText = self.bytes_to_text(f, text_offset)[0] self.create_Entry(root, pointer_offset, japText,1, "Other Strings", -1, "") def extract_Story_Pointers(self, f, bytecode, strings_offset, pointer_block_size): read = 0 text_offset = [] pointer_offset = [] while read < pointer_block_size: b = f.read(4) if b == bytecode: pointer_offset.append(f.tell()) text_offset.append(struct.unpack('= text_start and block_pointers_value[i] + base_offset <= text_max): pointers_offset.append(block_pointers_offset[i]) pointers_value.append(block_pointers_value[i]) is_bad_count = 0 elif block_pointers_value[i] != 0: is_bad_count += 1 f.read(step) f.close() #Only grab the good pointers good_indexes = [index for index,ele in enumerate(pointers_value) if ele != 0] pointers_offset = [pointers_offset[i] for i in good_indexes] pointers_value = [pointers_value[i] for i in good_indexes] return [pointers_offset, pointers_value] def get_Direct_Pointers(self, text_start, text_max, base_offset, pointers_list, section,file_path=''): if file_path == '': file_path = '../Data/{}/Misc/{}'.format(self.repo_name, self.eboot_name) f = open(file_path , "rb") pointers_offset = [] pointers_value = [] for pointer in pointers_list: f.seek(pointer, 0) value = struct.unpack("= text_start) and (value + base_offset <= text_max)): pointers_offset.append(pointer) pointers_value.append(value) f.close() #Only grab the good pointers good_indexes = [index for index,ele in enumerate(pointers_value) if ele != 0] pointers_offset = [pointers_offset[i] for i in good_indexes] pointers_value = [pointers_value[i] for i in good_indexes] return [pointers_offset, pointers_value] def create_Entry(self, strings_node, pointer_offset, text, to_translate, entry_type, speaker_id, unknown_pointer): #Add it to the XML node entry_node = etree.SubElement(strings_node, "Entry") etree.SubElement(entry_node,"PointerOffset").text = str(pointer_offset) text_split = re.split(self.COMMON_TAG, text) if len(text_split) > 1 and any(possible_value in text for possible_value in self.VALID_VOICEID): etree.SubElement(entry_node,"VoiceId").text = text_split[1] etree.SubElement(entry_node, "JapaneseText").text = ''.join(text_split[2:]) else: etree.SubElement(entry_node, "JapaneseText").text = text eng_text = '' etree.SubElement(entry_node,"EnglishText") etree.SubElement(entry_node,"Notes") etree.SubElement(entry_node,"Id").text = str(self.id) statusText = "To Do" if entry_type == "Struct": etree.SubElement(entry_node,"StructId").text = str(self.struct_id) etree.SubElement(entry_node,"SpeakerId").text = str(speaker_id) etree.SubElement(entry_node,"UnknownPointer").text = str(unknown_pointer) etree.SubElement(entry_node,"Status").text = statusText self.id += 1 def create_Node_XML(self, fileName, list_informations, parent): root = etree.Element(parent) sections = list(set([s for s, pointers_offset, text, to_translate in list_informations])) for section in sections: strings_node = etree.SubElement(root, 'Strings') etree.SubElement(strings_node, 'Section').text = section list_informations_filtered = [(s, pointers_offset, text, to_translate) for s, pointers_offset, text, to_translate in list_informations if s == section] for s, pointers_offset, text, to_translate in list_informations_filtered: self.create_Entry( strings_node, pointers_offset, text, to_translate, "Menu", -1, -1) return root def get_Starting_Offset(self, root, tss, base_offset): #String Pointers strings_pointers = [int(ele.find("PointerOffset").text) for ele in root.findall('Strings[Section="Other Strings"]/Entry')] strings_offset = [] structs_offset = [] for pointer_offset in strings_pointers: tss.seek(pointer_offset) strings_offset.append( struct.unpack("',')') tss.write(b'\x09') tss.write( self.text_to_bytes(voice_final)) bytes_text = self.get_node_bytes(struct_node) tss.write(bytes_text) tss.write(b'\x0C') tss.seek(tss.tell()-1) tss.write(b'\x00\x00\x00') #Construct Struct struct_dict[ int(struct_node.find("PointerOffset").text)] = struct.pack( " 0): fileRead.seek(offset, 0) pos = fileRead.tell() b = fileRead.read(1) while b != end_strings: #print(hex(fileRead.tell())) b = ord(b) #Normal character if (b >= 0x80 and b <= 0x9F) or (b >= 0xE0 and b <= 0xEA): c = (b << 8) + ord(fileRead.read(1)) try: final_text += self.jsonTblTags['TBL'][c] except KeyError: b_u = (c >> 8) & 0xff b_l = c & 0xff final_text += ("{%02X}" % b_u) final_text += ("{%02X}" % b_l) #Line break elif b == 0x0A: final_text += ("\n") elif b == 0x0C: final_text += "" #Find a possible Color elif b == 0x1: offset_temp = fileRead.tell() b2 = struct.unpack("') final_text += val elif chr(b) in self.PRINTABLE_CHARS: final_text += chr(b) elif b >= 0xA1 and b < 0xE0: final_text += struct.pack("B", b).decode("cp932") b = fileRead.read(1) return final_text, pos def text_to_bytes(self, text): splitLineBreak = text.split('\x0A') nb = len(splitLineBreak) bytesFinal = b'' i=0 for line in splitLineBreak: string_hex = re.split(self.HEX_TAG, line) string_hex = [sh for sh in string_hex if sh] #print(string_hex) for s in string_hex: if re.match(self.HEX_TAG, s): bytesFinal += struct.pack("B", int(s[1:3], 16)) else: s_com = re.split(self.COMMON_TAG, s) s_com = [sc for sc in s_com if sc] for c in s_com: if re.match(self.COMMON_TAG, c): if ":" in c: split = c.split(":") tag = split[0][1:] if tag == "icon": bytesFinal += struct.pack("B", 0xB) bytesFinal += int(split[1][:-1]).to_bytes(3, 'little') elif tag == "color": bytesFinal += struct.pack("B", 0x1) bytesFinal += struct.pack("B", int(split[1][:-1], 16)) else: bytesFinal += struct.pack("B", int(split[0][1:], 16)) bytesFinal += struct.pack("",")") bytesFinal += c.encode("cp932") if "VSM" in c: bytesFinal += struct.pack("B", 0x9) c = c.replace("<","(").replace(">",")") bytesFinal += c.encode("cp932") if c in self.icolors: bytesFinal += struct.pack("B", 0x1) bytesFinal += struct.pack("B", self.icolors[c]) else: for c2 in c: if c2 in self.itable.keys(): bytesFinal += self.itable[c2] else: bytesFinal += c2.encode("cp932") i=i+1 if (nb >=2 and i 0: copy_path = os.path.join("../Data/{}/Menu/New/{}".format(self.repo_name, final_name)) Path(os.path.dirname(copy_path)).mkdir(parents=True, exist_ok=True) shutil.copy( os.path.join(self.all_extract, final_name), copy_path) order['order'].append(hash_) json.dump(order, order_json, indent = 4) order_json.close() def pack_Main_Archive(self, debug_room=True): addrs = [] sizes = [] buffer = 0 print("Updating all.dat archive") with open( os.path.join( self.misc, 'order.json'), 'r') as order_file: order_hash = json.load(order_file) elf = open( self.elf_new, 'r+b') elf.seek(0x1FF624) #Menu files to reinsert menu_files = [ele['Hashes_Name'] for ele in self.menu_files_json if ele['Hashes_Name'] != ''] with open(self.all_new , 'wb') as all_file: for name in order_hash['order']: if name in self.hashes.keys(): name = self.hashes[name] data = b'' new_path = '' #Menu files to repack if os.path.dirname(name) in menu_files: new_path = '../Data/{}/Menu/New/{}'.format(self.repo_name, name) #Story files to repack elif os.path.basename(name).startswith("ep_"): #print(name) new_path = '../Data/{}/Story/New/{}'.format(self.repo_name, os.path.basename(name)) #Events files to repack elif os.path.basename(name).startswith("ep_"): #print(name) new_path = '../Data/{}/Events/New/{}'.format(self.repo_name, os.path.basename(name)) else: new_path = os.path.join( self.all_extract, name) with open( new_path, 'rb') as final_f: data = final_f.read() size = len(data) sizes.append(size) remainder = 0x800 - (size % 0x800) if remainder == 0x800: remainder = 0 addrs.append(buffer) buffer += size + remainder all_file.write(data) all_file.write(b'\x00' * remainder) for i in range(len(sizes)): elf.write(struct.pack('