#!/usr/bin/env python3 """Generate the DBCServer port's static tables from decomp evidence. Inputs (all derived, regenerable): decomp/DBCServer_Release_x64/rmi_to_proc.tsv RMI -> cmd -> procedures decomp/schema//routines.tsv procedure parameters Output: src/dbc_server/generated/tables.inc Run from the repo root: python3 decomp/scripts/gen_dbc_tables.py """ import csv, os ROOT = os.getcwd() OUT = os.path.join(ROOT, 'src/dbc_server/generated/tables.inc') # Database -> DBC connection type (DBC:0x1400a7aa0). The DB for a procedure is # where it lives in the schema dump (inference, see docs/DATABASE.md). DBTYPE = {'RustyHearts_Account': 'Web', 'RustyHearts_Auth': 'Auth', 'RustyHearts': 'Game', 'RustyHearts_Log': 'GameLog'} SQLTYPE = {'int': 'Int', 'smallint': 'SmallInt', 'tinyint': 'TinyInt', 'bigint': 'BigInt', 'bit': 'Bit', 'float': 'Float', 'uniqueidentifier': 'Guid', 'nvarchar': 'NVarChar', 'nchar': 'NVarChar', 'varchar': 'VarChar', 'char': 'VarChar', 'datetime': 'DateTime'} def rows(path): with open(path, newline='') as f: return list(csv.reader(f, delimiter='\t')) procs = {} # lower name -> (name, db, [(param, type, maxlen, out)]) for db in DBTYPE: for r in rows(f'{ROOT}/decomp/schema/{db}/routines.tsv')[1:]: if len(r) < 7 or r[0] != 'SQL_STORED_PROCEDURE': continue p = procs.setdefault(r[1].lower(), (r[1], db, [])) if r[2] != 'NULL': p[2].append((r[3], SQLTYPE.get(r[4], 'VarChar'), int(r[5]), r[6] == '1')) rmi = rows(f'{ROOT}/decomp/DBCServer_Release_x64/rmi_to_proc.tsv') hdr = rmi[0]; rmi = [dict(zip(hdr, r)) for r in rmi[1:]] used = [] for r in rmi: for p in filter(None, r['procs'].split(',')): if p.lower() not in procs: # Called by DBCServer but absent from the shipped schema: keep it so # the port fails like the original would (ADO error). DB guessed from name. procs[p.lower()] = (p, 'RustyHearts_Log' if '_log_' in p else 'RustyHearts', None) if p.lower() not in used: used.append(p.lower()) def cstr(s): return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"' out = ['// GENERATED by decomp/scripts/gen_dbc_tables.py - do not edit.', '// Sources: decomp/DBCServer_Release_x64/rmi_to_proc.tsv, decomp/schema/*/routines.tsv', ''] for i, key in enumerate(used): name, db, ps = procs[key] ps = ps or [] out.append(f'static const ProcParam kParams{i}[] = {{' + ''.join(f'{{{cstr(n)}, SqlType::{t}, {m}, {str(o).lower()}}},' for n, t, m, o in ps) + ('{nullptr, SqlType::Int, 0, false}' if not ps else '') + '};') out.append('') out.append('static const ProcDef kProcs[] = {') for i, key in enumerate(used): name, db, ps = procs[key] known = ps is not None out.append(f' {{{cstr(name)}, DbType::{DBTYPE[db]}, kParams{i}, {len(ps or [])}, {str(known).lower()}}},') out.append('};') out.append('') out.append('// rmi_id, name, cmd_id (0 = not queued / unknown), proc indexes (-1 = none)') out.append('static const RmiDef kRmis[] = {') for r in rmi: idx = [used.index(p.lower()) for p in filter(None, r['procs'].split(',')) if p.lower() in used] idx = (idx + [-1, -1, -1])[:3] missing = [p for p in filter(None, r['procs'].split(',')) if procs[p.lower()][2] is None] note = f' // missing in schema: {",".join(missing)}' if missing else '' out.append(f' {{{r["rmi_id"]}, {cstr(r["rmi_name"])}, {r["cmd_id"] or 0}, ' f'{{{idx[0]}, {idx[1]}, {idx[2]}}}}},{note}') out.append('};') os.makedirs(os.path.dirname(OUT), exist_ok=True) open(OUT, 'w').write('\n'.join(out) + '\n') print(f'{len(used)} procedures, {len(rmi)} RMIs -> {OUT}')