Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! python
- # Extract dungeon level files, replace spider with something else.
- import os.path, shutil, struct, zlib, sys
- # Monster to replace spider by on each level, 1..13
- replacements = [
- None,
- "green_slime", # level 1
- "green_slime", # level 2
- "green_slime", # level 3
- "green_slime", # level 4
- "green_slime", # level 5
- "green_slime", # level 6
- "green_slime", # level 7
- "green_slime", # level 8
- "green_slime", # level 9
- "green_slime", # level 10
- "green_slime", # level 11
- "green_slime", # level 12
- "green_slime" # level 13
- ]
- def backupDatFile(datfile):
- if (not os.path.exists(datfile)): raise "File not found: %s" % datfile
- backupfile = datfile + ".orig"
- if (not os.path.exists(backupfile)):
- print "Making backup of grimrock.dat file"
- shutil.copyfile(datfile, backupfile)
- else:
- print "Restoring backup of grimrock.dat file"
- shutil.copyfile(backupfile, datfile)
- print "... done."
- class GRAR:
- def __init__(self, filename):
- self.datfile = filename
- self.directory = self.readDatDirectory()
- def readDatDirectory(self):
- file = open(self.datfile, "rb")
- header = file.read(4)
- if (header != "GRAR"): raise "Bad file header: %4s" % header
- (fileCount,) = struct.unpack("I", file.read(4))
- result = {}
- for i in xrange(0, fileCount):
- hash, pos, length, ulength = struct.unpack("IIII", file.read(16))
- result[hash] = (i, pos, length, ulength)
- file.close()
- return result
- def filenameHash(self, s):
- v = 0x811c9dc5
- for c in s:
- v ^= ord(c)
- v = (v * 0x1000193) & 0xffffffff
- return v
- def getFileEntry(self, filename):
- hash = self.filenameHash(filename)
- return self.getHashEntry(hash)
- def getHashEntry(self, hash):
- if (hash in self.directory):
- return self.directory[hash]
- raise "File not found: %s" % filename
- def readFile(self, filename):
- index, pos, length, ulength = self.getFileEntry(filename)
- packed = True
- if (length == 0):
- length = ulength
- packed = false
- file = open(self.datfile, "rb")
- file.seek(pos)
- content = file.read(length)
- file.close()
- if packed:
- (unpackedLength,) = struct.unpack("I", content[0:4])
- if (unpackedLength != ulength): raise "Unexpected stored length of file %s" % filename
- content = zlib.decompress(content[4:])
- if (len(content) != ulength): raise "Unexpected unpacked data length of file %s" % filename
- return content
- def toggleFile(self, filename):
- # Toggles the recognition of a file in the archive by changing the lower bit of the
- # filename hash code.
- hash = self.filenameHash(filename)
- if (hash not in self.directory):
- if (hash ^ 1 in self.directory): hash = hash ^ 1
- else:
- raise "Unable to enable archive file"
- else:
- if (hash ^ 1 in self.directory):
- raise "Unable to disable archive file"
- index, pos, length, ulength = self.getHashEntry(hash)
- file = open(self.datfile, "rb+")
- file.seek(8 + index * 16)
- file.write(struct.pack("I", hash ^ 1))
- file.close()
- def levelFilename(n):
- return "assets/dungeons/grimrock/level%02i.lua" % n
- ###########################################################################
- # Read and write LuaJIT bytecode blocks.
- ###########################################################################
- # LuaJIT Byte Code string replacer.
- # Format (from LuaJIT source file lj_bcdump.h)
- #
- # dump = header proto+ 0U
- # header = ESC 'L' 'J' versionB flagsU [namelenU nameB*]
- # proto = lengthU pdata
- # pdata = phead bcinsW* uvdataH* kgc* knum* [debugB*]
- # phead = flagsB numparamsB framesizeB numuvB numkgcU numknU numbcU
- # [debuglenU [firstlineU numlineU]]
- # kgc = kgctypeU { ktab | (loU hiU) | (rloU rhiU iloU ihiU) | strB* }
- # knum = intU0 | (loU1 hiU)
- # ktab = narrayU nhashU karray* khash*
- # karray = ktabk
- # khash = ktabk ktabk
- # ktabk = ktabtypeU { intU | (loU hiU) | strB* }
- #
- # B = 8 bit, H = 16 bit, W = 32 bit, U = ULEB128 of W, U0/U1 = ULEB128 of W+1
- #
- class StringFile:
- "Read bytes from a string as if it was a file"
- def __init__(self, string):
- self.string = string
- self.pos = 0
- def read(self, n):
- result = self.string[self.pos:self.pos + n]
- self.pos += n
- return result
- class LuaBC:
- def __init__(self, header, flags, protos):
- self.header = header
- self.flags = flags
- self.protos = protos
- def write(self, filename):
- file = open(filename, "wb")
- file.write(self.header)
- LuaBC.writeU(file, self.flags)
- for proto in self.protos:
- LuaBC.writeU(file, len(proto))
- file.write(proto)
- LuaBC.writeU(file, 0)
- file.close()
- @staticmethod
- def writeU(file, number):
- while (number > 127):
- file.write(chr((number & 0x7f) | 0x80))
- number >>= 7
- file.write(chr(number))
- @staticmethod
- def readString(string):
- return LuaBC.read(StringFile(string))
- @staticmethod
- def readFile(filename):
- file = open(filename, "rb");
- result = LuaBC.read(file)
- file.close()
- return result
- @staticmethod
- def read(file):
- def readU(file):
- value = 0
- byteread = file.read(1)
- if (len(byteread) != 1): raise "EOF?"
- byte = ord(byteread)
- shift = 0
- while (byte > 127):
- value = value | ((byte & 0x7f) << shift)
- shift += 7
- byteread = file.read(1)
- if (len(byteread) != 1): raise "EOF?"
- byte = ord(byteread)
- value = value | (byte << shift)
- return value
- header = file.read(4);
- # Only support version 1.
- if (header != "\x1bLJ\x01"): raise "Invalid version: " + header
- flags = readU(file)
- if flags != 2: raise "Unexpected flags"
- # No namelenU in stripped mode (flags 0x02).
- protos = []
- length = readU(file)
- while (length != 0):
- protos.append(file.read(length))
- length = readU(file)
- return LuaBC(header, flags, protos)
- def replaceString(self, string, replacement):
- if (len(string) > 127 or len(replacement) > 127): raise "Unhandled"
- string = chr(len(string) + 5) + string
- replacement = chr(len(replacement) + 5) + replacement
- protos = self.protos
- change = False
- for i in xrange(0, len(protos)):
- proto = protos[i]
- newproto = proto.replace(string, replacement)
- if (proto != newproto):
- protos[i] = newproto
- change = True
- return change
- def main(grimrockDir):
- print "Spider remover for Legend of Grimrock"
- datfile = os.path.join(grimrockDir, "grimrock.dat")
- backupDatFile(datfile)
- grar = GRAR(datfile)
- for i in xrange(1, 14):
- print "Removing spiders from level %d" % i
- levelName = levelFilename(i)
- # Read (unpacked) level file from archive.
- content = grar.readFile(levelName)
- # Split into LuaJIT Bytecode "proto" blocks
- lua = LuaBC.readString(content)
- # Replace "spider" in each block.
- if (lua.replaceString("spider", replacements[i])):
- # If any changes, write the level file back out.
- physicalFile = os.path.join(grimrockDir, levelName)
- physicalPath = os.path.dirname(physicalFile)
- if not os.path.exists(physicalPath):
- os.makedirs(physicalPath)
- lua.write(physicalFile)
- # Disable the file in the archive, so the game reads the extracted file.
- grar.toggleFile(levelName)
- if __name__ == "__main__":
- grimrockDir = sys.argv[1]
- if not os.path.exists(grimrockDir) or not os.path.isdir(grimrockDir):
- raise "Directory not found: %s" % grimrockDir
- main(grimrockDir)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement