diff options
author | itsme <itsme@xs4all.nl> | 2021-07-09 17:13:12 +0200 |
---|---|---|
committer | itsme <itsme@xs4all.nl> | 2021-07-09 17:14:19 +0200 |
commit | 7acef7d17b95af8b88a7a5d2c947ef2c01da81a8 (patch) | |
tree | e9c12133a963f2b9f98d664538bf6de5e704699f | |
parent | b31f69506d91792e75fc7feb56127f19912eb554 (diff) |
added largefile support. added 'bankdump' subcommand, which uses 'readrec'. figured out .dat and .tad header bytes. replaced option with separate subcommand: .
-rw-r--r-- | crodump.py | 205 |
1 files changed, 153 insertions, 52 deletions
@@ -7,6 +7,7 @@ from hexdump import hexdump, asasc, tohex, unhex, strescape | |||
7 | from koddecoder import kodecode | 7 | from koddecoder import kodecode |
8 | from readers import ByteReader | 8 | from readers import ByteReader |
9 | import zlib | 9 | import zlib |
10 | from collections import defaultdict | ||
10 | 11 | ||
11 | """ | 12 | """ |
12 | python3 crodump.py crodump chechnya_proverki_ul_2012 | 13 | python3 crodump.py crodump chechnya_proverki_ul_2012 |
@@ -32,23 +33,50 @@ def enumunreferenced(ranges, filesize): | |||
32 | 33 | ||
33 | class Datafile: | 34 | class Datafile: |
34 | """ Represent a single .dat with it's .tad index file """ | 35 | """ Represent a single .dat with it's .tad index file """ |
35 | def __init__(self, dat, tad, need_decode=False): | 36 | def __init__(self, name, dat, tad): |
37 | self.name = name | ||
36 | self.dat = dat | 38 | self.dat = dat |
37 | self.tad = tad | 39 | self.tad = tad |
38 | 40 | ||
39 | self.need_decode = need_decode | 41 | self.readdathdr() |
40 | |||
41 | self.readtad() | 42 | self.readtad() |
42 | 43 | ||
43 | self.dat.seek(0, io.SEEK_END) | 44 | self.dat.seek(0, io.SEEK_END) |
44 | self.datsize = self.dat.tell() | 45 | self.datsize = self.dat.tell() |
45 | 46 | ||
47 | |||
48 | def readdathdr(self): | ||
49 | self.dat.seek(0) | ||
50 | hdrdata = self.dat.read(19) | ||
51 | |||
52 | magic, self.hdrunk, self.version, self.encoding, self.blocksize = struct.unpack("<8sH5sHH", hdrdata) | ||
53 | if magic != b"CroFile\x00": | ||
54 | print("unknown magic: ", magic) | ||
55 | raise Exception("not a Crofile") | ||
56 | self.use64bit = self.version == b'01.03' | ||
57 | |||
58 | # blocksize | ||
59 | # 0040 -> Bank | ||
60 | # 0400 -> Index or Sys | ||
61 | # 0200 -> Stru or Sys | ||
62 | |||
63 | # encoding | ||
64 | # 0000 | ||
65 | # 0001 --> 'KOD encoded' | ||
66 | # 0002 | ||
67 | # 0003 --> encrypted | ||
68 | |||
46 | def readtad(self): | 69 | def readtad(self): |
47 | self.tad.seek(0) | 70 | self.tad.seek(0) |
48 | hdrdata = self.tad.read(2*4) | 71 | hdrdata = self.tad.read(2*4) |
49 | self.tadhdr = struct.unpack("<2L", hdrdata) | 72 | self.nrdeleted, self.firstdeleted = struct.unpack("<2L", hdrdata) |
50 | indexdata = self.tad.read() | 73 | indexdata = self.tad.read() |
51 | self.tadidx = [ struct.unpack_from("<3L", indexdata, 12*_) for _ in range(len(indexdata)//12) ] | 74 | if self.use64bit: |
75 | # 01.03 has 64 bit file offsets | ||
76 | self.tadidx = [ struct.unpack_from("<QLL", indexdata, 16*_) for _ in range(len(indexdata)//16) ] | ||
77 | else: | ||
78 | # 01.02 and 01.04 have 32 bit offsets. | ||
79 | self.tadidx = [ struct.unpack_from("<LLL", indexdata, 12*_) for _ in range(len(indexdata)//12) ] | ||
52 | 80 | ||
53 | def readdata(self, ofs, size): | 81 | def readdata(self, ofs, size): |
54 | self.dat.seek(ofs) | 82 | self.dat.seek(ofs) |
@@ -68,25 +96,27 @@ class Datafile: | |||
68 | if not dat: | 96 | if not dat: |
69 | # empty record | 97 | # empty record |
70 | encdat = dat | 98 | encdat = dat |
71 | elif self.need_decode and not flags: | 99 | elif not flags: |
72 | extofs, extlen = struct.unpack("<LL", dat[:8]) | 100 | extofs, extlen = struct.unpack("<LL", dat[:8]) |
73 | encdat = dat[8:] | 101 | encdat = dat[8:] |
74 | while len(encdat)<extlen: | 102 | while len(encdat)<extlen: |
75 | dat = self.readdata(extofs, 0x200) | 103 | dat = self.readdata(extofs, self.blocksize) |
76 | extofs, = struct.unpack("<L", dat[:4]) | 104 | extofs, = struct.unpack("<L", dat[:4]) |
77 | encdat += dat[4:] | 105 | encdat += dat[4:] |
78 | encdat = encdat[:extlen] | 106 | encdat = encdat[:extlen] |
79 | else: | 107 | else: |
80 | encdat = dat | 108 | encdat = dat |
81 | 109 | ||
82 | if not self.need_decode: | 110 | if self.encoding == 1: |
83 | return encdat | 111 | encdat = kodecode(idx, encdat) |
84 | else: | 112 | if self.iscompressed(encdat): |
85 | return kodecode(idx, encdat) | 113 | encdat = self.decompress(encdat) |
114 | |||
115 | return encdat | ||
86 | 116 | ||
87 | 117 | ||
88 | def dump(self, args): | 118 | def dump(self, args): |
89 | print("tadhdr: %08x %08x" % tuple(self.tadhdr)) | 119 | print("hdr: %-6s dat: %04x %s enc:%04x bs:%04x, tad: %08x %08x" % (self.name, self.hdrunk, self.version, self.encoding, self.blocksize, self.nrdeleted, self.firstdeleted)) |
90 | ranges = [] # keep track of used bytes in the .dat file. | 120 | ranges = [] # keep track of used bytes in the .dat file. |
91 | for i, (ofs, ln, chk) in enumerate(self.tadidx): | 121 | for i, (ofs, ln, chk) in enumerate(self.tadidx): |
92 | if ln==0xFFFFFFFF: | 122 | if ln==0xFFFFFFFF: |
@@ -97,39 +127,50 @@ class Datafile: | |||
97 | ln &= 0xFFFFFFF | 127 | ln &= 0xFFFFFFF |
98 | dat = self.readdata(ofs, ln) | 128 | dat = self.readdata(ofs, ln) |
99 | ranges.append((ofs, ofs+ln, "item #%d" % i)) | 129 | ranges.append((ofs, ofs+ln, "item #%d" % i)) |
100 | decflag = ' ' | 130 | decflags = [' ', ' '] |
101 | infostr = "" | 131 | infostr = "" |
102 | tail = b'' | 132 | tail = b'' |
103 | 133 | ||
104 | if not dat: | 134 | if not dat: |
105 | # empty record | 135 | # empty record |
106 | encdat = dat | 136 | encdat = dat |
107 | elif self.need_decode and not flags: | 137 | elif not flags: |
108 | extofs, extlen = struct.unpack("<LL", dat[:8]) | 138 | if self.use64bit: |
139 | extofs, extlen = struct.unpack("<QL", dat[:12]) | ||
140 | o = 12 | ||
141 | else: | ||
142 | extofs, extlen = struct.unpack("<LL", dat[:8]) | ||
143 | o = 8 | ||
109 | infostr = "%08x;%08x" % (extofs, extlen) | 144 | infostr = "%08x;%08x" % (extofs, extlen) |
110 | encdat = dat[8:] | 145 | encdat = dat[o:] |
111 | while len(encdat)<extlen: | 146 | while len(encdat)<extlen: |
112 | dat = self.readdata(extofs, 0x200) | 147 | dat = self.readdata(extofs, self.blocksize) |
113 | ranges.append((extofs, extofs+0x200, "item #%d ext" % i)) | 148 | ranges.append((extofs, extofs+self.blocksize, "item #%d ext" % i)) |
114 | extofs, = struct.unpack("<L", dat[:4]) | 149 | if self.use64bit: |
150 | extofs, = struct.unpack("<Q", dat[:8]) | ||
151 | o = 8 | ||
152 | else: | ||
153 | extofs, = struct.unpack("<L", dat[:4]) | ||
154 | o = 4 | ||
115 | infostr += ";%08x" % (extofs) | 155 | infostr += ";%08x" % (extofs) |
116 | encdat += dat[4:] | 156 | encdat += dat[o:] |
117 | tail = encdat[extlen:] | 157 | tail = encdat[extlen:] |
118 | encdat = encdat[:extlen] | 158 | encdat = encdat[:extlen] |
119 | decflag = '+' | 159 | decflags[0] = '+' |
120 | else: | 160 | else: |
121 | encdat = dat | 161 | encdat = dat |
122 | decflag = '*' | 162 | decflags[0] = '*' |
123 | 163 | ||
124 | if self.need_decode: | 164 | if self.encoding == 1: |
125 | decdat = kodecode(i+1, encdat) | 165 | decdat = kodecode(i+1, encdat) |
126 | else: | 166 | else: |
127 | decdat = encdat | 167 | decdat = encdat |
128 | decflag = ' ' | 168 | decflags[0] = ' ' |
129 | 169 | ||
130 | if args.decompress and self.iscompressed(decdat): | 170 | if args.decompress and self.iscompressed(decdat): |
131 | decdat = self.decompress(decdat) | 171 | decdat = self.decompress(decdat) |
132 | print("%5d: %08x-%08x: (%02x:%08x) %s %s%s %s" % (i+1, ofs, ofs+ln, flags, chk, infostr, decflag, toout(args, decdat), tohex(tail))) | 172 | decflags[1] = '@' |
173 | print("%5d: %08x-%08x: (%02x:%08x) %s %s%s %s" % (i+1, ofs, ofs+ln, flags, chk, infostr, "".join(decflags), toout(args, decdat), tohex(tail))) | ||
133 | 174 | ||
134 | if args.verbose: | 175 | if args.verbose: |
135 | # output parts not referenced in the .tad file. | 176 | # output parts not referenced in the .tad file. |
@@ -231,19 +272,19 @@ class Database: | |||
231 | def __init__(self, dbdir): | 272 | def __init__(self, dbdir): |
232 | self.dbdir = dbdir | 273 | self.dbdir = dbdir |
233 | 274 | ||
234 | self.stru = self.getfile("Stru", need_decode=True) | 275 | self.stru = self.getfile("Stru") |
235 | self.index = self.getfile("Index") | 276 | self.index = self.getfile("Index") |
236 | self.bank = self.getfile("Bank") | 277 | self.bank = self.getfile("Bank") |
237 | self.sys = self.getfile("Sys", need_decode=True) | 278 | self.sys = self.getfile("Sys") |
238 | # BankTemp, Int | 279 | # BankTemp, Int |
239 | 280 | ||
240 | 281 | ||
241 | def getfile(self, name, need_decode=False): | 282 | def getfile(self, name): |
242 | try: | 283 | try: |
243 | datname = self.getname(name, "dat") | 284 | datname = self.getname(name, "dat") |
244 | tadname = self.getname(name, "tad") | 285 | tadname = self.getname(name, "tad") |
245 | if datname and tadname: | 286 | if datname and tadname: |
246 | return Datafile(open(datname, "rb"), open(tadname, "rb"), need_decode) | 287 | return Datafile(name, open(datname, "rb"), open(tadname, "rb")) |
247 | except IOError: | 288 | except IOError: |
248 | return | 289 | return |
249 | 290 | ||
@@ -259,21 +300,20 @@ class Database: | |||
259 | 300 | ||
260 | def dump(self, args): | 301 | def dump(self, args): |
261 | if self.stru: | 302 | if self.stru: |
262 | print("stru") | ||
263 | self.stru.dump(args) | 303 | self.stru.dump(args) |
264 | if args.struonly: | ||
265 | self.dumptabledefs(args) | ||
266 | return | ||
267 | if self.index: | 304 | if self.index: |
268 | print("index") | ||
269 | self.index.dump(args) | 305 | self.index.dump(args) |
270 | if self.bank: | 306 | if self.bank: |
271 | print("bank") | ||
272 | self.bank.dump(args) | 307 | self.bank.dump(args) |
273 | if self.sys: | 308 | if self.sys: |
274 | print("sys") | ||
275 | self.sys.dump(args) | 309 | self.sys.dump(args) |
276 | 310 | ||
311 | def strudump(self, args): | ||
312 | if not self.stru: | ||
313 | print("missing CroStru file") | ||
314 | return | ||
315 | self.dumptabledefs(args) | ||
316 | |||
277 | def dumptabledefs(self, args): | 317 | def dumptabledefs(self, args): |
278 | dbinfo = self.stru.readrec(1) | 318 | dbinfo = self.stru.readrec(1) |
279 | dbdef = destruct_bank_definition(args, dbinfo) | 319 | dbdef = destruct_bank_definition(args, dbinfo) |
@@ -289,6 +329,39 @@ class Database: | |||
289 | tbinfo = struct.pack("<B", 4) + idx | 329 | tbinfo = struct.pack("<B", 4) + idx |
290 | tbdef = destruct_base_definition(args, tbinfo) | 330 | tbdef = destruct_base_definition(args, tbinfo) |
291 | 331 | ||
332 | def bankdump(self, args): | ||
333 | if not self.bank: | ||
334 | print("No CroBank.dat found") | ||
335 | return | ||
336 | nerr = 0 | ||
337 | xref = defaultdict(int) | ||
338 | for i in range(args.maxrecs): | ||
339 | try: | ||
340 | data = self.bank.readrec(i) | ||
341 | if not args.stats: | ||
342 | if data is None: | ||
343 | print("%5d: <deleted>" % i) | ||
344 | else: | ||
345 | print("%5d: %s" % (i, toout(args, data))) | ||
346 | else: | ||
347 | if data is None: | ||
348 | xref["None"] += 1 | ||
349 | elif not len(data): | ||
350 | xref["Empty"] += 1 | ||
351 | else: | ||
352 | xref["%02x" % data[0]] += 1 | ||
353 | nerr = 0 | ||
354 | except IndexError: | ||
355 | break | ||
356 | except Exception as e: | ||
357 | print("%5d: <%s>" % (i, e)) | ||
358 | nerr += 1 | ||
359 | if nerr > 5: | ||
360 | break | ||
361 | if args.stats: | ||
362 | print("-- stats --") | ||
363 | for k, v in xref.items(): | ||
364 | print("%5d * %s" % (v, k)) | ||
292 | 365 | ||
293 | def incdata(data, s): | 366 | def incdata(data, s): |
294 | """ | 367 | """ |
@@ -359,9 +432,23 @@ def kod_hexdump(args): | |||
359 | def cro_dump(args): | 432 | def cro_dump(args): |
360 | """ handle 'crodump' subcommand """ | 433 | """ handle 'crodump' subcommand """ |
361 | db = Database(args.dbdir) | 434 | db = Database(args.dbdir) |
362 | |||
363 | db.dump(args) | 435 | db.dump(args) |
364 | 436 | ||
437 | def stru_dump(args): | ||
438 | """ handle 'strudump' subcommand """ | ||
439 | db = Database(args.dbdir) | ||
440 | db.strudump(args) | ||
441 | |||
442 | def bank_dump(args): | ||
443 | """ hexdump all records """ | ||
444 | if args.maxrecs: | ||
445 | args.maxrecs = int(args.maxrecs, 0) | ||
446 | else: | ||
447 | # an arbitrarily large number. | ||
448 | args.maxrecs = 0xFFFFFFFF | ||
449 | |||
450 | db = Database(args.dbdir) | ||
451 | db.bankdump(args) | ||
365 | 452 | ||
366 | def destruct(args): | 453 | def destruct(args): |
367 | """ | 454 | """ |
@@ -396,21 +483,35 @@ def main(): | |||
396 | ko.add_argument('filename', type=str, nargs='?', help="dump either stdin, or the specified file") | 483 | ko.add_argument('filename', type=str, nargs='?', help="dump either stdin, or the specified file") |
397 | ko.set_defaults(handler=kod_hexdump) | 484 | ko.set_defaults(handler=kod_hexdump) |
398 | 485 | ||
399 | cro = subparsers.add_parser('crodump', help='CROdumper') | 486 | p = subparsers.add_parser('crodump', help='CROdumper') |
400 | cro.add_argument('--verbose', '-v', action='store_true') | 487 | p.add_argument('--verbose', '-v', action='store_true') |
401 | cro.add_argument('--kodecode', '-k', action='store_true') | 488 | p.add_argument('--kodecode', '-k', action='store_true') |
402 | cro.add_argument('--ascdump', '-a', action='store_true') | 489 | p.add_argument('--ascdump', '-a', action='store_true') |
403 | cro.add_argument('--nokod', '-n', action='store_true') | 490 | p.add_argument('--nokod', '-n', action='store_true') |
404 | cro.add_argument('--struonly', action='store_true') | 491 | p.add_argument('--nodecompress', action='store_false', dest='decompress', default='true') |
405 | cro.add_argument('--nodecompress', action='store_false', dest='decompress', default='true') | 492 | p.add_argument('dbdir', type=str) |
406 | cro.add_argument('dbdir', type=str) | 493 | p.set_defaults(handler=cro_dump) |
407 | cro.set_defaults(handler=cro_dump) | 494 | |
408 | 495 | p = subparsers.add_parser('bankdump', help='BANKdumper') | |
409 | des = subparsers.add_parser('destruct', help='Stru dumper') | 496 | p.add_argument('--verbose', '-v', action='store_true') |
410 | des.add_argument('--verbose', '-v', action='store_true') | 497 | p.add_argument('--ascdump', '-a', action='store_true') |
411 | des.add_argument('--ascdump', '-a', action='store_true') | 498 | p.add_argument('--maxrecs', '-n', type=str, help="max nr or recots to output") |
412 | des.add_argument('--type', '-t', type=int, help='what type of record to destruct') | 499 | p.add_argument('--stats', action='store_true', help='calc table stats from the first byte of each record') |
413 | des.set_defaults(handler=destruct) | 500 | p.add_argument('dbdir', type=str) |
501 | p.set_defaults(handler=bank_dump) | ||
502 | |||
503 | p = subparsers.add_parser('strudump', help='STRUdumper') | ||
504 | p.add_argument('--verbose', '-v', action='store_true') | ||
505 | p.add_argument('--ascdump', '-a', action='store_true') | ||
506 | p.add_argument('dbdir', type=str) | ||
507 | p.set_defaults(handler=stru_dump) | ||
508 | |||
509 | |||
510 | p = subparsers.add_parser('destruct', help='Stru dumper') | ||
511 | p.add_argument('--verbose', '-v', action='store_true') | ||
512 | p.add_argument('--ascdump', '-a', action='store_true') | ||
513 | p.add_argument('--type', '-t', type=int, help='what type of record to destruct') | ||
514 | p.set_defaults(handler=destruct) | ||
414 | 515 | ||
415 | args = parser.parse_args() | 516 | args = parser.parse_args() |
416 | 517 | ||