def emevd_file_content_to_event_list(file_content, filetype_override=None): """Parses the binary data in file_content as a .emevd file. Returns the list of Events in the file. Raises a ValueError if file_content appears to be malformed or un-parseable. Filetype is (mostly) automatically detected using header information, but can be overridden by passing a EVD_Filetype value as filetype_override. """ filetype = filetype_override event_list = [] should_dcx = False if dcx_handler.appears_dcx(file_content): tmp = dcx_handler.uncompress_dcx_content(file_content) file_content = tmp should_dcx = True master_offset = 0 master_offset = consume_byte(file_content, master_offset, '\x45', 1) master_offset = consume_byte(file_content, master_offset, '\x56', 1) master_offset = consume_byte(file_content, master_offset, '\x44', 1) master_offset = consume_byte(file_content, master_offset, '\x00', 1) (version_part_1, version_part_2) = struct.unpack_from("<II", file_content, offset=master_offset) master_offset += struct.calcsize("<II") # Determine filetype from version bytes, if not overriden. if filetype == None: if version_part_1 == 0x00000000 and version_part_2 == 0x000000CC: filetype = EVD_FileType.DARK_SOULS_1 elif version_part_1 == 0x0000FF00 and version_part_2 == 0x000000CC: # We cannot tell the difference between BB and DS3Test from the header, # so default to BB unless the user forces an override. filetype = EVD_FileType.BLOODBORNE elif version_part_1 == 0x0001FF00 and version_part_2 == 0x000000CD: filetype = EVD_FileType.DARK_SOULS_3 else: raise ValueError(("Unrecognized version bytes %08x %08x." + "Could not determine EVD version.") % (version_part_1, version_part_2)) (file_size, ) = struct.unpack_from("<I", file_content, offset=master_offset) master_offset += struct.calcsize("<I") header_pair_format_string = "<II" if filetype == EVD_FileType.DARK_SOULS_1: header_pair_format_string = "<II" elif (filetype == EVD_FileType.BLOODBORNE or filetype == EVD_FileType.DARK_SOULS_3_TEST or filetype == EVD_FileType.DARK_SOULS_3): header_pair_format_string = "<QQ" (event_count, event_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) (instr_count, instr_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) # This table is unused in known formats, so it always has 0 count and dummy offset. (_, _) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) (eventlayer_count, eventlayer_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) (dynarg_count, dynarg_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) (linked_files_count, linked_files_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) (fixarg_length, fixarg_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) (strings_length, strings_offset) = struct.unpack_from(header_pair_format_string, file_content, offset=master_offset) master_offset += struct.calcsize(header_pair_format_string) master_offset = event_offset for i in range(event_count): event_reset_mode = 0 if filetype == EVD_FileType.DARK_SOULS_1: (event_id, num_of_instr, starting_instr_offset, num_of_dynarg, starting_dynarg_offset, event_reset_mode, zero) = struct.unpack_from("<IIIIiII", file_content, offset=master_offset) master_offset += struct.calcsize("<IIIIiII") elif filetype == EVD_FileType.BLOODBORNE or filetype == EVD_FileType.DARK_SOULS_3_TEST: (event_id, num_of_instr, starting_instr_offset, num_of_dynarg, starting_dynarg_offset, event_reset_mode, zero) = struct.unpack_from("<QQQQi4xII", file_content, offset=master_offset) master_offset += struct.calcsize("<QQQQi4xII") elif filetype == EVD_FileType.DARK_SOULS_3: (event_id, num_of_instr, starting_instr_offset, num_of_dynarg, starting_dynarg_offset, event_reset_mode, zero) = struct.unpack_from("<QQQQqII", file_content, offset=master_offset) master_offset += struct.calcsize("<QQQQqII") if zero != 0: raise ValueError( "Event terminator zero was expected for event ID %d, but received %d instead." % (event_id, zero)) offset = instr_offset + starting_instr_offset instr_list = [] for i in range(num_of_instr): complain_about_zero = False if filetype == EVD_FileType.DARK_SOULS_1: (instr_class, instr_index, fixarg_bytes, starting_fixarg_offset, starting_eventlayer_offset, zero) = struct.unpack_from("<IIIiiI", file_content, offset=offset) if zero != 0: complain_about_zero = True offset += struct.calcsize("<IIIiiI") elif filetype == EVD_FileType.BLOODBORNE or filetype == EVD_FileType.DARK_SOULS_3_TEST: (instr_class, instr_index, fixarg_bytes, starting_fixarg_offset, \ starting_eventlayer_offset, zero) = struct.unpack_from("<IIQi4xiI", file_content, offset=offset) if zero != 0: complain_about_zero = True offset += struct.calcsize("<IIQi4xi4x") elif filetype == EVD_FileType.DARK_SOULS_3: (instr_class, instr_index, fixarg_bytes, starting_fixarg_offset, starting_eventlayer_offset) = struct.unpack_from( "<IIQi4xq", file_content, offset=offset) offset += struct.calcsize("<IIQi4xq") if complain_about_zero: raise ValueError(( "Instruction terminator zero was expected for " + "instruction %d[%03d] of event ID %d, but received %d instead." ) % (instr_class, instr_index, event_id, zero)) # Parse Event Layer eventlayer = None if starting_eventlayer_offset != -1: # Known examples have a very strict format. If something deviates from this, we should look into it. if filetype == EVD_FileType.DARK_SOULS_1: # This is a guess, since DS1 never uses the event layer field. (two, eventlayer, zero, neg_one, one) = struct.unpack_from( "<IIIiI", file_content, offset=eventlayer_offset + starting_eventlayer_offset) elif (filetype == EVD_FileType.BLOODBORNE or filetype == EVD_FileType.DARK_SOULS_3_TEST or filetype == EVD_FileType.DARK_SOULS_3): # This is partially a guess, since BB/DS3T never uses the event layer field. (two, eventlayer, zero, neg_one, one) = struct.unpack_from( "<IIQqQ", file_content, offset=eventlayer_offset + starting_eventlayer_offset) if two != 2: ValueError( "Event Layer @ %08x initializer 2 was expected but received %d instead." % (eventlayer_offset + starting_eventlayer_offset, two)) if zero != 0: raise ValueError( "Event Layer @ %08x terminator zero was expected but received %d instead." % (eventlayer_offset + starting_eventlayer_offset, zero)) if neg_one != -1: raise ValueError( "Event Layer @ %08x terminator -1 was expected but received %d instead." % (eventlayer_offset + starting_eventlayer_offset, neg_one)) if one != 1: raise ValueError( "Event Layer @ %08x terminator 1 was expected but received %d instead." % (eventlayer_offset + starting_eventlayer_offset, one)) (instr_format_string, instr_arg_list) = unpack_arg_array( file_content, instr_class, instr_index, fixarg_offset + starting_fixarg_offset, fixarg_bytes, filetype) instr = emevd_handler.Instruction(instr_class, instr_index, instr_format_string, instr_arg_list, [], eventlayer) instr_list.append(instr) offset = dynarg_offset + starting_dynarg_offset for i in range(num_of_dynarg): if filetype == EVD_FileType.DARK_SOULS_1: (instr_index, destination_starting_byte, source_starting_byte, \ num_of_bytes, zero) = struct.unpack_from("<IIIII", file_content, offset=offset) offset += struct.calcsize("<IIIII") if zero != 0: raise ValueError( ("Parameter replacement #%d terminator zero" + " was expected but received %d instead.") % (i, zero)) elif (filetype == EVD_FileType.BLOODBORNE or filetype == EVD_FileType.DARK_SOULS_3_TEST or filetype == EVD_FileType.DARK_SOULS_3): (instr_index, destination_starting_byte, source_starting_byte, num_of_bytes) = struct.unpack_from("<QQQQ", file_content, offset=offset) offset += struct.calcsize("<QQQQ") param = emevd_handler.ParameterReplacement( instr_index, destination_starting_byte, source_starting_byte, num_of_bytes) instr_list[instr_index].append_parameter_replacement(param) event = emevd_handler.Event(event_id, event_reset_mode, instr_list) event_list.append(event) strings = b"" if strings_length != 0: strings = file_content[strings_offset:strings_offset + strings_length] master_offset = linked_files_offset linked_files_list = [] for i in range(linked_files_count): if filetype == EVD_FileType.DARK_SOULS_1: # DS1 never uses linked files, but why not attempt to support it? (linked_files_name_offset, ) = struct.unpack_from( "<I", file_content, offset=master_offset) linked_files_list.append(linked_files_name_offset) elif (filetype == EVD_FileType.BLOODBORNE or filetype == EVD_FileType.DARK_SOULS_3_TEST or filetype == EVD_FileType.DARK_SOULS_3): (linked_files_name_offset, ) = struct.unpack_from( "<Q", file_content, offset=master_offset) linked_files_list.append(linked_files_name_offset) return (filetype, event_list, strings, linked_files_list, should_dcx)
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 emeld_file_content_to_string_list(file_content): should_dcx = False if dcx_handler.appears_dcx(file_content): tmp = dcx_handler.uncompress_dcx_content(file_content) should_dcx = True file_content = tmp master_offset = 0 master_offset = consume_byte(file_content, master_offset, b'\x45', 1) master_offset = consume_byte(file_content, master_offset, b'\x4C', 1) master_offset = consume_byte(file_content, master_offset, b'\x44', 1) master_offset = consume_byte(file_content, master_offset, b'\x00', 1) ((version_part_1, version_part_2), master_offset) = extract_struct("<II", file_content, master_offset) if version_part_1 == 0x00000000 and version_part_2 == 0x00CC0065: filetype = EVD_FileType.DARK_SOULS_1 elif version_part_1 == 0x0000FF00 and version_part_2 == 0x00CC0065: filetype = EVD_FileType.BLOODBORNE else: raise ValueError(("Unrecognized version bytes %08x %08x." + "Could not determine ELD version.") % (version_part_1, version_part_2)) ((file_size, ), master_offset) = extract_struct("<I", file_content, master_offset) if filetype == EVD_FileType.DARK_SOULS_1: ((event_count, event_offset), master_offset) = extract_struct("<II", file_content, master_offset) ((_, _), master_offset) = extract_struct("<II", file_content, master_offset) # 0, strings_offset ((_, _), master_offset) = extract_struct("<II", file_content, master_offset) # 0, strings_offset ((strings_length, strings_offset), master_offset) = extract_struct("<II", file_content, master_offset) ((_, _), master_offset) = extract_struct("<II", file_content, master_offset) # 0, 0 else: ((event_count, event_offset), master_offset) = extract_struct("<QQ", file_content, master_offset) ((_, _), master_offset) = extract_struct("<QQ", file_content, master_offset) # 0, strings_offset ((_, _), master_offset) = extract_struct("<QQ", file_content, master_offset) # 0, strings_offset ((strings_length, strings_offset), master_offset) = extract_struct("<QQ", file_content, master_offset) master_offset = event_offset return_list = [] for i in range(event_count): if filetype == EVD_FileType.DARK_SOULS_1: ((event_id, string_offset, _), master_offset) = extract_struct("<III", file_content, master_offset) else: ((event_id, string_offset), master_offset) = extract_struct("<QQ", file_content, master_offset) (value, _) = extract_utf16z(file_content, strings_offset + string_offset) return_list.append((event_id, value)) return (return_list, filetype, should_dcx)