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