Shiftable

This commit is contained in:
jdflyer 2022-01-15 17:59:59 -07:00
parent c72012c5d8
commit e1246d9b0a
6 changed files with 512 additions and 8 deletions

View File

@ -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-%

View File

@ -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 */

View File

@ -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;

View File

@ -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__":

View File

@ -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]))

View File

@ -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)