Nicknine

SIM/SID Extractor

May 10th, 2022 (edited)
1,314
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.05 KB | None | 0 0
  1. import os
  2. import sys
  3. import struct
  4. import io
  5. import zipfile
  6.  
  7. import vdf
  8. from Crypto.Cipher import AES
  9.  
  10. def readNullTerminatedString(f,offset):
  11.     prevOffset=f.tell()
  12.     f.seek(offset)
  13.  
  14.     result=b""
  15.     while 1:
  16.         byte=f.read(1)
  17.         if byte==b"\x00": break
  18.         result+=byte
  19.  
  20.     f.seek(prevOffset)
  21.     return result.decode()
  22.  
  23. currentDisc=0
  24. volHandles=dict()
  25.  
  26. class Volume:
  27.     def __init__(self,volName):
  28.         self.f=open(volName,"rb")
  29.         self.size=os.fstat(self.f.fileno()).st_size
  30.  
  31. def openVolume(baseName,disc,vol):
  32.     volName="%s_disk%d_%d.sid" % (baseName,disc,vol)
  33.  
  34.     global currentDisc
  35.     if disc!=currentDisc:
  36.         # Changing disc - close all volumes.
  37.         closeVolumes()
  38.  
  39.     if vol not in volHandles:
  40.         while not os.path.isfile(volName):
  41.             input("Need %s, insert disc %d and press Enter..." % (os.path.basename(volName),disc))
  42.  
  43.         currentDisc=disc
  44.         volHandles[vol]=Volume(volName)
  45.  
  46.     return volHandles[vol].f
  47.  
  48. def openNextVolume(baseName,disc,vol):
  49.     vol+=1
  50.     volName="%s_disk%d_%d.sid" % (baseName,disc,vol)
  51.     if not os.path.isfile(volName):
  52.         # Change disc.
  53.         disc+=1
  54.         vol=0
  55.  
  56.     f=openVolume(baseName,disc,vol)
  57.     f.seek(0x00)
  58.     return f,disc,vol
  59.  
  60. def getVolumeSize(vol):
  61.     return volHandles[vol].size
  62.  
  63. def closeVolumes():
  64.     for vol in volHandles.values():
  65.         vol.f.close()
  66.     volHandles.clear()
  67.  
  68. def extractDepot(baseName,curDisc,targetDepot,keystring,outDir):
  69.     if keystring:
  70.         key=bytes.fromhex(keystring+"A8194D02193CD03792937D27590AECBD")
  71.     else:
  72.         key=None
  73.  
  74.     f=open("%s_disk%d.sim" % (baseName,curDisc),"rb")
  75.     magic,version,discs,stringsSize=struct.unpack("<IIII",f.read(0x10))
  76.     if magic!=0x3FD04C1F: raise Exception("Bad header magic in %s" % (f.name))
  77.     if version!=1: raise Exception("Unknown version %d in %s" % (version,f.name))
  78.  
  79.     s=io.BytesIO(f.read(stringsSize))
  80.     tableSize,numFiles=struct.unpack("<II",f.read(0x08))
  81.     t=io.BytesIO(f.read(tableSize))
  82.     f.close()
  83.  
  84.     for i in range(numFiles):
  85.         nameOffset,pathOffset,depot,offset,size,disc,vol,isEnc,pad=struct.unpack("<IIIQQBBBB",t.read(0x20))
  86.         if depot!=targetDepot:
  87.             continue
  88.  
  89.         name=readNullTerminatedString(s,nameOffset)
  90.         path=readNullTerminatedString(s,pathOffset)
  91.         path=os.path.normpath(path.replace("\\",os.sep))
  92.         print(os.path.join(path,name))
  93.         #print(t.tell()-0x20)
  94.  
  95.         if isEnc and not key:
  96.             print("File is encrypted and no depot key was provided! Skipping...")
  97.             continue
  98.  
  99.         f2=openVolume(baseName,disc,vol)
  100.         f2.seek(offset)
  101.         #print("%d %d 0x%08x" % (disc,vol,offset))
  102.  
  103.         os.makedirs(os.path.join(outDir,path),exist_ok=True)
  104.         fname=os.path.join(outDir,path,name)
  105.         out=open(fname,"wb")
  106.  
  107.         bytesWritten=0
  108.         while bytesWritten!=size:
  109.             if f2.tell()==getVolumeSize(vol):
  110.                 # Reached EOF, open next volume.
  111.                 f2,disc,vol=openNextVolume(baseName,disc,vol)
  112.  
  113.             chunkZSize,chunkSize=struct.unpack("<II",f2.read(0x08))
  114.             padSize=(chunkZSize>>24)&0xFF
  115.             chunkZSize&=0x00FFFFFF
  116.             flags=(chunkSize>>24)&0xFF
  117.             chunkSize&=0x00FFFFFF
  118.  
  119.             data=f2.read(chunkZSize)
  120.             bytesWritten+=chunkSize
  121.  
  122.             if flags&0x01:
  123.                 if not key:
  124.                     print("Encountered encrypted chunk and no depot key was provided! Skipping the file...")
  125.                     out.close()
  126.                     os.remove(fname)
  127.                     break
  128.  
  129.                 decSize=chunkZSize-padSize
  130.  
  131.                 # Decrypt IV.
  132.                 cipher=AES.new(key,AES.MODE_ECB)
  133.                 iv=cipher.decrypt(data[:0x10])
  134.  
  135.                 # Decrypt data.
  136.                 cipher=AES.new(key,AES.MODE_CBC,iv=iv)
  137.                 data=cipher.decrypt(data[0x10:])[:decSize]
  138.  
  139.             if flags&0x02:
  140.                 zipf=zipfile.ZipFile(io.BytesIO(data))
  141.                 out.write(zipf.read("zip"))
  142.                 zipf.close()
  143.             else:
  144.                 out.write(data)
  145.  
  146.         out.close()
  147.  
  148.     closeVolumes()
  149.  
  150. if __name__=="__main__":
  151.     if len(sys.argv)<4:
  152.         print("Usage: sim_extract.py <simpath> <depotid> <outpath>")
  153.         sys.exit(0)
  154.  
  155.     fname=sys.argv[1]
  156.     depotStr=sys.argv[2]
  157.     outDir=sys.argv[3]
  158.  
  159.     if not fname.lower().endswith(".sim"):
  160.         raise Exception("Not a SIM file")
  161.  
  162.     kv=vdf.load(open("legacydepotdata.vdf","r"))
  163.     if depotStr in kv["depots"]:
  164.         keystring=kv["depots"][depotStr]
  165.         print("Depot key: %s" % keystring)
  166.     else:
  167.         print("No depot key for depot %s in VDF, assuming the depot is unencrypted." % depotStr)
  168.         keystring=""
  169.  
  170.     pos=fname.lower().find("_disk")
  171.     if pos==-1:
  172.         raise Exception("Bad SIM name")
  173.  
  174.     baseName=fname[:pos]
  175.     disc=int(fname[pos+5:-4])
  176.     depot=int(depotStr)
  177.     extractDepot(baseName,disc,depot,keystring,outDir)
  178.  
Advertisement
Add Comment
Please, Sign In to add comment