Пример #1
0
def extract_all_models(rarc_path, filenames):
  with open(rarc_path, "rb") as f:
    data = BytesIO(f.read())
  rarc = RARC()
  rarc.read(data)
  
  rarc_basename = os.path.splitext(os.path.basename(rarc_path))[0]
  rarc_containing_folder = os.path.dirname(rarc_path)
  base_output_folder = rarc_containing_folder
  
  invalid_filenames = []
  for filename in filenames:
    file_entry = rarc.get_file_entry(filename)
    if file_entry is None:
      print("Requested invalid file to extract: %s" % filename)
      invalid_filenames.append(filename)
  if invalid_filenames:
    sys.exit(1)
  
  for file_entry in rarc.file_entries:
    if filenames and file_entry.name not in filenames:
      continue
    if file_entry.name.endswith(".bdl") or file_entry.name.endswith(".bmd"):
      extract_model_or_texture(file_entry, base_output_folder)
    if file_entry.name.endswith(".bti"):
      extract_model_or_texture(file_entry, base_output_folder)
    if os.path.splitext(file_entry.name)[1] in [".bck", ".brk", ".btk", ".btp", ".bas", ".bpk"]:
      extract_animation(file_entry, base_output_folder)
Пример #2
0
 def replace_arc(self, arc_path, new_data):
   if arc_path not in self.gcm.files_by_path:
     raise Exception("Cannot replace RARC that doesn't exist: " + arc_path)
   
   arc = RARC()
   arc.read(new_data)
   self.arcs_by_path[arc_path] = arc
Пример #3
0
 def get_arc(self, arc_path):
   arc_path = arc_path.replace("\\", "/")
   
   if arc_path in self.arcs_by_path:
     return self.arcs_by_path[arc_path]
   else:
     data = self.gcm.read_file_data(arc_path)
     arc = RARC()
     arc.read(data)
     self.arcs_by_path[arc_path] = arc
     return arc
Пример #4
0
def convert_all_player_models(orig_link_folder,
                              custom_player_folder,
                              repack_hands_model=False,
                              rarc_name="Link.arc",
                              no_skip_unchanged=False):
    orig_link_arc_path = os.path.join(orig_link_folder, rarc_name)
    with open(orig_link_arc_path, "rb") as f:
        rarc_data = BytesIO(f.read())
    link_arc = RARC()
    link_arc.read(rarc_data)

    all_model_basenames = []
    all_texture_basenames = []
    all_bone_anim_basenames = []
    all_tev_anim_basenames = []
    all_tex_anim_basenames = []
    all_btp_anim_basenames = []
    all_bas_anim_basenames = []
    all_bpk_anim_basenames = []
    for file_entry in link_arc.file_entries:
        if file_entry.is_dir:
            continue
        basename, file_ext = os.path.splitext(file_entry.name)
        if file_ext == ".bdl":
            all_model_basenames.append(basename)
        if file_ext == ".bti":
            all_texture_basenames.append(basename)
        if file_ext == ".bck":
            all_bone_anim_basenames.append(basename)
        if file_ext == ".brk":
            all_tev_anim_basenames.append(basename)
        if file_ext == ".btk":
            all_tex_anim_basenames.append(basename)
        if file_ext == ".btp":
            all_btp_anim_basenames.append(basename)
        if file_ext == ".bas":
            all_bas_anim_basenames.append(basename)
        if file_ext == ".bpk":
            all_bpk_anim_basenames.append(basename)

    found_any_files_to_modify = False

    for model_basename in all_model_basenames:
        if model_basename == "hands" and not repack_hands_model:
            continue

        new_model_folder = os.path.join(custom_player_folder, model_basename)
        if os.path.isdir(new_model_folder):
            out_bdl_path = os.path.join(new_model_folder,
                                        model_basename + ".bdl")

            found_any_files_to_modify = True

            should_rebuild_bdl = False
            if os.path.isfile(out_bdl_path) and not no_skip_unchanged:
                last_compile_time = os.path.getmtime(out_bdl_path)

                relevant_file_exts = ["dae", "png", "json"]
                for file_ext in relevant_file_exts:
                    relevant_file_paths = glob.glob(new_model_folder + "/*." +
                                                    file_ext)
                    for relevant_file_path in relevant_file_paths:
                        if os.path.getmtime(
                                relevant_file_path) > last_compile_time:
                            should_rebuild_bdl = True
                            break
                    if should_rebuild_bdl:
                        break
            else:
                should_rebuild_bdl = True

            if should_rebuild_bdl:
                convert_to_bdl(new_model_folder, model_basename)
            else:
                print("Skipping %s" % model_basename)

            orig_bdl_path = os.path.join(orig_link_folder, model_basename,
                                         model_basename + ".bdl")

            sections_to_copy = []
            if rarc_name.lower() == "Link.arc".lower() and model_basename in [
                    "cl"
            ]:
                # Link needs his original INF1/JNT1 to not crash the game.
                sections_to_copy.append("INF1")
                sections_to_copy.append("JNT1")

            link_arc.get_file_entry(model_basename +
                                    ".bdl").data = copy_original_sections(
                                        out_bdl_path, orig_bdl_path,
                                        sections_to_copy)

    for texture_basename in all_texture_basenames:
        # Create texture BTI from PNG
        texture_bti_path = os.path.join(custom_player_folder,
                                        texture_basename + ".bti")
        texture_png_path = os.path.join(custom_player_folder,
                                        texture_basename + ".png")

        if os.path.isfile(texture_png_path):
            found_any_files_to_modify = True

            print("Converting %s from PNG to BTI" % texture_basename)

            image = Image.open(texture_png_path)
            texture = link_arc.get_file(texture_basename + ".bti")

            tex_header_json_path = os.path.join(
                custom_player_folder, texture_basename + "_tex_header.json")
            if os.path.isfile(tex_header_json_path):
                with open(tex_header_json_path) as f:
                    tex_header = json.load(f)

                if "Format" in tex_header:
                    texture.image_format = ImageFormat[tex_header["Format"]]
                if "PaletteFormat" in tex_header:
                    texture.palette_format = PaletteFormat[
                        tex_header["PaletteFormat"]]
                if "WrapS" in tex_header:
                    texture.wrap_s = WrapMode[tex_header["WrapS"]]
                if "WrapT" in tex_header:
                    texture.wrap_t = WrapMode[tex_header["WrapT"]]
                if "MagFilter" in tex_header:
                    texture.mag_filter = FilterMode[tex_header["MagFilter"]]
                if "MinFilter" in tex_header:
                    texture.min_filter = FilterMode[tex_header["MinFilter"]]
                if "AlphaSetting" in tex_header:
                    texture.alpha_setting = tex_header["AlphaSetting"]
                if "LodBias" in tex_header:
                    texture.lod_bias = tex_header["LodBias"]
                if "unknown2" in tex_header:
                    texture.min_lod = (tex_header["unknown2"] & 0xFF00) >> 8
                    texture.max_lod = (tex_header["unknown2"] & 0x00FF)
                if "MinLOD" in tex_header:
                    texture.min_lod = tex_header["MinLOD"]
                if "MaxLOD" in tex_header:
                    texture.max_lod = tex_header["MaxLOD"]
                if "unknown3" in tex_header:
                    texture.unknown_3 = tex_header["unknown3"]

            texture.replace_image(image)
            texture.save_changes()
            with open(texture_bti_path, "wb") as f:
                texture.file_entry.data.seek(0)
                f.write(texture.file_entry.data.read())

        # Import texture BTI
        if os.path.isfile(texture_bti_path):
            found_any_files_to_modify = True

            with open(texture_bti_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(texture_basename + ".bti").data = data

    if not repack_hands_model:
        # Import hands texture
        hands_tex_png = os.path.join(custom_player_folder, "hands",
                                     "handsS3TC.png")
        if os.path.isfile(hands_tex_png) and link_arc.get_file(
                "hands.bdl") is not None:
            found_any_files_to_modify = True

            image = Image.open(hands_tex_png)
            hands_model = link_arc.get_file("hands.bdl")
            textures = hands_model.tex1.textures_by_name["handsS3TC"]
            for texture in textures:
                texture.replace_image(image)
            hands_model.save_changes()
            link_arc.get_file_entry(
                "hands.bdl").data = hands_model.file_entry.data

    # Repack animations.
    for anim_basename in all_bone_anim_basenames:
        anim_path = os.path.join(custom_player_folder, "#Bone animations",
                                 anim_basename + ".bck")
        if os.path.isfile(anim_path):
            found_any_files_to_modify = True

            with open(anim_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(anim_basename + ".bck").data = data

    for anim_basename in all_tev_anim_basenames:
        anim_brk_path = os.path.join(custom_player_folder,
                                     "#TEV register animations",
                                     anim_basename + ".brk")
        anim_json_path = os.path.join(custom_player_folder,
                                      "#TEV register animations",
                                      anim_basename + ".json")

        if os.path.isfile(anim_json_path):
            found_any_files_to_modify = True

            print("Converting %s from JSON to BRK" % anim_basename)

            brk = link_arc.get_file(anim_basename + ".brk")
            load_brk_from_json(brk, anim_json_path)

            brk.save_changes()
            with open(anim_brk_path, "wb") as f:
                f.write(read_all_bytes(brk.file_entry.data))

        if os.path.isfile(anim_brk_path):
            found_any_files_to_modify = True

            with open(anim_brk_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(anim_basename + ".brk").data = data

    for anim_basename in all_tex_anim_basenames:
        anim_path = os.path.join(custom_player_folder, "#Texture animations",
                                 anim_basename + ".btk")
        if os.path.isfile(anim_path):
            found_any_files_to_modify = True

            with open(anim_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(anim_basename + ".btk").data = data

    for anim_basename in all_btp_anim_basenames:
        anim_path = os.path.join(custom_player_folder,
                                 "#Texture swap animations",
                                 anim_basename + ".btp")
        if os.path.isfile(anim_path):
            found_any_files_to_modify = True

            with open(anim_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(anim_basename + ".btp").data = data

    for anim_basename in all_bas_anim_basenames:
        anim_path = os.path.join(custom_player_folder,
                                 "#Sound effect animations",
                                 anim_basename + ".bas")
        if os.path.isfile(anim_path):
            found_any_files_to_modify = True

            with open(anim_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(anim_basename + ".bas").data = data

    for anim_basename in all_bpk_anim_basenames:
        anim_path = os.path.join(custom_player_folder, "#Color animations",
                                 anim_basename + ".bpk")
        if os.path.isfile(anim_path):
            found_any_files_to_modify = True

            with open(anim_path, "rb") as f:
                data = BytesIO(f.read())
                link_arc.get_file_entry(anim_basename + ".bpk").data = data

    # Print out changed file sizes
    with open(orig_link_arc_path, "rb") as f:
        rarc_data = BytesIO(f.read())
    orig_link_arc = RARC()
    orig_link_arc.read(rarc_data)
    for file_entry in link_arc.file_entries:
        orig_file_entry = orig_link_arc.get_file_entry(file_entry.name)
        if file_entry.is_dir:
            continue
        if data_len(orig_file_entry.data) == data_len(file_entry.data):
            continue
        print("File %s, orig size %X, new size %X" %
              (file_entry.name, data_len(
                  orig_file_entry.data), data_len(file_entry.data)))

    link_arc.save_changes()
    link_arc_out_path = os.path.join(custom_player_folder, rarc_name)
    with open(link_arc_out_path, "wb") as f:
        link_arc.data.seek(0)
        f.write(link_arc.data.read())

    if not found_any_files_to_modify:
        print(
            "No models, textures, or animations to modify found. Repacked RARC with no changes."
        )
Пример #5
0
def convert_elf_to_rel(in_elf_path,
                       out_rel_path,
                       rel_id,
                       actor_profile_name,
                       rels_arc_path=None):
    elf = ELF()
    elf.read_from_file(in_elf_path)

    rel = REL()

    rel.id = rel_id

    # First convert the sections we want to include in the REL from ELF sections to REL sections.
    section_name_to_rel_section = {}
    elf_section_index_to_rel_section = {}
    for elf_section_index, elf_section in enumerate(elf.sections):
        if elf_section.name in ALLOWED_SECTIONS or elf_section.type == ELFSectionType.SHT_NULL:
            # Sections we will add to the REL.
            rel_section = RELSection()
            rel_section.data = make_copy_data(elf_section.data)
            rel.sections.append(rel_section)

            if elf_section.flags & ELFSectionFlags.SHF_EXECINSTR.value:
                rel_section.is_executable = True

            section_name_to_rel_section[elf_section.name] = rel_section
            elf_section_index_to_rel_section[elf_section_index] = rel_section

            if elf_section.type == ELFSectionType.SHT_NULL:
                rel_section.is_uninitialized = True
                rel_section.is_bss = False
            else:
                rel_section.is_uninitialized = False
                rel_section.is_bss = False
            # TODO: bss support

    # Then generate the relocations.
    for elf_section in elf.sections:
        if elf_section.type == ELFSectionType.SHT_RELA:
            assert elf_section.name.startswith(".rela")
            relocated_section_name = elf_section.name[len(".rela"):]

            if relocated_section_name not in section_name_to_rel_section:
                # Ignored section
                continue

            rel_section = section_name_to_rel_section[relocated_section_name]
            section_index = rel.sections.index(rel_section)
            for elf_relocation in elf.relocations[elf_section.name]:
                rel_relocation = RELRelocation()

                elf_symbol = elf.symbols[".symtab"][
                    elf_relocation.symbol_index]
                rel_relocation.relocation_type = RELRelocationType(
                    elf_relocation.type.value)
                #print("%X" % elf_symbol.section_index)

                if elf_symbol.section_index == 0:
                    raise Exception(
                        "Unresolved external symbol in main.dol: %s" %
                        elf_symbol.name)

                if elf_symbol.section_index == ELFSymbolSpecialSection.SHN_ABS.value:
                    # Symbol is located in main.dol.
                    module_num = 0

                    # I don't think this value is used for dol relocations.
                    # In vanilla, this was written as 4 for some reason?
                    rel_relocation.section_num_to_relocate_against = 0
                elif elf_symbol.section_index >= 0xFF00:
                    raise Exception(
                        "Special section number not implemented: %04X" %
                        elf_symbol.section_index)
                else:
                    # Symbol is located in the current REL.
                    # TODO: support for relocating against other rels besides the current one
                    module_num = rel.id

                    section_name_to_relocate_against = elf.sections[
                        elf_symbol.section_index].name
                    if section_name_to_relocate_against not in section_name_to_rel_section:
                        raise Exception(
                            "Section name \"%s\" could not be found for symbol \"%s\""
                            % (section_name_to_relocate_against,
                               elf_symbol.name))
                    rel_section_to_relocate_against = section_name_to_rel_section[
                        section_name_to_relocate_against]
                    rel_section_index_to_relocate_against = rel.sections.index(
                        rel_section_to_relocate_against)
                    rel_relocation.section_num_to_relocate_against = rel_section_index_to_relocate_against

                rel_relocation.symbol_address = elf_symbol.address + elf_relocation.addend
                rel_relocation.relocation_offset = elf_relocation.relocation_offset
                rel_relocation.curr_section_num = section_index

                if module_num not in rel.relocation_entries_for_module:
                    rel.relocation_entries_for_module[module_num] = []
                rel.relocation_entries_for_module[module_num].append(
                    rel_relocation)

    symbol = elf.symbols_by_name[".symtab"]["_prolog"]
    rel_section = elf_section_index_to_rel_section[symbol.section_index]
    rel_section_index = rel.sections.index(rel_section)
    rel.prolog_section = rel_section_index
    rel.prolog_offset = symbol.address

    symbol = elf.symbols_by_name[".symtab"]["_epilog"]
    rel_section = elf_section_index_to_rel_section[symbol.section_index]
    rel_section_index = rel.sections.index(rel_section)
    rel.epilog_section = rel_section_index
    rel.epilog_offset = symbol.address

    symbol = elf.symbols_by_name[".symtab"]["_unresolved"]
    rel_section = elf_section_index_to_rel_section[symbol.section_index]
    rel_section_index = rel.sections.index(rel_section)
    rel.unresolved_section = rel_section_index
    rel.unresolved_offset = symbol.address

    rel.save_to_file(out_rel_path)

    # TODO: instead of replacing a REL, add a new REL!
    if rels_arc_path is not None and os.path.isfile(rels_arc_path):
        with open(rels_arc_path, "rb") as f:
            data = BytesIO(f.read())
        rels_arc = RARC()
        rels_arc.read(data)

        # Update the profile list to properly reference the profile in the custom REL.
        # Then insert the custom REL and the updated profile list into RELS.arc and save it for quick testing.
        profile_list_data = rels_arc.get_file_entry(
            "f_pc_profile_lst.rel").data
        profile_list = REL()
        profile_list.read(profile_list_data)

        if rel.id not in profile_list.relocation_entries_for_module:
            raise Exception(
                "REL ID %02X could not be found in the profile list." % rel.id)
            # TODO: add new REL instead of replacing!

        if actor_profile_name not in elf.symbols_by_name[".symtab"]:
            raise Exception(
                "Could not find the actor profile. The specified symbol name for it was \"%s\", but that symbol could not be found in the ELF."
                % actor_profile_name)
        profile_symbol = elf.symbols_by_name[".symtab"][actor_profile_name]
        profile_list.relocation_entries_for_module[
            rel.id][0].symbol_address = profile_symbol.address

        section_with_profile = section_name_to_rel_section[".rodata"]
        new_section_index_with_profile = rel.sections.index(
            section_with_profile)
        profile_list.relocation_entries_for_module[rel.id][
            0].section_num_to_relocate_against = new_section_index_with_profile

        profile_list.save_changes(preserve_section_data_offsets=True)

        # Insert the RELs.
        rels_arc.get_file_entry(
            "f_pc_profile_lst.rel").data = profile_list.data
        found_rel = False
        for file_entry in rels_arc.file_entries:
            if file_entry.is_dir:
                continue
            file_entry.decompress_data_if_necessary()
            if read_u32(file_entry.data, 0) == rel.id:
                file_entry.data = rel.data
                found_rel = True
                break
        if not found_rel:
            raise Exception("Failed to find REL to replace with ID 0x%03X" %
                            rel.id)

        rels_arc.save_changes()
        with open(rels_arc_path, "wb") as f:
            f.write(read_all_bytes(rels_arc.data))