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: |
