Advertisement
Nicknine

Detroit Sound Extractor

Jan 21st, 2024 (edited)
888
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.24 KB | None | 0 0
  1. import os
  2. import struct
  3. import io
  4.  
  5. # Parameters
  6. idxFile=r"D:\Games\Steam\steamapps\common\Detroit Become Human\BigFile_PC.idx"
  7. bnkDir=r"E:\GameRips\detroit\pc"
  8. wemDir=r"E:\GameRips\detroit\pc\wem"
  9.  
  10. extractBnk=True
  11. extractWem=False
  12.  
  13.  
  14.  
  15. # Script starts here
  16. idx=dict()
  17. datHandles=dict()
  18. bigFileName=None
  19.  
  20. class FileEntry:
  21.     def __init__(self,f):
  22.         data=struct.unpack(">7I",f.read(0x1c))
  23.         self.typ=data[0]
  24.         self.count=data[1]
  25.         self.id=data[2]
  26.         self.offset=data[3]
  27.         self.size=data[4]
  28.         self.extraSize=data[5]
  29.         self.datNum=data[6]
  30.  
  31. def parseIdx(fname):
  32.     name,ext=os.path.splitext(fname)
  33.     global bigFileName
  34.     bigFileName=name
  35.  
  36.     f=open(fname,"rb")
  37.     f.seek(0x65)
  38.     numFiles=struct.unpack(">I",f.read(0x04))[0]
  39.     for i in range(numFiles):
  40.         entry=FileEntry(f)
  41.         idx[entry.typ,entry.id]=entry
  42.     f.close()
  43.  
  44. def loadFile(ref):
  45.     entry=idx[ref]
  46.     if entry.datNum in datHandles:
  47.         f=datHandles[entry.datNum]
  48.     else:
  49.         name,ext=os.path.splitext(bigFileName)
  50.         newExt=".dat" if entry.datNum==0 else ".d%02d" % (entry.datNum)
  51.         datName=name+newExt
  52.         f=open(datName,"rb")
  53.         datHandles[entry.datNum]=f
  54.  
  55.     f.seek(entry.offset)
  56.     data=f.read(entry.size)
  57.     return data
  58.  
  59. def readHeader(f,expectedMagic=None):
  60.     magic,ver=struct.unpack("<8sI",f.read(0x0c))
  61.     if expectedMagic and magic!=expectedMagic:
  62.         raise Exception("Bad header magic at 0x%08x! Expected %s, got %s!" %
  63.                         (f.tell()-0x0c,expectedMagic,magic))
  64.  
  65. def readString(f):
  66.     size=struct.unpack("<I",f.read(0x04))[0]
  67.     return f.read(size).decode()
  68.  
  69. def readQZip(f):
  70.     magic,flag=struct.unpack("<4sB",f.read(0x05))
  71.     if magic!=b"QZIP":
  72.         raise Exception("Bad QZIP magic at 0x%08x!" % (f.tell()-0x05))
  73.  
  74. def readComCont(f,dc=False):
  75.     readHeader(f,b"COM_CONT")
  76.     size,numRefs=struct.unpack("<II",f.read(0x08))
  77.     refs=list()
  78.     for i in range(numRefs):
  79.         typ,id,flag=struct.unpack("<IIB",f.read(0x09))
  80.         refs.append((typ,id))
  81.  
  82.     # Legacy thing from Fahrenheit and early Heavy Rain, always empty.
  83.     readHeader(f,b"LOADCONT")
  84.     if not dc:
  85.         size=struct.unpack("<I",f.read(0x04))[0]
  86.         f.seek(size,1)
  87.  
  88.     return refs
  89.  
  90. def closeDats():
  91.     for f in datHandles.values():
  92.         f.close()
  93.     datHandles.clear()
  94.  
  95. def extractSoundBanks(outpath):
  96.     print("Extracting BNK...")
  97.     os.makedirs(outpath,exist_ok=True)
  98.  
  99.     for ref in idx:
  100.         if ref[0]!=1022:
  101.             continue
  102.  
  103.         #print("%s: %s" % (ref[0],ref[1]))
  104.         f=io.BytesIO(loadFile(ref))
  105.  
  106.         comRefs=readComCont(f)
  107.         readHeader(f,b"CSNDBNK_")
  108.         size=struct.unpack("<I",f.read(0x04))[0]
  109.         bnkId=readString(f)
  110.         bnkName=readString(f)
  111.         print("%s: %s" % (bnkId,bnkName))
  112.  
  113.         f2=io.BytesIO(loadFile(comRefs[0]))
  114.  
  115.         # HACK: There's no offsets table for assets inside Data Container
  116.         # so we just look for CSNDBKDT string.
  117.         pos=f2.getvalue().find(b"CSNDBKDT")
  118.         if pos==-1:
  119.             print("No BNK data for %s!" % (bnkName))
  120.             continue
  121.  
  122.         f2.seek(pos)
  123.         readHeader(f2)
  124.         size=struct.unpack("<I",f2.read(0x04))[0]
  125.         bnkData=f2.read(size)
  126.         out=open(os.path.join(outpath,bnkName+".bnk"),"wb")
  127.         out.write(bnkData)
  128.         out.close()
  129.  
  130.     closeDats()
  131.  
  132. def extractSoundData(outpath):
  133.     print("Extracting WEM...")
  134.     os.makedirs(outpath,exist_ok=True)
  135.  
  136.     for ref in idx:
  137.         if ref[0]!=1033:
  138.             continue
  139.  
  140.         f=io.BytesIO(loadFile(ref))
  141.         readQZip(f)
  142.         readHeader(f,b"CSNDDATA")
  143.         size=struct.unpack("<I",f.read(0x04))[0]
  144.         unkStr=readString(f)
  145.         unkNum=struct.unpack("<I",f.read(0x04))[0]
  146.         wemName=readString(f)
  147.         wemSize=struct.unpack("<I",f.read(0x04))[0]
  148.         wemData=f.read(wemSize)
  149.         #print(wemName)
  150.         out=open(os.path.join(outpath,wemName+".wem"),"wb")
  151.         out.write(wemData)
  152.         out.close()
  153.  
  154.     closeDats()
  155.  
  156. if __name__=="__main__":
  157.     parseIdx(idxFile)
  158.  
  159.     if extractBnk:
  160.         extractSoundBanks(bnkDir)
  161.  
  162.     if extractWem:
  163.         extractSoundData(wemDir)
  164.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement