diff --git a/Makefile b/Makefile index 6ee58131ab9..b3a8a8bcc8e 100644 --- a/Makefile +++ b/Makefile @@ -125,6 +125,7 @@ clean_rels: tools: $(ELF2DOL) assets: + @mkdir -p game @cd game; $(PYTHON) ../tools/extract_game_assets.py ../$(IMAGENAME) docs: @@ -149,6 +150,14 @@ $(DOL_SHIFT): $(ELF_SHIFT) | tools shift: dirs $(DOL_SHIFT) +game: | shift rels + @mkdir -p game + @$(PYTHON) tools/package_game_assets.py game $(BUILD_DIR) + +rungame: game + @echo If you are playing on a shifted game make sure Hyrule Field Speed hack is disabled in dolphin! + dolphin-emu $(BUILD_DIR)/game/sys/main.dol + # $(BUILD_DIR)/%.o: %.cpp @mkdir -p $(@D) @@ -170,4 +179,4 @@ include tools/elf2dol/Makefile ### Debug Print ### print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true -.PHONY: default all dirs clean tools docs shift print-% +.PHONY: default all dirs clean tools docs shift game rungame print-% diff --git a/rel/d/a/e/d_a_e_wb/d_a_e_wb.cpp b/rel/d/a/e/d_a_e_wb/d_a_e_wb.cpp index b149a30ec29..246722a94c6 100644 --- a/rel/d/a/e/d_a_e_wb/d_a_e_wb.cpp +++ b/rel/d/a/e/d_a_e_wb/d_a_e_wb.cpp @@ -277,7 +277,7 @@ extern "C" static void cMtx_XrotM__FPA4_fs(); extern "C" static void JMAFastSqrt__Ff(); extern "C" static void dComIfGp_particle_set__FUlUsPC4cXyzPC5csXyzPC4cXyz(); extern "C" void cancelOriginalDemo__9daHorse_cFv(); -extern "C" void __ct__10JAISoundIDFUl(); +extern "C" void __ct__10JAISoundIDFUl(u32* this_replacement,u32 param_0); extern "C" static void dComIfGp_getVibration__Fv(); extern "C" void __ct__4cXyzFfff(); extern "C" void onDemoJumpDistance__9daHorse_cFff(); @@ -4970,8 +4970,8 @@ asm void daHorse_c::cancelOriginalDemo() { #pragma pop /* 807E27D0-807E27D8 -00001 0008+00 0/0 0/0 0/0 .text __ct__10JAISoundIDFUl */ -JAISoundID::JAISoundID(u32 param_0) { - *(u32*)this = (u32)(param_0); +extern "C" void __ct__10JAISoundIDFUl(u32* this_replacement,u32 param_0) { + *(u32*)this_replacement = (u32)(param_0); } /* 807E27D8-807E27E8 010438 0010+00 1/1 0/0 0/0 .text dComIfGp_getVibration__Fv */ diff --git a/src/DynamicLink.cpp b/src/DynamicLink.cpp index cbdda00e692..097227d06de 100644 --- a/src/DynamicLink.cpp +++ b/src/DynamicLink.cpp @@ -372,7 +372,7 @@ bool DynamicModuleControl::do_load() { } } if(mModule==NULL&&sFileCache!=NULL) { - mModule = (OSModuleInfo*)sFileCache->getResource(0x72656C73,buffer); + mModule = (OSModuleInfo*)sFileCache->getResource(0x72656C73/*rels*/,buffer); if(mModule!=NULL) { mSize = 0; mResourceType = 11; diff --git a/tools/extract_game_assets.py b/tools/extract_game_assets.py index 77eca329edc..44124d3d3cb 100644 --- a/tools/extract_game_assets.py +++ b/tools/extract_game_assets.py @@ -7,6 +7,13 @@ Usage: `python tools/extract_game_assets.py` """ fstInfoPosition = 0x424 +bootPosition = 0x0 +bootSize = 0x440 +bi2Position = 0x440 +bi2Size = 0x2000 +apploaderPosition = 0x2440 +dolInfoPosition = 0x420 + numFileEntries = 0 """ @@ -94,7 +101,7 @@ Write the current folder to disk and return it's name/last entry number def writeFolder(parsedFstBin, i): - folderPath = i["folderName"] + "/" + folderPath = i["folderName"] + "/" lastEntryNumber = i["lastEntryNumber"] if i["parentFolderEntryNumber"] == 0: @@ -125,6 +132,9 @@ def writeAssets(parsedFstBin, handler): # Write the folder structure and files to disc j = 0 folderStack = [] + if not os.path.exists("./files/"): + os.makedirs("./files/") + os.chdir('./files/') folderStack.append({"folderName": "./", "lastEntryNumber": numFileEntries}) for i in parsedFstBin: j += 1 @@ -143,6 +153,38 @@ def writeAssets(parsedFstBin, handler): while folderStack[-1]["lastEntryNumber"] == j + 1: folderStack.pop() +def writeSys(boot,bi2,apploader,dol,fst): + if not os.path.exists("./sys/"): + os.makedirs("./sys/") + open("./sys/boot.bin","wb").write(boot) + open("./sys/bi2.bin","wb").write(bi2) + open("./sys/apploader.img","wb").write(apploader) + open("./sys/main.dol","wb").write(dol) + open("./sys/fst.bin","wb").write(fst) + +def getDolInfo(disc): + disc.seek(dolInfoPosition) + dolOffset = int.from_bytes(bytearray(disc.read(4)), byteorder="big") + dolSize = 0 + for i in range(7): + disc.seek(dolOffset+(i*4)) + segmentOffset = int.from_bytes(bytearray(disc.read(4)), byteorder="big") + disc.seek(dolOffset+0x90+(i*4)) + segmentSize = int.from_bytes(bytearray(disc.read(4)), byteorder="big") + if (segmentOffset+segmentSize)>dolSize: + dolSize = segmentOffset + segmentSize + + for i in range(11): + disc.seek(dolOffset+0x1c+(i*4)) + dataOffset = int.from_bytes(bytearray(disc.read(4)), byteorder="big") + disc.seek(dolOffset+0xac+(i*4)) + dataSize = int.from_bytes(bytearray(disc.read(4)), byteorder="big") + if (dataOffset+dataSize)>dolSize: + dolSize = dataOffset + dataSize + + return dolOffset, dolSize + + def extract(path): with open(path, "rb") as f: @@ -150,10 +192,30 @@ def extract(path): f.seek(fstInfoPosition) fstOffset, fstSize = getFstInfo(f, fstInfoPosition) + f.seek(bootPosition) + bootBytes = bytearray(f.read(bootSize)) + + f.seek(bi2Position) + bi2Bytes = bytearray(f.read(bi2Size)) + + f.seek(apploaderPosition+0x14) + apploaderSize = int.from_bytes(bytearray(f.read(4)), byteorder="big") + f.seek(apploaderPosition+0x18) + trailerSize = int.from_bytes(bytearray(f.read(4)), byteorder="big") + apploaderMainSize = 0x20 + apploaderSize + trailerSize + f.seek(apploaderPosition) + apploaderBytes = bytearray(f.read(apploaderMainSize)) + dolOffset, dolSize = getDolInfo(f) + f.seek(dolOffset) + dolBytes = bytearray(f.read(dolSize)) + + # Seek to fst.bin and retrieve it f.seek(fstOffset) fstBinBytes = bytearray(f.read(fstSize)) + writeSys(bootBytes,bi2Bytes,apploaderBytes,dolBytes,fstBinBytes) + # Parse fst.bin parsedFstBin = parseFstBin(fstBinBytes) @@ -162,7 +224,7 @@ def extract(path): def main(): - extract(sys.argv[1], "rb") + extract(sys.argv[1]) if __name__ == "__main__": diff --git a/tools/package_game_assets.py b/tools/package_game_assets.py new file mode 100644 index 00000000000..5ca061f275b --- /dev/null +++ b/tools/package_game_assets.py @@ -0,0 +1,433 @@ +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])) \ No newline at end of file diff --git a/tools/tp.py b/tools/tp.py index ff70a71a413..64c0eed53ae 100644 --- a/tools/tp.py +++ b/tools/tp.py @@ -351,7 +351,7 @@ def check(debug, rels, game_path, build_path): CONSOLE.print(text) try: - check_sha1(game_path, build_path, rels) + check_sha1(game_path/"files", build_path, rels) text = Text(" OK") text.stylize("bold green") CONSOLE.print(text)