diff options
author | itsme <itsme@xs4all.nl> | 2021-07-07 10:25:29 +0200 |
---|---|---|
committer | itsme <itsme@xs4all.nl> | 2021-07-07 10:25:29 +0200 |
commit | 6ab29a7ec498d3893dd602b881ae4d354a784b10 (patch) | |
tree | 0f2947c84fa3c8eeca145cf752e188c57930044a | |
parent | 1604864c258cbe455bff6b46c8622cc58d14c1e9 (diff) |
several updates:
* added 'readrec' method to Datafile
* case insensitive filename matching
* added 'destruct' option to decode top level stru information.
-rw-r--r-- | crodump.py | 133 |
1 files changed, 110 insertions, 23 deletions
@@ -4,6 +4,8 @@ import struct | |||
4 | from binascii import b2a_hex | 4 | from binascii import b2a_hex |
5 | from hexdump import hexdump, asasc, tohex, unhex | 5 | from hexdump import hexdump, asasc, tohex, unhex |
6 | from koddecoder import kodecode | 6 | from koddecoder import kodecode |
7 | from readers import ByteReader | ||
8 | |||
7 | """ | 9 | """ |
8 | python3 crodump.py crodump chechnya_proverki_ul_2012 | 10 | python3 crodump.py crodump chechnya_proverki_ul_2012 |
9 | python3 crodump.py kodump -s 6 -o 0x4cc9 -e 0x5d95 chechnya_proverki_ul_2012/CroStru.dat | 11 | python3 crodump.py kodump -s 6 -o 0x4cc9 -e 0x5d95 chechnya_proverki_ul_2012/CroStru.dat |
@@ -28,10 +30,12 @@ def enumunreferenced(ranges, filesize): | |||
28 | 30 | ||
29 | class Datafile: | 31 | class Datafile: |
30 | """ Represent a single .dat with it's .tad index file """ | 32 | """ Represent a single .dat with it's .tad index file """ |
31 | def __init__(self, dat, tad): | 33 | def __init__(self, dat, tad, need_decode=False): |
32 | self.dat = dat | 34 | self.dat = dat |
33 | self.tad = tad | 35 | self.tad = tad |
34 | 36 | ||
37 | self.need_decode = need_decode | ||
38 | |||
35 | self.readtad() | 39 | self.readtad() |
36 | 40 | ||
37 | self.dat.seek(0, io.SEEK_END) | 41 | self.dat.seek(0, io.SEEK_END) |
@@ -48,9 +52,40 @@ class Datafile: | |||
48 | self.dat.seek(ofs) | 52 | self.dat.seek(ofs) |
49 | return self.dat.read(size) | 53 | return self.dat.read(size) |
50 | 54 | ||
55 | def readrec(self, idx): | ||
56 | ofs, ln, chk = self.tabidx[idx-1] | ||
57 | if ln==0xFFFFFFFF: | ||
58 | # deleted record | ||
59 | return | ||
60 | |||
61 | flags = ln>>24 | ||
62 | |||
63 | ln &= 0xFFFFFFF | ||
64 | dat = self.readdata(ofs, ln) | ||
65 | |||
66 | if not dat: | ||
67 | # empty record | ||
68 | encdat = dat | ||
69 | elif self.need_decode and not flags: | ||
70 | extofs, extlen = struct.unpack("<LL", dat[:8]) | ||
71 | encdat = dat[8:] | ||
72 | while len(encdat)<extlen: | ||
73 | dat = self.readdata(extofs, 0x200) | ||
74 | extofs, = struct.unpack("<L", dat[:4]) | ||
75 | encdat += dat[4:] | ||
76 | encdat = encdat[:extlen] | ||
77 | else: | ||
78 | encdat = dat | ||
79 | |||
80 | if not self.need_decode: | ||
81 | return encdat | ||
82 | else: | ||
83 | return kodecode(idx, encdat) | ||
84 | |||
85 | |||
51 | def dump(self, args, nodecode=False): | 86 | def dump(self, args, nodecode=False): |
52 | print("tadhdr: %08x %08x" % tuple(self.tadhdr)) | 87 | print("tadhdr: %08x %08x" % tuple(self.tadhdr)) |
53 | ranges = [] | 88 | ranges = [] # keep track of used bytes in the .dat file. |
54 | for i, (ofs, ln, chk) in enumerate(self.tadidx): | 89 | for i, (ofs, ln, chk) in enumerate(self.tadidx): |
55 | if ln==0xFFFFFFFF: | 90 | if ln==0xFFFFFFFF: |
56 | print("%5d: %08x %08x %08x" % (i, ofs, ln, chk)) | 91 | print("%5d: %08x %08x %08x" % (i, ofs, ln, chk)) |
@@ -60,11 +95,14 @@ class Datafile: | |||
60 | ln &= 0xFFFFFFF | 95 | ln &= 0xFFFFFFF |
61 | dat = self.readdata(ofs, ln) | 96 | dat = self.readdata(ofs, ln) |
62 | ranges.append((ofs, ofs+ln, "item #%d" % i)) | 97 | ranges.append((ofs, ofs+ln, "item #%d" % i)) |
63 | plain = b'' | ||
64 | decflag = ' ' | 98 | decflag = ' ' |
65 | infostr = "" | 99 | infostr = "" |
66 | tail = b'' | 100 | tail = b'' |
67 | if not nodecode and not flags: | 101 | |
102 | if not dat: | ||
103 | # empty record | ||
104 | encdat = dat | ||
105 | elif not nodecode and not flags: | ||
68 | extofs, extlen = struct.unpack("<LL", dat[:8]) | 106 | extofs, extlen = struct.unpack("<LL", dat[:8]) |
69 | infostr = "%08x;%08x" % (extofs, extlen) | 107 | infostr = "%08x;%08x" % (extofs, extlen) |
70 | encdat = dat[8:] | 108 | encdat = dat[8:] |
@@ -101,19 +139,48 @@ class Database: | |||
101 | def __init__(self, dbdir): | 139 | def __init__(self, dbdir): |
102 | self.dbdir = dbdir | 140 | self.dbdir = dbdir |
103 | 141 | ||
104 | self.stru = self.getfile("Stru") | 142 | self.stru = self.getfile("Stru", need_decode=True) |
105 | self.index = self.getfile("Index") | 143 | self.index = self.getfile("Index") |
106 | self.bank = self.getfile("Bank") | 144 | self.bank = self.getfile("Bank") |
107 | self.sys = self.getfile("Sys") | 145 | self.sys = self.getfile("Sys", need_decode=True) |
146 | # BankTemp, Int | ||
108 | 147 | ||
109 | def getfile(self, name): | 148 | |
149 | def getfile(self, name, need_decode=False): | ||
110 | try: | 150 | try: |
111 | return Datafile(open(self.getname(name, "dat"), "rb"), open(self.getname(name, "tad"), "rb")) | 151 | datname = self.getname(name, "dat") |
152 | tadname = self.getname(name, "tad") | ||
153 | if datname and tadname: | ||
154 | return Datafile(open(datname, "rb"), open(tadname, "rb"), need_decode) | ||
112 | except IOError: | 155 | except IOError: |
113 | return | 156 | return |
114 | 157 | ||
115 | def getname(self, name, ext): | 158 | def getname(self, name, ext): |
116 | return os.path.join(self.dbdir, "Cro%s.%s" % (name, ext)) | 159 | """ |
160 | get a case-insensitive filename match for 'name.ext'. | ||
161 | Returns None when no matching file was not found. | ||
162 | """ | ||
163 | basename = "Cro%s.%s" % (name, ext) | ||
164 | for fn in os.scandir(self.dbdir): | ||
165 | if basename.lower() == fn.name.lower(): | ||
166 | return os.path.join(self.dbdir, fn.name) | ||
167 | |||
168 | def dump(self, args): | ||
169 | if self.stru: | ||
170 | print("stru") | ||
171 | self.stru.dump(args) | ||
172 | if args.struonly: | ||
173 | return | ||
174 | if self.index: | ||
175 | print("index") | ||
176 | self.index.dump(args) | ||
177 | if self.bank: | ||
178 | print("bank") | ||
179 | self.bank.dump(args) | ||
180 | if self.sys: | ||
181 | print("sys") | ||
182 | self.sys.dump(args) | ||
183 | |||
117 | 184 | ||
118 | def incdata(data, s): | 185 | def incdata(data, s): |
119 | """ | 186 | """ |
@@ -179,22 +246,38 @@ def cro_dump(args): | |||
179 | """ handle 'crodump' subcommand """ | 246 | """ handle 'crodump' subcommand """ |
180 | db = Database(args.dbdir) | 247 | db = Database(args.dbdir) |
181 | 248 | ||
182 | if db.stru: | 249 | db.dump(args) |
183 | print("stru") | ||
184 | db.stru.dump(args) | ||
185 | if args.struonly: | ||
186 | return | ||
187 | if db.index: | ||
188 | print("index") | ||
189 | db.index.dump(args, nodecode=True) | ||
190 | if db.bank: | ||
191 | print("bank") | ||
192 | db.bank.dump(args, nodecode=True) | ||
193 | if db.sys: | ||
194 | print("sys") | ||
195 | db.sys.dump(args) | ||
196 | 250 | ||
197 | 251 | ||
252 | def destruct(args): | ||
253 | """ | ||
254 | decode the index#1 structure information record | ||
255 | Takes hex input from stdin. | ||
256 | """ | ||
257 | import sys | ||
258 | data = sys.stdin.buffer.read() | ||
259 | data = unhex(data) | ||
260 | |||
261 | d = dict() | ||
262 | |||
263 | rd = ByteReader(data) | ||
264 | |||
265 | o = 0 | ||
266 | startbyte = rd.readbyte() | ||
267 | while not rd.eof(): | ||
268 | keylen = rd.readbyte() | ||
269 | keyname = rd.readbytes(keylen).decode('ascii') | ||
270 | if keyname in d: | ||
271 | print("duplicate key: %s" % keyname) | ||
272 | |||
273 | index_or_length = rd.readdword() | ||
274 | if index_or_length >> 31: | ||
275 | d[keyname] = rd.readbytes(index_or_length & 0x7FFFFFFF) | ||
276 | print("%-20s - %s" % (keyname, toout(args, d[keyname]))) | ||
277 | else: | ||
278 | d[keyname] = index_or_length | ||
279 | print("%-20s -> %s" % (keyname, d[keyname])) | ||
280 | |||
198 | def main(): | 281 | def main(): |
199 | import argparse | 282 | import argparse |
200 | parser = argparse.ArgumentParser(description='CRO hexdumper') | 283 | parser = argparse.ArgumentParser(description='CRO hexdumper') |
@@ -222,6 +305,10 @@ def main(): | |||
222 | cro.add_argument('dbdir', type=str) | 305 | cro.add_argument('dbdir', type=str) |
223 | cro.set_defaults(handler=cro_dump) | 306 | cro.set_defaults(handler=cro_dump) |
224 | 307 | ||
308 | des = subparsers.add_parser('destruct', help='Stru dumper') | ||
309 | des.add_argument('--ascdump', '-a', action='store_true') | ||
310 | des.set_defaults(handler=destruct) | ||
311 | |||
225 | args = parser.parse_args() | 312 | args = parser.parse_args() |
226 | 313 | ||
227 | if args.handler: | 314 | if args.handler: |