Guest User

parsesavegame.py

a guest
Apr 29th, 2012
278
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2. # python 2.7
  3.  
  4. import argparse
  5. import struct
  6. import zlib
  7. import re
  8. import io
  9.  
  10. cimg_ids    = []
  11. cimg_blocks = dict()
  12.  
  13. # Savegame format:
  14. # ----------------
  15. # 4752 494D 0400 0000 F14E 0B00 789C EC7D ... ...
  16. # ^^^^ ^^^^                                             Block name = "GRIM"
  17. #           ^^^^ ^^^^                                   Block size = 4 (little-endian)
  18. #                     ^^^^ ^^^^                         Uncompressed data size in bytes (little-endian)
  19. #                               ^^^^                    Zlib data header (always 789C)
  20. #                                    ^^^^ ^^^^ ^^^^     Zlib data
  21.  
  22. def parse_savegame(filename, insert_cimg = False, keep_uncompressed = False):
  23.     global cimg_ids, cimg_blocks
  24.     if insert_cimg:
  25.         cimg_ids = []
  26.     else:
  27.         cimg_blocks = dict()
  28.  
  29.     savefile_in, savefile_out, uncompfile_out = None, None, None
  30.     try:
  31.         savefile_in = open(filename,'rb')
  32.         print "Parsing %s:" % filename
  33.          
  34.         if insert_cimg:
  35.            out_filename = re.sub(r'\.sav', '.edited.sav', filename)
  36.            if out_filename == filename:
  37.                 raise Exception("Input savefiles must have the .sav extension")
  38.            savefile_out = open(out_filename, 'wb')
  39.  
  40.         uncomp_data_out = ""
  41.  
  42.         filetype = savefile_in.read(4)
  43.         if filetype != "GRIM":
  44.             raise Exception("Not a valid savefile")
  45.  
  46.         block_size= savefile_in.read(8)     # Skip the next 8 bytes    
  47.         zlib_data = savefile_in.read(-1)    # Read all the rest (= the compressed data)
  48.  
  49.         data_stream = io.BytesIO(zlib.decompress(zlib_data))
  50.  
  51.         while True:
  52.             skip_flag = False
  53.  
  54.             block_name = data_stream.read(4)
  55.             if block_name == "":
  56.                 break
  57.             block_size = struct.unpack("<L",data_stream.read(4))[0]
  58.             block_data = data_stream.read(block_size)
  59.  
  60.             if block_name == "CHAR":
  61.                 char_id  = struct.unpack("<H",block_data[10:12])[0]
  62.                 name_tag =                    block_data[12:16]
  63.                 name_sz  = struct.unpack("<L",block_data[24:28])[0]
  64.                 if name_tag == "CHAM":
  65.                     print "    Found character %s (ID: %04X)" % (str(block_data[28:28+name_sz]), char_id)
  66.  
  67.                 if insert_cimg:
  68.                     cimg_ids.append(char_id)
  69.                     if char_id not in cimg_blocks:
  70.                         raise Exception("No custom portrait for character ID %04X" % char_id)
  71.  
  72.             elif block_name == "CIMG":
  73.                 if insert_cimg:
  74.                     skip_flag = True        # Skip this block (will be replaced by a new custom img)
  75.                 else:
  76.                     char_id  = struct.unpack("<H",block_data[10:12])[0]
  77.                     print "    Found custom portrait (ID: %04X)" % char_id
  78.                     cimg_blocks[char_id] = block_name + struct.pack("<L", block_size) + block_data
  79.  
  80.             elif block_name == "PRTY":
  81.                 if insert_cimg:
  82.                     for char_id in cimg_ids:
  83.                         uncomp_data_out += cimg_blocks[char_id]
  84.                     print "    Inserted custom portraits"
  85.  
  86.             if not skip_flag:
  87.                 uncomp_data_out += block_name + struct.pack("<L", block_size) + block_data
  88.  
  89.         if insert_cimg:
  90.             savefile_out.write("GRIM" + struct.pack("<LL", 4, len(uncomp_data_out)))
  91.             savefile_out.write(zlib.compress(uncomp_data_out))
  92.            
  93.         if keep_uncompressed:
  94.             uncomp_filename = re.sub(r'\.sav', '.uncompressed.sav', filename)
  95.             if uncomp_filename == filename:
  96.                 raise Exception("Input savefiles must have the .sav extension")
  97.             uncompfile_out = open(uncomp_filename, 'wb')
  98.             uncompfile_out.write(uncomp_data_out)
  99.  
  100.     finally:
  101.         if savefile_in:
  102.             savefile_in.close()
  103.         if savefile_out:
  104.             savefile_out.close()
  105.         if uncompfile_out:
  106.             uncompfile_out.close()
  107.  
  108. #
  109. # Main()
  110. #
  111. def main():
  112.     parser = argparse.ArgumentParser(description='Copy custom portraits from one LoG save file to another')
  113.     parser.add_argument('savefiles', metavar='savefile', nargs=2,
  114.                        help='LoG save files (v1.1.4 or above) with .sav extension')
  115.     parser.add_argument('-k', dest='keep_compressed', action='store_true',
  116.                         help='Keep the uncompressed data (with extension .uncompressed.sav)')
  117.  
  118.     args = parser.parse_args()
  119.    
  120.     # Parse the first savefile to get the custom portraits
  121.     parse_savegame(args.savefiles[0])
  122.  
  123.     # Insert the custom portraits in the second savefile (or, replace existing ones)
  124.     parse_savegame(args.savefiles[1], True, args.keep_compressed)
  125.  
  126. if __name__ == "__main__":
  127.     main()
RAW Paste Data