diff options
-rw-r--r-- | crodump.py | 161 |
1 files changed, 104 insertions, 57 deletions
@@ -217,33 +217,42 @@ class Datafile: | |||
217 | return result | 217 | return result |
218 | 218 | ||
219 | 219 | ||
220 | def dump_bank_definition(args, bankdict): | 220 | def dump_db_definition(args, dbdict): |
221 | """ | 221 | """ |
222 | decode the 'bank' / database definition | 222 | decode the 'bank' / database definition |
223 | """ | 223 | """ |
224 | for k, v in bankdict.items(): | 224 | for k, v in dbdict.items(): |
225 | if re.search(b'[^\x0d\x0a\x09\x20-\x7e\xc0-\xff]', v): | 225 | if re.search(b'[^\x0d\x0a\x09\x20-\x7e\xc0-\xff]', v): |
226 | print("%-20s - %s" % (k, toout(args, v))) | 226 | print("%-20s - %s" % (k, toout(args, v))) |
227 | else: | 227 | else: |
228 | print("%-20s - \"%s\"" % (k, strescape(v))) | 228 | print("%-20s - \"%s\"" % (k, strescape(v))) |
229 | 229 | ||
230 | class FieldDefinition: | ||
231 | def __init__(self, data): | ||
232 | self.decode(data) | ||
230 | 233 | ||
231 | def decode_field(data): | 234 | def decode(self, data): |
232 | rd = ByteReader(data) | 235 | self.defdata = data |
233 | typ = rd.readword() | 236 | |
234 | idx1 = rd.readdword() | 237 | rd = ByteReader(data) |
235 | name = rd.readname() | 238 | self.typ = rd.readword() |
236 | unk1 = rd.readdword() | 239 | self.idx1 = rd.readdword() |
237 | unk2 = rd.readbyte() # Always 1 | 240 | self.name = rd.readname() |
238 | if typ: | 241 | self.flags = rd.readdword() |
239 | idx2 = rd.readdword() | 242 | self.minval = rd.readbyte() # Always 1 |
240 | unk3 = rd.readdword() # max value or length | 243 | if self.typ: |
241 | unk4 = rd.readdword() # Always 0x00000009 or 0x0001000d | 244 | self.idx2 = rd.readdword() |
242 | remain = rd.readbytes() | 245 | self.maxval = rd.readdword() # max value or length |
243 | 246 | self.unk4 = rd.readdword() # Always 0x00000009 or 0x0001000d | |
244 | print("Type: %2d (%2d/%2d) %04x,(%d-%4d),%04x - %-40s -- %s" % (typ, idx1, idx2, unk1, unk2, unk3, unk4, "'%s'" % name, tohex(remain))) | 247 | else: |
245 | else: | 248 | self.idx2 = self.maxval = self.unk4 = None |
246 | print("Type: %2d %2d %d,%d - '%s'" % (typ, idx1, unk1, unk2, name)) | 249 | self.remaining = rd.readbytes() |
250 | |||
251 | def __str__(self): | ||
252 | if self.typ: | ||
253 | return "Type: %2d (%2d/%2d) %04x,(%d-%4d),%04x - %-40s -- %s" % (self.typ, self.idx1, self.idx2, self.flags, self.minval, self.maxval, self.unk4, "'%s'" % self.name, tohex(self.remaining)) | ||
254 | else: | ||
255 | return "Type: %2d %2d %d,%d - '%s'" % (self.typ, self.idx1, self.flags, self.minval, self.name) | ||
247 | 256 | ||
248 | """ | 257 | """ |
249 | 2 Base000 - 000001 050001 000000000000000546696c657302464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f0010000000000000000010000000000000000 | 258 | 2 Base000 - 000001 050001 000000000000000546696c657302464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f0010000000000000000010000000000000000 |
@@ -268,47 +277,60 @@ def decode_field(data): | |||
268 | 2 Base000 - 00000300090102000000000000000000000005d4e0e9ebfb02464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f001000000000000000000000000060000000000000000ffffffffffffffffffffffffffffffffffffffff1700000003ffffffffffffffffffffffff06000000010000000000 | 277 | 2 Base000 - 00000300090102000000000000000000000005d4e0e9ebfb02464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f001000000000000000000000000060000000000000000ffffffffffffffffffffffffffffffffffffffff1700000003ffffffffffffffffffffffff06000000010000000000 |
269 | """ | 278 | """ |
270 | 279 | ||
271 | def destruct_base_definition(args, data): | 280 | class TableDefinition: |
272 | """ | 281 | def __init__(self, data): |
273 | decode the 'base' / table definition | 282 | self.decode(data) |
274 | """ | 283 | |
275 | rd = ByteReader(data) | 284 | def decode(self, data): |
285 | """ | ||
286 | decode the 'base' / table definition | ||
287 | """ | ||
288 | rd = ByteReader(data) | ||
289 | |||
290 | self.unk1 = rd.readword() | ||
291 | self.version = rd.readbyte() | ||
292 | if self.version > 1: | ||
293 | _ = rd.readbyte() # always 0 anyway | ||
294 | self.unk2 = rd.readbyte() # if this is not 5 (but 9), there's another 4 bytes inserted, this could be a length-byte. | ||
295 | self.unk3 = rd.readbyte() | ||
296 | |||
297 | if self.unk2 > 5: # seen only 5 and 9 for now with 9 implying an extra dword | ||
298 | _ = rd.readdword() | ||
276 | 299 | ||
277 | unk1 = rd.readword() | 300 | self.tableid = rd.readdword() |
278 | version = rd.readbyte() | 301 | self.unk5 = rd.readdword() |
279 | if version > 1: | ||
280 | _ = rd.readbyte() # always 0 anyway | ||
281 | unk2 = rd.readbyte() # if this is not 5 (but 9), there's another 4 bytes inserted | ||
282 | unk3 = rd.readbyte() | ||
283 | 302 | ||
284 | if unk2 > 5: # seen only 5 and 9 for now with 9 implying an extra dword | 303 | self.tablename = rd.readname() |
285 | _ = rd.readdword() | 304 | self.abbrev = rd.readname() |
305 | self.unk7 = rd.readdword() | ||
306 | nrfields = rd.readdword() | ||
286 | 307 | ||
287 | unk4 = rd.readdword() | 308 | self.headerdata = data[:rd.o] |
288 | unk5 = rd.readdword() | ||
289 | 309 | ||
290 | tablename = rd.readname() | 310 | self.fields = [] |
291 | abbrev = rd.readname() | 311 | for _ in range(nrfields): |
292 | unk7 = rd.readdword() | 312 | l = rd.readword() |
293 | nrfields = rd.readdword() | 313 | fielddef = rd.readbytes(l) |
314 | self.fields.append(FieldDefinition(fielddef)) | ||
294 | 315 | ||
295 | if args.verbose: | 316 | self.remainingdata = rd.readbytes() |
296 | print("table: %s" % tohex(data[:rd.o])) | ||
297 | print("%d,%d,%d,%d,%d,%d %d,%d '%s' '%s'" % (unk1, version, unk2, unk3, unk4, unk5, unk7, nrfields, tablename, abbrev)) | ||
298 | 317 | ||
299 | fields = [] | 318 | def dump(self, args): |
300 | for _ in range(nrfields): | 319 | if args.verbose: |
301 | l = rd.readword() | 320 | print("table: %s" % tohex(self.headerdata)) |
302 | fielddef = rd.readbytes(l) | 321 | print("%d,%d,%d,%d,%d,%d %d,%d '%s' '%s'" % ( self.unk1, self.version, self.unk2, self.unk3, self.tableid, self.unk5, self.unk7, len(self.fields), self.tablename, self.abbrev)) |
322 | |||
323 | for field in self.fields: | ||
324 | if args.verbose: | ||
325 | print("field: @%04x: %04x - %s" % (field.byteoffset, len(field.defdata), tohex(field.defdata))) | ||
326 | print(str(field)) | ||
303 | if args.verbose: | 327 | if args.verbose: |
304 | print("field: @%04x: %04x - %s" % (rd.o, l, tohex(fielddef))) | 328 | print("remaining: %s" % tohex(self.remainingdata)) |
305 | fields.append(decode_field(fielddef)) | ||
306 | remaining = rd.readbytes() | ||
307 | 329 | ||
308 | print("rem: %s" % tohex(remaining)) | ||
309 | 330 | ||
310 | def destruct_sys3_def(rd): | 331 | def destruct_sys3_def(rd): |
311 | pass | 332 | pass |
333 | |||
312 | def destruct_sys4_def(rd): | 334 | def destruct_sys4_def(rd): |
313 | n = rd.readdword() | 335 | n = rd.readdword() |
314 | for _ in range(n): | 336 | for _ in range(n): |
@@ -335,7 +357,7 @@ def destruct_sys_definition(args, data): | |||
335 | 357 | ||
336 | 358 | ||
337 | class Database: | 359 | class Database: |
338 | """ represent the entire database, consisting of stru, index and bank files """ | 360 | """ represent the entire database, consisting of Stru, Index and Bank files """ |
339 | def __init__(self, dbdir): | 361 | def __init__(self, dbdir): |
340 | self.dbdir = dbdir | 362 | self.dbdir = dbdir |
341 | 363 | ||
@@ -379,9 +401,9 @@ class Database: | |||
379 | if not self.stru: | 401 | if not self.stru: |
380 | print("missing CroStru file") | 402 | print("missing CroStru file") |
381 | return | 403 | return |
382 | self.dumptabledefs(args) | 404 | self.dump_db_table_defs(args) |
383 | 405 | ||
384 | def decode_bank_definition(self, data): | 406 | def decode_db_definition(self, data): |
385 | """ | 407 | """ |
386 | decode the 'bank' / database definition | 408 | decode the 'bank' / database definition |
387 | """ | 409 | """ |
@@ -403,7 +425,7 @@ class Database: | |||
403 | d[keyname] = refdata[1:] | 425 | d[keyname] = refdata[1:] |
404 | return d | 426 | return d |
405 | 427 | ||
406 | def dumptabledefs(self, args): | 428 | def dump_db_table_defs(self, args): |
407 | """ | 429 | """ |
408 | decode the table defs from recid #1, which always has table-id #3 | 430 | decode the table defs from recid #1, which always has table-id #3 |
409 | Note that I don't know if it is better to refer to this by recid, or by table-id. | 431 | Note that I don't know if it is better to refer to this by recid, or by table-id. |
@@ -414,13 +436,37 @@ class Database: | |||
414 | dbinfo = self.stru.readrec(1) | 436 | dbinfo = self.stru.readrec(1) |
415 | if dbinfo[:1] != b"\x03": | 437 | if dbinfo[:1] != b"\x03": |
416 | print("WARN: expected dbinfo to start with 0x03") | 438 | print("WARN: expected dbinfo to start with 0x03") |
417 | dbdef = self.decode_bank_definition(dbinfo[1:]) | 439 | dbdef = self.decode_db_definition(dbinfo[1:]) |
418 | dump_bank_definition(args, dbdef) | 440 | dump_db_definition(args, dbdef) |
419 | 441 | ||
420 | for k, v in dbdef.items(): | 442 | for k, v in dbdef.items(): |
421 | if k.startswith("Base") and k[4:].isnumeric(): | 443 | if k.startswith("Base") and k[4:].isnumeric(): |
422 | print("== %s ==" % k) | 444 | print("== %s ==" % k) |
423 | tbdef = destruct_base_definition(args, v) | 445 | tbdef = TableDefinition(v) |
446 | tbdef.dump(args) | ||
447 | |||
448 | def enumerate_tables(self): | ||
449 | dbinfo = self.stru.readrec(1) | ||
450 | if dbinfo[:1] != b"\x03": | ||
451 | print("WARN: expected dbinfo to start with 0x03") | ||
452 | dbdef = self.decode_db_definition(dbinfo[1:]) | ||
453 | |||
454 | for k, v in dbdef.items(): | ||
455 | if k.startswith("Base") and k[4:].isnumeric(): | ||
456 | yield TableDefinition(v) | ||
457 | |||
458 | def enumerate_records(self, table): | ||
459 | """ | ||
460 | usage: | ||
461 | for tab in db.enumerate_tables(): | ||
462 | for rec in db.enumerate_records(tab): | ||
463 | print(sqlformatter(tab, rec)) | ||
464 | """ | ||
465 | for i in range(1, args.maxrecs+1): | ||
466 | data = db.readrec(i) | ||
467 | if data and struct.unpack_from("<B", data, 0) == table.tableid: | ||
468 | yield data[1:].split(b"\x1e") | ||
469 | |||
424 | 470 | ||
425 | def recdump(self, args): | 471 | def recdump(self, args): |
426 | if args.index: | 472 | if args.index: |
@@ -594,9 +640,10 @@ def destruct(args): | |||
594 | data = unhex(data) | 640 | data = unhex(data) |
595 | 641 | ||
596 | if args.type==1: | 642 | if args.type==1: |
597 | destruct_bank_definition(args, data) | 643 | destruct_db_definition(args, data) |
598 | elif args.type==2: | 644 | elif args.type==2: |
599 | destruct_base_definition(args, data) | 645 | tbdef = TableDefinition(data) |
646 | tbdef.dump(args) | ||
600 | elif args.type==3: | 647 | elif args.type==3: |
601 | destruct_sys_definition(args, data) | 648 | destruct_sys_definition(args, data) |
602 | 649 | ||