These were sitting in stash@{0} ("On canary_experimental: doc", 2026-04-11) as
untracked files only -- no tracked change was ever stashed with them, so they
were one `git stash drop` away from being lost:
docs/ppc_instructions.{json,md} PPC instruction reference
docs/xbox360_exports.{json,md} XAM / xboxkrnl export tables
docs/xex2_format.md XEX2 container format
tools/generate_export_docs.py the generator behind them
xex2_format.md in particular is the spec a static XEX reader would need to
pull the title's XACH achievement table and .rdata tables off the disc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
339 lines
11 KiB
Python
339 lines
11 KiB
Python
#!/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()
|