def string_list_to_binary_emeld(string_list, filetype, should_dcx): header = b'\x45\x4C\x44\x00' if filetype == EVD_FileType.DARK_SOULS_1: header += struct.pack("<II", 0x00000000, 0x00CC0065) header_size = 0x38 header_pair_format_string = "<II" else: header += struct.pack("<II", 0x0000FF00, 0x00CC0065) header_size = 0x50 header_pair_format_string = "<QQ" packed_data = b'' packed_strings = b'' current_string_offset = 0 for (event_id, value) in string_list: if filetype == EVD_FileType.DARK_SOULS_1: packed_data += struct.pack("<III", event_id, current_string_offset, 0) else: packed_data += struct.pack("<QQ", event_id, current_string_offset) string_to_pack = value.encode("utf-16le") + b'\x00\x00' packed_strings += string_to_pack current_string_offset += len(string_to_pack) # Pad file to multiple of 8 bytes to match existing files. current_file_size = header_size + len(packed_data) + len(packed_strings) if current_file_size % 8 != 0: packed_strings += b'\x00' * (8 - current_file_size % 8) current_file_size += (8 - current_file_size % 8) strings_offset = header_size + len(packed_data) header += struct.pack("<I", current_file_size) header += struct.pack(header_pair_format_string, len(string_list), header_size) header += struct.pack(header_pair_format_string, 0, strings_offset) header += struct.pack(header_pair_format_string, 0, strings_offset) header += struct.pack(header_pair_format_string, len(packed_strings), strings_offset) if filetype == EVD_FileType.DARK_SOULS_1: header += struct.pack(header_pair_format_string, 0, 0) binary_emeld = header + packed_data + packed_strings if should_dcx: if filetype == EVD_FileType.DARK_SOULS_1: return dcx_handler.compress_dcx_content(binary_emeld, False) else: return dcx_handler.compress_dcx_content(binary_emeld, True) else: return binary_emeld
def runTest(self): comp = compress_dcx_content(b'MEMES') assert self.uncompress_dcx_instance(comp) == b'MEMES', \ "uncompression pass"
def export_to_gameparam(self): if self.game_version.get() == rngopts.RandOptGameVersion.PTDE: paths_to_search = PTDE_GAMEPARAM_PATH_LIST elif self.game_version.get() == rngopts.RandOptGameVersion.REMASTERED: paths_to_search = DS1R_GAMEPARAM_PATH_LIST else: paths_to_search = [] has_gameparam = False for filepath in paths_to_search: normed_path = os.path.normpath(os.path.join(os.getcwd(), filepath)) if os.path.isfile(normed_path): has_gameparam = True gameparam_filepath = normed_path gameparambak_filepath = normed_path + ".bak" is_remastered = ( self.game_version.get() == rngopts.RandOptGameVersion.REMASTERED) if not has_gameparam: self.msg_area.config(state="normal") self.msg_area.delete(1.0, "end") self.msg_area.insert("end", "\n\n") self.msg_area.insert("end", "ERROR", "error_red") self.msg_area.insert( "end", ": GameParam.parambnd[.dcx] is missing or cannot be opened." + " Check that this program is in the correct directory and GameParam.parambnd[.dcx] is present and retry.\n\n" + "Click \"Continue\" to continue in seed-information-only mode, or" + " click \"Quit\" to exit.") self.msg_area.tag_config("error_red", foreground="red") self.msg_area.config(state="disabled") self.export_button.config(state="disabled") self.lift_msg_area() else: if is_remastered: gp_filename = "GameParam.parambnd.dcx" else: gp_filename = "GameParam.parambnd" with open(gameparam_filepath, "rb") as f: content = f.read() try: if is_remastered: if not dcx_handler.appears_dcx(content): raise ValueError( ".dcx file does not appear to be DCX-compressed.") content = dcx_handler.uncompress_dcx_content(content) content_list = bnd_rebuilder.unpack_bnd(content) except: self.msg_area.config(state="normal") self.msg_area.delete(1.0, "end") self.msg_area.insert("end", "\n\n") self.msg_area.insert("end", "ERROR", "error_red") self.msg_area.insert( "end", ": " + gp_filename + " is malformed or corrupted and cannot be" + " parsed to export randomized items. If possible, restore " + gp_filename + " from a backup copy.\n\n" + "Click \"Continue\" to continue in seed-information-only mode, or" + " click \"Quit\" to exit.") self.msg_area.tag_config("error_red", foreground="red") self.msg_area.config(state="disabled") self.export_button.config(state="disabled") self.lift_msg_area() return # Back up GameParam.parambnd if needed. if not os.path.isfile(gameparambak_filepath): shutil.copy2(gameparam_filepath, gameparambak_filepath) if self.is_seed_empty(): self.get_new_seed() for index, (file_id, filepath, filedata) in enumerate(content_list): if (filepath == "N:\FRPG\data\INTERROOT_win32\param\GameParam\CharaInitParam.param" or filepath == "N:\FRPG\data\INTERROOT_x64\param\GameParam\CharaInitParam.param" ): chr_init_data = filedata # TODO: Implement this system correctly by passing chr_init_data # instead of None to preserve externally modified characters (e.g. another mod). # However, we need some way to determine external modifications # compared to data left over from a previous run that changed # ChrInit data. (options, randomized_data, rng) = self.randomize_data(None) (item_table, randomized_chr_data) = randomized_data syncnum = self.get_syncnum_string(rng) result_ilp = item_table.build_itemlotparam() ilp_binary_export = result_ilp.export_as_binary() result_slp = item_table.build_shoplineup() slp_binary_export = result_slp.export_as_binary() cip_binary_export = randomized_chr_data.export_as_binary() for index, (file_id, filepath, filedata) in enumerate(content_list): if (filepath == "N:\FRPG\data\INTERROOT_win32\param\GameParam\ItemLotParam.param" or filepath == "N:\FRPG\data\INTERROOT_x64\param\GameParam\ItemLotParam.param" ): content_list[index] = (file_id, filepath, ilp_binary_export) if (filepath == "N:\FRPG\data\INTERROOT_win32\param\GameParam\ShopLineupParam.param" or filepath == "N:\FRPG\data\INTERROOT_x64\param\GameParam\ShopLineupParam.param" ): content_list[index] = (file_id, filepath, slp_binary_export) if (filepath == "N:\FRPG\data\INTERROOT_win32\param\GameParam\CharaInitParam.param" or filepath == "N:\FRPG\data\INTERROOT_x64\param\GameParam\CharaInitParam.param" ): content_list[index] = (file_id, filepath, cip_binary_export) new_content = bnd_rebuilder.repack_bnd(content_list) if is_remastered: new_content = dcx_handler.compress_dcx_content(new_content) with open(gameparam_filepath, "wb") as f: f.write(new_content) seed_folder = self.export_seed_info( (options, randomized_data, rng)) self.msg_continue_button.lower() self.msg_area.config(state="normal") self.msg_area.delete(1.0, "end") self.msg_area.insert("end", "\n\n") self.msg_area.insert("end", "SUCCESS", "yay") self.msg_area.insert( "end", "! " + gp_filename + " has been modified successfully.\n\n" + "The information for this seed has been exported in the directory\n\n " + seed_folder + "\n\n") self.msg_area.insert( "end", "SyncNum: " + syncnum + "\n (When racing, all SyncNums should be equal or settings do not match.)\n\n" ) self.msg_area.insert( "end", "Click \"Back\" to begin again, or click \"Quit\" to exit.") self.msg_area.tag_config("yay", foreground="green") self.msg_area.config(state="disabled") self.msg_area.lift() self.back_button.lift() self.msg_quit_button.lift()
def export_as_emevd(self): event_table_binary = b"" instr_table_binary = b"" argument_data_binary = b"" param_table_binary = b"" eventlayer_list = [] current_instr_table_offset = 0 current_instr_count = 0 current_argument_data_length = 0 current_param_table_offset = 0 current_param_count = 0 for e in self.event_list: (e_bin, i_bin, a_bin, p_bin, eventlayer_list) = e.export_as_binary(current_instr_table_offset, current_argument_data_length, current_param_table_offset, eventlayer_list, self.filetype) event_table_binary += e_bin instr_table_binary += i_bin argument_data_binary += a_bin param_table_binary += p_bin current_instr_table_offset += len(i_bin) current_instr_count += e.count_instructions() current_argument_data_length += len(a_bin) current_param_table_offset += len(p_bin) current_param_count += e.count_total_parameter_replacements() eventlayer_binary = b"".join(eventlayer_list) linked_files_offset_binary = b"" for l in self.linked_files_name_offset: if self.filetype == EVD_FileType.DARK_SOULS_1: linked_files_offset_binary += struct.pack("<I", l) elif (self.filetype == EVD_FileType.BLOODBORNE or self.filetype == EVD_FileType.DARK_SOULS_3_TEST or self.filetype == EVD_FileType.DARK_SOULS_3): linked_files_offset_binary += struct.pack("<Q", l) # Prepare header tables and offsets. emevd_header_binary = b"" emevd_data_binary = b"" header_pair_format_string = "<II" if self.filetype == EVD_FileType.DARK_SOULS_1: header_pair_format_string = "<II" header_size = 0x54 elif (self.filetype == EVD_FileType.BLOODBORNE or self.filetype == EVD_FileType.DARK_SOULS_3_TEST or self.filetype == EVD_FileType.DARK_SOULS_3): header_pair_format_string = "<QQ" header_size = 0x90 emevd_header_binary += struct.pack(header_pair_format_string, len(self.event_list), header_size + 0) emevd_data_binary += event_table_binary emevd_header_binary += struct.pack(header_pair_format_string, current_instr_count, header_size + len(emevd_data_binary)) emevd_data_binary += instr_table_binary # Unused table. emevd_header_binary += struct.pack(header_pair_format_string, 0, header_size + len(emevd_data_binary)) emevd_data_binary += b"" emevd_header_binary += struct.pack(header_pair_format_string, len(eventlayer_list), header_size + len(emevd_data_binary)) emevd_data_binary += eventlayer_binary # Argument data is packed here, but the header entry comes later. if self.filetype == EVD_FileType.DARK_SOULS_1: argument_data_binary += b"\x00\x00\x00\x00" # Termination z4 for the packed ArgumentData elif (self.filetype == EVD_FileType.BLOODBORNE or self.filetype == EVD_FileType.DARK_SOULS_3_TEST or self.filetype == EVD_FileType.DARK_SOULS_3): # Pad data with NUL to multiple of 16. current_data_length = header_size + len(emevd_data_binary) + len(argument_data_binary) to_next_multiple = 16 - (current_data_length % 16) if to_next_multiple < 16: current_data_length += to_next_multiple argument_data_binary += b"\x00" * to_next_multiple argument_header_binary = struct.pack(header_pair_format_string, len(argument_data_binary), header_size + len(emevd_data_binary)) emevd_data_binary += argument_data_binary emevd_header_binary += struct.pack(header_pair_format_string, current_param_count, header_size + len(emevd_data_binary)) emevd_data_binary += param_table_binary emevd_header_binary += struct.pack(header_pair_format_string, len(self.linked_files_name_offset), header_size + len(emevd_data_binary)) emevd_data_binary += linked_files_offset_binary # Argument data header entry. emevd_header_binary += argument_header_binary emevd_header_binary += struct.pack(header_pair_format_string, len(self.strings), header_size + len(emevd_data_binary)) emevd_data_binary += self.strings # DS1 has a header-terminating z4, which removed in later formats. if self.filetype == EVD_FileType.DARK_SOULS_1: emevd_header_binary += b"\x00\x00\x00\x00" filesize = header_size + len(emevd_data_binary) emevd_data = emevd_header_binary + emevd_data_binary # Build EVD file. emevd_header = b"EVD\x00" if self.filetype == EVD_FileType.DARK_SOULS_1: emevd_header += struct.pack("<II", 0x00000000, 0x000000CC) elif self.filetype == EVD_FileType.BLOODBORNE or self.filetype == EVD_FileType.DARK_SOULS_3_TEST: emevd_header += struct.pack("<II", 0x0000FF00, 0x000000CC) elif self.filetype == EVD_FileType.DARK_SOULS_3: emevd_header += struct.pack("<II", 0x0001FF00, 0x000000CD) else: raise ValueError("Unrecognized filetype. Cannot write version bytes.") emevd_header += struct.pack("<I", filesize) emevd = emevd_header + emevd_data if self.should_dcx: if self.filetype == EVD_FileType.DARK_SOULS_1: return dcx_handler.compress_dcx_content(emevd, False) else: return dcx_handler.compress_dcx_content(emevd, True) else: return emevd