Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- # python 2.7
- import argparse
- import struct
- import zlib
- import re
- import io
- cimg_ids = []
- cimg_blocks = dict()
- # Savegame format:
- # ----------------
- # 4752 494D 0400 0000 F14E 0B00 789C EC7D ... ...
- # ^^^^ ^^^^ Block name = "GRIM"
- # ^^^^ ^^^^ Block size = 4 (little-endian)
- # ^^^^ ^^^^ Uncompressed data size in bytes (little-endian)
- # ^^^^ Zlib data header (always 789C)
- # ^^^^ ^^^^ ^^^^ Zlib data
- def parse_savegame(filename, insert_cimg = False, keep_uncompressed = False):
- global cimg_ids, cimg_blocks
- if insert_cimg:
- cimg_ids = []
- else:
- cimg_blocks = dict()
- savefile_in, savefile_out, uncompfile_out = None, None, None
- try:
- savefile_in = open(filename,'rb')
- print "Parsing %s:" % filename
- if insert_cimg:
- out_filename = re.sub(r'\.sav', '.edited.sav', filename)
- if out_filename == filename:
- raise Exception("Input savefiles must have the .sav extension")
- savefile_out = open(out_filename, 'wb')
- uncomp_data_out = ""
- filetype = savefile_in.read(4)
- if filetype != "GRIM":
- raise Exception("Not a valid savefile")
- block_size= savefile_in.read(8) # Skip the next 8 bytes
- zlib_data = savefile_in.read(-1) # Read all the rest (= the compressed data)
- data_stream = io.BytesIO(zlib.decompress(zlib_data))
- while True:
- skip_flag = False
- block_name = data_stream.read(4)
- if block_name == "":
- break
- block_size = struct.unpack("<L",data_stream.read(4))[0]
- block_data = data_stream.read(block_size)
- if block_name == "CHAR":
- char_id = struct.unpack("<H",block_data[10:12])[0]
- name_tag = block_data[12:16]
- name_sz = struct.unpack("<L",block_data[24:28])[0]
- if name_tag == "CHAM":
- print " Found character %s (ID: %04X)" % (str(block_data[28:28+name_sz]), char_id)
- if insert_cimg:
- cimg_ids.append(char_id)
- if char_id not in cimg_blocks:
- raise Exception("No custom portrait for character ID %04X" % char_id)
- elif block_name == "CIMG":
- if insert_cimg:
- skip_flag = True # Skip this block (will be replaced by a new custom img)
- else:
- char_id = struct.unpack("<H",block_data[10:12])[0]
- print " Found custom portrait (ID: %04X)" % char_id
- cimg_blocks[char_id] = block_name + struct.pack("<L", block_size) + block_data
- elif block_name == "PRTY":
- if insert_cimg:
- for char_id in cimg_ids:
- uncomp_data_out += cimg_blocks[char_id]
- print " Inserted custom portraits"
- if not skip_flag:
- uncomp_data_out += block_name + struct.pack("<L", block_size) + block_data
- if insert_cimg:
- savefile_out.write("GRIM" + struct.pack("<LL", 4, len(uncomp_data_out)))
- savefile_out.write(zlib.compress(uncomp_data_out))
- if keep_uncompressed:
- uncomp_filename = re.sub(r'\.sav', '.uncompressed.sav', filename)
- if uncomp_filename == filename:
- raise Exception("Input savefiles must have the .sav extension")
- uncompfile_out = open(uncomp_filename, 'wb')
- uncompfile_out.write(uncomp_data_out)
- finally:
- if savefile_in:
- savefile_in.close()
- if savefile_out:
- savefile_out.close()
- if uncompfile_out:
- uncompfile_out.close()
- #
- # Main()
- #
- def main():
- parser = argparse.ArgumentParser(description='Copy custom portraits from one LoG save file to another')
- parser.add_argument('savefiles', metavar='savefile', nargs=2,
- help='LoG save files (v1.1.4 or above) with .sav extension')
- parser.add_argument('-k', dest='keep_compressed', action='store_true',
- help='Keep the uncompressed data (with extension .uncompressed.sav)')
- args = parser.parse_args()
- # Parse the first savefile to get the custom portraits
- parse_savegame(args.savefiles[0])
- # Insert the custom portraits in the second savefile (or, replace existing ones)
- parse_savegame(args.savefiles[1], True, args.keep_compressed)
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement