mirror of
https://github.com/astromaddie/HaloCE-VR.git
synced 2026-10-19 07:00:13 +02:00
581 lines
26 KiB
Python
581 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
"""Writes port/include/xdk/xdk_pdb.h from the January build's PDB.
|
|
|
|
The game was compiled against the Xbox SDK, and its debug information
|
|
(cachebeta.pdb, read by tools/pdb200_types.py) records every type, typedef
|
|
and enumeration it was compiled with, and the prototype of every library
|
|
function it links. This writes the ones the game and the native ports'
|
|
platform layer use (the names in port/include/xdk/pdb_names.txt), and what
|
|
they depend on, as C declarations:
|
|
|
|
python tools/xdk_headers.py --pdb path/to/cachebeta.pdb
|
|
|
|
Every structure layout written is then checked, for each port's target,
|
|
against the sizes and offsets the PDB records; where it needs structure
|
|
packing to agree, the declaration gets it. Macros, inline functions and
|
|
anything else the PDB cannot describe are written by hand in the other
|
|
headers of port/include/xdk (see its README).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Set, Tuple
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from pdb200_types import ( # noqa: E402
|
|
BASIC, LF_ARRAY, LF_BITFIELD, LF_CLASS, LF_ENUM, LF_MODIFIER,
|
|
LF_POINTER, LF_PROCEDURE, LF_STRUCTURE, LF_UNION, Aggregate, Member, Pdb, PdbError, numeric,
|
|
u8, u16, u32,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
XDK = ROOT / "port" / "include" / "xdk"
|
|
NAMES = XDK / "pdb_names.txt"
|
|
OUTPUT = XDK / "xdk_pdb.h"
|
|
|
|
# the targets whose layouts must agree with the PDB's (each port's ABI flags)
|
|
TARGETS = {
|
|
"linux": ["--target=i686-linux-gnu", "-m32", "-malign-double", "-fshort-wchar"],
|
|
"windows": ["--target=i686-pc-windows-msvc"],
|
|
"android": ["--target=arm64_32-apple-watchos", "-fshort-wchar"],
|
|
}
|
|
|
|
AGGREGATE_LEAVES = (LF_STRUCTURE, LF_CLASS, LF_UNION, LF_ENUM)
|
|
|
|
# arrays whose bound a unit may choose (a Winsock fd_set holds FD_SETSIZE
|
|
# sockets, 64 unless the unit defines more, as the ports' prefixes do): the
|
|
# member's bound is written as the macro, whose default the layout check uses
|
|
MACRO_BOUNDS = {("fd_set", "fd_array"): ("FD_SETSIZE", 64)}
|
|
|
|
|
|
def anonymous(name: str) -> bool:
|
|
return "__unnamed" in name or name.startswith("<")
|
|
|
|
|
|
class Writer:
|
|
def __init__(self, pdb: Pdb):
|
|
self.pdb = pdb
|
|
self.definitions: Dict[Tuple[str, str], Aggregate] = {} # (kind, name) -> definition
|
|
self.forwards: Set[Tuple[str, str]] = set()
|
|
self.enums: Dict[str, Aggregate] = {}
|
|
self.typedefs: Dict[str, int] = {}
|
|
self.data: Dict[str, int] = {}
|
|
self.prototypes: Dict[str, int] = {}
|
|
self.inline_in_sdk: Set[str] = set()
|
|
self.packing: Dict[Tuple[str, str], int] = {}
|
|
self.empty_classes: Set[Tuple[str, str]] = set()
|
|
self.warnings: List[str] = []
|
|
|
|
# ---------- what is needed
|
|
|
|
def want(self, name: str) -> bool:
|
|
pdb = self.pdb
|
|
if name in pdb.typedefs:
|
|
self.typedefs[name] = self._typedef_type(name)
|
|
self._need_type(self.typedefs[name], by_value=True)
|
|
return True
|
|
for kind in ("struct", "union"):
|
|
definition = pdb.definition(kind, name)
|
|
if definition is not None:
|
|
self._need_aggregate(definition)
|
|
return True
|
|
enum = self._enum_of(name)
|
|
if enum is not None:
|
|
self._need_aggregate(enum)
|
|
return True
|
|
if name in pdb.procedures:
|
|
self.prototypes[name] = self._procedure_type(name)
|
|
self._need_type(self.prototypes[name], by_value=False)
|
|
return True
|
|
if name in pdb.data:
|
|
self.data[name] = pdb.data[name][0]
|
|
self._need_type(self.data[name], by_value=True)
|
|
return True
|
|
return False
|
|
|
|
def _typedef_choices(self, name: str) -> List[int]:
|
|
"""A typedef's types, best first: where units disagree, a C unit's
|
|
(not a C++ class's, nor an unrelated structure that took the same
|
|
name)."""
|
|
def rank(index: int) -> Tuple[bool, bool]:
|
|
item = self.pdb.aggregates.get(index)
|
|
return (bool(item and item.cplusplus), bool(item and item.name == name))
|
|
return sorted(self.pdb.typedefs[name], key=rank)
|
|
|
|
def _typedef_type(self, name: str) -> int:
|
|
choices = self._typedef_choices(name)
|
|
rendered = {self.declaration(t, name) for t in choices}
|
|
if len(rendered) > 1:
|
|
self.warnings.append(f"typedef {name} has {len(rendered)} definitions; using "
|
|
f"{self.declaration(choices[0], name)}")
|
|
return choices[0]
|
|
|
|
def _procedure_type(self, name: str) -> int:
|
|
procedures = self.pdb.procedures[name]
|
|
chosen = [p for p in procedures if not p.local] or procedures
|
|
if all(p.local for p in procedures):
|
|
# a static function in every unit that has it: the SDK's inline
|
|
# functions, which the ports must implement themselves
|
|
self.inline_in_sdk.add(name)
|
|
by_prototype: Dict[str, int] = {}
|
|
for p in chosen:
|
|
if self.pdb.leaf(p.type) == LF_PROCEDURE:
|
|
by_prototype.setdefault(self.prototype(name, p.type), p.type)
|
|
if not by_prototype:
|
|
raise PdbError(f"{name} has no procedure type in the PDB")
|
|
if len(by_prototype) > 1:
|
|
# a static copy's convention can differ where a unit took the
|
|
# function's address; the most common (then stdcall) wins
|
|
conventions = [self.pdb.procedure(p.type)[1] for p in chosen]
|
|
best = max(by_prototype.values(),
|
|
key=lambda t: (conventions.count(self.pdb.procedure(t)[1]),
|
|
self.pdb.procedure(t)[1] == "__stdcall"))
|
|
self.warnings.append(f"{name} has {len(by_prototype)} prototypes; using {self.prototype(name, best)}")
|
|
return best
|
|
return next(iter(by_prototype.values()))
|
|
|
|
def _enum_of(self, enumerator: str) -> Optional[Aggregate]:
|
|
for item in self.pdb.aggregates.values():
|
|
if item.kind == "enum" and not item.forward and any(n == enumerator for n, _ in item.enumerators):
|
|
return item
|
|
return None
|
|
|
|
def _need_aggregate(self, item: Aggregate) -> None:
|
|
if item.kind == "enum":
|
|
if item.name not in self.enums:
|
|
self.enums[item.name] = item
|
|
return
|
|
key = (item.kind, item.name)
|
|
if anonymous(item.name):
|
|
for member in self.pdb.members(item):
|
|
self._need_type(member.type, by_value=True)
|
|
return
|
|
if key in self.definitions:
|
|
return
|
|
self.forwards.add(key)
|
|
if not self.pdb.members(item):
|
|
# a C++ class with no data (D3DDevice): opaque to C, an empty
|
|
# class to C++ (whose units may derive from it)
|
|
self.empty_classes.add(key)
|
|
return
|
|
self.definitions[key] = item
|
|
for member in self.pdb.members(item):
|
|
self._need_type(member.type, by_value=True)
|
|
|
|
def _need_type(self, index: int, by_value: bool) -> None:
|
|
pdb = self.pdb
|
|
if index < 0x1000:
|
|
return
|
|
record = pdb.record(index)
|
|
leaf = u16(record, 2)
|
|
if leaf == LF_MODIFIER:
|
|
self._need_type(u32(record, 4), by_value)
|
|
elif leaf == LF_POINTER:
|
|
self._need_type(u32(record, 4), by_value=False)
|
|
elif leaf == LF_ARRAY:
|
|
self._need_type(u32(record, 4), by_value)
|
|
elif leaf == LF_BITFIELD:
|
|
self._need_type(u32(record, 4), by_value)
|
|
elif leaf == LF_PROCEDURE:
|
|
returns, _, args, _ = pdb.procedure(index)
|
|
for t in [returns, *args]:
|
|
self._need_type(t, by_value=False)
|
|
elif leaf in AGGREGATE_LEAVES:
|
|
item = pdb.aggregates[index]
|
|
definition = pdb.resolve(index)
|
|
if self.c_alias(item):
|
|
return
|
|
if item.kind == "enum":
|
|
if definition is None:
|
|
raise PdbError(f"enum {item.name} has no definition")
|
|
self._need_aggregate(definition)
|
|
elif anonymous(item.name):
|
|
self._need_aggregate(definition or item)
|
|
elif by_value and definition is not None:
|
|
self._need_aggregate(definition)
|
|
else:
|
|
self.forwards.add((item.kind, item.name))
|
|
|
|
# ---------- declarations
|
|
|
|
def declaration(self, index: int, inner: str, indent: str = "") -> str:
|
|
"""C declaration of `inner` (a declarator) with type `index`."""
|
|
pdb = self.pdb
|
|
if index < 0x1000:
|
|
base = BASIC.get(index & 0xFF)
|
|
if base is None:
|
|
raise PdbError(f"basic type {index:#x}")
|
|
if base == "wchar_t":
|
|
base = "unsigned short" # the Xbox's 16-bit wchar_t, which C spells so
|
|
return join(base, "*" + inner if (index >> 8) & 7 else inner)
|
|
record = pdb.record(index)
|
|
leaf = u16(record, 2)
|
|
if leaf == LF_MODIFIER:
|
|
target, attr = u32(record, 4), u16(record, 8)
|
|
qualifiers = " ".join(q for bit, q in ((1, "const"), (2, "volatile")) if attr & bit)
|
|
if pdb.leaf(target) == LF_POINTER:
|
|
return self.declaration(target, join(qualifiers, inner), indent)
|
|
return join(qualifiers, self.declaration(target, inner, indent))
|
|
if leaf == LF_POINTER:
|
|
target, attr = u32(record, 4), u32(record, 8)
|
|
qualifiers = " ".join(q for bit, q in ((0x400, "const"), (0x200, "volatile")) if attr & bit)
|
|
pointer = "*" + join(qualifiers, inner) if qualifiers else "*" + inner
|
|
if pdb.leaf(target) == LF_PROCEDURE:
|
|
returns, convention, args, variadic = pdb.procedure(target)
|
|
convention = "" if convention == "__cdecl" else convention + " "
|
|
return self.declaration(returns, f"({convention}{pointer})({self.arguments(args, variadic)})", indent)
|
|
if pdb.leaf(target) == LF_ARRAY:
|
|
pointer = f"({pointer})"
|
|
return self.declaration(target, pointer, indent)
|
|
if leaf == LF_ARRAY:
|
|
element = u32(record, 4)
|
|
size, _ = numeric(record, 12)
|
|
element_size = pdb.size(element)
|
|
count = size // element_size if element_size else 0
|
|
return self.declaration(element, f"{inner}[{count}]", indent)
|
|
if leaf == LF_PROCEDURE:
|
|
returns, convention, args, variadic = pdb.procedure(index)
|
|
convention = "" if convention == "__cdecl" else convention + " "
|
|
return self.declaration(returns, f"{convention}{inner}({self.arguments(args, variadic)})", indent)
|
|
if leaf in AGGREGATE_LEAVES:
|
|
item = pdb.aggregates[index]
|
|
alias = self.c_alias(item)
|
|
if alias:
|
|
return join(alias, inner)
|
|
if anonymous(item.name):
|
|
definition = pdb.resolve(index) or item
|
|
body = self.body(definition, indent + " ")
|
|
return join(f"{item.kind} {{\n" + "\n".join(body) + f"\n{indent}}}", inner)
|
|
return join(f"{item.kind} {item.name}", inner)
|
|
raise PdbError(f"type {index:#x} (leaf {leaf:#x}) has no C declaration")
|
|
|
|
def c_alias(self, item: Aggregate) -> Optional[str]:
|
|
"""For a C++ library's class that C units know by a typedef of
|
|
another structure (D3DX's D3DXMATRIX, a _D3DMATRIX to C): the
|
|
typedef's name, which prototypes use instead."""
|
|
if item.kind == "enum" or self.pdb.definition(item.kind, item.name) is None:
|
|
return None
|
|
if not self.pdb.definition(item.kind, item.name).cplusplus or item.name not in self.pdb.typedefs:
|
|
return None
|
|
target = self.pdb.aggregates.get(self._typedef_choices(item.name)[0])
|
|
if target is None or target.name == item.name:
|
|
return None
|
|
if item.name in self.typedefs:
|
|
return item.name
|
|
self.typedefs[item.name] = self._typedef_choices(item.name)[0]
|
|
self._need_type(self.typedefs[item.name], by_value=True)
|
|
return item.name
|
|
|
|
def arguments(self, args: List[int], variadic: bool) -> str:
|
|
if not args:
|
|
return "..." if variadic else "void"
|
|
text = ", ".join(self.declaration(a, "") for a in args)
|
|
return text + ", ..." if variadic else text
|
|
|
|
def prototype(self, name: str, index: int) -> str:
|
|
return self.declaration(index, name)
|
|
|
|
# ---------- aggregate bodies
|
|
|
|
def member_size(self, member: Member) -> int:
|
|
return self.pdb.size(member.type)
|
|
|
|
def bitfield(self, member: Member) -> Optional[Tuple[int, int, int]]:
|
|
"""(underlying type, width, position) of a bit field member"""
|
|
if self.pdb.leaf(member.type) != LF_BITFIELD:
|
|
return None
|
|
record = self.pdb.record(member.type)
|
|
return u32(record, 4), u8(record, 8), u8(record, 9)
|
|
|
|
def continues(self, previous: Member, member: Member) -> bool:
|
|
"""Whether a bit field shares its predecessor's storage unit."""
|
|
a, b = self.bitfield(previous), self.bitfield(member)
|
|
return bool(a and b and previous.offset == member.offset and b[2] > a[2])
|
|
|
|
def body(self, item: Aggregate, indent: str) -> List[str]:
|
|
if item.kind == "enum":
|
|
lines = []
|
|
for name, value in item.enumerators:
|
|
lines.append(f"{indent}{name} = {literal(value)},")
|
|
return lines
|
|
members = self.pdb.members(item)
|
|
return self.render(self.group(members, item.kind == "union"), indent, item.name)
|
|
|
|
def group(self, members: List[Member], union: bool) -> list:
|
|
"""Members as the C declaration needs them: the PDB lists an
|
|
anonymous structure's or union's members in their container, at
|
|
their offsets, so overlapping runs are regrouped."""
|
|
if union:
|
|
runs: List[List[Member]] = []
|
|
for member in members:
|
|
if runs and (member.offset > runs[-1][-1].offset or self.continues(runs[-1][-1], member)):
|
|
runs[-1].append(member)
|
|
else:
|
|
runs.append([member])
|
|
return [("member", r[0]) if len(r) == 1 else ("struct", self.group(r, False)) for r in runs]
|
|
items: list = []
|
|
i = 0
|
|
while i < len(members):
|
|
member = members[i]
|
|
if items and not self.continues(self._last(items), member) and member.offset < self._end(items):
|
|
# an anonymous union: its first alternative is what the
|
|
# output already holds from the member at this offset on
|
|
start = next(n for n, it in enumerate(items) if self._first(it).offset == member.offset)
|
|
alternatives = [items[start:]]
|
|
del items[start:]
|
|
union_start = member.offset
|
|
union_end = max(self._end(alternatives[0]), member.offset + self.member_size(member))
|
|
current = [("member", member)]
|
|
i += 1
|
|
while i < len(members):
|
|
other = members[i]
|
|
if other.offset == union_start and not self.continues(self._last(current), other):
|
|
alternatives.append(current)
|
|
current = [("member", other)]
|
|
elif other.offset >= union_end and not self.continues(self._last(current), other):
|
|
break
|
|
else:
|
|
current.append(("member", other))
|
|
union_end = max(union_end, other.offset + self.member_size(other))
|
|
i += 1
|
|
alternatives.append(current)
|
|
grouped = []
|
|
for alternative in alternatives:
|
|
flat = [m for m in self._flatten(alternative)]
|
|
grouped.append(alternative[0] if len(alternative) == 1 else ("struct", self.group(flat, False)))
|
|
items.append(("union", grouped))
|
|
continue
|
|
items.append(("member", member))
|
|
i += 1
|
|
return items
|
|
|
|
def _flatten(self, items: list) -> List[Member]:
|
|
result = []
|
|
for item in items:
|
|
if item[0] == "member":
|
|
result.append(item[1])
|
|
else:
|
|
result.extend(self._flatten(item[1]))
|
|
return result
|
|
|
|
def _first(self, item) -> Member:
|
|
return item[1] if item[0] == "member" else self._first(item[1][0])
|
|
|
|
def _last(self, items) -> Member:
|
|
last = items[-1]
|
|
return last[1] if last[0] == "member" else self._last(last[1])
|
|
|
|
def _end(self, items) -> int:
|
|
return max(m.offset + self.member_size(m) for m in self._flatten(items))
|
|
|
|
def render(self, items: list, indent: str, tag: str) -> List[str]:
|
|
"""Grouped members as lines; tag is the structure or union they
|
|
belong to (for MACRO_BOUNDS)."""
|
|
lines = []
|
|
for item in items:
|
|
if item[0] == "member":
|
|
member = item[1]
|
|
bits = self.bitfield(member)
|
|
if bits:
|
|
lines.append(f"{indent}{self.declaration(bits[0], member.name, indent)} : {bits[1]};")
|
|
else:
|
|
line = self.declaration(member.type, member.name, indent)
|
|
bound = MACRO_BOUNDS.get((tag, member.name))
|
|
if bound:
|
|
macro, default = bound
|
|
line, count = re.subn(rf"\[{default}\]$", f"[{macro}]", line)
|
|
if count != 1:
|
|
raise PdbError(f"{tag}.{member.name} is not an array of {default}")
|
|
lines.append(f"{indent}{line};")
|
|
else:
|
|
lines.append(f"{indent}{item[0]} {{")
|
|
lines.extend(self.render(item[1], indent + " ", tag))
|
|
lines.append(f"{indent}}};")
|
|
return lines
|
|
|
|
# ---------- output
|
|
|
|
def definition_order(self) -> List[Aggregate]:
|
|
"""Definitions, each after those it contains by value."""
|
|
order: List[Aggregate] = []
|
|
placed: Set[Tuple[str, str]] = set()
|
|
|
|
def contained(item: Aggregate) -> List[Tuple[str, str]]:
|
|
result = []
|
|
for member in self.pdb.members(item):
|
|
result.extend(self._by_value(member.type))
|
|
return result
|
|
|
|
def place(key: Tuple[str, str], stack: Set[Tuple[str, str]]) -> None:
|
|
if key in placed or key not in self.definitions:
|
|
return
|
|
if key in stack:
|
|
raise PdbError(f"{key} contains itself")
|
|
for dependency in contained(self.definitions[key]):
|
|
place(dependency, stack | {key})
|
|
placed.add(key)
|
|
order.append(self.definitions[key])
|
|
|
|
for key in sorted(self.definitions, key=lambda k: k[1]):
|
|
place(key, set())
|
|
return order
|
|
|
|
def _by_value(self, index: int) -> List[Tuple[str, str]]:
|
|
pdb = self.pdb
|
|
if index < 0x1000:
|
|
return []
|
|
leaf = pdb.leaf(index)
|
|
record = pdb.record(index)
|
|
if leaf in (LF_MODIFIER, LF_ARRAY, LF_BITFIELD):
|
|
return self._by_value(u32(record, 4))
|
|
if leaf in (LF_STRUCTURE, LF_CLASS, LF_UNION):
|
|
item = pdb.aggregates[index]
|
|
if anonymous(item.name):
|
|
definition = pdb.resolve(index) or item
|
|
return [k for m in pdb.members(definition) for k in self._by_value(m.type)]
|
|
return [(item.kind, item.name)]
|
|
return []
|
|
|
|
def write(self) -> str:
|
|
out = [
|
|
"/*",
|
|
"XDK_PDB.H",
|
|
"",
|
|
"The Xbox SDK types, enumerations and function prototypes the game and the",
|
|
"platform layer use, as the January build's debug information",
|
|
"(cachebeta.pdb) records them. Generated by tools/xdk_headers.py from the",
|
|
"names in pdb_names.txt: do not edit, regenerate (see README.md).",
|
|
"*/",
|
|
"",
|
|
"#ifndef HALO_XDK_PDB_H",
|
|
"#define HALO_XDK_PDB_H",
|
|
"",
|
|
"/* ---------- enumerations */",
|
|
"",
|
|
]
|
|
for name in sorted(self.enums):
|
|
item = self.enums[name]
|
|
tag = "" if anonymous(name) else f" {name}"
|
|
out += [f"enum{tag} {{", *self.body(item, " "), "};", ""]
|
|
out += ["/* ---------- structures and unions */", ""]
|
|
for kind, name in sorted(self.forwards, key=lambda k: (k[1], k[0])):
|
|
out.append(f"{kind} {name};")
|
|
out.append("")
|
|
if self.empty_classes:
|
|
out.append("#ifdef __cplusplus")
|
|
for kind, name in sorted(self.empty_classes, key=lambda k: k[1]):
|
|
out.append(f"{kind} {name} {{}};")
|
|
out += ["#endif", ""]
|
|
for item in self.definition_order():
|
|
pack = self.packing.get((item.kind, item.name))
|
|
if pack:
|
|
out.append(f"#pragma pack(push, {pack})")
|
|
out += [f"{item.kind} {item.name} {{", *self.body(item, " "), "};"]
|
|
if pack:
|
|
out.append("#pragma pack(pop)")
|
|
out.append("")
|
|
out += ["/* ---------- types */", ""]
|
|
for name in sorted(self.typedefs):
|
|
out.append(f"typedef {self.declaration(self.typedefs[name], name)};")
|
|
out += ["", "/* ---------- data */", ""]
|
|
for name in sorted(self.data):
|
|
out.append(f"extern {self.declaration(self.data[name], name)};")
|
|
out += ["", "/* ---------- functions */", ""]
|
|
for name in sorted(n for n in self.prototypes if n not in self.inline_in_sdk):
|
|
out.append(f"{self.prototype(name, self.prototypes[name])};")
|
|
out += ["", "/* functions the SDK defined inline (see xdk_d3d8.h) */", ""]
|
|
for name in sorted(n for n in self.prototypes if n in self.inline_in_sdk):
|
|
out.append(f"{self.prototype(name, self.prototypes[name])};")
|
|
out += ["", "#endif", ""]
|
|
return "\n".join(out)
|
|
|
|
# ---------- checking layouts against the PDB
|
|
|
|
def layout_checks(self) -> Dict[Tuple[str, str], List[str]]:
|
|
checks: Dict[Tuple[str, str], List[str]] = {}
|
|
for key, item in self.definitions.items():
|
|
kind, name = key
|
|
lines = [f"_Static_assert(sizeof({kind} {name}) == {item.size}, \"{name} size\");"]
|
|
for member in self.pdb.members(item):
|
|
if not member.name or self.bitfield(member):
|
|
continue
|
|
lines.append(f"_Static_assert(__builtin_offsetof({kind} {name}, {member.name}) == {member.offset}, "
|
|
f"\"{name} {member.name}\");")
|
|
checks[key] = lines
|
|
return checks
|
|
|
|
def failing(self, header: str, target: List[str]) -> Set[Tuple[str, str]]:
|
|
checks = self.layout_checks()
|
|
with tempfile.TemporaryDirectory() as work:
|
|
source = Path(work) / "layout.c"
|
|
defaults = "".join(f"#define {macro} {default}\n" for macro, default in MACRO_BOUNDS.values())
|
|
source.write_text(defaults + header + "\n" + "\n".join(line for lines in checks.values() for line in lines) + "\n")
|
|
result = subprocess.run(["clang", *target, "-fms-extensions", "-std=gnu11", "-w", "-fsyntax-only",
|
|
"-ferror-limit=0", str(source)], capture_output=True, text=True)
|
|
if result.returncode and "static assertion failed" not in result.stderr and "static_assert" not in result.stderr:
|
|
raise PdbError("the generated declarations do not compile:\n" + result.stderr[:4000])
|
|
names = set(re.findall(r'static assertion failed[^"]*"([^" ]+)', result.stderr))
|
|
return {key for key in checks if key[1] in names}
|
|
|
|
def fix_layouts(self) -> None:
|
|
"""Packs each structure that needs it, until every layout agrees
|
|
with the PDB on every target."""
|
|
for _ in range(4):
|
|
failures = set()
|
|
for target in TARGETS.values():
|
|
failures |= self.failing(self.write(), target)
|
|
if not failures:
|
|
return
|
|
for key in failures:
|
|
smaller = {8: 4, 4: 2, 2: 1}.get(self.packing.get(key, 8))
|
|
if smaller is None:
|
|
raise PdbError(f"{key[1]} does not lay out as the PDB records")
|
|
self.packing[key] = smaller
|
|
raise PdbError("layouts still differ: " + " ".join(sorted(k[1] for k in failures)))
|
|
|
|
|
|
def join(left: str, right: str) -> str:
|
|
if not left:
|
|
return right
|
|
if not right:
|
|
return left
|
|
return f"{left} {right}"
|
|
|
|
|
|
def literal(value: int) -> str:
|
|
return f"{value:#x}" if value > 0xFFFF else str(value)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument("--pdb", type=Path, required=True, help="cachebeta.pdb of the January build")
|
|
parser.add_argument("--names", type=Path, default=NAMES)
|
|
parser.add_argument("--output", type=Path, default=OUTPUT)
|
|
args = parser.parse_args()
|
|
writer = Writer(Pdb(args.pdb))
|
|
missing = []
|
|
for line in args.names.read_text(encoding="utf-8").splitlines():
|
|
name = line.split("#", 1)[0].strip()
|
|
if name and not writer.want(name):
|
|
missing.append(name)
|
|
if missing:
|
|
sys.exit("not in the PDB: " + " ".join(missing))
|
|
writer.fix_layouts()
|
|
args.output.write_text(writer.write(), encoding="utf-8")
|
|
for warning in writer.warnings:
|
|
print("note:", warning, file=sys.stderr)
|
|
print(f"{args.output}: {len(writer.enums)} enumerations, {len(writer.definitions)} structures and unions, "
|
|
f"{len(writer.typedefs)} types, {len(writer.prototypes)} functions "
|
|
f"({len(writer.inline_in_sdk)} the SDK defined inline), {len(writer.data)} data")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|