#!/usr/bin/env python3 """Populate the import ledger from Wine 11.15 Win16 specs and target evidence.""" from __future__ import annotations import csv import hashlib import re import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parent.parent LEDGER = ROOT / "IMPORT_RECONSTRUCTION.tsv" WINE_TAG = "wine-11.15" WINE_BASE = f"https://gitlab.winehq.org/wine/wine/-/raw/{WINE_TAG}" SPEC_SOURCES = { "USER": ( "dlls/user.exe16/user.exe16.spec", "d873fa88079c4f922cbba2fb3a75f40e2ff23b4d56434b0f4326169397539e32", ), "GDI": ( "dlls/gdi.exe16/gdi.exe16.spec", "3bd84b26a9d4b7aa91a9114d0a46dc13e4d6fcd71290120afdd9b7cfaffe76ab", ), "KERNEL": ( "dlls/krnl386.exe16/krnl386.exe16.spec", "8d799b701d1cf16d4e882b5e354dc6e357056d4adcd83e629571dc4b33b02075", ), "MMSYSTEM": ( "dlls/mmsystem.dll16/mmsystem.dll16.spec", "31e54179db61fa95a1d90a9a230d2ae5afb6013ba37ae54e78990d1795694a73", ), "KEYBOARD": ( "dlls/keyboard.drv16/keyboard.drv16.spec", "ca9a9e697e9afe0baf5cff7099ebb442169f36fa05293e78a0bb50fbefaa096d", ), } SPEC_FUNCTION = re.compile( r"^\s*(?P\d+)\s+" r"(?Ppascal|cdecl|varargs|stub|register)" r"(?P(?:\s+-\S+)*)\s+" r"(?P[A-Za-z0-9_@]+)\((?P[^)]*)\)" ) SPEC_EQUATE = re.compile( r"^\s*(?P\d+)\s+equate\s+(?P[A-Za-z0-9_@]+)\s+(?P\S+)" ) def fetch_specs() -> dict[str, tuple[str, list[str]]]: specs = {} for library, (path, expected_digest) in SPEC_SOURCES.items(): url = f"{WINE_BASE}/{path}" with urllib.request.urlopen(url, timeout=30) as response: payload = response.read() digest = hashlib.sha256(payload).hexdigest() if digest != expected_digest: raise RuntimeError( f"Wine source drift for {path}: expected {expected_digest}, got {digest}" ) specs[library] = (url, payload.decode("utf-8").splitlines()) return specs def parse_specs( specs: dict[str, tuple[str, list[str]]], ) -> dict[tuple[str, str], dict[str, str]]: declarations = {} for library, (url, lines) in specs.items(): for line_number, line in enumerate(lines, 1): match = SPEC_FUNCTION.match(line) if match: name = match.group("name") flags = match.group("flags").strip() kind = match.group("kind") prefix = f"{kind}16" if flags: prefix += f" {flags}" declaration = f"{prefix} {name}({match.group('arguments').strip()})" declarations[(library, name.upper())] = { "prototype": declaration, "evidence": f"Wine {WINE_TAG} {url}#L{line_number}", "notes": ( f"Wine Win16 ordinal {match.group('ordinal')}; " "every TDKPIN call site still requires argument-level review." ), "status": "partial", "stage": "prototype-sourced", } continue match = SPEC_EQUATE.match(line) if match: name = match.group("name") declarations[(library, name.upper())] = { "prototype": f"equate16 {name} = {match.group('value')}", "evidence": f"Wine {WINE_TAG} {url}#L{line_number}", "notes": ( f"Wine Win16 ordinal {match.group('ordinal')}; this is runtime data, " "not a callable function." ), "status": "verified", "stage": "runtime-data", } return declarations def recover_special_declarations( declarations: dict[tuple[str, str], dict[str, str]], ) -> None: keyboard_url = f"{WINE_BASE}/{SPEC_SOURCES['KEYBOARD'][0]}" declarations[("KEYBOARD", "ORDINAL_5")] = { "prototype": "pascal16 -ret16 AnsiToOem(str ptr)", "evidence": f"Wine {WINE_TAG} {keyboard_url}#L5", "notes": "Imported by ordinal; Wine identifies ordinal 5 as AnsiToOem.", "status": "partial", "stage": "prototype-sourced", } declarations[("KEYBOARD", "ORDINAL_6")] = { "prototype": "pascal16 -ret16 OemToAnsi(str ptr)", "evidence": f"Wine {WINE_TAG} {keyboard_url}#L6", "notes": "Imported by ordinal; Wine identifies ordinal 6 as OemToAnsi.", "status": "partial", "stage": "prototype-sourced", } declarations[("MMTIMER", "SYSTEMTIMERMAKE")] = { "prototype": ( "pascal16 -ret16 SystemTimerMake(" "HWND16 recipient, word delay_ms, word resolution_ms, word one_shot)" ), "evidence": ( "MMTIMER.DLL 1000:0039 disassembly; export ordinal 1; " "f1d9ac980c7bfba5dc53eaa9e7cb2c3cd9b82f879ee8962ad40bf863d641bb49" ), "notes": ( "Binary-reviewed wrapper around timeSetEvent16: it installs MMTIMER 1000:0002, " "posts message 0x0580 to recipient, and returns the multimedia timer id." ), "status": "verified", "stage": "binary-reviewed", } def main() -> int: with LEDGER.open(newline="", encoding="utf-8") as handle: reader = csv.DictReader(handle, delimiter="\t") columns = list(reader.fieldnames or ()) rows = list(reader) if "stage" not in columns: columns.insert(columns.index("status") + 1, "stage") declarations = parse_specs(fetch_specs()) recover_special_declarations(declarations) import_keys = {(row["library"], row["import_name"].upper()) for row in rows} missing = sorted(import_keys - set(declarations)) if missing: raise RuntimeError(f"no Win16 declaration for: {missing}") for row in rows: recovered = declarations[(row["library"], row["import_name"].upper())] preserve_review = row.get("stage") not in { "", "untyped-import", "prototype-sourced", "runtime-data", "binary-reviewed", } preserved = ( {key: row.get(key, "") for key in ("status", "stage", "evidence", "notes")} if preserve_review else None ) row.update(recovered) if preserved: row.update(preserved) temporary = LEDGER.with_suffix(LEDGER.suffix + ".tmp") with temporary.open("w", newline="", encoding="utf-8") as handle: writer = csv.DictWriter( handle, fieldnames=columns, delimiter="\t", lineterminator="\n" ) writer.writeheader() writer.writerows(rows) temporary.replace(LEDGER) statuses = {} for row in rows: statuses[row["status"]] = statuses.get(row["status"], 0) + 1 print(f"updated_import_slots={len(rows)}") print( f"unique_declarations={len({(row['library'], row['import_name']) for row in rows})}" ) print("statuses=" + ",".join(f"{key}:{statuses[key]}" for key in sorted(statuses))) return 0 if __name__ == "__main__": raise SystemExit(main())