Guest User

parsesavegame.py

a guest
Apr 29th, 2012
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.81 KB | None | 0 0
  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. # Savefile 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. def parse_savefile(filename, insert_cimg = False, keep_uncompressed = False):
  22.     global cimg_ids, cimg_blocks
  23.     if insert_cimg:
  24.         cimg_ids = []
  25.     else:
  26.         cimg_blocks = dict()
  27.  
  28.     savefile_in, savefile_out, uncompfile_out = None, None, None
  29.     try:
  30.         savefile_in = open(filename,'rb')
  31.         print "Parsing %s:" % filename
  32.          
  33.         if insert_cimg:
  34.            out_filename = re.sub(r'\.sav', '.edited.sav', filename)
  35.            if out_filename == filename:
  36.                 raise Exception("Input savefiles must have the .sav extension")
  37.            savefile_out = open(out_filename, 'wb')
  38.  
  39.         uncomp_data_out = ""
  40.  
  41.         filetype = savefile_in.read(4)
  42.         if filetype != "GRIM":
  43.             raise Exception("Not a valid savefile")
  44.  
  45.         block_size= savefile_in.read(8)     # Skip the next 8 bytes    
  46.         zlib_data = savefile_in.read(-1)    # Read all the rest (= the compressed data)
  47.  
  48.         data_stream = io.BytesIO(zlib.decompress(zlib_data))
  49.  
  50.         while True:
  51.             skip_flag = False
  52.  
  53.             block_name = data_stream.read(4)
  54.             if block_name == "":
  55.                 break
  56.             block_size = struct.unpack("<L",data_stream.read(4))[0]
  57.             block_data = data_stream.read(block_size)
  58.  
  59.             if block_name == "CHAR":
  60.                 char_id  = struct.unpack("<H",block_data[10:12])[0]
  61.                 name_tag =                    block_data[12:16]
  62.                 name_sz  = struct.unpack("<L",block_data[24:28])[0]
  63.                 if name_tag == "CHAM":
  64.                     print "    Found character %s (ID: %04X)" % (str(block_data[28:28+name_sz]), char_id)
  65.  
  66.                 if insert_cimg:
  67.                     cimg_ids.append(char_id)
  68.                     if char_id not in cimg_blocks:
  69.                         raise Exception("No custom portrait for character ID %04X" % char_id)
  70.  
  71.             elif block_name == "CIMG":
  72.                 if insert_cimg:
  73.                     skip_flag = True        # Skip this block (will be replaced by a new custom img)
  74.                 else:
  75.                     char_id  = struct.unpack("<H",block_data[10:12])[0]
  76.                     print "    Found custom portrait (ID: %04X)" % char_id
  77.                     cimg_blocks[char_id] = block_name + struct.pack("<L", block_size) + block_data
  78.  
  79.             elif block_name == "PRTY":
  80.                 if insert_cimg:
  81.                     for char_id in cimg_ids:
  82.                         uncomp_data_out += cimg_blocks[char_id]
  83.                     print "    Inserted custom portraits"
  84.  
  85.             if not skip_flag:
  86.                 uncomp_data_out += block_name + struct.pack("<L", block_size) + block_data
  87.  
  88.         if insert_cimg:
  89.             savefile_out.write("GRIM" + struct.pack("<LL", 4, len(uncomp_data_out)))
  90.             savefile_out.write(zlib.compress(uncomp_data_out))
  91.            
  92.         if keep_uncompressed:
  93.             uncomp_filename = re.sub(r'\.sav', '.uncompressed.sav', filename)
  94.             if uncomp_filename == filename:
  95.                 raise Exception("Input savefiles must have the .sav extension")
  96.             uncompfile_out = open(uncomp_filename, 'wb')
  97.             uncompfile_out.write(uncomp_data_out)
  98.  
  99.     finally:
  100.         if savefile_in:
  101.             savefile_in.close()
  102.         if savefile_out:
  103.             savefile_out.close()
  104.         if uncompfile_out:
  105.             uncompfile_out.close()
  106.  
  107. #
  108. # Main()
  109. #
  110. def main():
  111.     parser = argparse.ArgumentParser(description='Parse LoG savefiles')
  112.     parser.add_argument('savefiles', metavar='savefile', nargs=2,
  113.                        help='Log Save Files (v1.1.4 or above) with .sav extension')
  114.     parser.add_argument('-k', dest='keep_compressed', action='store_true',
  115.                         help='Keep the uncompressed data (with extension .uncompressed.sav)')
  116.  
  117.     args = parser.parse_args()
  118.    
  119.     # Parse the first savefile to get the custom portraits
  120.     parse_savefile(args.savefiles[0])            
  121.  
  122.     # Insert the custom portraits in the second savefile (or, replace existing ones)
  123.     parse_savefile(args.savefiles[1], True, args.keep_compressed)  
  124.  
  125. if __name__ == "__main__":
  126.     main()
Advertisement
Add Comment
Please, Sign In to add comment