tp/tools/package_game_assets.py

527 lines
13 KiB
Python

import os
import sys
import shutil
import extract_game_assets
from pathlib import Path
import hashlib
import struct
import ctypes
import oead
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:
if os.path.getmtime(inFile) > os.path.getmtime(outputFile):
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)
hash = ctypes.c_ushort(hash)
hash = hash.value
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.unk1 = 0
fileData = open(path, "rb")
compressedData = oead.yaz0.compress(fileData.read())
padding = 0x20 - (len(compressedData) % 0x20)
file.fileLength = len(compressedData)
file.fileOffset = sizeIndex
sizeIndex = sizeIndex + file.fileLength + padding
data += compressedData
data += bytearray(padding)
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)
if relArcFound == False:
print(
str(fullPath)
+ " -> "
+ str(buildPath / "game/files/rel/Final/Release" / file)
)
relSource = open(fullPath, "rb")
data = relSource.read()
relSource.close()
data = oead.yaz0.compress(data)
relNew = open(
buildPath / "game/files/rel/Final/Release" / file, "wb"
)
relNew.write(data)
relNew.truncate()
relNew.close()
else:
relArcPaths.append(fullPath)
arcHeader = HEADER()
arcHeader.RARC = 0x52415243
arcHeader.headerLength = 0x20
arcHeader.unk1 = 0
arcHeader.unk2 = 0
infoBlock = INFO()
infoBlock.numNodes = 3
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 = ".\0..\0rels\0amem\0"
for rel in aMemList:
stringTable = stringTable + rel + "\0"
stringTable = stringTable + "mmem\0"
for rel in mMemList:
stringTable = stringTable + rel + "\0"
stringTable = stringTable + "\0\0\0\0\0\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
dirs.append(unkDir)
dirs.append(unkDir2)
dirIndex = dirIndex + 2
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
unkDir3 = DIRECTORY()
unkDir3.dirIndex = 0xFFFF
unkDir3.stringHash = 0x2E
unkDir3.type = 0x200
unkDir3.stringOffset = 0
unkDir3.fileOffset = 2
unkDir3.fileLength = 0x10
unkDir3.unk1 = 0
unkDir4 = DIRECTORY()
unkDir4.dirIndex = 0xFFFF
unkDir4.stringHash = 0xB8
unkDir4.type = 0x200
unkDir4.stringOffset = 2
unkDir4.fileOffset = 0
unkDir4.fileLength = 0x10
unkDir4.unk1 = 0
dirs.append(unkDir3)
dirs.append(unkDir4)
dirIndex = dirIndex + 2
arcHeader.length = (
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 = 0x8E
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.unk1,
arcHeader.fileDataLen2,
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,
)
)
outputArcFile.write(bytearray(8))
strBytearray = bytearray()
strBytearray.extend(map(ord, stringTable))
outputArcFile.write(strBytearray)
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]))