Skip to content

Instantly share code, notes, and snippets.

@NWPlayer123
Created January 16, 2018 22:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NWPlayer123/503934b4d029af7d0449e6b8dc646d63 to your computer and use it in GitHub Desktop.
Save NWPlayer123/503934b4d029af7d0449e6b8dc646d63 to your computer and use it in GitHub Desktop.
Recursive SARCExtract (goes through all ones in a directory, but not subdirectories)
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