View difference between Paste ID: gvqVyUw7 and A2zNMRj4
SHOW: | | - or go back to the newest paste.
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:
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-
def parse_savefile(filename, insert_cimg = False, keep_uncompressed = False):
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-
    parser = argparse.ArgumentParser(description='Parse LoG savefiles')
111+
112
    parser = argparse.ArgumentParser(description='Copy custom portraits from one LoG save file to another')
113-
                       help='Log Save Files (v1.1.4 or above) with .sav extension')
113+
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_savefile(args.savefiles[0])            
120+
121
    parse_savegame(args.savefiles[0])
122
123-
    parse_savefile(args.savefiles[1], True, args.keep_compressed)   
123+
124
    parse_savegame(args.savefiles[1], True, args.keep_compressed)
125
126
if __name__ == "__main__":
127
    main()