Advertisement
Guest User

Grimrock spider remover (v0.1)

a guest
Apr 17th, 2012
755
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.62 KB | None | 0 0
  1. #! python
  2.  
  3. # Extract dungeon level files, replace spider with something else.
  4. import os.path, shutil, struct, zlib, sys
  5.  
  6. # Monster to replace spider by on each level, 1..13
  7. replacements = [
  8.   None,
  9.   "green_slime",  # level 1
  10.   "green_slime",  # level 2
  11.   "green_slime",  # level 3
  12.   "green_slime",  # level 4
  13.   "green_slime",  # level 5
  14.   "green_slime",  # level 6
  15.   "green_slime",  # level 7
  16.   "green_slime",  # level 8
  17.   "green_slime",  # level 9
  18.   "green_slime",  # level 10
  19.   "green_slime",  # level 11
  20.   "green_slime",  # level 12
  21.   "green_slime"   # level 13
  22. ]
  23.  
  24.  
  25. def backupDatFile(datfile):
  26.   if (not os.path.exists(datfile)): raise "File not found: %s" % datfile
  27.   backupfile = datfile + ".orig"
  28.   if (not os.path.exists(backupfile)):
  29.     print "Making backup of grimrock.dat file"
  30.     shutil.copyfile(datfile, backupfile)
  31.   else:
  32.     print "Restoring backup of grimrock.dat file"
  33.     shutil.copyfile(backupfile, datfile)
  34.   print "... done."
  35.  
  36. class GRAR:
  37.   def __init__(self, filename):
  38.     self.datfile = filename
  39.     self.directory = self.readDatDirectory()
  40.  
  41.   def readDatDirectory(self):
  42.     file = open(self.datfile, "rb")
  43.     header = file.read(4)
  44.     if (header != "GRAR"): raise "Bad file header: %4s" % header
  45.     (fileCount,) = struct.unpack("I", file.read(4))
  46.     result = {}
  47.     for i in xrange(0, fileCount):
  48.       hash, pos, length, ulength = struct.unpack("IIII", file.read(16))
  49.       result[hash] = (i, pos, length, ulength)
  50.     file.close()
  51.     return result
  52.  
  53.   def filenameHash(self, s):
  54.     v = 0x811c9dc5
  55.     for c in s:
  56.       v ^= ord(c)
  57.       v = (v * 0x1000193) & 0xffffffff
  58.     return v
  59.  
  60.   def getFileEntry(self, filename):
  61.     hash = self.filenameHash(filename)
  62.     return self.getHashEntry(hash)
  63.  
  64.   def getHashEntry(self, hash):
  65.     if (hash in self.directory):
  66.       return self.directory[hash]
  67.     raise "File not found: %s" % filename
  68.  
  69.   def readFile(self, filename):
  70.     index, pos, length, ulength = self.getFileEntry(filename)
  71.     packed = True
  72.     if (length == 0):
  73.       length = ulength
  74.       packed = false
  75.     file = open(self.datfile, "rb")
  76.     file.seek(pos)
  77.     content = file.read(length)
  78.     file.close()
  79.     if packed:
  80.       (unpackedLength,) = struct.unpack("I", content[0:4])
  81.       if (unpackedLength != ulength): raise "Unexpected stored length of file %s" % filename
  82.       content = zlib.decompress(content[4:])
  83.       if (len(content) != ulength): raise "Unexpected unpacked data length of file %s" % filename
  84.     return content
  85.  
  86.   def toggleFile(self, filename):
  87.     # Toggles the recognition of a file in the archive by changing the lower bit of the
  88.     # filename hash code.
  89.     hash = self.filenameHash(filename)
  90.     if (hash not in self.directory):
  91.       if (hash ^ 1 in self.directory): hash = hash ^ 1
  92.       else:
  93.         raise "Unable to enable archive file"
  94.     else:
  95.       if (hash ^ 1 in self.directory):
  96.         raise "Unable to disable archive file"
  97.     index, pos, length, ulength = self.getHashEntry(hash)
  98.     file = open(self.datfile, "rb+")
  99.     file.seek(8 + index * 16)
  100.     file.write(struct.pack("I", hash ^ 1))
  101.     file.close()
  102.  
  103.  
  104. def levelFilename(n):
  105.   return "assets/dungeons/grimrock/level%02i.lua" % n
  106.  
  107.  
  108. ###########################################################################
  109. # Read and write LuaJIT bytecode blocks.
  110.  
  111. ###########################################################################
  112. # LuaJIT Byte Code string replacer.
  113.  
  114. # Format (from LuaJIT source file lj_bcdump.h)
  115. #
  116. # dump   = header proto+ 0U
  117. # header = ESC 'L' 'J' versionB flagsU [namelenU nameB*]
  118. # proto  = lengthU pdata
  119. # pdata  = phead bcinsW* uvdataH* kgc* knum* [debugB*]
  120. # phead  = flagsB numparamsB framesizeB numuvB numkgcU numknU numbcU
  121. #          [debuglenU [firstlineU numlineU]]
  122. # kgc    = kgctypeU { ktab | (loU hiU) | (rloU rhiU iloU ihiU) | strB* }
  123. # knum   = intU0 | (loU1 hiU)
  124. # ktab   = narrayU nhashU karray* khash*
  125. # karray = ktabk
  126. # khash  = ktabk ktabk
  127. # ktabk  = ktabtypeU { intU | (loU hiU) | strB* }
  128. #
  129. # B = 8 bit, H = 16 bit, W = 32 bit, U = ULEB128 of W, U0/U1 = ULEB128 of W+1
  130. #
  131.  
  132. class StringFile:
  133.   "Read bytes from a string as if it was a file"
  134.   def __init__(self, string):
  135.     self.string = string
  136.     self.pos = 0
  137.  
  138.   def read(self, n):
  139.     result = self.string[self.pos:self.pos + n]
  140.     self.pos += n
  141.     return result
  142.  
  143.  
  144. class LuaBC:
  145.   def __init__(self, header, flags, protos):
  146.     self.header = header
  147.     self.flags = flags
  148.     self.protos = protos
  149.  
  150.   def write(self, filename):
  151.     file = open(filename, "wb")
  152.     file.write(self.header)
  153.     LuaBC.writeU(file, self.flags)
  154.     for proto in self.protos:
  155.       LuaBC.writeU(file, len(proto))
  156.       file.write(proto)
  157.     LuaBC.writeU(file, 0)
  158.     file.close()
  159.  
  160.   @staticmethod
  161.   def writeU(file, number):
  162.     while (number > 127):
  163.       file.write(chr((number & 0x7f) | 0x80))
  164.       number >>= 7
  165.     file.write(chr(number))
  166.  
  167.   @staticmethod
  168.   def readString(string):
  169.     return LuaBC.read(StringFile(string))
  170.  
  171.   @staticmethod
  172.   def readFile(filename):
  173.     file = open(filename, "rb");
  174.     result = LuaBC.read(file)
  175.     file.close()
  176.     return result
  177.  
  178.   @staticmethod
  179.   def read(file):
  180.     def readU(file):
  181.       value = 0
  182.       byteread = file.read(1)
  183.       if (len(byteread) != 1): raise "EOF?"
  184.       byte = ord(byteread)
  185.       shift = 0
  186.       while (byte > 127):
  187.         value = value | ((byte & 0x7f) << shift)
  188.         shift += 7
  189.         byteread = file.read(1)
  190.         if (len(byteread) != 1): raise "EOF?"
  191.         byte = ord(byteread)
  192.       value = value | (byte << shift)
  193.       return value
  194.     header = file.read(4);
  195.     # Only support version 1.
  196.     if (header != "\x1bLJ\x01"): raise "Invalid version: " + header
  197.     flags = readU(file)
  198.     if flags != 2: raise "Unexpected flags"
  199.     # No namelenU in stripped mode (flags 0x02).
  200.     protos = []
  201.     length = readU(file)
  202.     while (length != 0):
  203.       protos.append(file.read(length))
  204.       length = readU(file)
  205.     return LuaBC(header, flags, protos)
  206.  
  207.   def replaceString(self, string, replacement):
  208.     if (len(string) > 127 or len(replacement) > 127): raise "Unhandled"
  209.     string = chr(len(string) + 5) + string
  210.     replacement = chr(len(replacement) + 5) + replacement
  211.     protos = self.protos
  212.     change = False
  213.     for i in xrange(0, len(protos)):
  214.       proto = protos[i]
  215.       newproto = proto.replace(string, replacement)
  216.       if (proto != newproto):
  217.         protos[i] = newproto
  218.         change = True
  219.     return change
  220.  
  221.  
  222. def main(grimrockDir):
  223.   print "Spider remover for Legend of Grimrock"
  224.   datfile = os.path.join(grimrockDir, "grimrock.dat")
  225.   backupDatFile(datfile)
  226.   grar = GRAR(datfile)
  227.   for i in xrange(1, 14):
  228.     print "Removing spiders from level %d" % i
  229.     levelName = levelFilename(i)
  230.     # Read (unpacked) level file from archive.
  231.     content = grar.readFile(levelName)
  232.     # Split into LuaJIT Bytecode "proto" blocks
  233.     lua = LuaBC.readString(content)
  234.     # Replace "spider" in each block.
  235.     if (lua.replaceString("spider", replacements[i])):
  236.       # If any changes, write the level file back out.
  237.       physicalFile = os.path.join(grimrockDir, levelName)
  238.       physicalPath = os.path.dirname(physicalFile)
  239.       if not os.path.exists(physicalPath):
  240.         os.makedirs(physicalPath)
  241.       lua.write(physicalFile)
  242.       # Disable the file in the archive, so the game reads the extracted file.
  243.       grar.toggleFile(levelName)
  244.  
  245.  
  246. if __name__ == "__main__":
  247.   grimrockDir = sys.argv[1]
  248.   if not os.path.exists(grimrockDir) or not os.path.isdir(grimrockDir):
  249.     raise "Directory not found: %s" % grimrockDir
  250.   main(grimrockDir)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement