mirror of https://github.com/zeldaret/tp
add asset extract script
This commit is contained in:
parent
b728ec1ef5
commit
a26a9eda24
|
|
@ -12,6 +12,10 @@ vtable.lcf
|
|||
# Game Assets
|
||||
game/
|
||||
|
||||
# Disk Images
|
||||
*.iso
|
||||
*.gcm
|
||||
|
||||
# Generated documentation
|
||||
docs/doxygen/
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
"""
|
||||
Extracts the game assets and stores them in the game folder
|
||||
Usage: `python tools/extract_game_assets.py`
|
||||
"""
|
||||
|
||||
fstInfoPosition = 0x424
|
||||
numFileEntries = 0
|
||||
|
||||
"""
|
||||
Returns the offset address and size of fst.bin
|
||||
"""
|
||||
def getFstInfo(handler, fstOffsetPosition):
|
||||
fstOffset = int.from_bytes(bytearray(handler.read(4)), byteorder='big')
|
||||
handler.seek(fstOffsetPosition + 4) # Get the size which is 4 bytes after the offset
|
||||
fstSize = int.from_bytes(bytearray(handler.read(4)), byteorder='big')
|
||||
return fstOffset, fstSize
|
||||
|
||||
"""
|
||||
Parses the fst.bin into a list of dictionaries containing
|
||||
the file name, file offset into the ISO, the file size,
|
||||
and the folder structure
|
||||
"""
|
||||
def parseFstBin(fstBinBytes):
|
||||
currentByte = 0
|
||||
numFileEntries = int.from_bytes(fstBinBytes[10:12], byteorder='big') # fst.bin offset
|
||||
stringTableOffset = numFileEntries * 0xC
|
||||
|
||||
ret = []
|
||||
|
||||
while currentByte != (numFileEntries*12):
|
||||
currentByte += 12
|
||||
|
||||
# lazy
|
||||
if currentByte == (numFileEntries*12):
|
||||
break
|
||||
|
||||
fileFolder = fstBinBytes[currentByte]
|
||||
filenameOffset = int.from_bytes(fstBinBytes[currentByte+1:currentByte+4], byteorder='big')
|
||||
fileOffsetOrParentEntryNum = int.from_bytes(fstBinBytes[currentByte+4:currentByte+8], byteorder='big')
|
||||
fileSizeOrLastEntryNum = int.from_bytes(fstBinBytes[currentByte+8:currentByte+12], byteorder='big')
|
||||
currentFilenameOffset = stringTableOffset+filenameOffset
|
||||
|
||||
# Figure out the filename by checking for null string terminator
|
||||
i = 0
|
||||
while fstBinBytes[currentFilenameOffset+i] != 0:
|
||||
i += 1
|
||||
|
||||
fileName = (fstBinBytes[currentFilenameOffset:currentFilenameOffset+i]).decode()
|
||||
|
||||
if fileFolder == 0:
|
||||
ret.append({"type": "File","fileName": fileName,"fileOffset":fileOffsetOrParentEntryNum,"fileSize":fileSizeOrLastEntryNum})
|
||||
else:
|
||||
ret.append({"type": "Folder","folderName": fileName,"parentFolderEntryNumber": fileOffsetOrParentEntryNum, "lastEntryNumber": fileSizeOrLastEntryNum})
|
||||
|
||||
return ret
|
||||
|
||||
"""
|
||||
Write the current folder to disk and return it's name/last entry number
|
||||
"""
|
||||
def writeFolder(parsedFstBin,i):
|
||||
folderPath = i["folderName"]+"/"
|
||||
lastEntryNumber = i["lastEntryNumber"]
|
||||
|
||||
if i["parentFolderEntryNumber"] == 0:
|
||||
if not os.path.exists(folderPath):
|
||||
os.makedirs(folderPath)
|
||||
else:
|
||||
parentFolderEntry = parsedFstBin[i["parentFolderEntryNumber"]-1]
|
||||
while True:
|
||||
folderPath = parentFolderEntry["folderName"] + "/" + folderPath
|
||||
if parentFolderEntry["parentFolderEntryNumber"] == 0:
|
||||
break
|
||||
|
||||
nextParentFolderEntryNumber = parentFolderEntry["parentFolderEntryNumber"]
|
||||
parentFolderEntry = parsedFstBin[nextParentFolderEntryNumber-1]
|
||||
|
||||
if not os.path.exists(folderPath):
|
||||
os.makedirs(folderPath)
|
||||
|
||||
return folderPath, lastEntryNumber
|
||||
|
||||
"""
|
||||
Use the parsed fst.bin contents to write assets to file
|
||||
"""
|
||||
def writeAssets(parsedFstBin, handler):
|
||||
# Write the folder structure and files to disc
|
||||
j = 0
|
||||
folderStack = []
|
||||
folderStack.append({"folderName": "./", "lastEntryNumber": numFileEntries})
|
||||
for i in parsedFstBin:
|
||||
j += 1
|
||||
if i["type"] == "Folder":
|
||||
currentFolder, lastEntryNumber = writeFolder(parsedFstBin,i)
|
||||
folderStack.append({"folderName": currentFolder, "lastEntryNumber": lastEntryNumber})
|
||||
else:
|
||||
handler.seek(i["fileOffset"])
|
||||
with open((folderStack[-1]["folderName"]+i["fileName"]), "wb") as currentFile:
|
||||
currentFile.write(bytearray(handler.read(i["fileSize"])))
|
||||
|
||||
while folderStack[-1]["lastEntryNumber"] == j+1:
|
||||
folderStack.pop()
|
||||
|
||||
def main():
|
||||
with open(sys.argv[1], "rb") as f:
|
||||
# Seek to fst offset information and retrieve it
|
||||
f.seek(fstInfoPosition)
|
||||
fstOffset,fstSize = getFstInfo(f,fstInfoPosition)
|
||||
|
||||
# Seek to fst.bin and retrieve it
|
||||
f.seek(fstOffset)
|
||||
fstBinBytes = bytearray(f.read(fstSize))
|
||||
|
||||
# Parse fst.bin
|
||||
parsedFstBin = parseFstBin(fstBinBytes)
|
||||
|
||||
# Write assets to file
|
||||
writeAssets(parsedFstBin, f)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue