diff options
| author | itsme <itsme@xs4all.nl> | 2021-07-13 15:38:22 +0200 |
|---|---|---|
| committer | itsme <itsme@xs4all.nl> | 2021-07-13 15:38:22 +0200 |
| commit | f61b66c9a3e50b46f762f17ebd32b3a01a2a54ce (patch) | |
| tree | 891295ae3799a166a7ba7b48085881b4c61f56a6 | |
| parent | 45556df05e442147a3ffa7c15e81f222868ed7c1 (diff) | |
sutff:
* changed field + db definition parsers into objects.
* added enumerate_tables and enumate_records.
* changed naming convention to use 'table' for 'base', and 'db' for 'bank'
| -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 | ||
