From f61b66c9a3e50b46f762f17ebd32b3a01a2a54ce Mon Sep 17 00:00:00 2001 From: itsme Date: Tue, 13 Jul 2021 15:38:22 +0200 Subject: 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' --- crodump.py | 161 +++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 57 deletions(-) diff --git a/crodump.py b/crodump.py index af4dc36..cc0a7f2 100644 --- a/crodump.py +++ b/crodump.py @@ -217,33 +217,42 @@ class Datafile: return result -def dump_bank_definition(args, bankdict): +def dump_db_definition(args, dbdict): """ decode the 'bank' / database definition """ - for k, v in bankdict.items(): + for k, v in dbdict.items(): if re.search(b'[^\x0d\x0a\x09\x20-\x7e\xc0-\xff]', v): print("%-20s - %s" % (k, toout(args, v))) else: print("%-20s - \"%s\"" % (k, strescape(v))) +class FieldDefinition: + def __init__(self, data): + self.decode(data) -def decode_field(data): - rd = ByteReader(data) - typ = rd.readword() - idx1 = rd.readdword() - name = rd.readname() - unk1 = rd.readdword() - unk2 = rd.readbyte() # Always 1 - if typ: - idx2 = rd.readdword() - unk3 = rd.readdword() # max value or length - unk4 = rd.readdword() # Always 0x00000009 or 0x0001000d - remain = rd.readbytes() - - print("Type: %2d (%2d/%2d) %04x,(%d-%4d),%04x - %-40s -- %s" % (typ, idx1, idx2, unk1, unk2, unk3, unk4, "'%s'" % name, tohex(remain))) - else: - print("Type: %2d %2d %d,%d - '%s'" % (typ, idx1, unk1, unk2, name)) + def decode(self, data): + self.defdata = data + + rd = ByteReader(data) + self.typ = rd.readword() + self.idx1 = rd.readdword() + self.name = rd.readname() + self.flags = rd.readdword() + self.minval = rd.readbyte() # Always 1 + if self.typ: + self.idx2 = rd.readdword() + self.maxval = rd.readdword() # max value or length + self.unk4 = rd.readdword() # Always 0x00000009 or 0x0001000d + else: + self.idx2 = self.maxval = self.unk4 = None + self.remaining = rd.readbytes() + + def __str__(self): + if self.typ: + 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)) + else: + return "Type: %2d %2d %d,%d - '%s'" % (self.typ, self.idx1, self.flags, self.minval, self.name) """ 2 Base000 - 000001 050001 000000000000000546696c657302464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f0010000000000000000010000000000000000 @@ -268,47 +277,60 @@ def decode_field(data): 2 Base000 - 00000300090102000000000000000000000005d4e0e9ebfb02464c01000000010000001b000000000000000fd1e8f1f2e5ecedfbe920edeeece5f001000000000000000000000000060000000000000000ffffffffffffffffffffffffffffffffffffffff1700000003ffffffffffffffffffffffff06000000010000000000 """ -def destruct_base_definition(args, data): - """ - decode the 'base' / table definition - """ - rd = ByteReader(data) +class TableDefinition: + def __init__(self, data): + self.decode(data) + + def decode(self, data): + """ + decode the 'base' / table definition + """ + rd = ByteReader(data) + + self.unk1 = rd.readword() + self.version = rd.readbyte() + if self.version > 1: + _ = rd.readbyte() # always 0 anyway + self.unk2 = rd.readbyte() # if this is not 5 (but 9), there's another 4 bytes inserted, this could be a length-byte. + self.unk3 = rd.readbyte() + + if self.unk2 > 5: # seen only 5 and 9 for now with 9 implying an extra dword + _ = rd.readdword() - unk1 = rd.readword() - version = rd.readbyte() - if version > 1: - _ = rd.readbyte() # always 0 anyway - unk2 = rd.readbyte() # if this is not 5 (but 9), there's another 4 bytes inserted - unk3 = rd.readbyte() + self.tableid = rd.readdword() + self.unk5 = rd.readdword() - if unk2 > 5: # seen only 5 and 9 for now with 9 implying an extra dword - _ = rd.readdword() + self.tablename = rd.readname() + self.abbrev = rd.readname() + self.unk7 = rd.readdword() + nrfields = rd.readdword() - unk4 = rd.readdword() - unk5 = rd.readdword() + self.headerdata = data[:rd.o] - tablename = rd.readname() - abbrev = rd.readname() - unk7 = rd.readdword() - nrfields = rd.readdword() + self.fields = [] + for _ in range(nrfields): + l = rd.readword() + fielddef = rd.readbytes(l) + self.fields.append(FieldDefinition(fielddef)) - if args.verbose: - print("table: %s" % tohex(data[:rd.o])) - print("%d,%d,%d,%d,%d,%d %d,%d '%s' '%s'" % (unk1, version, unk2, unk3, unk4, unk5, unk7, nrfields, tablename, abbrev)) + self.remainingdata = rd.readbytes() - fields = [] - for _ in range(nrfields): - l = rd.readword() - fielddef = rd.readbytes(l) + def dump(self, args): + if args.verbose: + print("table: %s" % tohex(self.headerdata)) + 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)) + + for field in self.fields: + if args.verbose: + print("field: @%04x: %04x - %s" % (field.byteoffset, len(field.defdata), tohex(field.defdata))) + print(str(field)) if args.verbose: - print("field: @%04x: %04x - %s" % (rd.o, l, tohex(fielddef))) - fields.append(decode_field(fielddef)) - remaining = rd.readbytes() + print("remaining: %s" % tohex(self.remainingdata)) - print("rem: %s" % tohex(remaining)) def destruct_sys3_def(rd): pass + def destruct_sys4_def(rd): n = rd.readdword() for _ in range(n): @@ -335,7 +357,7 @@ def destruct_sys_definition(args, data): class Database: - """ represent the entire database, consisting of stru, index and bank files """ + """ represent the entire database, consisting of Stru, Index and Bank files """ def __init__(self, dbdir): self.dbdir = dbdir @@ -379,9 +401,9 @@ class Database: if not self.stru: print("missing CroStru file") return - self.dumptabledefs(args) + self.dump_db_table_defs(args) - def decode_bank_definition(self, data): + def decode_db_definition(self, data): """ decode the 'bank' / database definition """ @@ -403,7 +425,7 @@ class Database: d[keyname] = refdata[1:] return d - def dumptabledefs(self, args): + def dump_db_table_defs(self, args): """ decode the table defs from recid #1, which always has table-id #3 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: dbinfo = self.stru.readrec(1) if dbinfo[:1] != b"\x03": print("WARN: expected dbinfo to start with 0x03") - dbdef = self.decode_bank_definition(dbinfo[1:]) - dump_bank_definition(args, dbdef) + dbdef = self.decode_db_definition(dbinfo[1:]) + dump_db_definition(args, dbdef) for k, v in dbdef.items(): if k.startswith("Base") and k[4:].isnumeric(): print("== %s ==" % k) - tbdef = destruct_base_definition(args, v) + tbdef = TableDefinition(v) + tbdef.dump(args) + + def enumerate_tables(self): + dbinfo = self.stru.readrec(1) + if dbinfo[:1] != b"\x03": + print("WARN: expected dbinfo to start with 0x03") + dbdef = self.decode_db_definition(dbinfo[1:]) + + for k, v in dbdef.items(): + if k.startswith("Base") and k[4:].isnumeric(): + yield TableDefinition(v) + + def enumerate_records(self, table): + """ + usage: + for tab in db.enumerate_tables(): + for rec in db.enumerate_records(tab): + print(sqlformatter(tab, rec)) + """ + for i in range(1, args.maxrecs+1): + data = db.readrec(i) + if data and struct.unpack_from("