summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoritsme <itsme@xs4all.nl>2021-07-09 17:13:12 +0200
committeritsme <itsme@xs4all.nl>2021-07-09 17:14:19 +0200
commit7acef7d17b95af8b88a7a5d2c947ef2c01da81a8 (patch)
treee9c12133a963f2b9f98d664538bf6de5e704699f
parentb31f69506d91792e75fc7feb56127f19912eb554 (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.py205
1 files changed, 153 insertions, 52 deletions
diff --git a/crodump.py b/crodump.py
index 80daab9..148b3cf 100644
--- a/crodump.py
+++ b/crodump.py
@@ -7,6 +7,7 @@ from hexdump import hexdump, asasc, tohex, unhex, strescape
7from koddecoder import kodecode 7from koddecoder import kodecode
8from readers import ByteReader 8from readers import ByteReader
9import zlib 9import zlib
10from collections import defaultdict
10 11
11""" 12"""
12python3 crodump.py crodump chechnya_proverki_ul_2012 13python3 crodump.py crodump chechnya_proverki_ul_2012
@@ -32,23 +33,50 @@ def enumunreferenced(ranges, filesize):
32 33
33class Datafile: 34class 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
293def incdata(data, s): 366def incdata(data, s):
294 """ 367 """
@@ -359,9 +432,23 @@ def kod_hexdump(args):
359def cro_dump(args): 432def 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
437def stru_dump(args):
438 """ handle 'strudump' subcommand """
439 db = Database(args.dbdir)
440 db.strudump(args)
441
442def 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
366def destruct(args): 453def 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