Created
January 16, 2018 22:25
-
-
Save NWPlayer123/503934b4d029af7d0449e6b8dc646d63 to your computer and use it in GitHub Desktop.
Recursive SARCExtract (goes through all ones in a directory, but not subdirectories)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from struct import pack, unpack | |
from os import mkdir, makedirs, listdir | |
from os.path import exists | |
import sys | |
def half(en, f): | |
return unpack("%sH" % en, f.read(2))[0] | |
def full(en, f): | |
return unpack("%sI" % en, f.read(4))[0] | |
def getstr(f): | |
ret = "";char = f.read(1) | |
while char != "\x00": | |
ret += char | |
char = f.read(1) | |
return ret | |
def warn(a, b): | |
try: assert a == True | |
except Exception, err: | |
sys.stderr.write("WARNING: " + b) | |
def err(a, b): | |
try: assert a == True | |
except Exception, err: | |
sys.stderr.write("ERROR: " + b) | |
sys.exit(1) | |
def gethash(name, multiplier): | |
result = 0 | |
for i in range(len(name)): | |
result = ord(name[i]) + (result * multiplier) | |
result &= 0xFFFFFFFF | |
return result | |
def getext(magic): | |
if magic == "BNTX": #Switch Texture | |
return "bntx" | |
elif magic == "BNSH": #Switch Shader ??? | |
return "bnsh" | |
elif magic == "FLAN": #Wii U Layout Animation | |
return "bflan" | |
elif magic == "FLYT": #Wii U Layout Metadata | |
return "bflyt" | |
else: | |
return "bin" | |
if __name__ == "__main__": | |
print("SARCExtract vIDK by NWPlayer123") | |
print("written June 12th, 17th 2017") | |
print("hopefully futureproof this time") | |
print("") | |
for name in listdir("."): | |
if name.endswith(".sarc"): | |
with open(name, "rb") as f: | |
#------------------------------------------------------------- | |
err(f.read(4) == b"SARC", "not a SARC file") | |
f.seek(6) #need to read endian first before 0x14 | |
en = ["<", ">"][unpack(">H", f.read(2))[0] & 1] | |
f.seek(4) #now we can check it w/endian | |
err(half(en, f) == 0x14, "header size != 0x14, not supp.") | |
f.seek(0, 2) #end of file, verify correct size | |
size = f.tell() | |
f.seek(8) #stored offset | |
err(full(en, f) == size, "filesize mismatch, truncated?") | |
start = full(en, f) #of raw data | |
#err(full(en, f) == 0x100, "number at 0x10 is not 256, ?????") | |
f.seek(4, 1) # ^^^ was giving issues | |
#------------------------------------------------------------- | |
err(f.read(4) == b"SFAT", "magic mismatch, SFAT missing?") | |
err(half(en, f) == 12, "SFAT header size != 12, not supp.") | |
count = half(en, f) | |
multi = full(en, f) | |
warn(multi == 0x65, "hash mult. not 0x65, may not be supp.") | |
attr = [unpack("%s4I" % en, f.read(16)) for i in range(count)] | |
#------------------------------------------------------------- | |
err(f.read(4) == b"SFNT", "magic mismatch, SFNT missing?") | |
err(half(en, f) == 8, "SFNT header size != 8, not supp.") | |
err(half(en, f) == 0, "SFNT header half 2 != 0, ?????") | |
SFNT = f.tell() | |
base = ".".join(name.split(".")[:-1]) #remove extension | |
if not exists(base): mkdir(base) | |
#------------------------------------------------------------- | |
print("%s%s %s" % ("NAME".ljust(80), "SIZE", "HASH")) | |
unknown = 0 | |
for i in range(count): | |
if attr[i][1] & 0x01000000: #file has name | |
f.seek(SFNT + ((attr[i][1] & 0xFFFF) * 4)) | |
name = getstr(f) | |
err(gethash(name, multi) == attr[i][0], | |
"name hash failed: " + name) | |
else: | |
f.seek(start + attr[i][2]) | |
name = "UNKNOWN%d.%s" % (unknown, getext(f.read(4))) | |
unknown += 1 | |
if len(name) < 80: | |
print("%s%08X %08X" % (name.ljust(80), attr[i][3]\ | |
- attr[i][2], attr[i][0])) | |
else: | |
trun = "..." + name[-76:] + " " | |
print("%s%08X %08X" % (trun, attr[i][3]-attr[i][2],\ | |
attr[i][0])) | |
if "/" in name: #assume all are encoded this way | |
path = base + "/" + "/".join(name.split("/")[:-1]) | |
else: | |
path = base | |
if not exists(path): makedirs(path) | |
with open(base + "/" + name, "wb") as o: | |
f.seek(start + attr[i][2]) | |
o.write(f.read(attr[i][3]-attr[i][2])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment