-
-
Notifications
You must be signed in to change notification settings - Fork 40
Maintaining DWM projections
OpenGlass models the private uDWM.dll and dwmcore.dll ABI with generated typed Symbol and Layout handles backed by startup-time module registries. The Legacy and MILComp schemas are independent because common class or symbol names do not imply compatible object layouts or function ABIs.
The editable inventories are:
OpenGlass/ProjectionSchemas/legacy/udwm.json
OpenGlass/ProjectionSchemas/legacy/dwmcore.json
OpenGlass/ProjectionSchemas/milcomp/udwm.json
OpenGlass/ProjectionSchemas/milcomp/dwmcore.json
Generated C++ is a build artifact and never a second data source.
-
Zero-cost runtime metadata dispatch. Version selection, descriptor validation, and Symbol/Layout resolution happen during startup. After
Freezeand commit, hot-path field access is a typed offset load plus address calculation, and projected calls dispatch through the already selected typed slot. Normal rendering must not rescan cases, parse metadata, allocate, lock, or perform registry/symbol work. - Write once, use everywhere, with type safety. Declare each private field, function, variable, or vtable slot once in the architecture schema. Code generation produces the typed handle or wrapper used by every consumer and version binding. Call sites do not repeat offsets, names, casts, or ABI declarations; incompatible function ABIs remain separate typed logical Symbols and fail at build or validation time instead of becoming unchecked runtime calls.
- Structured and maintainable. Logical IDs, typed Symbols, ordered non-overlapping bindings, bounded Layout cases, fallbacks, and evidence notes make version differences explicit data rather than scattered conditionals. Architecture inventories remain independent, generated files remain disposable, and a maintainer can audit one item without reconstructing an informal table from handler code.
All four inventories use schema_version: 4. Stable IDs identify logical Symbols; exact symbol_names values inside their selected bindings are the sole matching truth. Do not add name-only, substring, decorated-name, or first-match fallback behavior.
Optional top-level min_inclusive and max_exclusive values bound the complete module inventory. ModuleRegistry::Freeze rejects a version outside that range before symbol collection or hook preparation:
- Legacy explicitly covers
[17763.0, 28000.0). - MILComp starts at
28000.0and deliberately has no upper bound.
Every schema also has a required known_builds list. Startup suppresses the new-Windows-version warning only when both uDWM.dll and dwmcore.dll recognize their current build. Build recognition controls that warning only: it is independent of the module range, Required projection validation, semantic evidence, and real-OS support.
A Layout case's until value is an exclusive right boundary. A final otherwise: true carries the last known offset through later versions only within the module range. For MILComp, this open end is a compatibility policy that keeps ordinary servicing revisions usable; it is not proof that an unaudited future binary retained the layout.
Each logical Symbol owns one typed slot and a non-empty ordered bindings array. Every binding contains exact symbol_names and one [min_inclusive, max_exclusive) resolution interval. Bindings within a logical Symbol must not overlap; a version not covered by any binding makes that Symbol inactive rather than unresolved. Merge versioned entries only when their module, semantic target, kind/usage, requirement, fallback, and underlying ABI are identical. If an ABI changes, keep separate typed logical Symbols even when their intervals are mutually exclusive.
The only exceptional projected ABI forms are:
-
discard_return, when the canonical wrapper intentionally ignores an older return value; -
extra_trailing_argument, when the canonical wrapper supplies one extra trailing Win64 argument.
Both require an explicit source type and are compile-time checked. They do not permit reordered, removed, or incompatible middle arguments.
Both modules resolve as one startup prerequisite: Reset → Collect → Validate → Commit. Neither module publishes slots or projected variables unless both validate. Required applies only when a logical Symbol has an active binding for the current version; inactive gaps do not participate in Required validation. A version-inactive or Optional projected function needs an ABI-compatible fallback; otherwise generated code uses the typed FailFast thunk.
Every projected wrapper must be inline and contain only an OPENGLASS_MUSTTAIL return Projection::Invoke<&Target>(...) dispatch. A wrapper declaration is not a consumer. Every projected function needs a real runtime call site or direct typed Symbol consumer such as a Detour; remove unused wrappers and logical Symbols instead of making them Required startup gates.
Private DWM fields used by handlers must be represented by typed Layout accessors. Add or correct a schema Layout rather than embedding byte arithmetic or padding-based fake layouts in handler code. Instruction-pattern navigation, COM vtable slots, and application-owned storage are separate concerns.
After Freeze, normal FieldHandle::read/ref/address access directly loads the selected offset and performs address arithmetic. It must not scan cases, consult the registry, allocate, lock, or throw. The checked offset() API remains for cold-path diagnostics.
Field access preserves the base object's constness. mutable_address() and mutable_ref() are reserved for a projected class facade whose established API intentionally exposes a mutable private subobject from a const method; handler code must not use them directly to bypass constness.
An exact function-pointer ABI does not by itself prove that a private DWM function is safe to detour through an ordinary C++ dispatcher. Whole-program optimization can let direct callers rely on a known callee or wrapper preserving volatile GPRs, XMM lanes, or EFLAGS beyond the public Windows x64 ABI.
Audit every hooked function on the exact binary: enumerate all direct callers and intervening wrappers, identify values live across the call, follow their downstream semantic use, and eliminate infeasible paths or value-independent idioms. When a private contract is verified, preserve it with the narrowest version-specific CustomDispatchDetour and assembly shim. Keep replacement dispatch, the original call, and HookRundown release inside the preserved region.
SetMargin is the current concrete example. Optimized UpdateMarginsDependentOnStyle callers on the affected builds reuse RCX after a short wrapper returns. OpenGlass conservatively uses the ABI-compatible RCX-preserving physical dispatcher across all supported Legacy builds, but this does not claim that earlier builds share the private contract. Do not generalize the verified contract to other functions without independent evidence.
Scripts/dump_symbols.py uses DbgHelp to download the PDB paired with an input image and prints exact UNDNAME_COMPLETE names:
python Scripts/dump_symbols.py --input "$env:WINDIR\System32\dwmcore.dll" --grep "COcclusionContext::PreSubgraph"Symbols default to %TEMP%\symbols. Use --output PATH to override the cache and --rva to print image-relative addresses. This tool provides schema input, not sufficient production evidence.
The repository provides the $maintain-dwm-offsets skill. Connect the coding agent to IDA Pro using ida-pro-mcp, and specify the architecture, module, exact sample, and audit scope. Use the connected tool schemas as the API authority; client-side tool prefixes may differ.
The updated MCP API routes analysis through a database session ID returned by idb_open. An active IDA window is not an implicit target, and global instance selection is no longer the routing mechanism.
- Call
idb_list({})and match the exact input path. A discovered GUI entry can have an emptysession_idandadopted: false; it cannot be queried until adopted. - For an existing GUI database, call
idb_openwith its reportedinput_path,mode: "prefer_gui",run_auto_analysis: false,build_caches: false, andinit_hexrays: false. Preserve a reported.i64path rather than substituting a similarly named DLL. Check the result and retain the returned session ID. - Call
server_healthwith thatdatabaseID to verify the input path, module, image base, and readiness. Usesurvey_binarywithdetail_level: "minimal"as the first binary-analysis query, then verify its sample hash against the paired PE/PDB evidence. - Pass the same verified
databaseID on every subsequent query. Recheck identity when switching samples, reconnecting, or recovering from a stale session. Never fall back to an empty ID or whichever GUI is active. - Keep work on different samples serialized. Explicit IDs prevent ambiguous routing but do not change the repository's audit and session-ownership rules.
For a raw PE that needs initial analysis, preserve the original and open a disposable working copy with mode: "force_headless". Enable automatic analysis, cache building, and Hex-Rays initialization for that task-owned sample. For an existing IDB, use the available analysis without silently rerunning warmup or modifying the database.
Use narrow function queries, decompilation, disassembly, and cross-references to establish semantics. Inspect per-item errors and truncation indicators, and follow pagination before claiming that all callers were checked. If IDAPython is needed, py_eval retains locals by default; use new_locals: true for a self-contained audit snippet and import its dependencies explicitly.
Do not rename, annotate, apply types, patch, or save an IDB during an audit-only request. Leave pre-existing user sessions open. Close only task-created sessions with idb_close({database: session_id, save: false}); this terminates an owned worker without saving the database.
The repository's IDA MCP workflow contains the full capability map, ownership rules, and private hook-contract audit procedure.
The report must identify the exact PE/PDB sample, architecture, selected Layout entry or Symbol binding and interval, semantic evidence, independent cross-checks, unresolved items, proposed changes, and runtime-validation status. Symbol results must also record the active Required/Optional state, exact complete name, and RVA. Successful tool calls alone do not establish compatibility.
python .agents/skills/maintain-dwm-offsets/scripts/validate_projection_layouts.py . --architecture legacy --module udwm --id STABLE_ID --version BUILD.REVISION
python .agents/skills/maintain-dwm-offsets/scripts/validate_projection_symbols.py . --architecture legacy
python Scripts/audit_symbol_resolution.py . --architecture legacy --module udwm --version BUILD.REVISION --image PATH_TO_DLL --symbol-path PATH_TO_SYMBOLS --configuration release
python Scripts/audit_winbindex_revisions.py . --architecture legacy --module udwm --build BUILD --list-only
python -m unittest discover -s .agents/skills/maintain-dwm-offsets/tests -p "test_*.py"
python Scripts/test_audit_symbol_resolution.py
python Scripts/test_projection_codegen.pyUse milcomp explicitly for the other architecture and select the appropriate udwm or dwmcore module. Supply --id and --module to query one exact stable ID; omit --id to validate the complete selected inventory. The Layout validator preserves offset expressions, applies exclusive right boundaries, and reports module_unsupported outside the top-level range. The Symbol validator checks logical IDs, slot ownership, bindings, non-overlapping binding ranges, requirements, exact complete-name candidates, ABI separation, and consumers. The resolution auditor verifies image/PDB GUID and age, reports the selected binding for the requested version, and only then evaluates its exact DbgHelp names.
audit_winbindex_revisions.py inventories every indexed x64 revision of one Windows build and, without --list-only, checks the downloaded images and matching PDBs against the projection symbols. This is static symbol-resolution evidence only; it does not establish ABI semantics or runtime support.
Keep three layers distinct:
- Static verification validates schema structure and exact paired PE/PDB resolution.
- IDA semantic audit establishes field meaning, the language-level ABI, and any hook-only private calling contract from accessors, mutators, constructors, producers/consumers, direct callers, wrappers, and downstream use.
- Real-OS validation exercises injection, rendering, recovery, service lifecycle, and the affected feature on the exact build.
No layer substitutes for another. Exact symbol uniqueness does not establish semantics, and an IDA result does not prove runtime safety.
Do not infer a Layout from a neighboring member, allocation size, prior build, or marketing version. Do not infer a function ABI from a short name or first same-name match. If IDA is unavailable, use another disassembler and follow the semantic anchors recorded in schema notes; unverified guesses must not enter production descriptors.