import os import sys import shutil import extract_game_assets from pathlib import Path import hashlib import struct def sha1_from_data(data): sha1 = hashlib.sha1() sha1.update(data) return sha1.hexdigest().upper() def copy(path,destPath): for root,dirs,files in os.walk(str(path)): for file in files: outputDir = destPath/Path(str(root)) #print(str(outputDir.absolute())+file) if not outputDir.absolute().exists(): os.makedirs(outputDir.absolute()) outputFile = Path(str(outputDir.absolute())+"/"+str(file)) inFile = Path(str(Path(root).absolute())+"/"+str(file)) if not outputFile.exists(): print(str(inFile)+" -> "+str(outputFile)) shutil.copyfile(inFile,outputFile) else: inf = open(inFile,"rb") inSum = sha1_from_data(bytearray(inf.read())) outf = open(outputFile,"rb") outSum = sha1_from_data(bytearray(outf.read())) if inSum!=outSum: print(str(inFile)+" -> "+str(outputFile)) shutil.copyfile(inFile,outputFile) aMemRels = """d_a_alldie.rel d_a_andsw2.rel d_a_bd.rel d_a_canoe.rel d_a_cstaf.rel d_a_demo_item.rel d_a_door_bossl1.rel d_a_econt.rel d_a_e_dn.rel d_a_e_fm.rel d_a_e_ga.rel d_a_e_hb.rel d_a_e_nest.rel d_a_e_rd.rel d_a_fr.rel d_a_grass.rel d_a_kytag05.rel d_a_kytag10.rel d_a_kytag11.rel d_a_kytag14.rel d_a_mg_fish.rel d_a_npc_besu.rel d_a_npc_fairy_seirei.rel d_a_npc_fish.rel d_a_npc_henna.rel d_a_npc_kakashi.rel d_a_npc_kkri.rel d_a_npc_kolin.rel d_a_npc_maro.rel d_a_npc_taro.rel d_a_npc_tkj.rel d_a_obj_bhashi.rel d_a_obj_bkdoor.rel d_a_obj_bosswarp.rel d_a_obj_cboard.rel d_a_obj_digplace.rel d_a_obj_eff.rel d_a_obj_fmobj.rel d_a_obj_gptaru.rel d_a_obj_hhashi.rel d_a_obj_kanban2.rel d_a_obj_kbacket.rel d_a_obj_kgate.rel d_a_obj_klift00.rel d_a_obj_ktonfire.rel d_a_obj_ladder.rel d_a_obj_lv2candle.rel d_a_obj_magne_arm.rel d_a_obj_metalbox.rel d_a_obj_mgate.rel d_a_obj_nameplate.rel d_a_obj_ornament_cloth.rel d_a_obj_rope_bridge.rel d_a_obj_stick.rel d_a_obj_stonemark.rel d_a_obj_swallshutter.rel d_a_obj_swpropeller.rel d_a_obj_swpush5.rel d_a_obj_yobikusa.rel d_a_scene_exit2.rel d_a_shop_item.rel d_a_sq.rel d_a_swc00.rel d_a_tag_ajnot.rel d_a_tag_attack_item.rel d_a_tag_cstasw.rel d_a_tag_gstart.rel d_a_tag_hinit.rel d_a_tag_hjump.rel d_a_tag_hstop.rel d_a_tag_lv2prchk.rel d_a_tag_magne.rel d_a_tag_mhint.rel d_a_tag_mstop.rel d_a_tag_spring.rel d_a_tag_statue_evt.rel d_a_ykgr.rel""" mMemRels = """d_a_andsw.rel d_a_arrow.rel d_a_bg.rel d_a_bg_obj.rel d_a_boomerang.rel d_a_crod.rel d_a_demo00.rel d_a_disappear.rel d_a_dmidna.rel d_a_door_dbdoor00.rel d_a_door_knob00.rel d_a_door_shutter.rel d_a_door_spiral.rel d_a_dshutter.rel d_a_ep.rel d_a_hitobj.rel d_a_kytag00.rel d_a_kytag04.rel d_a_kytag17.rel d_a_mg_rod.rel d_a_midna.rel d_a_nbomb.rel d_a_obj_brakeeff.rel d_a_obj_burnbox.rel d_a_obj_carry.rel d_a_obj_ito.rel d_a_obj_life_container.rel d_a_obj_movebox.rel d_a_obj_swpush.rel d_a_obj_timer.rel d_a_obj_yousei.rel d_a_path_line.rel d_a_scene_exit.rel d_a_set_bgobj.rel d_a_spinner.rel d_a_suspend.rel d_a_swhit0.rel d_a_tag_allmato.rel d_a_tag_attention.rel d_a_tag_camera.rel d_a_tag_chkpoint.rel d_a_tag_event.rel d_a_tag_evt.rel d_a_tag_evtarea.rel d_a_tag_evtmsg.rel d_a_tag_howl.rel d_a_tag_kmsg.rel d_a_tag_lantern.rel d_a_tag_mist.rel d_a_tag_msg.rel d_a_tag_push.rel d_a_tag_telop.rel d_a_tbox.rel d_a_tbox2.rel d_a_vrbox.rel d_a_vrbox2.rel f_pc_profile_lst.rel""" #Because libarc is only geared toward reading from arcs I'm writing my own arc writer in this file class HEADER: RARC : int length : int headerLength : int fileDataOffset : int fileDataLen : int fileDataLen2 : int unk1 : int unk2 : int class INFO: numNodes : int firstNodeOffset : int totalDirNum : int firstDirOffset : int stringTableLen : int stringTableOffset : int numDirsThatAreFiles : int unk1 : int unk2 : int class NODE: NAME : int stringTableOffset: int hash : int numDirs : int firstDirIndex : int class DIRECTORY: dirIndex : int stringHash : int type : int stringOffset : int fileOffset : int fileLength : int unk1 : int def computeHash(string): hash = 0 for char in string: hash = hash*3 hash = hash + ord(char) if hash>65535: hash=hash%65535 return hash def addFile(index,sizeIndex,dirs,name,stringTable,paths,data): file = DIRECTORY() file.dirIndex = index file.stringHash = computeHash(name) file.type = 0xA500 file.stringOffset = stringTable.find(name) path = None for relPath in paths: if str(relPath).find(name)!=-1: path = relPath file.fileLength = os.path.getsize(path) file.fileOffset = sizeIndex sizeIndex = sizeIndex + file.fileLength + 1 file.unk1 = 0 fileData = open(path,"rb") data += fileData.read(file.fileLength) fileData.close() dirs.append(file) return dirs,data,sizeIndex def copyRelFiles(buildPath,aMemList,mMemList): relArcPaths = [] for root,dirs,files in os.walk(str(buildPath/"rel")): for file in files: if file.find(".rel")!=-1: relArcFound = False for rel in aMemList: if rel==file: relArcFound = True for rel in mMemList: if rel==file: relArcFound = True fullPath = Path(root+"/"+file) print(str(fullPath)+" -> "+str(buildPath/"game/files/rel/Final/Release"/file)) shutil.copy(fullPath,buildPath/"game/files/rel/Final/Release/") #We're copying uncompressed rels here, we should compress in the future! if relArcFound==True: relArcPaths.append(fullPath) #print(relArcPaths) #After writing this all I found out we don't actually need RELS.arc to load rels lol, keeping it here for the future in case we want to match RELS.arc arcHeader = HEADER() arcHeader.RARC = 0x52415243 arcHeader.headerLength = 0x20 arcHeader.unk1 = 0 arcHeader.unk2 = 0 infoBlock = INFO() infoBlock.numNodes = 5 infoBlock.numDirsThatAreFiles = 142 rootNode = NODE() rootNode.NAME = 0x524F4F54 rootNode.numDirs = 4 rootNode.firstDirIndex = 0 rootNode.hash = computeHash("rels") aMemNode = NODE() aMemNode.NAME = 0x414d454d aMemNode.hash = computeHash("amem") aMemNode.numDirs = 79 aMemNode.firstDirIndex = 4 mMemNode = NODE() mMemNode.hash = computeHash("mmem") mMemNode.NAME = 0x4d4d454d mMemNode.numDirs = 59 mMemNode.firstDirIndex = 83 stringTable = "\x2E\0\x2E\x2E\0rels\0amem\0" for rel in aMemList: stringTable = stringTable+rel+'\0' stringTable = stringTable+"mmem\0" for rel in mMemList: stringTable = stringTable+rel+'\0' rootNode.stringTableOffset = stringTable.find("rels") aMemNode.stringTableOffset = stringTable.find("amem") mMemNode.stringTableOffset = stringTable.find("mmem") aMemDir = DIRECTORY() aMemDir.dirIndex = 0xFFFF aMemDir.type = 0x200 aMemDir.stringOffset = stringTable.find("amem") aMemDir.stringHash = computeHash("amem") aMemDir.fileOffset = 1 aMemDir.fileLength = 0x10 aMemDir.unk1 = 0 mMemDir = DIRECTORY() mMemDir.dirIndex = 0xFFFF mMemDir.type = 0x200 mMemDir.stringOffset = stringTable.find("mmem") mMemDir.stringHash = computeHash("mmem") mMemDir.fileOffset = 2 mMemDir.fileLength = 0x10 mMemDir.unk1 = 0 unkDir = DIRECTORY() unkDir.dirIndex = 0xFFFF unkDir.stringHash = 0x2E unkDir.type = 0x200 unkDir.stringOffset = 0 unkDir.fileOffset = 0 unkDir.fileLength = 0x10 unkDir.unk1 = 0 unkDir2 = DIRECTORY() unkDir2.dirIndex = 0xFFFF unkDir2.stringHash = 0xB8 unkDir2.type = 0x200 unkDir2.stringOffset = 2 unkDir2.fileOffset = 0xFFFFFFFF unkDir2.fileLength = 0x10 unkDir2.unk1 = 0 dirs = [aMemDir,mMemDir,unkDir,unkDir2] data = bytearray() dirIndex = 4 sizeIndex = 0 for rel in aMemList: retdirs,retdata,retSize = addFile(dirIndex,sizeIndex,dirs,rel,stringTable,relArcPaths,data) dirIndex = dirIndex+1 sizeIndex = retSize dirs = retdirs data = retdata for rel in mMemList: retdirs,retdata,retSize = addFile(dirIndex,sizeIndex,dirs,rel,stringTable,relArcPaths,data) dirIndex = dirIndex+1 sizeIndex = retSize #print(hex(dirIndex)) dirs = retdirs data = retdata arcHeader.length = sizeIndex+len(stringTable)+0x20+0x20+0x30+(len(dirs)*0x14)+len(data) arcHeader.fileDataOffset=0x14E0 arcHeader.fileDataLen=len(data) arcHeader.fileDataLen2=arcHeader.fileDataLen infoBlock.firstNodeOffset = 0x20 infoBlock.firstDirOffset = 0x60 infoBlock.stringTableLen = len(stringTable) infoBlock.stringTableOffset = 0xB80 infoBlock.unk1 = 0x100 infoBlock.unk2 = 0 infoBlock.totalDirNum = len(dirs) outputArcFile = open(buildPath/"game/files/RELS.arc","wb") outputArcFile.seek(0) outputArcFile.write(struct.pack(">IIIIIIII",arcHeader.RARC,arcHeader.length,arcHeader.headerLength,arcHeader.fileDataOffset,arcHeader.fileDataLen,arcHeader.fileDataLen2,arcHeader.unk1,arcHeader.unk2)) outputArcFile.write(struct.pack(">IIIIIIHHI",infoBlock.numNodes,infoBlock.firstNodeOffset,infoBlock.totalDirNum,infoBlock.firstDirOffset,infoBlock.stringTableLen,infoBlock.stringTableOffset,infoBlock.numDirsThatAreFiles,infoBlock.unk1,infoBlock.unk2)) outputArcFile.write(struct.pack(">IIHHI",rootNode.NAME,rootNode.stringTableOffset,rootNode.hash,rootNode.numDirs,rootNode.firstDirIndex)) outputArcFile.write(struct.pack(">IIHHI",aMemNode.NAME,aMemNode.stringTableOffset,aMemNode.hash,aMemNode.numDirs,aMemNode.firstDirIndex)) outputArcFile.write(struct.pack(">IIHHI",mMemNode.NAME,mMemNode.stringTableOffset,mMemNode.hash,mMemNode.numDirs,mMemNode.firstDirIndex)) outputArcFile.write(bytearray(16)) for dir in dirs: outputArcFile.write(struct.pack(">HHHHIII",dir.dirIndex,dir.stringHash,dir.type,dir.stringOffset,dir.fileOffset,dir.fileLength,dir.unk1)) unkData = [0x8A ,0xCF ,0x7F ,0xA5 ,0x00 ,0x09 ,0x36 ,0x00 ,0x0D ,0x08 ,0xA0 ,0x00 ,0x00 ,0x0C ,0xD8 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x8B ,0x9B ,0xF7 ,0xA5 ,0x00 ,0x09 ,0x45 ,0x00 ,0x0D ,0x15 ,0x80 ,0x00 ,0x00 ,0x21 ,0xED ,0x00 ,0x00 ,0x00 ,0x00 ,0xFF ,0xFF ,0x00 ,0x2E ,0x02 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x02 ,0x00 ,0x00 ,0x00 ,0x10 ,0x00 ,0x00 ,0x00 ,0x00 ,0xFF ,0xFF ,0x00 ,0xB8 ,0x02 ,0x00 ,0x00 ,0x02 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x10 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, 0x00] outputArcFile.write(bytearray(unkData)) strBytearray = bytearray() strBytearray.extend(map(ord,stringTable)) outputArcFile.write(strBytearray) outputArcFile.write(bytearray(6)) outputArcFile.write(data) outputArcFile.truncate() outputArcFile.close() def main(gamePath,buildPath): if not gamePath.exists(): gamePath.mkdir(parents=True, exist_ok=True) iso = Path("gz2e01.iso") if not iso.exists() or not iso.is_file(): print("gz2e01.iso doesn't exist in project directory!") sys.exit(1) if not (gamePath/"files").exists() or not (gamePath/"sys").exists(): print("ISO is not extracted; extracting...") previousDir = os.getcwd() os.chdir(str(gamePath.absolute())) extract_game_assets.extract("../" + str(iso)) os.chdir(previousDir) print("Copying game files...") copy(gamePath,buildPath.absolute()) print(str(buildPath/"main_shift.dol")+" -> "+str(buildPath/"game/sys/main.dol")) shutil.copyfile(buildPath/"main_shift.dol",buildPath/"game/sys/main.dol") copyRelFiles(buildPath,aMemRels.splitlines(),mMemRels.splitlines()) if __name__ == "__main__": main(Path(sys.argv[1]),Path(sys.argv[2]))