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)
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." )
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))