diff options
-rw-r--r-- | crodump.py | 56 | ||||
-rw-r--r-- | docs/cronos-research.md | 14 |
2 files changed, 55 insertions, 15 deletions
@@ -87,6 +87,9 @@ class Datafile: | |||
87 | return self.dat.read(size) | 87 | return self.dat.read(size) |
88 | 88 | ||
89 | def readrec(self, idx): | 89 | def readrec(self, idx): |
90 | """ | ||
91 | extract and decode a single record. | ||
92 | """ | ||
90 | ofs, ln, chk = self.tadidx[idx-1] | 93 | ofs, ln, chk = self.tadidx[idx-1] |
91 | if ln==0xFFFFFFFF: | 94 | if ln==0xFFFFFFFF: |
92 | # deleted record | 95 | # deleted record |
@@ -120,6 +123,9 @@ class Datafile: | |||
120 | 123 | ||
121 | 124 | ||
122 | def dump(self, args): | 125 | def dump(self, args): |
126 | """ | ||
127 | dump decodes all references data, and optionally will print out all unused bytes in the .dat file. | ||
128 | """ | ||
123 | 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)) | 129 | 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)) |
124 | ranges = [] # keep track of used bytes in the .dat file. | 130 | ranges = [] # keep track of used bytes in the .dat file. |
125 | for i, (ofs, ln, chk) in enumerate(self.tadidx): | 131 | for i, (ofs, ln, chk) in enumerate(self.tadidx): |
@@ -183,20 +189,31 @@ class Datafile: | |||
183 | print("%08x-%08x: %s" % (o, o+l, toout(args, dat))) | 189 | print("%08x-%08x: %s" % (o, o+l, toout(args, dat))) |
184 | 190 | ||
185 | def iscompressed(self, data): | 191 | def iscompressed(self, data): |
192 | """ | ||
193 | Note that the compression header uses big-endian numbers. | ||
194 | """ | ||
186 | if len(data)<11: | 195 | if len(data)<11: |
187 | return | 196 | return |
188 | size, flag = struct.unpack_from(">HH", data, 0) | ||
189 | if size+5 != len(data): | ||
190 | return | ||
191 | if flag!=0x800: | ||
192 | return | ||
193 | if data[-3:] != b"\x00\x00\x02": | 197 | if data[-3:] != b"\x00\x00\x02": |
194 | return | 198 | return |
199 | o = 0 | ||
200 | while o < len(data)-3: | ||
201 | size, flag = struct.unpack_from(">HH", data, o) | ||
202 | if flag!=0x800 and flag!=0x008: | ||
203 | return | ||
204 | o += size + 2 | ||
195 | return True | 205 | return True |
196 | 206 | ||
197 | def decompress(self, data): | 207 | def decompress(self, data): |
198 | C = zlib.decompressobj(-15) | 208 | result = b"" |
199 | return C.decompress(data[8:-3]) | 209 | o = 0 |
210 | while o < len(data)-3: | ||
211 | size, flag, crc = struct.unpack_from(">HHL", data, o) | ||
212 | C = zlib.decompressobj(-15) | ||
213 | result += C.decompress(data[o+8:o+8+size]) | ||
214 | o += size + 2 | ||
215 | return result | ||
216 | |||
200 | 217 | ||
201 | def dump_bank_definition(args, bankdict): | 218 | def dump_bank_definition(args, bankdict): |
202 | """ | 219 | """ |
@@ -222,9 +239,9 @@ def decode_field(data): | |||
222 | unk4 = rd.readdword() # Always 0x00000009 or 0x0001000d | 239 | unk4 = rd.readdword() # Always 0x00000009 or 0x0001000d |
223 | remain = rd.readbytes() | 240 | remain = rd.readbytes() |
224 | 241 | ||
225 | print("Type: %d (%02d/%02d) %04x,(%d-%d),%04x - '%s' -- %s" % (typ, idx1, idx2, unk1, unk2, unk3, unk4, name, tohex(remain))) | 242 | print("Type: %2d (%2d/%2d) %04x,(%d-%4d),%04x - '%s' -- %s" % (typ, idx1, idx2, unk1, unk2, unk3, unk4, name, tohex(remain))) |
226 | else: | 243 | else: |
227 | print("Type: %d %2d %d,%d - '%s'" % (typ, idx1, unk1, unk2, name)) | 244 | print("Type: %2d %2d %d,%d - '%s'" % (typ, idx1, unk1, unk2, name)) |
228 | 245 | ||
229 | """ | 246 | """ |
230 | 2 Base000 - 000001 050001 000000000000000546696c657302464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f0010000000000000000010000000000000000 | 247 | 2 Base000 - 000001 050001 000000000000000546696c657302464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f0010000000000000000010000000000000000 |
@@ -272,7 +289,11 @@ def destruct_base_definition(args, data): | |||
272 | abbrev = rd.readname() | 289 | abbrev = rd.readname() |
273 | unk7 = rd.readdword() | 290 | unk7 = rd.readdword() |
274 | nrfields = rd.readdword() | 291 | nrfields = rd.readdword() |
292 | |||
293 | if args.verbose: | ||
294 | print("table: %s" % tohex(data[:rd.o])) | ||
275 | print("%d,%d,%d,%d,%d,%d %d,%d '%s' '%s'" % (unk1, version, unk2, unk3, unk4, unk5, unk7, nrfields, tablename, abbrev)) | 295 | print("%d,%d,%d,%d,%d,%d %d,%d '%s' '%s'" % (unk1, version, unk2, unk3, unk4, unk5, unk7, nrfields, tablename, abbrev)) |
296 | |||
276 | fields = [] | 297 | fields = [] |
277 | for _ in range(nrfields): | 298 | for _ in range(nrfields): |
278 | l = rd.readword() | 299 | l = rd.readword() |
@@ -396,12 +417,20 @@ class Database: | |||
396 | if not self.bank: | 417 | if not self.bank: |
397 | print("No CroBank.dat found") | 418 | print("No CroBank.dat found") |
398 | return | 419 | return |
420 | if args.skipencrypted and self.bank.encoding==3: | ||
421 | print("Skipping encrypted CroBank") | ||
422 | return | ||
399 | nerr = 0 | 423 | nerr = 0 |
400 | xref = defaultdict(int) | 424 | xref = defaultdict(int) |
401 | for i in range(args.maxrecs): | 425 | for i in range(args.maxrecs): |
402 | try: | 426 | try: |
403 | data = self.bank.readrec(i) | 427 | data = self.bank.readrec(i) |
404 | if not args.stats: | 428 | if args.find1d: |
429 | if data and (data.find(b"\x1d")>0 or data.find(b"\x1b")>0): | ||
430 | print("%d -> %s" % (i, b2a_hex(data))) | ||
431 | break | ||
432 | |||
433 | elif not args.stats: | ||
405 | if data is None: | 434 | if data is None: |
406 | print("%5d: <deleted>" % i) | 435 | print("%5d: <deleted>" % i) |
407 | else: | 436 | else: |
@@ -426,6 +455,11 @@ class Database: | |||
426 | for k, v in xref.items(): | 455 | for k, v in xref.items(): |
427 | print("%5d * %s" % (v, k)) | 456 | print("%5d * %s" % (v, k)) |
428 | 457 | ||
458 | def readrec(self, sysnum): | ||
459 | data = self.bank.readrec(sysnum) | ||
460 | tabnum, = struct.unpack_from("<B", data, 0) | ||
461 | fields = data[1:].split(b"\x1e") | ||
462 | |||
429 | def incdata(data, s): | 463 | def incdata(data, s): |
430 | """ | 464 | """ |
431 | add 's' to each byte. | 465 | add 's' to each byte. |
@@ -576,6 +610,8 @@ def main(): | |||
576 | p.add_argument('--verbose', '-v', action='store_true') | 610 | p.add_argument('--verbose', '-v', action='store_true') |
577 | p.add_argument('--ascdump', '-a', action='store_true') | 611 | p.add_argument('--ascdump', '-a', action='store_true') |
578 | p.add_argument('--maxrecs', '-n', type=str, help="max nr or recots to output") | 612 | p.add_argument('--maxrecs', '-n', type=str, help="max nr or recots to output") |
613 | p.add_argument('--find1d', action='store_true') | ||
614 | p.add_argument('--inclencrypted', action='store_false', dest='skipencrypted', default='true', help='include encrypted records in the output') | ||
579 | p.add_argument('--stats', action='store_true', help='calc table stats from the first byte of each record') | 615 | p.add_argument('--stats', action='store_true', help='calc table stats from the first byte of each record') |
580 | p.add_argument('dbdir', type=str) | 616 | p.add_argument('dbdir', type=str) |
581 | p.set_defaults(handler=bank_dump) | 617 | p.set_defaults(handler=bank_dump) |
diff --git a/docs/cronos-research.md b/docs/cronos-research.md index 1ee75c4..5d0a508 100644 --- a/docs/cronos-research.md +++ b/docs/cronos-research.md | |||
@@ -157,7 +157,9 @@ The toplevel table-id for CroStru and CroSys is #3, while referenced records hav | |||
157 | 157 | ||
158 | CroBank.dat contains the actual database entries for multiple tables as described in the CroStru file. After each chunk is re-assembled (and potentially decoded with the per block offset being the record number in the .tad file). | 158 | CroBank.dat contains the actual database entries for multiple tables as described in the CroStru file. After each chunk is re-assembled (and potentially decoded with the per block offset being the record number in the .tad file). |
159 | 159 | ||
160 | Its first byte defines, which table it belongs to. It is encoded in cp1251 (or possibly IBM866) with actual column data separated by 0x1e. There is an extra concept of sub fields in those columns, indicated by a 0x1d byte. | 160 | Its first byte defines, which table it belongs to. It is encoded in cp1251 (or possibly IBM866) with actual column data separated by 0x1e. |
161 | There is an extra concept of sub fields in those columns, indicated by a 0x1d byte. | ||
162 | Also files seem have have special fields, starting with a 0x1b byte. | ||
161 | 163 | ||
162 | 164 | ||
163 | ## structure definitions | 165 | ## structure definitions |
@@ -246,10 +248,12 @@ Other unassigned values in the table entry definition are | |||
246 | 248 | ||
247 | some records are compressed, the format is like this: | 249 | some records are compressed, the format is like this: |
248 | 250 | ||
249 | uint16 size | 251 | multiple-chunks { |
250 | uint8 head[2] = { 8, 0 } | 252 | uint16 size; // stored in bigendian format. |
251 | uint32 crc32 | 253 | uint8 head[2] = { 8, 0 } |
252 | uint8 compdata[size-4] | 254 | uint32 crc32 |
255 | uint8 compdata[size-6] | ||
256 | } | ||
253 | uint8 tail[3] = { 0, 0, 2 } | 257 | uint8 tail[3] = { 0, 0, 2 } |
254 | 258 | ||
255 | ## encrypted records | 259 | ## encrypted records |