daily pastebin goal
49%
SHARE
TWEET

renamecharacters.py

a guest Apr 30th, 2012 553 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. # Rename characters in LoG save files.
  5.  
  6. import argparse
  7. import struct
  8. import zlib
  9. import re
  10. import io
  11.  
  12. MAX_CHAR_NAME_SZ = 20       # Max character name size is set to 20 characters
  13.  
  14. # Savegame format:
  15. # ----------------
  16. # 4752 494D 0400 0000 F14E 0B00 789C EC7D ... ...
  17. # ^^^^ ^^^^                                             Block name = "GRIM"
  18. #           ^^^^ ^^^^                                   Block size = 4 (little-endian)
  19. #                     ^^^^ ^^^^                         Uncompressed data size in bytes (little-endian)
  20. #                               ^^^^                    Zlib data header (always 789C)
  21. #                                    ^^^^ ^^^^ ^^^^     Zlib data
  22.  
  23. def parse_savegame(filename, keep_uncompressed = False):
  24.     savefile_in, savefile_out, uncompfile_out = None, None, None
  25.     try:
  26.         savefile_in = open(filename,'rb')
  27.         print "Parsing %s:" % filename
  28.          
  29.         out_filename = re.sub(r'\.sav', '.edited.sav', filename)
  30.         if out_filename == filename:
  31.             raise Exception("Input savefiles must have the .sav extension")
  32.  
  33.         uncomp_data_out = ""
  34.  
  35.         filetype = savefile_in.read(4)
  36.         if filetype != "GRIM":
  37.             raise Exception("Not a valid savefile")
  38.  
  39.         block_size= savefile_in.read(8)     # Skip the next 8 bytes    
  40.         zlib_data = savefile_in.read(-1)    # Read all the rest (= the compressed data)
  41.  
  42.         data_stream = io.BytesIO(zlib.decompress(zlib_data))
  43.  
  44.         while True:
  45.             skip_flag = False
  46.  
  47.             block_name = data_stream.read(4)
  48.             if block_name == "":
  49.                 break
  50.             block_size      = struct.unpack("<L",data_stream.read(4))[0]
  51.             block_data      = data_stream.read(block_size)
  52.             block_data_edit = bytearray(block_data)
  53.             delta_sz = 0
  54.  
  55.             if block_name == "CHAR":
  56.                 char_id       = struct.unpack("<H",block_data[10:12])[0]
  57.                 name_tag      =                    block_data[12:16]
  58.                 name_block_sz = struct.unpack("<L",block_data[16:20])[0]
  59.                 name_sz       = struct.unpack("<L",block_data[24:28])[0]
  60.                 if name_tag == "CHAM":
  61.                     print "    Found character %s" % str(block_data[28:28+name_sz])
  62.                     name_too_long = True
  63.                     while name_too_long:
  64.                         new_name = raw_input("    New name (press Enter to keep identical)? ")
  65.                         name_too_long = (len(new_name) > MAX_CHAR_NAME_SZ)
  66.                         if name_too_long:
  67.                             print "    Name too long (%d chars max)" % MAX_CHAR_NAME_SZ
  68.  
  69.                     if len(new_name) != 0:
  70.                         delta_sz                       = len(new_name) - name_sz
  71.                         block_data_edit[16:20]         = struct.pack("<L", name_block_sz + delta_sz)
  72.                         block_data_edit[24:28]         = struct.pack("<L", len(new_name))
  73.                         block_data_edit[28:28+name_sz] = bytearray(new_name)
  74.  
  75.             uncomp_data_out += block_name + struct.pack("<L", block_size + delta_sz) + str(block_data_edit)
  76.  
  77.         savefile_out = open(out_filename, 'wb')
  78.         savefile_out.write("GRIM" + struct.pack("<LL", 4, len(uncomp_data_out)))
  79.         savefile_out.write(zlib.compress(uncomp_data_out))
  80.  
  81.         print "\nEdited savegame is: %s" % out_filename
  82.  
  83.         if keep_uncompressed:
  84.             uncomp_filename = re.sub(r'\.sav', '.uncompressed.sav', filename)
  85.             if uncomp_filename == filename:
  86.                 raise Exception("Input savefiles must have the .sav extension")
  87.             uncompfile_out = open(uncomp_filename, 'wb')
  88.             uncompfile_out.write(uncomp_data_out)
  89.  
  90.     finally:
  91.         if savefile_in:
  92.             savefile_in.close()
  93.         if savefile_out:
  94.             savefile_out.close()
  95.         if uncompfile_out:
  96.             uncompfile_out.close()
  97.  
  98. #
  99. # Main()
  100. #
  101. def main():
  102.     parser = argparse.ArgumentParser(description='Rename characters LoG save files')
  103.     parser.add_argument('savefiles', metavar='savefile', nargs=1,
  104.                        help='LoG save files (v1.1.4 or above) with .sav extension')
  105.     parser.add_argument('-k', dest='keep_uncompressed', action='store_true',
  106.                         help='Keep the uncompressed data (with extension .uncompressed.sav)')
  107.  
  108.     args = parser.parse_args()
  109.  
  110.     # Parse the first savefile and rename characters
  111.     parse_savegame(args.savefiles[0], keep_uncompressed = args.keep_uncompressed)
  112.  
  113. if __name__ == "__main__":
  114.     main()
RAW Paste Data
Top