aluigi wrote:Where is located that 32bit value? is it a little endian magic at offset 0? Like 0xEFC70728 -> 28 07 c7 ef
id-daemon knows what's going on saying those are the actual names of the resTypes, so I don't know how they are "translated".
so basically "49B156D4=MeshSet", "6BDE20BA=Texture" so on and so forth.
As far as I can tell, or mostly speculating, its the value inside each bundle where is located in every sb/toc where it tells what files to extract, if a bundle has lets say 10ebx 10meshset 10resType those entries should hold that info, unfortunately I am not that knowledgeable, every python script made for any frostbite game has that info, not 100% sure though, located somewhere in these lines it should explain how it gets that offset/entry.
Code: Select all
def hex2(num): return hexlify(pack(">I",num)) #e.g. 10 => '0000000a'
class Stub(): pass #generic struct for the cat entry
def readCat(catDict, catPath, rootPath):
"""Take a dict and fill it using a cat file: sha1 vs (offset, size, cas path)"""
cat=cas.unXor(catPath)
cat.seek(0,2) #get eof
catSize=cat.tell()
cat.seek(40) #skip nyan
casDirectory=os.path.dirname(catPath)+"\\" #get the full path so every entry knows whether it's from the patched or unpatched cat.
while cat.tell()<catSize:
entry=Stub()
sha1=cat.read(20)
entry.offset, entry.size, dummy, casNum = unpack("<IIII",cat.read(16))
entry.path=casDirectory+"cas_"+("0"+str(casNum) if casNum<10 else str(casNum))+".cas"
#if (dummy==0 and entry.size>4000000):
if dummy==0: catDict[sha1]=entry
def dump(tocPath, targetFolder):
"""Take the filename of a toc and dump all files to the targetFolder."""
print "Dumping '%s'..." % tocPath
#Depending on how you look at it, there can be up to 2*(3*3+1)=20 different cases:
# The toc has a cas flag which means all assets are stored in the cas archives. => 2 options
# Each bundle has either a delta or base flag, or no flag at all. => 3 options
# Each file in the bundle is one of three types: ebx/res/chunks => 3 options
# The toc itself contains chunks. => 1 option
#
#Simplify things by ignoring base bundles (they just state that the unpatched bundle is used),
#which is alright, as the user needs to dump the unpatched files anyway.
#
#Additionally, add some common fields to the ebx/res/chunks entries so they can be treated the same.
#=> 6 cases.
toc=cas.readToc(tocPath)
if not (toc.get("bundles") or toc.get("chunks")): return #there's nothing to extract (the sb might not even exist)
sbPath=tocPath[:-3]+"sb"
sb=open(sbPath,"rb")
for tocEntry in toc.bundles:
if tocEntry.get("base"): continue
sb.seek(tocEntry.offset)
###read the bundle depending on the four types (+cas+delta, +cas-delta, -cas+delta, -cas-delta) and choose the right function to write the payload
if toc.get("cas"):
bundle=cas.Entry(sb)
#make empty lists for every type to make it behave the same way as noncas
for listType in ("ebx","res","chunks"):
if listType not in vars(bundle):
vars(bundle)[listType]=[]
#The noncas chunks already have originalSize calculated in Bundle.py (it was necessary to seek through the entries).
#Calculate it for the cas chunks too. From here on, both cas and noncas ebx/res/chunks (within bundles) have size and originalSize.
for chunk in bundle.chunks:
chunk.originalSize=chunk.logicalOffset+chunk.logicalSize
#pick the right function
if tocEntry.get("delta"):
writePayload=casPatchedPayload
sourcePath=None #the noncas writing function requires a third argument, while the cas one does not. Hence make a dummy variable.
else:
writePayload=casPayload
sourcePath=None
else:
if tocEntry.get("delta"):
#The sb currently points at the delta file.
#Read the unpatched toc of the same name to get the base bundle.
#First of all though, get the correct path.
#Does it work like this?
# Update\Patch\Data\Win32\XP1\Levels\XP1_003\XP1_003.toc
#=> Update\Xpack1\Data\Win32\XP1\Levels\XP1_003\XP1_003.toc
xpNum=os.path.basename(tocPath)[2] #"XP1_003.toc" => "1"
split=tocPath.lower().rfind("patch")
baseTocPath=tocPath[:split]+"xpack"+xpNum+tocPath[split+5:]
if not os.path.exists(baseTocPath): #Nope? Then it must work like this:
# Update\Patch\Data\Win32\XP1Weapons.toc
#=> Data\Win32\XP1Weapons.toc
baseTocPath=tocPath[:split-7]+tocPath[split+6:] #just cut out Update\Patch
#now open the file and get the correct bundle (with the same name as the delta bundle)
baseToc=cas.readToc(baseTocPath)
for baseTocEntry in baseToc.bundles:
if baseTocEntry.id.lower() == tocEntry.id.lower():
break
else: #if no base bundle has with this name has been found:
pass #use the last base bundle. This is okay because it is actually not used at all (the delta has uses instructionType 3 only).
basePath=baseTocPath[:-3]+"sb"
base=open(basePath,"rb")
base.seek(baseTocEntry.offset)
bundle = noncas.patchedBundle(base, sb) #create a patched bundle using base and delta
base.close()
writePayload=noncasPatchedPayload
sourcePath=[basePath,sbPath] #base, delta
else:
bundle=noncas.unpatchedBundle(sb)
writePayload=noncasPayload
sourcePath=sbPath
###pick a good filename, make sure the file does not exist yet, create folders, call the right function to write the payload
for entry in bundle.ebx:
targetPath=targetFolder+"/bundles/ebx/"+entry.name+".ebx"
#if "sound/" in entry.name: continue
if prepareDir(targetPath): continue
writePayload(entry, targetPath, sourcePath)
for entry in bundle.res: #always add resRid to the filename. Add resMeta if it's not just nulls. resType becomes file extension.
targetPath=targetFolder+"/bundles/res/"+entry.name+" "+hexlify(pack(">Q",entry.resRid))
#if not "d_assault_newera_skb_01" in entry.name: continue
if entry.resMeta!="\0"*16: targetPath+=" "+hexlify(entry.resMeta)
if entry.resType not in resTypes: targetPath+=".unknownres "+hex2(entry.resType)
else: targetPath+=resTypes[entry.resType]
if prepareDir(targetPath): continue
writePayload(entry, targetPath, sourcePath)
for i in xrange(len(bundle.chunks)): #id becomes the filename. If meta is not empty, add it to filename.
entry=bundle.chunks[i]
targetPath=targetFolder+"/chunks/"+hexlify(entry.id) +".chunk" #keep the .chunk extension for legacy reasons
#if bundle.chunkMeta[i].meta!="\x00": targetPath+=" firstMip"+str(unpack("B",bundle.chunkMeta[i].meta[10])[0])
#chunkMeta is useless. The same payload may have several values for firstMips so chunkMeta contains info specific to bundles, not the file itself.
if prepareDir(targetPath): continue
#if hexlify(entry.id)[:8]=="882CD8F1":
writePayload(entry, targetPath, sourcePath)
#Deal with the chunks which are defined directly in the toc.
#These chunks do NOT know their originalSize.
#Available fields: id, offset, size
for entry in toc.chunks:
targetPath=targetFolder+"/chunks/"+hexlify(entry.id)+".chunk"
if prepareDir(targetPath): continue
if toc.get("cas"):
try:
catEntry=cat[entry.sha1]
#if not checkchunk(catEntry.path,catEntry.offset):
#if hexlify(entry.id)[:8]=="1f175f8e":
try:
process = subprocess.Popen(["fb_zstd.exe",catEntry.path,str(catEntry.offset),str(catEntry.size),targetPath],stderr=subprocess.PIPE,startupinfo=startupinfo)
process.communicate() #this should set the returncode
if process.returncode:
print process.stderr.readlines()
except:
print "Error executing fb_zstd."
print catEntry.path,str(catEntry.offset),str(catEntry.size),targetPath
except:
continue
else:
if not checkchunk(sbPath,entry.offset):
LZ77.decompressUnknownOriginalSize(sbPath,entry.offset,entry.size,targetPath)
sb.close()
Sorry If I cant be of much help.