#!/usr/bin/env python3 """Generate documentation of all Xbox 360 system exports in Xenia Canary. Parses the export table .inc files and implementation .cc files to produce a comprehensive listing of all exports with their implementation status. Usage: python3 tools/generate_export_docs.py """ import json import pathlib import re from collections import OrderedDict REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent # --- Configuration --- TABLE_FILES = { "xboxkrnl": REPO_ROOT / "src/xenia/kernel/xboxkrnl/xboxkrnl_table.inc", "xam": REPO_ROOT / "src/xenia/kernel/xam/xam_table.inc", "xbdm": REPO_ROOT / "src/xenia/kernel/xbdm/xbdm_table.inc", } IMPL_DIRS = { "xboxkrnl": REPO_ROOT / "src/xenia/kernel/xboxkrnl", "xam": REPO_ROOT / "src/xenia/kernel/xam", "xbdm": REPO_ROOT / "src/xenia/kernel/xbdm", } MODULE_NAMES = { "xboxkrnl": "xboxkrnl.exe", "xam": "xam.xex", "xbdm": "xbdm.xex", } MODULE_MAP = {"XBOXKRNL": "xboxkrnl", "XAM": "xam", "XBDM": "xbdm"} # Regex patterns XE_EXPORT_RE = re.compile( r"XE_EXPORT\(\s*(\w+)\s*,\s*(0x[0-9A-Fa-f]+)\s*,\s*(\w+)\s*,\s*k(\w+)\s*\)" ) DECLARE_RE = re.compile( r"DECLARE_(XBOXKRNL|XAM|XBDM)_EXPORT\d*\(\s*(\w+)\s*," r"\s*k(\w+)\s*,\s*(.+?)\)\s*;", re.DOTALL, ) TAG_RE = re.compile(r"k(\w+)") SHIM_RE = re.compile(r'SHIM_SET_MAPPING\(\s*"[^"]+"\s*,\s*(\w+)\s*,') VAR_MAPPING_RE = re.compile(r"ordinals::(\w+)") EXPORT_KVAR_RE = re.compile(r"EXPORT_KVAR\((\w+)\)") def parse_table_files(): """Phase A: Parse all XE_EXPORT entries from *_table.inc files.""" exports = OrderedDict() for module, path in TABLE_FILES.items(): content = path.read_text() for match in XE_EXPORT_RE.finditer(content): mod, ordinal_hex, name, typ = match.groups() exports[(mod, name)] = { "module": mod, "ordinal": int(ordinal_hex, 16), "ordinal_hex": ordinal_hex.upper().replace("0X", "0x"), "name": name, "type": "variable" if typ == "Variable" else "function", "status": "not_implemented", "tags": [], "category": "", "source_file": "", } return exports def scan_declare_macros(exports): """Phase B: Scan DECLARE_*_EXPORT macros in .cc files.""" for mod_key, dir_path in IMPL_DIRS.items(): for cc_file in sorted(dir_path.glob("*.cc")): content = cc_file.read_text() for match in DECLARE_RE.finditer(content): mod_prefix, name, category, tags_str = match.groups() module = MODULE_MAP[mod_prefix] tags = TAG_RE.findall(tags_str) key = (module, name) if key in exports: entry = exports[key] entry["category"] = category entry["tags"] = tags entry["source_file"] = str( cc_file.relative_to(REPO_ROOT) ) if "Implemented" in tags: entry["status"] = "implemented" elif "Stub" in tags: entry["status"] = "stub" elif "Sketchy" in tags: entry["status"] = "sketchy" else: entry["status"] = "stub" def scan_legacy_patterns(exports): """Phase C: Handle SHIM_SET_MAPPING and SetVariableMapping.""" # SHIM_SET_MAPPING in xboxkrnl_strings.cc strings_file = IMPL_DIRS["xboxkrnl"] / "xboxkrnl_strings.cc" if strings_file.exists(): content = strings_file.read_text() for match in SHIM_RE.finditer(content): name = match.group(1) key = ("xboxkrnl", name) if key in exports: exports[key]["status"] = "implemented" exports[key]["source_file"] = str( strings_file.relative_to(REPO_ROOT) ) if not exports[key]["tags"]: exports[key]["tags"] = ["Implemented"] # SetVariableMapping / EXPORT_KVAR in xboxkrnl_module.cc module_file = IMPL_DIRS["xboxkrnl"] / "xboxkrnl_module.cc" if module_file.exists(): content = module_file.read_text() for match in VAR_MAPPING_RE.finditer(content): name = match.group(1) key = ("xboxkrnl", name) if key in exports: exports[key]["status"] = "implemented" exports[key]["source_file"] = str( module_file.relative_to(REPO_ROOT) ) if not exports[key]["tags"]: exports[key]["tags"] = ["Implemented"] for match in EXPORT_KVAR_RE.finditer(content): name = match.group(1) key = ("xboxkrnl", name) if key in exports: exports[key]["status"] = "implemented" exports[key]["source_file"] = str( module_file.relative_to(REPO_ROOT) ) if not exports[key]["tags"]: exports[key]["tags"] = ["Implemented"] def compute_statistics(exports): """Compute per-module statistics.""" stats = {} for module in TABLE_FILES: module_exports = [e for e in exports.values() if e["module"] == module] stats[module] = { "total": len(module_exports), "implemented": sum( 1 for e in module_exports if e["status"] == "implemented" ), "stub": sum(1 for e in module_exports if e["status"] == "stub"), "sketchy": sum( 1 for e in module_exports if e["status"] == "sketchy" ), "not_implemented": sum( 1 for e in module_exports if e["status"] == "not_implemented" ), "functions": sum( 1 for e in module_exports if e["type"] == "function" ), "variables": sum( 1 for e in module_exports if e["type"] == "variable" ), } return stats def generate_markdown(exports, stats): """Generate the Markdown documentation.""" lines = [] lines.append("# Xbox 360 System Exports - Xenia Canary") lines.append("") lines.append( "Auto-generated by `tools/generate_export_docs.py`. " "Do not edit manually." ) lines.append("") # Overall summary lines.append("## Summary") lines.append("") total_all = sum(s["total"] for s in stats.values()) impl_all = sum(s["implemented"] for s in stats.values()) stub_all = sum(s["stub"] for s in stats.values()) sketchy_all = sum(s["sketchy"] for s in stats.values()) not_impl_all = sum(s["not_implemented"] for s in stats.values()) lines.append( f"**Total exports: {total_all}** | " f"Implemented: {impl_all} | " f"Stub: {stub_all} | " f"Sketchy: {sketchy_all} | " f"Not implemented: {not_impl_all}" ) lines.append("") lines.append( "| Module | File | Total | Implemented | Stub | Sketchy | Not Implemented | Functions | Variables |" ) lines.append( "|--------|------|------:|------------:|-----:|--------:|----------------:|----------:|----------:|" ) for module in TABLE_FILES: s = stats[module] lines.append( f"| {module} | {MODULE_NAMES[module]} | {s['total']} | " f"{s['implemented']} | {s['stub']} | {s['sketchy']} | " f"{s['not_implemented']} | {s['functions']} | {s['variables']} |" ) lines.append("") # Per-module tables for module in TABLE_FILES: s = stats[module] lines.append(f"## {module} ({MODULE_NAMES[module]})") lines.append("") lines.append( f"Total: {s['total']} | " f"Implemented: {s['implemented']} | " f"Stub: {s['stub']} | " f"Sketchy: {s['sketchy']} | " f"Not implemented: {s['not_implemented']}" ) lines.append("") lines.append( "| Ordinal | Name | Type | Status | Category | Source |" ) lines.append( "|--------:|------|------|--------|----------|--------|" ) module_exports = sorted( [e for e in exports.values() if e["module"] == module], key=lambda e: e["ordinal"], ) for e in module_exports: ordinal = f"0x{e['ordinal']:03X}" status_icon = { "implemented": "implemented", "stub": "stub", "sketchy": "sketchy", "not_implemented": "-", }.get(e["status"], e["status"]) source = e["source_file"] if source: # Show just the filename for brevity source = pathlib.Path(source).name lines.append( f"| {ordinal} | {e['name']} | {e['type']} | " f"{status_icon} | {e['category']} | {source} |" ) lines.append("") return "\n".join(lines) def generate_json(exports, stats): """Generate the JSON documentation.""" data = { "description": "Xbox 360 system exports implemented in Xenia Canary", "generator": "tools/generate_export_docs.py", "statistics": stats, "modules": {}, } for module in TABLE_FILES: module_exports = sorted( [e for e in exports.values() if e["module"] == module], key=lambda e: e["ordinal"], ) data["modules"][module] = { "file": MODULE_NAMES[module], "exports": module_exports, } return json.dumps(data, indent=2) def main(): print("Parsing export tables...") exports = parse_table_files() print(f" Found {len(exports)} exports across {len(TABLE_FILES)} modules") print("Scanning DECLARE_*_EXPORT macros...") scan_declare_macros(exports) print("Scanning legacy registration patterns...") scan_legacy_patterns(exports) stats = compute_statistics(exports) for module, s in stats.items(): print( f" {module}: {s['total']} total, " f"{s['implemented']} implemented, " f"{s['stub']} stub, " f"{s['sketchy']} sketchy, " f"{s['not_implemented']} not implemented" ) # Generate outputs docs_dir = REPO_ROOT / "docs" docs_dir.mkdir(exist_ok=True) md_path = docs_dir / "xbox360_exports.md" md_content = generate_markdown(exports, stats) md_path.write_text(md_content) print(f"Written: {md_path.relative_to(REPO_ROOT)}") json_path = docs_dir / "xbox360_exports.json" json_content = generate_json(exports, stats) json_path.write_text(json_content) print(f"Written: {json_path.relative_to(REPO_ROOT)}") if __name__ == "__main__": main()