/
save_file.py
273 lines (243 loc) · 9.54 KB
/
save_file.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# -*- coding: utf-8 -*-
# Copyright (C) 2011 Daniele Simonetti
# refer to: http://www.uesp.net/wiki/Tes5Mod:Save_File_Format
import os
import data
import globdata
class SaveGame(object):
def __init__(self):
pass
def parse_lite(self, file):
if not os.path.exists(file):
return False
with open(file, 'rb') as fp:
self.parse_header(fp)
return True
def parse_lite_2(self, file):
if not os.path.exists(file):
return False
with open(file, 'rb') as fp:
self.parse_header(fp)
self.parse_plugins(fp)
self.parse_file_location_table(fp)
return True
def parse_full(self, file):
if not os.path.exists(file):
return False
with open(file, 'rb') as fp:
self.parse_header(fp)
self.parse_plugins(fp)
self.parse_file_location_table(fp)
self.global_data_table1 = []
for i in xrange(0, self.glob_data_table1_count):
self.global_data_table1.append(self.parse_global_data(fp))
self.global_data_table2 = []
for i in xrange(0, self.glob_data_table2_count):
self.global_data_table2.append(self.parse_global_data(fp))
self.changed_forms = []
for i in xrange(0, self.changed_form_count):
self.changed_forms.append(self.parse_changed_form(fp))
self.global_data_table3 = []
for i in xrange(0, self.glob_data_table3_count+1):
self.global_data_table3.append(self.parse_global_data(fp))
self.unknown_table1 = ''
self.unknown_table2 = ''
self.unknown_table3 = []
self.unknown_table1_count = data.f_uint32(fp)
#print 'unknown table1 count: %d' % self.unknown_table1_count
if self.unknown_table1_count > 0:
self.unknown_table1 = data.f_buf(self.unknown_table1_count*4, fp)
self.unknown_table2_count = data.f_uint32(fp)
#print 'unknown table2 count: %d' % self.unknown_table2_count
if self.unknown_table2_count > 0:
self.unknown_table2 = data.f_buf(self.unknown_table2_count*4, fp)
self.unknown_table3_size = data.f_uint32(fp)
#print 'unknown table3 size: %d' % self.unknown_table3_size
self.unknown_table3_count = data.f_uint32(fp)
#print 'unknown table3 count: %d' % self.unknown_table3_count
if self.unknown_table3_count > 0:
for i in xrange(0, self.unknown_table3_count):
self.unknown_table3.append( data.f_wstring(fp) )
return True
def parse_step_2(self):
self.global_data = []
for itm in (self.global_data_table1 +
self.global_data_table2 +
self.global_data_table3):
obj = globdata.gdata_factory(itm)
if obj:
self.global_data.append(obj)
def parse_header(self, fp):
self.file_magic = data.f_string(13, fp)
self.hd_size = data.f_uint32(fp)
self.version = data.f_uint32(fp)
self.save_num = data.f_uint32(fp)
self.player_nm = data.f_wstring(fp)
self.player_lvl = data.f_uint32(fp)
self.player_loc = data.f_wstring(fp)
self.ingame_dt = data.f_wstring(fp)
self.player_race = data.f_wstring(fp)
self.unknown_1 = data.f_uint16(fp)
self.unknown_2 = data.f_float(fp)
self.unknown_3 = data.f_float(fp)
self.filetime = data.f_filetime(fp)
self.shot_w = data.f_uint32(fp)
self.shot_h = data.f_uint32(fp)
self.shot_data = data.f_buf(3*self.shot_h*self.shot_w, fp)
self.form_ver = data.f_uint8(fp)
def parse_plugins(self, fp):
self.plugin_info_size = data.f_int32(fp)
self.plugin_count = data.f_uint8(fp)
self.plugins = []
for i in xrange(0, self.plugin_count):
self.plugins.append( data.f_wstring(fp) )
def parse_file_location_table(self, fp):
self.unknown_table1_offset = data.f_uint32(fp)
self.unknown_table3_offset = data.f_uint32(fp)
self.glob_data_table1_offset = data.f_uint32(fp)
self.glob_data_table2_offset = data.f_uint32(fp)
self.changed_forms_offset = data.f_uint32(fp)
self.glob_data_table3_offset = data.f_uint32(fp)
self.glob_data_table1_count = data.f_uint32(fp)
self.glob_data_table2_count = data.f_uint32(fp)
self.glob_data_table3_count = data.f_uint32(fp)
self.changed_form_count = data.f_uint32(fp)
self.unknown_4 = data.f_buf(4*15, fp)
def parse_global_data(self, fp):
gb = data._GLOBALDATA()
gb.type_ = data.f_uint32(fp)
gb.len_ = data.f_uint32(fp)
gb.data = data.f_buf(gb.len_, fp)
return gb
def parse_changed_form(self, fp):
cf = data._CHANGEDFORM()
cf.unknown_1 = data.f_uint8(fp)
cf.unknown_2 = data.f_uint8(fp)
cf.unknown_3 = data.f_uint8(fp)
cf.unknown_4 = data.f_uint32(fp)
cf.flags = data.f_uint8(fp)
cf.version = data.f_uint8(fp)
len_flag = (cf.flags & 0xC0) >> 6
#print "flags: %02X, version: %d, len_flag: %d" % (cf.flags, cf.version, len_flag)
if len_flag == 0:
cf.len_1 = data.f_uint8(fp)
cf.len_2 = data.f_uint8(fp)
elif len_flag == 1:
cf.len_1 = data.f_uint16(fp)
cf.len_2 = data.f_uint16(fp)
elif len_flag == 2:
cf.len_1 = data.f_uint32(fp)
cf.len_2 = data.f_uint32(fp)
#print "len1: %d, len2: %d" % (cf.len_1, cf.len_2)
if cf.len_1 > 0:
cf.data = data.f_buf(cf.len_1, fp)
return cf
def dump_text(self):
print "\nHEADER:"
print str(self)
print "\nSCREENSHOT DATA:"
print data.h_dump(self.shot_data)
print "\nPLUGINS:"
for p in self.plugins:
print "\t"+p
dump_1 = str.format("""\
Unknown Table1 Offset : {0}
Unknown Table3 Offset : {1}
Global Data Table 1 Offset: {2}
Global Data Table 2 Offset: {3}
Changed Forms Offset : {4}
Global Data Table 3 Offset: {5}
Global Data Table 1 Count : {6}
Global Data Table 2 Count : {7}
Global Data Table 3 Count : {8}
Changed Forms Count : {9}
""",
self.unknown_table1_offset,
self.unknown_table3_offset,
self.glob_data_table1_offset,
self.glob_data_table2_offset,
self.changed_forms_offset,
self.glob_data_table3_offset,
self.glob_data_table1_count,
self.glob_data_table2_count,
self.glob_data_table3_count,
self.changed_form_count)
print "\nFILE LOCATION TABLE:"
print dump_1
print "\nGLOBAL DATA TABLE 1:"
rec_n = 1
for rec in self.global_data_table1:
print str.format("""\nRec n. {0} {1}""",
rec_n, rec)
print data.h_dump(rec.data)
rec_n += 1
print "\nGLOBAL DATA TABLE 2:"
rec_n = 1
for rec in self.global_data_table2:
print str.format("""\nRec n. {0} {1}""",
rec_n, rec)
print data.h_dump(rec.data)
rec_n += 1
print "\nGLOBAL DATA TABLE 3:"
rec_n = 1
for rec in self.global_data_table3:
print str.format("""\nRec n. {0} {1}""",
rec_n, rec)
print data.h_dump(rec.data)
rec_n += 1
print "\nCHANGED FORMS:"
rec_n = 1
for cf in self.changed_forms:
print str.format("""\nRec n. {0} {1}""",
rec_n, cf)
if cf.len_1 > 0:
print data.h_dump(cf.data)
rec_n += 1
print "\nUNKNOWN TABLE 3:"
for rec in self.unknown_table3:
print rec
print "\n#####################\n"
for itm in self.global_data:
print str(itm) + "\n"
def __str__(self):
return str.format("""\
File Magic : {0}
Header Size : {1}
Version : {2}
Save Number : {3}
Player Name : {4}
Player Level : {5}
Player Loc : {6}
Ingame Date : {7}
Player Race : {8}
Unknown 1 : {9}
Unknown 2 : {10}
Unknown 3 : {11}
Filetime : {12}
Shot Width : {13}
Shot Height : {14}
""",
self.file_magic,
self.hd_size,
self.version,
self.save_num,
self.player_nm,
self.player_lvl,
self.player_loc,
self.ingame_dt,
self.player_race,
repr(self.unknown_1),
repr(self.unknown_2),
repr(self.unknown_3),
self.filetime,
self.shot_w,
self.shot_h)
def main():
import sys
save_game = SaveGame()
save_game.parse_full(sys.argv[1])
save_game.parse_step_2()
#save_game.parse_lite_2(sys.argv[1])
save_game.dump_text()
if __name__ == '__main__':
main()