From f962a9f4e0df9f5de1050556e9beeaa19eea5f58 Mon Sep 17 00:00:00 2001 From: Stewie Date: Sun, 18 Sep 2022 18:31:48 -0400 Subject: [PATCH] Rename py files for NDX --- ToolsNDX.py | 1311 +++++++++++++++++++++++++++++++++++++------------- ToolsTOPX.py | 1048 ---------------------------------------- 2 files changed, 974 insertions(+), 1385 deletions(-) delete mode 100644 ToolsTOPX.py diff --git a/ToolsNDX.py b/ToolsNDX.py index 40c50a2..56469d9 100644 --- a/ToolsNDX.py +++ b/ToolsNDX.py @@ -6,406 +6,1043 @@ 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 -import comptolib from xml.dom import minidom from pathlib import Path -import string -import io -import fps4 - -class ToolsNDX(ToolsTales): - - - POINTERS_BEGIN = 0x1FF624 # Offset to all.dat - POINTERS_END = 0xE60C8 - - - - #Path to used - allDat_Original = '../Data/NDX/Disc/Original/PSP_GAME/USRDIR/all.dat' - allDat_New = '../Data/NDX/Disc/New/all.dat' - elf_Original = '../Data/NDX/MISC/ULJS00293.BIN' - elf_New = '../Data/NDX/Disc/New/ULJS00293.BIN' - story_Path = '../Data/NDX/Story/' - skits_Path = '../Data/NDX/Skits/' - events_Path = '../Data/NDX/Events/' - all_Path_Extract = '../Data/NDX/DAT/' +class ToolsTOPX(ToolsTales): def __init__(self, tbl): - super().__init__("NDX", tbl) + super().__init__("NDX", tbl, "Narikiri-Dungeon-X") - self.struct_byte_code = b'\x18\x00\x0C\x04' - self.strings_byte_code = b'\x00\x00\x82\x02' + 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()} + 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()]) - json_file = open('../Data/NDX/Misc/hashes.json', 'r') + 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 + + + + + #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) - self.extract_names = True + json_file.close() - def make_Dirs(self): + 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_original = '../Data/{}/Misc/EBOOT.bin'.format(self.repo_name) + 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' - base_path = "../Data/NDX/All/" - self.mkdir(base_path) - self.mkdir(base_path+'battle') - self.mkdir(base_path+'battle/character') - self.mkdir(base_path+'battle/charsnd') - self.mkdir(base_path+'battle/data') - self.mkdir(base_path+'battle/effect') - self.mkdir(base_path+'battle/event') - self.mkdir(base_path+'battle/gui') - self.mkdir(base_path+'battle/map') - self.mkdir(base_path+'battle/resident') - self.mkdir(base_path+'battle/tutorial') - self.mkdir(base_path+'chat') - self.mkdir(base_path+'gim') - self.mkdir(base_path+'map') - self.mkdir(base_path+'map/data') - self.mkdir(base_path+'map/pack') - self.mkdir(base_path+'movie') - self.mkdir(base_path+'snd') - self.mkdir(base_path+'snd/init') - self.mkdir(base_path+'snd/se3') - self.mkdir(base_path+'snd/se3/map_mus') - self.mkdir(base_path+'snd/strpck') - self.mkdir(base_path+'sysdata') - - - def bytes_to_text_TOPX(self, fileRead, offset=-1, end_strings = b"\x00"): + self.make_dirs() + ############################# + # + # Extraction of files and unpacking + # + ############################# - finalText = '' + # 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): + + print("Extracting Story") + path = os.path.join( self.all_extract, 'map/pack/') + story_new = "../Data/{}/Story/New".format(self.repo_name) + #self.mkdir(self.story_XML_extract) + #files = glob.glob(story_new+'/*') + #for f in files: + # os.remove(f) + + for f in os.listdir( path ): + if os.path.isfile( path+f) and '.cab' in f: + + #Unpack the CAB into PAK3 file + shutil.copy( path+f,os.path.join(story_new, f)) + self.extract_Cab(f, f.replace(".cab", ".pak3"), story_new) + + #Decompress using PAKCOMPOSER + Comptoe + self.pakComposer_Comptoe(f.replace(".cab", ".dat"), "-d", "-3",0, os.path.join( story_new, f.replace(".cab","")) ) + + #Extract from XML + self.extract_Story_File(os.path.join(story_new, f.replace(".cab", ""), f.replace(".cab", ""))) + + + + + + #super().pakComposerAndComptoe(fileName, "-d", "-3") + + # Extract one single CAB file to the XML format + def extract_Story_File(self, pak3_folder): + + + self.id = 1 + self.speaker_id = 1 + self.struct_id = 1 + + + if os.path.exists(pak3_folder): + #2) Grab TSS file from PAK3 folder + tss, file_tss = self.get_tss_from_pak3( pak3_folder) + + #3) Extract TSS to XML + self.extract_tss_XML(tss, pak3_folder) + + 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) + + def extract_Event_File(self, event_file): + + self.id = 1 + self.speaker_id = 1 + self.struct_id = 1 + + #1) Extract CAB to folder + event_path = '../Data/{}/Events/New/map'.format(self.repo_name) + file_path = os.path.join(event_path, event_file) + self.extract_Cab(event_file, event_file, event_path) + + #2) Grab TSS file from the decompressed CAB file + tss, file_tss = self.get_tss_from_event( os.path.join(event_path, event_file.replace(".bin",""), event_file.replace(".bin",".dat"))) + + #3) Extract TSS to XML + self.extract_tss_XML(tss, event_file, '../Data/{}/Events'.format(self.repo_name)) + return tss + + def get_tss_from_event(self, event_file): + + with open(event_file, "rb") as event_f: + data = event_f.read() + file_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").text = '' + etree.SubElement(entry_node,"Notes").text = '' + 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 = self.elf_original + + 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) + etree.SubElement(entry_node,"JapaneseText").text = str(text) + eng_text = '' + + if to_translate == 0: + statusText = 'Done' + eng_text = str(text) + + etree.SubElement(entry_node,"EnglishText").text = eng_text + etree.SubElement(entry_node,"Notes").text = '' + 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,"UnknownPointer").text = str(unknown_pointer) + + if to_translate == 1: + etree.SubElement(entry_node,"SpeakerId").text = str(speaker_id) + + etree.SubElement(entry_node,"ToTranslate").text = str(to_translate) + 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(" 0): fileRead.seek(offset, 0) pos = fileRead.tell() - b = fileRead.read(2) - while_condition = b'' - while while_condition != b'\x00\x00': + b = fileRead.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): + #Normal character + if (b >= 0x80 and b <= 0x9F) or (b >= 0xE0 and b <= 0xEA): c = (b << 8) + ord(fileRead.read(1)) - # if str(c) not in json_data.keys(): - # json_data[str(c)] = char_index[decode(c)] try: - finalText += (self.jsonTblTags['TBL'][str(c)]) + final_text += self.jsonTblTags['TBL'][c] except KeyError: b_u = (c >> 8) & 0xff b_l = c & 0xff - finalText += ("{%02X}" % b_u) - finalText += ("{%02X}" % b_l) - elif b == 0x1: - finalText += ("\n") - elif b in (0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xB, 0xC, 0xD, 0xE, 0xF): - b2 = struct.unpack("" % (tag_name, b2)) - else: - finalText += "<%02X:%08X>" % (b, b2) - elif chr(b) in self.PRINTABLE_CHARS: - finalText += chr(b) - elif b >= 0xA1 and b < 0xE0: - finalText += struct.pack("B", b).decode("cp932") - elif b in (0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19): - finalText += "{%02X}" % b - next_b = b"" - while next_b != b"\x80": - - next_b = fileRead.read(1) - finalText += "{%02X}" % ord(next_b) - #if next_b != b'': - - #else: - # next_b = b"\x80" - elif b == 0x81: - next_b = fileRead.read(1) - if next_b == b"\x40": - finalText += " " - else: - finalText += "{%02X}" % b - finalText += "{%02X}" % ord(next_b) - else: - finalText += "{%02X}" % b - b = fileRead.read(2) - - - end = fileRead.tell() - size = fileRead.tell() - pos - 1 - fileRead.seek(pos) - hex_string = fileRead.read(size).hex() - hex_values = ' '.join(a+b for a,b in zip(hex_string[::2], hex_string[1::2])) - fileRead.seek(end) - return finalText, hex_values - - - def extract_Files(self, input_file, size, filename): - hash_name = filename - if filename in self.hashes.keys(): - hash_name = self.hashes[filename] - - data = input_file.read(size) - base_path = '../Data/NDX/All/' - if self.extract_names: - try: - output_file = open(base_path + hash_name, 'wb') - except: - output_file = open(base_path + filename, 'wb') - else: - output_file = open(base_path + filename, 'wb') - output_file.write(data) - output_file.close() - - def extract_All_Pak3(self): - - - #Story - story_pak3 = [self.story_Path+"New/"+ele for ele in os.listdir(self.story_Path+"New")] - res = [ self.pakComposer_Comptoe(ele, "-d", "-3", False) for ele in story_pak3] - - def extract_FPS4(self): - - print("potato") - self.fps4_action("-d", "../Data/NDX/All/battle/data/bt_data.b", "../Data/NDX/All/battle/data/bt_data_battle.dat", "../Data/NDX/Menu/New") - - def extract_All_Cab(self): - - menuCab = {} - menuCab['File'] = [] - - event_path_extract = "../Data/NDX/All\map" - skits_path_extract = "../Data/NDX/All\chat" - story_path_extract = "../Data/NDX/All\map\pack" - path_exceptions = [event_path_extract, skits_path_extract, story_path_extract, "../Data/NDX/All"] - - - for path, subdirs, files in os.walk("../Data/NDX/All"): - print(path) - if path not in path_exceptions: - for name in files: - with open(os.path.join(path,name), "rb") as f: - data =f.read() + #Pad the tag to be even number of characters + hex_value = self.hex2(b2) + if len(hex_value) < 4 and tag_name not in ['icon','speed']: + hex_value = "0"*(4-len(hex_value)) + hex_value - if data[:4] == b'MSCF': - file_name = name.split(".")[0] - self.extract_Cab( os.path.join(path, name), "../Data/NDX/Menu/New/{}.bin".format(file_name)) + final_text += '<{}:{}>'.format(tag_name, hex_value) + else: + final_text += "<%02X:%08X>" % (b, b2) + + #Found a name tag + elif b in [0x4, 0x9]: + + + val="" + while fileRead.read(1) != b"\x29": + fileRead.seek(fileRead.tell()-1) + val += fileRead.read(1).decode("cp932") + val += ')' + val = val.replace('(','<').replace(')','>') + + 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] - - #Events - events_files = [file for file in os.listdir(event_path_extract) if ".bin" in file] - print(events_files) - for file in events_files: - new_file_name = self.events_Path + 'New/{}'.format(file) - self.extract_Cab( os.path.join(event_path_extract,file), new_file_name) + for c in s_com: + + if re.match(self.COMMON_TAG, c): + if ":" in c: + split = c.split(":") + + if split[0][1:] in self.itags.keys(): + bytesFinal += struct.pack("B", self.itags[split[0][1:]]) + 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", 0x5) + bytesFinal += struct.pack("=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): + addrs = [] + sizes = [] + buffer = 0 - def extract_Story_Pointers(self, f): - - f.read(12) - baseOffset = struct.unpack(' 0: - # etree.SubElement(structNode, "Type").text = "VoiceId" - #else: - # etree.SubElement(structNode, "Type").text = "NoVoiceId" - - - #addEntry(japText, structNode, listVoiceId) - - - return root, personPointers - - - def buildStaticEntry(self, storyFile, f, stringDict, baseOffset, blockSize, root, personPointers): + def extract_Decripted_Eboot(self): + print("Extracting Eboot") + args = ["deceboot", "../Data/{}/Disc/Original/PSP_GAME/SYSDIR/EBOOT.BIN".format(self.repo_name), "../Data/{}/Misc/EBOOT.BIN".format(self.repo_name)] + listFile = subprocess.run( + args, + cwd= os.getcwd(), + ) - fileSize = os.path.getsize(storyFile) - stringOffset = [value+baseOffset for (key,value) in stringDict.items()] - - - def getSize(ind, ele): - - listRes =[x for x in personPointers if x > ele] - firstGreater = 1000000000000000 - if( len(listRes) > 0): - - firstGreater = listRes[0] - return min(firstGreater, stringOffset[ind+1] ) - ele - - - n = len(stringOffset) - stringSize = [ getSize(ind, ele) if ind < n-1 else fileSize - ele for (ind, ele) in enumerate(stringOffset)] - - i=0 - for (stringOffset, stringValueOffset) in stringDict.items(): - - #print("Static : {}".format(hex( stringValueOffset+baseOffset))) - staticNode = etree.SubElement(root, "Static") - text = extractText(f, stringValueOffset + baseOffset, stringSize[i], tbl) - #print(text) - etree.SubElement(staticNode, "PointerOffset").text = str(stringOffset) - etree.SubElement(staticNode, "TextOffset").text = str(stringValueOffset + baseOffset) - etree.SubElement(staticNode, "JapaneseText").text = text - etree.SubElement(staticNode, "EnglishText").text = "" - etree.SubElement(staticNode, "Notes").text = "" - i=i+1 - return root - - def extract_Story_File(self, file): - - root = etree.Element('SceneText') - etree.SubElement(root, "OriginalName").text = file - - fileName = os.path.splitext(file)[0] - #print(file) - - - #file = r"G:\TalesHacking\TOP_Narikiri\PSP_GAME\USRDIR\all\map\pack\ep_000_010\ep_000_010_0000.unknown" - #file = r"G:\TalesHacking\TOP_Narikiri\Scripts\AMUI00.SCP.decompressed" - with open(file, 'rb') as f: - - print("\nFILE : {}\n".format(file)) - - structDict, stringDict, baseOffset, blockSize = self.extract_Story_Pointers(f) - - fileFolder = os.path.basename(file).replace("_0000.unknown", "") - root, personPointers = self.build_Struct_Entry(file, f,structDict, baseOffset, blockSize, root) - - #Write the static data to the XML - root = self.build_Static_Entry(file, f,stringDict, baseOffset, blockSize, tbl, root, personPointers) - - txt=etree.tostring(root, encoding="UTF-8", pretty_print=True) - - with open(os.path.join( r"G:\TalesHacking\TOP_Narikiri\GithubProject\TranslationApp\TranslationApp\bin\Debug", fileName+".xml"), "wb") as xmlFile: - xmlFile.write(txt) + def pakcomposer(action, file_name, working_directory): + args = [ "pakcomposer", action, os.path.basename(file_name), "-3", "-x", "-u", "-v"] + listFile = subprocess.run( + args, + cwd=working_directory + ) \ No newline at end of file diff --git a/ToolsTOPX.py b/ToolsTOPX.py deleted file mode 100644 index 56469d9..0000000 --- a/ToolsTOPX.py +++ /dev/null @@ -1,1048 +0,0 @@ -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 ToolsTOPX(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()} - 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 - - - - - #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_original = '../Data/{}/Misc/EBOOT.bin'.format(self.repo_name) - 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): - - print("Extracting Story") - path = os.path.join( self.all_extract, 'map/pack/') - story_new = "../Data/{}/Story/New".format(self.repo_name) - #self.mkdir(self.story_XML_extract) - #files = glob.glob(story_new+'/*') - #for f in files: - # os.remove(f) - - for f in os.listdir( path ): - if os.path.isfile( path+f) and '.cab' in f: - - #Unpack the CAB into PAK3 file - shutil.copy( path+f,os.path.join(story_new, f)) - self.extract_Cab(f, f.replace(".cab", ".pak3"), story_new) - - #Decompress using PAKCOMPOSER + Comptoe - self.pakComposer_Comptoe(f.replace(".cab", ".dat"), "-d", "-3",0, os.path.join( story_new, f.replace(".cab","")) ) - - #Extract from XML - self.extract_Story_File(os.path.join(story_new, f.replace(".cab", ""), f.replace(".cab", ""))) - - - - - - #super().pakComposerAndComptoe(fileName, "-d", "-3") - - # Extract one single CAB file to the XML format - def extract_Story_File(self, pak3_folder): - - - self.id = 1 - self.speaker_id = 1 - self.struct_id = 1 - - - if os.path.exists(pak3_folder): - #2) Grab TSS file from PAK3 folder - tss, file_tss = self.get_tss_from_pak3( pak3_folder) - - #3) Extract TSS to XML - self.extract_tss_XML(tss, pak3_folder) - - 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) - - def extract_Event_File(self, event_file): - - self.id = 1 - self.speaker_id = 1 - self.struct_id = 1 - - #1) Extract CAB to folder - event_path = '../Data/{}/Events/New/map'.format(self.repo_name) - file_path = os.path.join(event_path, event_file) - self.extract_Cab(event_file, event_file, event_path) - - #2) Grab TSS file from the decompressed CAB file - tss, file_tss = self.get_tss_from_event( os.path.join(event_path, event_file.replace(".bin",""), event_file.replace(".bin",".dat"))) - - #3) Extract TSS to XML - self.extract_tss_XML(tss, event_file, '../Data/{}/Events'.format(self.repo_name)) - return tss - - def get_tss_from_event(self, event_file): - - with open(event_file, "rb") as event_f: - data = event_f.read() - file_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").text = '' - etree.SubElement(entry_node,"Notes").text = '' - 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 = self.elf_original - - 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) - etree.SubElement(entry_node,"JapaneseText").text = str(text) - eng_text = '' - - if to_translate == 0: - statusText = 'Done' - eng_text = str(text) - - etree.SubElement(entry_node,"EnglishText").text = eng_text - etree.SubElement(entry_node,"Notes").text = '' - 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,"UnknownPointer").text = str(unknown_pointer) - - if to_translate == 1: - etree.SubElement(entry_node,"SpeakerId").text = str(speaker_id) - - etree.SubElement(entry_node,"ToTranslate").text = str(to_translate) - 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(" 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, Icon - elif b in (0x1, 0xB): - - offset_temp = fileRead.tell() - next_4 = 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(":") - - if split[0][1:] in self.itags.keys(): - bytesFinal += struct.pack("B", self.itags[split[0][1:]]) - 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", 0x5) - bytesFinal += struct.pack("=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): - 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('