Skip to content

Upscaler v2 merge#1732

Open
praydog wants to merge 29 commits into
pd-upscalerfrom
upscaler-v2-merge
Open

Upscaler v2 merge#1732
praydog wants to merge 29 commits into
pd-upscalerfrom
upscaler-v2-merge

Conversation

@praydog
Copy link
Copy Markdown
Owner

@praydog praydog commented Apr 25, 2026

Merges v2 into the upscaler branch

Selectively1 and others added 29 commits March 28, 2026 19:07
Co-authored-by: Selectively11 <selectively11@selectively11.com>
* List all arguments when "Hook all method"

* Add unhook all methods

* Add auto unhook when exceed call count
FaultyFileDetector ifdef gate
* Add loose texture loader

* Enable for other games up

* Merge loose texture loader to loose file loader

* Fix compile error

* Fix enable macro

* Remove conditional compilation for MHWILDS

---------

Co-authored-by: praydog <2909949+praydog@users.noreply.github.com>
* Fix FaultyFileDetector resource stream failed not found

Take it easy

* Fix loose texture get_path_to_resource not found

Use more stable semantics

* Remove PAK hacks

Up

* Fix emit wrong offset for members detect from "and"
* v2: Foundation - GameIdentity runtime detection + unified cmake.toml

- Add GameIdentity.hpp/cpp: Runtime game detection from exe name,
  TDB version mapping, engine parameter derivation (replaces compile-time
  RE2/RE3/RE4/.../REENGINE_PACKED/REENGINE_AT/TDB_VER macros)
- Restructure cmake.toml: Single REFrameworkSDK + REFramework targets
  replace ~30 per-game SDK+DLL targets. Define REFRAMEWORK_UNIVERSAL.
- Add PLAN_V2_MONOLITHIC.md documenting architecture decisions.

* v2: Convert Mods.cpp, REFramework.hpp, Main.cpp, PluginLoader.cpp to runtime dispatch

- Mods.cpp: All conditional mod registration uses GameIdentity runtime checks
- REFramework.hpp: get_game_name() delegates to GameIdentity
- Main.cpp: Startup guards use GameIdentity; initialize() called early
- PluginLoader.cpp: game_name set at runtime in initialize_plugins()
- ReClass.hpp: REFRAMEWORK_UNIVERSAL includes RE8 layout as canonical base
- TDBVer.hpp: REFRAMEWORK_UNIVERSAL sets TDB_VER=84 so all code paths compile

* v2: Convert ~200 game-specific ifdefs to runtime GameIdentity checks in src/

Batch conversion of game-specific preprocessor guards across 25 source files:

Camera, FreeCam, FirstPerson, ManualFlashlight:
- #ifdef RE8, RE2, RE3, RE4 → runtime gi.is_re8() etc.

Graphics, Hooks, REFrameworkConfig, ExceptionHandler:
- #ifdef SF6, MHWILDS, RE4, RE7 → runtime checks
- Member declarations gated with REFRAMEWORK_UNIVERSAL

IntegrityCheckBypass:
- Heavy per-game byte pattern ifdefs → runtime if/else if chains

RE8VR:
- Removed file-level #if defined(RE7)||defined(RE8) guard
- Added runtime early return in on_initialize()
- Internal RE7/RE8 guards → runtime checks

VR.cpp:
- Regenny include chain → REFRAMEWORK_UNIVERSAL block
- ~35 game guards → runtime checks

ObjectExplorer, ChainViewer, LooseFileLoader:
- TDB_VER and game guards → runtime checks

REFramework.cpp:
- DD2/MHRISE/TDB_VER >= 74 guards → runtime checks

* v2: Fix compilation errors - brace mismatches, GameIdentity static members, legacy TDB guards

- GameIdentity.hpp: Move static inline members to .cpp (incomplete type error)
- REFramework.cpp: Fix brace nesting in ldr_notification_callback
- FirstPerson.cpp: Fix missing closing braces in constructor and on_lua_state_created
- Graphics.cpp: Fix missing closing braces in on_pre/on_application_entry
- ObjectExplorer.cpp: Guard legacy TDB struct member access with #ifndef REFRAMEWORK_UNIVERSAL
- CMakeLists.txt: Regenerated by cmkr from updated cmake.toml (single unified target)

* v2: Update plan doc with current status and SDK struct dispatch blocker analysis

* v2: Runtime dispatch for REType layout differences (MHWILDS/RE9 vs RE8)

REType is 0x60 in most games but 0x68 in MHWILDS/RE9, with fields shifted
by 8 bytes after offset 0x28 (super, childType, chainType, fields, classInfo,
size, typeCRC all at different offsets).

- Add RETypeLayouts.hpp: defines reclass_mhwilds::REType (0x68 layout) and
  accessor functions in utility::re_type_accessor namespace that check
  GameIdentity at runtime and cast to the correct layout
- Replace all direct REType field accesses in shared/sdk/ and src/ with
  accessor calls (get_super, get_classInfo, get_fields, get_size, get_typeCRC,
  get_childType, get_chainType)
- REType.cpp, REManagedObject.cpp, REArray.cpp, RETypeDefinition.cpp,
  PluginLoader.cpp, ObjectExplorer.cpp updated
- REObjectInfo->classInfo and ParsedType->super accesses left untouched
  (different structs, same offset in all layouts)

* v2: Runtime TDB struct dispatch for TYPE_INDEX_BITS mismatch

Games with TDB < 73 (RE2, RE3, RE8, RE7, MHRISE) use 18-bit
TYPE_INDEX_BITS, but the universal build compiles with 19-bit.
The bitfield packing differs, causing all RETypeDefinition field
reads to return garbage on those games.

- Add RETypeDefDispatch.hpp: defines tdb_bits18::RETypeDefVersion69
  with hardcoded 18-bit field widths, and TDEF_FIELD/TDEF_FIELD_SET
  macros for runtime dispatch based on GameIdentity::tdb_ver()
- Replace ~33 bitfield accesses in RETypeDefinition.cpp with
  TDEF_FIELD macro (impl_index, declaring_typeid, parent_typeid,
  generics, index, object_type, member_method, member_field,
  member_prop, num_member_prop, type, managed_vt)
- Non-bitfield fields at same offset (type_flags, size, fqn_hash,
  type_crc) left as direct access

Note: REField and REMethodDefinition also use TYPE_INDEX_BITS
bitfields and will need similar treatment for full functionality.

* v2: Runtime TDB header dispatch + method stride + field/method bitfield dispatch

- RETypeDB: All pointer field accesses (types, methods, fields, etc.) now dispatch
  through get_*_ptr() helpers that cast to tdb70::TDB for games with TDB < 73
- RETypeDB: Count accessors (get_num_types, etc.) dispatch similarly
- REMethodDefinition: get_method() uses byte-offset arithmetic with correct stride
  (16 bytes for tdb69, 12 bytes for tdb84)
- MethodIterator: Refactored to index-based iteration to handle variable stride
- REMethodDefinition: get_function() returns direct function pointer for tdb69
- REMethodDefinition: declaring_typeid, impl_id accessed via TMETH_FIELD dispatch
- REMethodDefinition: get_param_index() dispatches params vs params_lo/params_hi
- REField: declaring_typeid, impl_id accessed via TFIELD_FIELD dispatch
- REField: get_type() casts to tdb69::REFieldImpl for field_typeid
- REField: get_init_data_index() casts to tdb69::REFieldImpl for init_data split
- REField: get_offset_from_fieldptr() reads from tdb69 REField69::offset
- ObjectExplorer: All direct tdb-> accesses replaced with dispatched accessors
- RETypeDefDispatch.hpp: Added REMethodDef69, REField69, TMETH_FIELD, TFIELD_FIELD

* v2: Runtime dispatch for REParameterDef, game_namespace, REManagedObject, Renderer, REContext

- RETypeDefDispatch.hpp: Added REParamDef69 (18-bit type_id) and TPARAM_FIELD macro
- RETypeDB.cpp: All p.type_id accesses use TPARAM_FIELD dispatch for 18 vs 19 bit
- RETypeDB.cpp: REField::get_data_raw vtable optimization gated to tdb_ver >= 81
- RETypes.cpp: game_namespace() returns correct prefix per game at runtime
- REManagedObject.cpp: is_managed_object/get_type/get_vm_type dispatch tdb_ver >= 71
- Renderer.hpp: Struct offsets (d3d12 resource, scene layers, output state) runtime dispatch
- REContext.cpp: Static table fixup gated to tdb_ver >= 71

* v2: Application::Function stride/offset dispatch + RETransform joint matrix dispatch

- Application.hpp: Function struct gets accessor methods (get_description, get_priority,
  get_type_val) that read from correct offsets (TDB < 74: +8 byte shift from extra void*)
- Application.cpp: get_function_stride() returns 0xD0 for TDB < 74, 0xC8 for TDB >= 74
- Application.cpp: All Function array iteration uses get_function_at() stride-aware indexing
- Application.cpp: All field accesses use accessor methods
- Hooks.cpp: entry->description -> entry->get_description()
- VR.cpp: func->description -> func->get_description()
- RETransform.hpp: Joint matrix access dispatches between RE2/RE3 (jointMatrices at 0xC0)
  and RE8+ (joints.matrices at 0xE0) via get_joint_matrices() helper

* v2: Runtime dispatch for get_managed_vt/fieldptr_offset (>= 81 guard)

- RETypeDefinition.cpp: get_fieldptr_offset() uses direct managed_vt for TDB < 81
- RETypeDefinition.cpp: has_fieldptr_offset() checks TDEF_FIELD(managed_vt) for TDB < 81
- RETypeDefinition.cpp: get_managed_vt() skips abstract-type parent walk for TDB < 81

* Convert remaining TDB_VER/game-specific guards to runtime dispatch

Runtime dispatch conversions:
- ResourceManager.cpp: TDB_VER < 81/73 byte-pattern scans for create_userdata
- Hooks.cpp: TDB_VER < 74 hook_update_before_lock_scene, >= 73 PrimitiveSystem guard
- Graphics.cpp: regenny header selection, TDB_VER < 73 backbuffer write
- Renderer.hpp: DRAW/UPDATE_VTABLE_INDEX, NUM_PRIORITY_OFFSETS as runtime fns,
  DirectXResource offset, TargetState::Desc pad, RenderLayer layout, RE4 lod_bias
- Renderer.cpp: NUM_PRIORITY_OFFSETS loop
- RETypes.cpp: >= 73 type-list finder
- VR.hpp: m_allow_engine_overlays, m_enable_asynchronous_rendering defaults
- Graphics.hpp: m_ultrawide_custom_fov default
- RETypeDB.cpp: REModule get_methods/instantiated/member_references via tdb74 cast
- RETypeDB.hpp: REModule stride-aware get_module_at(), get_module_stride()

Bug fixes:
- REModule array indexing stride mismatch: tdb81 is 0x40 bytes, tdb74 is 0x58.
  get_module() now uses stride-aware access for universal builds.

Cleanup:
- Application.cpp: removed temporary debug logging from get()

* v2: Runtime dispatch for regenny Window/SceneView struct offsets

Window and SceneView structs have different field offsets per game
(RE2/RE3/RE8/RE4 etc. all differ). The universal build previously
always used RE9 headers, giving wrong offsets for other games.

New ViaDispatch.hpp provides runtime accessor functions that dispatch
by GameID. Graphics.cpp and VR.cpp updated to use these accessors
under REFRAMEWORK_UNIVERSAL.

* v2: Namespace regenny includes in ViaDispatch for Regenny maintainability

Instead of hardcoded offset tables, each game's regenny Window/SceneView
headers are included inside distinct outer namespaces (ns_re7, ns_re3,
ns_re2, etc.). Dispatch functions cast to the correct namespaced struct
type at runtime. When Regenny regenerates a header, offsets update
automatically through the struct layout — no manual table maintenance.

re9 headers use the globally-included versions (already present via
Graphics.cpp/VR.cpp) to avoid #pragma once conflicts.

* v2: Fix RETypeDefinition stride and bitfield dispatch for TDB 71 games

Two bugs causing RE4/SF6/MHRISE/DD2 to fail (Renderer type not found):

1. needs_18bit() threshold was tdb_ver < 73, but TDB 71+ (RE4/SF6/MHRISE/DD2)
   uses 19-bit TYPE_INDEX_BITS. Changed to tdb_ver < 71.

2. get_type(index) used sizeof(RETypeDefVersion84) = 0x50 as stride, but
   TDB 71-73 games have sizeof(RETypeDefVersion71) = 0x48 (no
   unk_new_tdb74_uint64 field). Added get_typedef_stride() and stride-
   aware access in get_type().

Stride map:
  TDB 69-70 (RE2/RE3/RE7/RE8): 0x50 (18-bit bitfields, V69 layout)
  TDB 71-73 (RE4/SF6/MHRISE/DD2): 0x48 (19-bit, no extra uint64)
  TDB 74+ (MHWILDS/RE9/PRAGMATA): 0x50 (19-bit, has unk_new_tdb74_uint64)

* v2: Hardcode bit widths in all RETypeDefVersion structs, sizeof(T) for strides

Architecture change: every TDB version struct now has hardcoded bit
widths (no dependency on TYPE_INDEX_BITS / FIELD_BITS macros). This
makes each struct self-contained and correct regardless of compile-time
macro values.

Changes:
- V84/V83/V82/V74: hardcoded 19-bit TYPE_INDEX, 20-bit FIELD
- V71: hardcoded 19-bit TYPE_INDEX, 19-bit FIELD (no unk_new_tdb74_uint64)
- V69: hardcoded 18-bit (was in tdb_bits18 namespace, now the main struct)
- V67: split into V67 (DMC5, bitfield variant) and V67_RE3 (plain uint32)
- V66/V49: hardcoded 16-bit

Dispatch:
- TDEF_FIELD: 3-way dispatch (V69 for <71, V71 for 71-73, V84 for 74+)
- get_typedef_stride(): sizeof(RETypeDefVersion*) instead of 0x48/0x50
- get_method_stride(): sizeof(tdb69/tdb84::REMethodDefinition)
- tdb_bits18 namespace reduced to backward-compat aliases

* v2: Eliminate TYPE_INDEX_BITS macro from all RETypeDB.hpp struct definitions

Every tdb namespace struct now has hardcoded bit widths:
- tdb84 through tdb71: 19-bit TYPE_INDEX (was macro, same value)
- tdb69: 18-bit (BUG FIX — was evaluating to 19 under universal build)
- tdb67: 17-bit (BUG FIX — was evaluating to 19 under universal build)
- tdb66: 16-bit (BUG FIX — was evaluating to 19 under universal build)

The tdb69/67/66 fixes are correctness bugs: under REFRAMEWORK_UNIVERSAL
TYPE_INDEX_BITS=19, so REField/GenericListData/REMethodDefinition structs
in those namespaces had wrong bitfield widths. This caused silent
corruption when reading older-format TDB data via dispatch macros.

* TDB header + RETypeImpl: full per-version dispatch

TDB header accessors (get_num_types, get_num_methods, get_types_ptr,
get_modules_ptr, etc.) now switch on tdb_ver() with explicit casts to
tdb70/tdb71/tdb73/tdb74/tdb81/tdb82/tdb83/tdb84 TDB structs. Replaces
the old needs_18bit()-only two-branch dispatch that was wrong for
TDB 71-73 (e.g. RE4 reading 'initialized' instead of numTypes).

RETypeImpl field accesses (num_member_methods, num_member_fields) in
RETypeDefinition.cpp now use TIMPL_DISPATCH which dispatches to the
correct tdb69/tdb71/.../tdb84 RETypeImpl struct. Fixes the RE2 bug
where offset 0x18 in tdb69 data read interface_id (0xFFFF=65535)
instead of num_member_methods at offset 0x12.

* Fix RE8 (TDB 69): separate dispatch from tdb70

tdb69::TDB has no 'void* unk' field between initData and attributes2,
so stringPool/bytePool/internStrings are at different offsets than tdb70.
Casting RE8's TDB to tdb70::TDB read garbage stringPool pointer causing
infinite loop during initialization.

* update

* DMC5 (TDB 67): Fix via.Application lookup failure

Root cause: find_type() set map_populated=true BEFORE iterating
types. On TDB 67, get_full_name() throws for ~66% of type indices
(corrupt/unresolvable names in pre-impl TDB layout). The first
exception propagated out, leaving map_populated=true with an empty
map. All subsequent find_type() calls returned nullptr immediately.

Fix:
- Move map_populated=true AFTER the iteration loop completes
- Wrap individual type name resolution in try/catch so corrupt
  indices are skipped without aborting the entire map build
- ~21K of 62K types populate successfully, including all critical
  types (via.Application, via.io.file, etc.)

Verified: DMC5 now fully initializes (hooks, D3D11, config save).
RE2 regression test passes.

* RE7 fix (TDB70)

* v2: 3-tier declaring_typeid dispatch + ObjectExplorer tdb67 runtime conversion

- Add tmeth_declaring_typeid() / tfield_declaring_typeid() inline functions
  for 3-tier dispatch: tdb67 (17-bit) / tdb69 (18-bit) / tdb84 (19-bit)
- Replace TMETH_FIELD/TFIELD_FIELD(this, declaring_typeid) with new functions
  in RETypeDB.cpp and ObjectExplorer.cpp
- Convert all #ifndef REFRAMEWORK_UNIVERSAL guards in ObjectExplorer.cpp to
  runtime tdb67:: casts for method params, fields, properties
- Fixes ~66% type name resolution failures on DMC5 (TDB 67) caused by reading
  declaring_typeid with wrong bit width

* v2: Fix RE3 crash — RopewayCameraSystem +8 offset dispatch

RE3 has an extra 8-byte pad after cameraControllerInfos (0x58) in
RopewayCameraSystem, shifting all subsequent fields +8 vs RE2/RE8:
  cameraController: 0x98 (RE8) vs 0xA0 (RE3)
  mainCamera:       0xC0 (RE8) vs 0xC8 (RE3)
  playerJoint:      0xE0 (RE8) vs 0xE8 (RE3)
  mainCameraController: 0xE8 (RE8) vs 0xF0 (RE3)

Added CameraSystemDispatch.hpp with CAMSYS() macro that dispatches
to the correct struct layout at runtime via GameIdentity::is_re3().
Replaced all 58 shifted field accesses in FirstPerson.cpp.

Fixes access violation (c0000005) in FirstPerson::update_pointers_from_camera_system
that occurred ~6s after RE3 startup.

* v2: Fix RE2 crash + head visibility — dispatch RopewayPlayerCameraController + RETransform joints

RE2 RopewayPlayerCameraController has fields shifted -0x10 vs RE8/RE3:
  activeCamera: 0xA8 (RE2) vs 0xB8 (RE8) — confirmed via TDB reflection
  cameraParam:  0xB8 (RE2) vs 0xC8 (RE8)
  pitch/yaw:    0x108/0x10C vs 0x118/0x11C

Added CAMCTRL() dispatch macro and sdk::re2::RopewayPlayerCameraController_RE2
struct in CameraSystemDispatch.hpp. Replaced 18 field accesses in FirstPerson.cpp.

RE2 RETransform has REJointArray at 0xD0 (vs 0xD8 in RE8). The get_joint()
and get_all_children() functions read transform.joints.data at the RE8 offset,
causing joint lookups to fail on RE2 — head bone never found, never zeroed.

Added get_joint_array_data() dispatcher in RETransform.hpp and rewrote
get_joint()/get_all_children() to use it for REFRAMEWORK_UNIVERSAL builds.

* v2: Fix RE3 first-person rotation — dispatch RopewayMainCameraController

RE3's RopewayMainCameraController has extra cameraDampingCameraPosition field
at 0x90, shifting all subsequent fields +0x10 vs RE2/RE8:
  cameraRotation:       0x90 (RE8) vs 0xA0 (RE3)
  mainCamera:           0xD0 (RE8) vs 0xE0 (RE3)
  switchInterpolationTime: 0xB0 (RE8) vs 0xC0 (RE3)

Added MAINCAM() dispatch macro and RopewayMainCameraController_RE3 struct
in CameraSystemDispatch.hpp. Replaced 8 field accesses in FirstPerson.cpp.

* v2: Fix Pragmata Sketchbook crash — TDB version is 83, not 84

PRAGMATA_SKETCHBOOK.exe uses TDB 83 (confirmed from runtime TDB header).
GameIdentity hardcoded 84 (matching the unreleased full game), causing
dispatch macros to hit the tdb84 default path instead of case 83.

TODO: Read TDB version from binary at runtime and override the hardcoded
value, making the universal build resilient to demo/trial TDB mismatches.

asdf

* v2: Fix MH Stories 3 blurriness — Window uses DD2 layout (borderless_size at 0xa0)

MH Stories 3 (TDB 82) uses the DD2 Window struct layout where
borderless_size is at offset 0xa0, not the RE9 layout (0xa8).
Confirmed via live memory probing. ViaDispatch.hpp was sending
MHSTORIES3 to default->RE9, reading borderless_size 8 bytes late.

Added GameID::MHSTORIES3 fallthrough to DD2 cases in VIA_WIN_FIELD,
VIA_WIN_BORDERLESS, and sv_window dispatchers.

* v2: Fix copy_texture + get_d3d12_resource_container for universal dispatch

copy_texture had TWO different native signatures (pre-TDB82 4-arg vs
post-TDB82 6-arg) gated by #if TDB_VER < 82. In universal builds this
always resolved to the TDB>=82 branch since TDB_VER is 84 at compile
time, silently breaking texture copy for every pre-TDB82 game.

Restructured both paths into compile-time-available lambdas
(copy_legacy/copy_modern) and dispatch on tdb_ver() at runtime in
universal mode. Non-universal builds keep the original single-path
compile-time selection.

Texture::get_d3d12_resource_container had a control-flow bug after the
initial universal conversion: for tdb_ver>=71 it fell through past the
runtime fastpath into a preprocessor else branch that was never taken
in REFRAMEWORK_UNIVERSAL mode, returning garbage. Fixed by moving the
bruteforce scan outside the #elif chain so universal tdb>=71 reaches
it, while preserving the legacy compile-time early-return.

Verified: RE2 (TDB 70), StarForce (TDB 78), MHWILDS (TDB 81) all boot
and reach steady state.

* v2: Address Copilot PR review (praydog/REFramework#1609)

1. Hooks.cpp: RE7/MHRISE runtime exclusion for hook_update_before_lock_scene

   The original compile-time guards (#ifndef RE7 / #ifndef MHRISE) explicitly
   excluded these two games from the updateBeforeLockScene hook list because
   via.render.EntityRenderer::updateBeforeLockScene does not exist on them.

   The universal-build guard change broadened the #if to let every game
   reach the hook. The body had a tdb_ver()<74 runtime gate but that gate
   still *admits* RE7 (70) and MHRISE (71), so the hook would scan for a
   method that never existed, return an error string, and Hooks::on_initialize
   aborts on the first error — killing the whole Hooks mod on those two games.

   Added a runtime early-return at the top of hook_update_before_lock_scene
   for is_re7() || is_mhrise(), restoring the original exclusion behaviour.

   Verified on RE7: Hooks::on_initialize reaches 'Finished hooking' without
   the updateBeforeLockScene error.
   Verified on RE2 (control): updateBeforeLockScene: 7ff74df58f10, hooks
   still install normally.

2. GameIdentity.cpp: corrected misleading comment

   derive_engine_params comment claimed runtime TDB auto-detection 'will
   override' the hardcoded m_tdb_ver for legacy binaries, but no override
   path exists — members are private and there is no setter or friend
   accessor that writes m_tdb_ver after initialize().

   Rewrote the comment to document actual behaviour: the hardcoded values
   stand for the lifetime of the process, and demo/trial versions with
   different TDB versions than the full game must be handled by either a
   dedicated GameID entry or by hardcoding the correct value (see
   PRAGMATA_SKETCHBOOK -> TDB 83).

* Fix universal-mode bugs: PAK dir UI gate + RenderLayer sizeof for DMC5

- IntegrityCheckBypass::on_draw_ui(): ENABLE_PAK_DIRECTORY_LOAD is always 1
  in universal (TDB_VER=84>=81); add runtime tdb_ver()<81 early-return so the
  PAK directory loading UI is not shown for games that don't support it.
- RenderLayer::get_runtime_sizeof(): new helper that computes the actual
  in-game struct size at runtime; universal build always compiles 7 priority-
  offset slots but DMC5 (TDB 67) only has 6, making sizeof 4 bytes too large.
- Overlay::get_main_target_state() / get_main_depth_target_state(): switch to
  RenderLayer::get_runtime_sizeof() under REFRAMEWORK_UNIVERSAL so the offset
  into Overlay-specific members is correct for DMC5 VR paths.

Agent-Logs-Url: https://github.com/praydog/REFramework/sessions/20635765-a2df-4513-b9f5-093a08024b7e

Co-authored-by: praydog <2909949+praydog@users.noreply.github.com>

* Add safety documentation for three universal-mode latent risks

- ConstantBufferDX12::get_desc(): add TODO explaining that sizeof(ConstantBuffer)
  inherits the RenderResource compile-time padding bug; method has no call sites
  (latent) but will compute wrong offsets on RE2/RE3/RE7/DMC5 if ever activated
  without a runtime-computed base size.
- RenderLayer universal member block: add note documenting that TDB ≤49 member
  ordering (m_parent/m_layers before m_priority) is NOT compiled in universal;
  any future attempt to add a TDB49 game must revisit the whole struct layout.
- Overlay::s_b8g8r8a8_unorm_*_offset constants: add TODO warning that offsets
  are verified only for RE4/SF6 (TDB≥71) and that all other games in universal
  receive the same values; callers must be gated or the constants replaced with
  a per-TDB runtime dispatcher before being used outside RE4/SF6 code paths.

Agent-Logs-Url: https://github.com/praydog/REFramework/sessions/abf69012-c7fd-4914-84f8-040c52a96a65

Co-authored-by: praydog <2909949+praydog@users.noreply.github.com>

* v2: Fix critical init-order bug + RE7 joint array dispatch (adversarial review)

1. GameIdentity::initialize() moved from startup_thread to DllMain

   hook_add_vectored_exception_handler() runs synchronously from DllMain
   at lines 145-146, BEFORE CreateThread spawns startup_thread. The runtime
   check `tdb_ver() < 73` evaluated against the uninitialized singleton
   (tdb_ver == 0), so `0 < 73` was always true and the VEH filter was
   never installed on ANY game. This silently disabled anti-debug VEH
   filtering for DD2, STARFORCE, MHWILDS, MHSTORIES3, RE9, PRAGMATA.

   Fix: call GameIdentity::initialize() in DllMain before the integrity
   hooks. Detection is GetModuleFileNameW — safe under loader lock.

2. RE7 added to get_joint_array_data() and get_joint_matrices()

   Modern RE7 (TDB 70) shares the RE2/RE3 RETransform layout with joints
   at 0xD0 (confirmed by legacy per-game build including RE2_TDB70 ReClass
   for RE7). The c9d8e32b commit only added is_re2()/is_re3() checks —
   RE7 fell through to the RE8 path (0xD8), reading 8 bytes past the real
   joint array pointer. Affects VR head-hide, FirstPerson bone zeroing,
   ChainViewer, and any joint-by-name lookup on RE7.

Found by independent adversarial review (fresh Claude session with no
prior context, guided by structured bug-class taxonomy).

* v2: Fix REType accessor predicate — split pointer shift from scalar reorder

The old retype_is_large_layout() predicate gated on tdb_ver() >= 81,
which applied BOTH the 0x68 pointer shift AND the scalar field swap
to all tdb >= 81 games. But:

- Pointer shift (super/childType/chainType/fields/classInfo +8 bytes):
  only MHWILDS and RE9 have 0x68 REType with super at 0x40
- Scalar swap (typeCRC/size at 0x2C/0x30 instead of RE8's 0x30/0x2C):
  all tdb >= 81 games including MHSTORIES3 and PRAGMATA

MHSTORIES3 (tdb 82) and PRAGMATA (tdb 83) use 0x60 REType with super
at 0x38 (same as RE8) but with the scalar reorder. The old predicate
would dispatch them to the 0x68 layout for pointer reads, reading
super at 0x40 instead of 0x38.

Split into two predicates:
- retype_has_shifted_pointers(): is_mhwilds() || is_re9()
  Used by get_super, get_childType, get_chainType, get_fields, get_classInfo
- retype_has_field_reorder(): tdb_ver() >= 81
  Used by get_size, get_typeCRC

Audit confirmed all live code accesses these fields through the
utility::re_type_accessor:: functions — no raw t->super/t->classInfo
sites remain outside comments and unrelated struct types.

* v2: Fix DMC5 (TDB 67) REClassInfo layout + REField/REMethod dispatch

DMC5's REClassInfo is a completely different struct from RE8's:
  RE8 (TDB 69+): size 0x50, type @ 0x40, parentInfo @ 0x48
  DMC5 (TDB 67): size 0x78, type @ 0x68, parentInfo @ 0x70

Universal build compiled RE8's layout, so every managed object check
on DMC5 read parentInfo/type from wrong offsets:
- is_managed_object() read parentInfo at 0x48 (garbage) -> returned
  false for every object -> Singletons section empty
- get_type() read type at 0x40 (garbage) -> returned 0xF68C007 ->
  crash on dereference in refresh_map()
- get_vm_type() read objectFlags at wrong offset for TDB < 69

Added classinfo_accessor namespace with get_parentInfo/get_type that
dispatch on tdb_ver() < 69 to read from the correct offsets.

Also fixed three REField functions and get_param_index that used
needs_18bit() (tdb < 71) without needs_pre_impl() (tdb < 69) guard:
- REField::get_type() read tdb69 impl_id on DMC5 -> garbage index
- REField::get_init_data_index() same pattern
- REField::get_offset_from_fieldptr() same pattern
- REMethodDefinition::get_param_index() read tdb69 params bitfield
  instead of tdb67 params -> garbage byte pool offset -> null param
  name -> strlen crash

Also fixed REGlobals singleton scan: universal build compiles with
TDB_VER=84, so the compile-time TDB_VER >= 78 condition was always
true, meaning the SingletonBehavior type scan always ran. My earlier
runtime conversion to tdb_ver() >= 78 broke this for DMC5 (67 < 78
= skip). Restored by using unconditional block in universal mode.

Verified: DMC5 singletons visible, fields expandable, methods
expandable, no crashes in ObjectExplorer.

* v2: Fix direct this->field accesses bypassing TDEF_FIELD dispatch

get_size(), get_fqn_hash(), and get_flags() on RETypeDefinition read
fields directly via this->size / this->fqn_hash / this->type_flags,
using the compiled-in tdb84 struct layout. For DMC5 (TDB 67), these
fields are at different offsets in RETypeDefVersion67 — the direct
access read garbage, causing get_size() to return 0 and the
AutoGenerated Types section in ObjectExplorer to show nothing.

Changed all three to use TDEF_FIELD(this, field) which dispatches
to the correct version-specific struct at runtime.

Verified: DMC5 AutoGenerated Types now discovers managed objects.

* v2: Fix GenericListData bitfield + reflection null guards + TDEF_FIELD consistency

GenericListData bitfield layout differs between TDB versions:
  tdb67 (DMC5):  definition_typeid:17, num:14
  tdb69+:        definition_typeid:19, num:13

Universal build compiled the tdb84 layout, so on DMC5 every direct
->num and ->definition_typeid read got the bits offset by 2 \u2014
typically producing num==0 for non-generic types. This silently
routed many TDB67 types into generate_full_name_via_reflection(),
which then risked an access violation if the reflection pipeline
wasn't ready (System.RuntimeType not found).

Added sdk::generic_list_accessor::{get_num, get_definition_typeid,
get_type_at} that dispatch on tdb_ver() < 69. Wired into 8 call
sites in RETypeDefinition.cpp covering get_full_name(), is_generic_
type_definition(), get_generic_type_definition(), get_generic_
argument_types().

Also fixed:

1. get_crc_hash() fallback used this->type_crc reading the tdb84
   offset (0x1C). For DMC5, type_crc is at 0x0C. Changed to
   TDEF_FIELD(this, type_crc) for proper dispatch when get_type()
   returns null.

2. get_managed_vt() abstract-type-flag check used this->type_flags.
   Currently safe because the surrounding tdb_ver() >= 81 guard means
   the layout always matches tdb84, but bypassed TDEF_FIELD discipline.
   Changed to TDEF_FIELD(this, type_flags) for consistency.

3. generate_full_name_via_reflection() lambda dereferenced
   system_runtime_type and get_full_name_method without null checks,
   despite either potentially being null on TDB67 where System.
   RuntimeType doesn't hash to 0x99ff88e6. Added explicit null guards
   that fall through silently \u2014 the string-built full_name from
   the surrounding code already populated full_name.

4. get_underlying_type() called get_name_method->call(...) without
   first verifying get_name_method != nullptr. Added the guard with
   the same g_underlying_types[this] = nullptr cache write that the
   sibling guard at line 432 uses.

Audit also confirmed (no fixes needed):
- All REMethodDefinition / REField / REProperty / REParameterDef
  bare this->field accesses live inside TDB 71+ paths where the
  layout matches the compiled tdb84.
- All needs_18bit() checks are now guarded by needs_pre_impl() first.
- REClassInfo / REObjectInfo access sites are either dispatched via
  classinfo_accessor or are compile-time dead code in universal
  builds.

Verified: DMC5 ObjectExplorer \u2014 singletons visible, fields and
methods expandable without crash, AutoGenerated Types discovers
inner managed objects.

* v2: Fix GameObjectsDisplay silently disabled on D3D11 games

on_frame() gated on !m_d3d12.initialized even though the mod has a
working D3D11/legacy-mode ImGui fallback path (line 380-381:
world_to_screen + draw_list->AddText). initialize_d3d_resources()
only populates m_d3d12.initialized for the D3D12 branch; the D3D11
branch has just '// TODO'. So for any game running on D3D11 (DMC5
and others), the gate returned early before reaching the ImGui
fallback \u2014 the mod was effectively disabled.

Same behavior on upstream master; DMC5 was never getting object
labels even with the mod enabled.

Split the gate: m_d3d12.initialized is only required when actually
taking the D3D12 3D-text path. For legacy mode or D3D11, proceed
to the ImGui fallback.

Verified: DMC5 (D3D11) now draws game object labels when the mod
is enabled.

* v2: Fix Assembly list stride mismatch for TDB < 81 games in ObjectExplorer

ObjectExplorer::on_draw_dev_ui() iterated modules with
    auto& module = tdb->get_modules_ptr()[i];
which uses sizeof(sdk::REModule) as the array stride. In the universal
build, sdk::REModule inherits from tdb81::REModule (0x40 bytes) because
TDB_VER=84 selects the >= 81 branch in the struct alias chain.

For every TDB < 81 game (DMC5/RE2/RE3/RE7/RE8/MHRISE/RE4/SF6/DD2), the
real REModule struct is 0x58 bytes (has methods/instantiations/
member_references arrays). The 0x18-byte delta meant modules after
index 0 were read at wrong addresses, producing garbage assembly
names, module names, locations, types, methods, and member references.

MHWILDS, MHSTORIES3, RE9, PRAGMATA worked because their runtime stride
matches the compiled tdb81::REModule.

The stride-aware accessor RETypeDB::get_module_at(i) already exists
and dispatches on tdb_ver() >= 81. Changed the call site to use it.

Knock-on effect: the .NET assemblies generation feature depends on
iterating the assembly list, so it was broken on all TDB < 81 games
but worked on MHWILDS.

* v2: Fix plugin ABI TDB count fields bypassing dispatch

Plugin ABI lambdas in PluginLoader.cpp accessed RETypeDB count fields
directly via ->numTypes / ->numMethods etc. In universal builds these
hit the compiled tdb84 layout (numTypes at offset 0x08), but TDB <74
games have a different header with 'initialized' at offset 0x08 and
numTypes at 0x0C.

Diagnostic on RE2 (TDB 70):
  TDB at 0x7ff754e2e9d0
  u32[0..3] = 00424454 00000046 00000000 00013677
              ^magic   ^ver=70  ^init=0  ^numTypes=79479
  tdb84 read: numTypes=0   <- WRONG (read 'initialized')
  tdb70 cast read: numTypes=79479  <- correct
  get_num_types() = 79479  <- correct (uses TDB_DISPATCH)

The .NET AssemblyGenerator's FillValidEntries iterated context.Types
which uses TypeDefinitionIterator::MoveNext, which compares against
context.GetNumTypes(), which is the broken plugin ABI lambda. So
GetNumTypes() returned 0, the iterator never advanced, validTypes
stayed empty, and every MakeFromTypeEntry returned an empty
CompilationUnit -> 2KB DLLs with 0 types.

MHWILDS (TDB 81+) worked because its layout matches tdb84.

Fixed: 8 lambdas now use the SDK getter functions instead of direct
field access:
  numTypes -> get_num_types()
  numMethods -> get_num_methods()
  numFields -> get_num_fields()
  numProperties -> get_num_properties()
  numStringPool -> get_string_pool_size()
  numBytePool -> get_byte_pool_size()
  stringPool -> get_stringPool_ptr()
  bytePool -> get_bytePool_ptr()

Verified on RE2: assemblies now generate correctly with real types:
  _mscorlib: 390 types
  _System: 18 types
  _System.Core: 10 types
  viacore: 6356 types
  application: 13432 types

* Fix FaultyFileDetector crash

* v2: Collapse workflows to single monolithic REFramework target

build-pr.yml and dev-release.yml previously iterated a matrix of 17
per-game targets (RE2, RE2_TDB66, RE3, RE3_TDB67, RE4, RE7, RE7_TDB49,
RE8, RE9, DMC5, MHRISE, SF6, DD2, MHWILDS, PRAGMATA, MHSTORIES3,
STARFORCE). Those targets no longer exist in the universal build —
there is a single 'REFramework' target producing one 'dinput8.dll'
that detects the game at runtime.

- build-pr: drop matrix, build 'REFramework' once, upload the single DLL
- dev-release: drop matrix, build 'REFramework' once, pack single
  REFramework.zip containing dinput8.dll + openvr_api.dll + openxr_loader.dll
  + scripts + revision marker
- csharp-release: drop single-entry matrix (was [csharp-api]); name
  zip/artifact directly as csharp-api.zip / csharp-api
- nightly-push: unchanged — it globs artifacts/**/*.zip and will pick
  up both REFramework.zip and csharp-api.zip
- nightly-body.md: replace legacy 'TDB in the name' guidance with
  accurate description of REFramework.zip as monolithic + csharp-api.zip
  as optional C# plugin framework

Nightly CI now produces 2 artifacts (REFramework.zip, csharp-api.zip)
instead of 18 (17 per-game builds + csharp-api).

* v2: Address adversarial review R2 findings (R2-1, R2-2, R2-4, R2-6)

R2-1 (HIGH, latent): TargetState::m_desc offset wrong on 11/14 games

RenderResource::get_runtime_size() returns 0x10 / 0x18 / 0x20 depending
on game/TDB version, but the compiled member `TargetState::m_desc` is
fixed at sizeof(compiled RenderResource) = 0x20 past 'this'. All
accessors (get_rtv_count, get_rtvs_ptr, get_rtv, set_rtv, get_desc,
clone, get_native_resource_d3d12) dereferenced through `&m_desc` and
read 8 or 16 bytes past the real Desc on games where runtime RR size
is smaller than the compiled layout.

Affected games: DMC5 (67), RE8 (69), RE2/RE3/RE7 (70), MHRise (71),
RE4/SF6 (71), DD2 (73), Starforce (78), MHWilds (81). Unaffected:
MHStories3 (82), Pragmata/RE9 (83).

No live callers in src/, shared/sdk/, or csharp-api/ today — only the
plugin ABI exposes these to external consumers. Latent today, crash or
garbage result if any plugin calls TargetState::get_native_resource_d3d12,
get_rtv_count, get_rtv, or clone on 11 of 14 games.

Fix: replace `&m_desc` in the universal branch with
`(uintptr_t)this + RenderResource::get_runtime_size()` via a new
get_desc_base() helper; rewrite get_desc() to reinterpret at that
runtime-correct address. Remove the 'pad = tdb <= 67 ? 8 : 0' kludge
that was compensating for the compile-time offset mismatch — unneeded
once we use get_runtime_size() directly. Verified offsets match the
non-universal static_asserts for all 14 games.

R2-2 (LOW, dev-UI only): ObjectExplorer method-address validator
bounds check used `num_methods * sizeof(void*)` (always 8) as the
upper bound, and `sizeof(sdk::REMethodDefinition)` (compiled tdb84
size = 0x0C) as the alignment modulus. Real stride varies: 0x0C on
tdb71+, 0x10 on tdb69-70, 0x20 on tdb67 (DMC5). Silently rejected
valid method addresses on every TDB < 84 game.

Fix: use tdb->get_method_stride() for both the bounds upper limit
and the alignment check.

R2-4 (LOW, doc rot): PLAN_V2_MONOLITHIC.md's §2 promised runtime
TDB-version auto-detection from the binary header; that path was not
implemented and legacy non-RT RE2/RE3/RE7 binaries are out of scope.
§7 listed 'SDK TDB Struct Dispatch' as an unsolved BLOCKER; the
decision (reinterpret + switch via TDB_DISPATCH) was made and shipped.
Rewrote §2, §'Legacy TDB Versions', and §'Current Status' to reflect
shipped reality. Game compatibility table updated with TDB versions
from GameIdentity.cpp (authoritative source).

R2-6 (LOW): Added a migration note to nightly-body.md listing the
legacy per-game zip names that are no longer produced, to give
downstream consumers a hint when they 404 on the old names.

Verified: RE2 and DMC5 boot without new errors or crashes. Assembly
generation unaffected (cache hits show consistent behavior).

* v2: Fix IL2CPP dump crashes on DMC5 and other TDB<69 games

Two crash sites in ObjectExplorer::generate_sdk, both hit by the DMC5
user flow (Tools → Object Explorer → Generate SDK):

1. Direct bitfield reads bypassing TDB_DISPATCH (primary crash):
   generate_sdk read tdef->declaring_typeid and tdef->parent_typeid
   directly through the compiled (tdb84/V71) struct layout:

     if (tdef->declaring_typeid != 0) {
         desc->owner = init_type(il2cpp_dump, tdb, tdef->declaring_typeid);
     }

   On DMC5 (TDB 67), the RETypeDefVersion67 struct packs those fields
   at different bit positions (V67: 17-bit fields in a different order
   from V71's 19-bit fields). Reading through the V71 layout yielded
   garbage typeids, which were then passed to init_type() →
   init_type_min() → tdb->get_type(garbage_index) → nullptr → dereference
   of null reference → crash in get_namespace() on null `this`.

   Fix: read both via TDEF_FIELD, which dispatches to the correct
   per-version struct layout. Also added a defensive null-check in
   init_type_min so a bad typeid returns a sentinel desc with
   t=nullptr, and guarded the fqn_hash lookup in init_type against
   that sentinel.

2. init_data_offset out-of-bounds read (secondary crash after #1):
   The field-loop read init_data_index as a bitfield via the compiled
   layout, looked it up in initData, and dereferenced the byte pool:

     init_data_offset = (*initData)[init_data_index];
     init_data = &bytePool[init_data_offset];

   On TDB<69 games the init_data_index bitfield doesn't match the
   compiled layout, so init_data_offset can land past the end of the
   byte pool. Passing (char*)init_data to nlohmann::json for a
   System.String field then calls strlen on an unmapped address.

   Fix: bounds-check init_data_offset against
   tdb->get_byte_pool_size() (and symmetrically for the string-pool
   negative-offset branch). Also corrected a pre-existing dead branch:
   the original code tested 'init_data_offset < 0' on a uint32_t,
   which is always false; interpret the high bit as a signed-negative
   marker to preserve the (probably-intended) string-pool path.

Verified: SDK dump runs to completion on both DMC5 (TDB 67, previous
crash point) and PRAGMATA (TDB 83, sanity check for no regression).

* Port ce9df1fe81e897c117d85ac9c4446a1a453b938f to universal branch

* v2: Fix R3 adversarial review findings (6 of 14)

Addresses 5 CRITICAL and 1 HIGH finding from the R3 structural audit
of direct struct field accesses bypassing TDB dispatch.

F5 (CRITICAL): REType::size read directly in Renderer.cpp

  RenderTargetView::get_texture_d3d12() and get_target_state_d3d12()
  read rtv_type->size at the compiled RE8 offset (0x2C). On TDB>=81
  games (MHWilds, MHStories3, RE9, Pragmata), size and typeCRC are
  swapped — 0x2C holds typeCRC (a 32-bit hash). The hash value was
  used as a struct size for pointer arithmetic to locate D3D12
  resources.

  Fix: 4 sites replaced with utility::re_type_accessor::get_size()
  which handles the swap.

F1 (CRITICAL): RenderTargetView::get_desc() at wrong offset

  Same class of bug as the TargetState fix from the R2 round.
  RenderTargetView inherits from RenderResource; compiled m_desc
  offset is this+0x20, but runtime RR size is 0x10/0x18/0x20.
  RTV::clone() at Renderer.cpp:1659,1669 passes &get_desc() to
  create_render_target_view — on 11 of 14 games, the Desc read is
  8 or 16 bytes past the real struct.

  Fix: added REFRAMEWORK_UNIVERSAL dispatch using
  RenderResource::get_runtime_size(), mirroring TargetState.

F2 (CRITICAL): REField::get_index() uses sizeof(sdk::REField) as stride

  Compiled REField is 8 bytes (tdb69-84 single uint64_t bitfield).
  DMC5's tdb67::REField is 0x18 (24 bytes — has name_offset, flags,
  init_data_index, offset as discrete fields). Division by 8 when
  stride is 24 returns 3x the real index.

  Fix: added get_field_stride() to RETypeDB (returns 0x18 for
  pre-impl, 8 otherwise). REField::get_index() uses it under
  REFRAMEWORK_UNIVERSAL.

F3 (CRITICAL): REProperty getter/setter bitfield on TDB67

  Compiled REProperty packs getter/setter as bitfields in a uint64_t.
  DMC5's tdb67::REProperty has them as plain uint32_t at offsets
  0x08/0x0C. ObjectExplorer's SDK dump read the bitfield layout —
  garbage method IDs on DMC5.

  Fix: dispatch on tdb_ver() < 69, cast to tdb67::REProperty.

F4 (CRITICAL): REParameterDef type_id/flags bitfield on TDB69

  Compiled layout: type_id 19 bits, flags 13 bits. tdb69: type_id
  18 bits, flags 14 bits. Bit 18 of the uint32_t is part of flags
  in tdb69 but part of type_id in V84. ObjectExplorer read wrong
  parameter types on RE2/RE3/RE7/RE8.

  Fix: replaced p.type_id/p.flags/p.name_offset/p.modifier with
  TPARAM_FIELD(&p, ...) which dispatches via needs_18bit().

F6 (HIGH): type_flags direct read in ObjectExplorer

  RETypeDefinition::type_flags is at offset 0x10 in V84/V69/V71
  but 0x20 in V67 (DMC5). ObjectExplorer read pad bytes as flags.

  Fix: replaced t.type_flags with TDEF_FIELD(tdef, type_flags).

F8/F9 (HIGH, reviewer): FALSE POSITIVES

  The reviewer cited RE7's ReClass_Internal_RE7.hpp which has
  REFieldList::deserializer at 0x30 and VariableDescriptor::function
  at 0x60. But that file is from the legacy TDB49 build (out of
  scope). Modern RE7 (TDB70) uses the same layout as RE2/RE3 TDB70:
  deserializer at 0x28 and function at 0x10, matching the compiled
  RE8 layout. Verified against ReClass_Internal_RE2_TDB70.hpp and
  ReClass_Internal_RE3_TDB70.hpp.

* v2: Fix REGameObject::transform offset on SF6/RE4 (0x20 vs 0x18)

SF6 and RE4 have REGameObject::transform at offset 0x20 (11 bytes of
padding before the pointer), while all other games have it at 0x18
(1 byte of padding). The compiled RE8 canonical layout has it at 0x18.

Direct access via game_object->transform read the padding bytes on
SF6/RE4, returning nullptr. This caused:
- Ultrawide UI fix (fix_ui_element) to early-return without applying
  FitSmallRatioAxis, leaving all UI stretched to the full ultrawide
  resolution instead of clamped to 16:9.
- Every re_component::find() call that walks from transform to find
  child components to fail silently.
- Camera transform lookups to return nullptr.

Per-game offsets from ReClass headers:
  RE8/RE2/RE3/RE7(70)/DMC5/DD2/MHRISE/MHWilds/MHStories3/RE9/Pragmata: 0x18
  SF6/RE4: 0x20
  RE7(49, legacy, out of scope): 0x28

Fix:
- Added REGameObject::get_transform() with runtime dispatch via
  GameIdentity (is_sf6 || is_re4 → offset 0x20, else 0x18).
- Declaration in ReClass_Internal_RE8.hpp (the universal canonical),
  implementation in REGameObject.cpp.
- Replaced all 99 direct ->transform accesses across 14 files with
  ->get_transform().

Verified: SF6 ultrawide UI fix now applies correctly (UI clamped to
16:9 at 32:9 resolution). RE2/DMC5 boot clean (no regression).

* v2: Add direct struct field access audit script

scripts/audit_direct_access.py scans the codebase for direct member
access (->field) on struct types whose layout varies per RE Engine game.
Flags violations that should use an accessor method instead.

Covers 11 guarded struct types:
  REGameObject, REComponent, REManagedObject, REFieldList,
  VariableDescriptor, FunctionDescriptor, REType,
  TargetState, RenderTargetView, Buffer

Field names are split into two tiers:
  - Unambiguous (ownerGameObject, transform, shouldDraw, super,
    classInfo, deserializer, functionPtr, etc.) — always flagged
  - Ambiguous (name, size, type, info, flags, etc.) — only flagged
    with --pedantic since they collide with non-guarded structs

Whitelists accessor implementation files (REGameObject.cpp,
RETypeLayouts.hpp, RETypeDefDispatch.hpp, etc.) and all
ReClass_Internal_*.hpp struct definitions.

Current state: 99 unambiguous violations across 14 files.
Top offenders: ownerGameObject(36), shouldDraw(11), super(9),
typeName(9), referenceCount(6), functionPtr(6).

Usage:
  python scripts/audit_direct_access.py              # default scan
  python scripts/audit_direct_access.py --summary    # counts only
  python scripts/audit_direct_access.py --pedantic   # include ambiguous fields
  python scripts/audit_direct_access.py --varies-only # skip FOOTGUN category
  python scripts/audit_direct_access.py --json       # machine-readable

Exit code 1 if violations found (CI integration ready).

* v2: Add accessors for REComponent/REGameObject, eliminate 57 direct field accesses

Added runtime-dispatching accessors to REGameObject and forwarding
accessors to REComponent, then replaced all direct field accesses
across 11 source files.

REGameObject accessors (dispatch via GameIdentity):
  get_shouldDraw() / set_shouldDraw()  — offset 0x13 on RE8/RE2/RE3/DMC5, 0x11 on SF6/RE4/RE9+
  get_shouldUpdate() / set_shouldUpdate() — offset 0x12 vs 0x10
  get_folder()      — transform_offset + sizeof(void*)
  get_transform()   — refactored to share offset helper with get_folder()

REComponent accessors (forwarding today, dispatch-ready):
  get_game_object()       — replaces ->ownerGameObject
  get_child_component()   — replaces ->childComponent
  get_prev_component()    — replaces ->prevComponent
  get_next_component()    — replaces ->nextComponent
  child_component_ref()   — for find_replaceable() pointer splice

Replacements: 57 direct field accesses converted to accessor calls.
Audit script (scripts/audit_direct_access.py) drops from 99 to 42
unambiguous violations. Remaining 42 are on REType, REFieldList,
VariableDescriptor, FunctionDescriptor — next round.

Files modified: 11 source files + 2 headers + 1 impl.

* v2: Add libclang-based type-aware direct access audit

scripts/audit_direct_access_clang.py uses libclang to parse C++ source
files with full type information and find MemberExpr nodes where the
base expression's type is a guarded struct. Unlike the regex-based
audit_direct_access.py, this eliminates false positives from generic
field names (name, size, type, info, etc.) by checking the actual
resolved type of the expression.

Requires: pip install libclang
Extracts include paths and defines from the MSVC vcxproj automatically.

Tested on PluginLoader.cpp: correctly identifies REManagedObject::
referenceCount, REType::name, REFieldList::deserializer, FunctionDescriptor::
functionPtr, VariableDescriptor::function — and correctly skips
`name` and `size` on non-guarded structs that the regex script
would flag as false positives.

Both scripts coexist:
  audit_direct_access.py       — fast regex, no dependencies, CI-ready
  audit_direct_access_clang.py — precise, needs libclang, developer tool

* v2: Fix remaining direct field access violations + annotate stable sites

Addresses violations found by the libclang audit script:

Fixed (7 violations eliminated):
- Graphics.cpp: 4x REGameObject::name — replaced direct ->name
  (wrong offset + wrong type on SF6/RE4) with utility::re_game_object::get_name()
  which uses the reflected get_Name method.
- FirstPerson.cpp: 2x REGameObject::name — same fix. Also found
  an additional ->name site at L741 that the original audit missed.
- ObjectExplorer.cpp: REGameObject::transform and ::folder offsetof()
  calls replaced with runtime-computed offsets via GameIdentity
  (0x20 for SF6/RE4, 0x18 for others).

Annotated (48 remaining — all stable across supported games):
- PluginLoader.cpp (5): REManagedObject::referenceCount (0x08),
  REType::name (0x20), REFieldList::deserializer (0x28),
  FunctionDescriptor::functionPtr (0x18), VariableDescriptor::function (0x10).
  All documented with offset and stability status.
- ObjectExplorer.cpp (39): REType::name, REFieldList::variables/methods/num/
  deserializer, FunctionDescriptor::name, VariableDescriptor::* fields.
  Block comments added at 7 sections noting offsets are stable across
  TDB 67-84 and tracked by audit_direct_access_clang.py for future games.
- REVariableDescriptor.hpp (4): utility function accessing flags.

libclang audit: 55 → 48 violations. All 48 remaining are annotated
stable-offset accesses in dev-UI or plugin ABI code with no supported
game where the offset differs.

* v2: Add accessors for all remaining guarded structs, zero audit violations

Added 17 forwarding accessor methods across 5 struct types:

  REManagedObject:     get_ref_count()
  REFieldList:         get_next(), get_methods(), get_num(), get_maxItems(),
                       get_variables(), get_deserializer()
  FunctionDescriptor:  get_name(), get_functionPtr()
  VariableDescriptor:  get_name(), get_function(), get_flags(), get_typeFqn(),
                       get_typeName(), get_variableType(), get_staticVariableData()
  REType:              get_type_name()

Replaced all 49 remaining direct field accesses across PluginLoader.cpp
and ObjectExplorer.cpp with the new accessors.

ObjectExplorer REComponent offsetof() calls replaced with constexpr
numeric offsets (0x10/0x18/0x20/0x28) guarded by static_assert against
the compiled layout. This satisfies the audit while keeping the
compile-time verification.

REVariableDescriptor.hpp: fixed the get_flags() utility to use
offsetof() instead of &v->get_flags() (can't take address of rvalue).

libclang audit result: 0 violations across all 5 scanned files.
Previous: 55 → 48 (annotated) → 0 (all accessor-ized).

Scripts updated:
  - REVariableDescriptor.hpp added to both audit whitelists
  - static_assert lines filtered in libclang script (they reference
    field names inside offsetof but are compile-time guards, not
    runtime accesses)

* v2: Complete accessor migration — zero violations across entire tree

Extended accessor coverage to every remaining direct field access found
by the libclang audit:

REManagedObject: added set_ref_count(), ref_count_ptr() for write and
atomic-increment patterns. Replaced all ->referenceCount reads/writes
in REManagedObject.cpp (including _InterlockedIncrement), Sdk.cpp (3),
and RETypeDefinition.cpp (1 write).

REType::name: replaced ->name with ->get_type_name() in RETypes.cpp (5),
REGlobals.cpp (6), REManagedObject.cpp (14 null checks + IsBadReadPtr),
Renderer.cpp (4).

REFieldList::deserializer: replaced in REManagedObject.cpp (2 — these
were missed because the file was over-whitelisted).

VariableDescriptor::function, FunctionDescriptor::functionPtr: replaced
in REManagedObject.hpp (2 — accessor implementation file that also
consumed the fields it didn't implement accessors for).

Renderer.cpp camera_gameobject->name: replaced with
utility::re_game_object::get_name() (REGameObject::name varies on SF6/RE4).

Narrowed the libclang audit whitelist — removed REManagedObject.cpp,
REManagedObject.hpp, Renderer.cpp, REComponent.hpp, RETypeDB.cpp/hpp,
RETypeDefinition.cpp, REType.cpp from the blanket whitelist. Only files
that ARE accessor implementations (REGameObject.cpp, RETypeLayouts.hpp,
RETypeDefDispatch.hpp, ViaDispatch.hpp, REVariableDescriptor.hpp) remain
whitelisted.

Reverted incorrect VMContext::referenceCount changes (REThreadContext has
its own referenceCount at 0x78, unrelated to REManagedObject's at 0x08 —
different struct, same field name).

libclang audit: 0 violations across 18 files scanned.

* v2: Move hardcoded struct offsets to reusable static methods

ObjectExplorer had inline hardcoded offsets (0x10, 0x18, 0x20, 0x28)
for REComponent fields and duplicated GameIdentity dispatch logic for
REGameObject::transform/folder. If a game shifted these layouts,
ObjectExplorer would need manual updates — same problem the accessor
migration was supposed to solve.

Moved to static methods on the struct types themselves:

  REComponent::offset_of_game_object()     // 0x10
  REComponent::offset_of_child_component() // 0x18
  REComponent::offset_of_prev_component()  // 0x20
  REComponent::offset_of_next_component()  // 0x28

  REGameObject::offset_of_transform()  // dispatches: 0x18 or 0x20
  REGameObject::offset_of_folder()     // transform + sizeof(void*)

REComponent offsets are constexpr (stable today). REGameObject offsets
dispatch at runtime via go_transform_offset() — same logic as
get_transform()/get_folder(), now exposed publicly instead of
duplicated by consumers.

ObjectExplorer now calls these methods instead of hardcoding numbers.

* v2: Make guarded struct fields private — compiler-enforced accessor use

Fields on 5 struct types are now private. Direct ->field access from
outside the struct is a compile error — the compiler enforces accessor
use instead of relying on audit scripts and code review.

Private fields:

  REManagedObject:     referenceCount, N000071AE, pad_000E
  REComponent:         ownerGameObject, childComponent, prevComponent, nextComponent
  REGameObject:        pad_0010, shouldUpdate, shouldDraw, shouldUpdateSelf,
                       shouldDrawSelf, shouldSelect, pad_0017, transform,
                       folder, name, N00000DDA, timescale, pad_0038
  REFieldList:         unknown, pad_0004, next, methods, num, maxItems,
                       variables, deserializer, N00000730
  FunctionDescriptor:  name, params, pad_0010, numParams, functionPtr,
                       returnTypeFlag, typeIndex, returnTypeName, pad_0030
  VariableDescriptor:  name, nameHash, flags1, N00008140, function, flags,
                       typeFqn, typeName, getter, variableType/destructor,
                       staticVariableData, setter, attributes, pad_0040

NOT privatized (deferred):
  REType — the re_type_accessor dispatch layer uses free functions in
  a namespace that read raw fields (size, typeCRC, super, childType,
  chainType, fields, classInfo). Converting these to member functions
  is a separate refactor. REType fields remain public for now.

Additional accessors added to support the privatization:
  REGameObject::get_name_field() — returns the raw name field at the
    runtime-correct offset (0x28 or 0x30), used by the get_name()
    fallback path when the reflected method isn't available.
  FunctionDescriptor::get_numParams(), get_params(), get_typeIndex(),
    get_returnTypeName() — used by ObjectExplorer SDK dump.
  VariableDescriptor::get_attributes(), offset_of_attributes(),
    offset_of_flags() — used by ObjectExplorer and REVariableDescriptor.
  REVariableList::get_num() — used by REType.cpp field iteration.

Build: clean. Audit: 0 violations on all privatized types.

* v2: Privatize REType fields — all 6 guarded structs now compiler-enforced

Converted utility::re_type_accessor free functions into REType member
methods. The dispatch logic (retype_has_shifted_pointers,
retype_has_field_reorder) is unchanged — just moved from namespace
free functions into inline member method implementations in
RETypeLayouts.hpp.

Member methods added to REType:
  get_size()      — dispatches size/typeCRC swap on TDB>=81
  get_typeCRC()   — same swap dispatch
  get_super()     — dispatches +8 pointer shift on MHWILDS/RE9
  get_childType() — same shift
  get_chainType() — same shift
  get_fields()    — same shift
  get_classInfo() — same shift
  get_flags()     — forwarding (stable offset)
  get_classIndex() — forwarding (stable offset)
  get_type_name() — forwarding (stable offset, already existed)

Backward-compatible aliases preserved in utility::re_type_accessor
namespace — existing code using `get_fields(t)` via
`using namespace utility::re_type_accessor` continues to compile.
New code should use `t->get_fields()` directly.

REType fields now private: N000003B4, classIndex, flags, pad_000E,
fastClassIndex, typeIndexProbably, pad_001C, name, pad_0028, size,
typeCRC, miscFlags, super, childType, chainType, fields, classInfo.

All 6 guarded struct types are now compiler-enforced:
  REType, REManagedObject, REComponent, REGameObject,
  REFieldList, FunctionDescriptor, VariableDescriptor.

Direct `->field` access on any of these produces:
  error C2248: cannot access private member

* v2: Extract guarded structs from ReClass header into proper type headers

Moved 7 struct types from ReClass_Internal_RE8.hpp into dedicated
headers under shared/sdk/types/:

  types/REObject.hpp         - REObject (base class, info pointer)
  types/REType.hpp           - REType (runtime type info, dispatching accessors)
  types/REManagedObject.hpp  - REManagedObject (ref counting)
  types/REComponent.hpp      - REComponent + RECamera
  types/REGameObject.hpp     - REGameObject (transform, name, visibility)
  types/REReflection.hpp     - REFieldList, FunctionHolder, FunctionDescriptor,
                               VariableDescriptor

These are no longer auto-generated ReClass output — they have private
fields, public accessors, dispatch logic, and documentation. They
deserve proper files.

Changes from the ReClass naming:
  - N000003B4 → vtable
  - N000071AE → _unk_000C
  - N00000DDA → _unk_0030
  - pad_XXXX → _pad_XXXX
  - referenceCount → m_ref_count
  - ownerGameObject → m_owner
  - childComponent → m_child
  - prevComponent → m_prev
  - nextComponent → m_next
  - shouldDraw → m_shouldDraw (etc.)
  - transform → m_transform
  - folder → m_folder
  - name → m_name (REGameObject)
  - All REFieldList/FunctionDescriptor/VariableDescriptor fields prefixed m_

ReClass_Internal_RE8.hpp now includes these headers at the top and
contains only the non-guarded types (RECamera detail fields, REJoint,
RETransform, REString, etc.). The old struct definitions are replaced
with one-line comments pointing to the new location.

Build: clean. Zero errors, zero warnings from the extraction.

* v2: Decouple RETypeDefinition from compiled struct inheritance

Under REFRAMEWORK_UNIVERSAL, RETypeDefinition no longer inherits from
RETypeDefVersion84. It is now an opaque struct — direct field access
like tdef->declaring_typeid, tdef->type_flags, tdef->managed_vt does
not compile.

All field access must go through:
  TDEF_FIELD(ptr, field)          — dispatches across all TDB versions
  TDEF_FIELD_69(ptr, field)       — TDB>=69 fields (impl_index, etc.)
  TDEF_FIELD_PRE_IMPL(ptr, field) — TDB<69 fields (element_size, etc.)
  TDEF_FIELD_SET(ptr, field, val) — write dispatch (object_type)

Updated the TDEF_FIELD/TDEF_FIELD_69/TDEF_FIELD_SET macros: the default
(TDB>=74) fallback case now does reinterpret_cast<RETypeDefVersion84*>
instead of (ptr)->field, since ptr is no longer a V84-derived type.

Non-universal (per-game) builds retain the inheritance via #ifdef —
they still compile one game at a time with the correct version struct.

Fixes applied:
  - REManagedObject.cpp: td->managed_vt and td->type wrapped in
    TDEF_FIELD under REFRAMEWORK_UNIVERSAL
  - FirstPerson.cpp: 3x via_motion_def->type replaced with ->get_type()
  - ObjectExplorer.cpp: tdef->element_typeid_TBD replaced with
    explicit V84 reinterpret_cast (already gated by tdb_ver() >= 71)

This is the last struct type that had its fields publicly accessible
through inheritance. The compiler now rejects:
  - Direct field access on RETypeDefinition (opaque under universal)
  - Direct field access on REType (private fields, member accessors)
  - Direct field access on REGameObject/REComponent/REManagedObject
    (private fields, member accessors)
  - Direct field access on REFieldList/FunctionDescriptor/VariableDescriptor
    (private fields, member accessors)

* v2: Decouple RETypeDB, REMethodDefinition, REField, REModule from inheritance

Same refactor as RETypeDefinition: under REFRAMEWORK_UNIVERSAL, these
4 struct types no longer inherit from their compiled-version aliases.
Direct this->field access does not compile.

RETypeDB: dispatch macros (TDB_DISPATCH, TDB_DISPATCH_69,
TDB_DISPATCH_PTR, TDB_DISPATCH_PTR_69) updated to cast default cases
to tdb84::TDB* instead of relying on (this)->field through inheritance.
decltype(this->field) replaced with decltype on a nullptr cast.
Added get_version() for the stable version field at offset 0x04.

REMethodDefinition: TMETH_FIELD default case casts to
tdb84::REMethodDefinition*. Direct accesses to params_hi/params_lo
and encoded_offset replaced with explicit V84 casts in the universal
path. tmeth_declaring_typeid() default case updated.

REField: TFIELD_FIELD default case casts to tdb84::REField*. Direct
accesses to field_typeid, impl_id, init_data_hi replaced with
TFIELD_FIELD or explicit V84 casts. tfield_declaring_typeid() default
case updated.

REModule: inline version accessors (get_major/minor/build/revision)
dispatch via tdb74::REModule cast under universal. Module string
accessors (get_assembly_name, get_location, get_module_name,
get_types) cast to tdb74::REModule.

Non-universal builds retain inheritance via #ifdef.

Build: clean. All 8 layout-variant types in the TDB subsystem now
reject direct field access under REFRAMEWORK_UNIVERSAL.

* v2: Fix array stride breakage from REField/REProperty/REMethodDefinition decoupling

62c4aaec removed inheritance from REField, REProperty, REMethodDefinition
under REFRAMEWORK_UNIVERSAL, making them empty structs (sizeof == 1).

Three array-indexing sites in RETypeDB.cpp used the pattern:
    return &(*get_*_ptr())[index];
which computes base + index * sizeof(element_type). With sizeof == 1,
this returned base + index instead of base + index * real_stride.

Symptoms: get_field() and get_property() returned pointers 1 byte
apart instead of 8/16 bytes apart. Every field and property lookup
returned garbage. On MHWILDS: Application::functions offset scan
failed (menu never appeared). On RE2: crash in is_by_ref shortly
after initialization.

get_method() was accidentally safe — its universal path already used
byte arithmetic unconditionally because the stride != sizeof guard
(0x0C != 1) was always true. But that was fragile coincidence, not
intentional safety. Fixed it to use explicit byte arithmetic too.

Fix: all three accessors (get_method, get_field, get_property) now
use stride-based byte arithmetic under REFRAMEWORK_UNIVERSAL:
    stride = get_method_stride() / get_field_stride() / versioned sizeof
    return reinterpret_cast<T*>(base + index * stride)

The &(*ptr)[index] fallback is now #else-only (non-universal builds
still inherit the versioned struct, so sizeof is correct there).

* v2: Add stride-aware Impl/Param accessors, eliminate all direct TDB array indexing

Added 6 stride-aware element accessors to RETypeDB:
  get_type_impl_at(index)     — RETypeImpl (0x30 stride)
  get_field_impl_at(index)    — REFieldImpl (~12B stride)
  get_method_impl_at(index)   — REMethodImpl (~12B stride)
  get_property_impl_at(index) — REPropertyImpl (~8B stride)
  get_param_at(index)         — REParameterDef (~12B stride)
  get_init_data_at(index)     — int32_t (4B stride, convenience wrapper)

Each uses byte arithmetic with an explicit stride constant derived from
the versioned struct sizeof. When a new TDB version changes an Impl
struct's size, the fix is one line in the stride constant — not 50+
call sites.

Replaced 28 direct array indexing sites:
  RETypeDB.cpp:        14 sites (6 REFieldImpl, 4 REMethodImpl, 3 REParameterDef, 1 initData)
  RETypeDefinition.cpp: 6 sites (all RETypeImpl)
  ObjectExplorer.cpp:   8 sites (2 REMethodImpl, 2 REFieldImpl, 2 REPropertyImpl, 1 REParameterDef, 1 initData)

Non-universal #else/#elif paths left unchanged — they still inherit
the versioned structs so sizeof is correct there.

No TDB array in the codebase is directly indexed under
REFRAMEWORK_UNIVERSAL anymore. Every access goes through a
stride-aware method.

* v2: Proactive hardening — RETransform joints dispatch, C++/CLI refcount, private TDB array ptrs

Three fixes from the proactive structural audit:

1. RETransform::get_joints() — runtime offset dispatch

   REJointArray offset varies: 0xD0 (RE2/RE3 TDB70, DMC5), 0xD8 (RE8+).
   Compiled at 0xD8. Every transform.joints access was 8 bytes past the
   real joint array on RE2/RE3/DMC5. Affected: joint lookups, joint
   matrices, joint iteration in RETransform.hpp utilities.

   25 direct .joints / ->joints accesses replaced with .get_joints() /
   ->get_joints() across RETransform.hpp and RETransform.cpp.

2. C++/CLI ManagedObject hardcoded refcount offset

   ManagedObject.hpp had two hardcoded +0x8 offset reads for
   referenceCount. …
Remove V2_CONTEXT.md, PLAN_V2_MONOLITHIC.md, and
PR_ADVERSARIAL_REVIEW.md — session artifacts that don't belong in the
repo.

Move audit_direct_access_clang.py and strip_non_universal.py from
scripts/ to dev/ since they are build/CI tooling, not game scripts.
Update .github/workflows/build-pr.yml and dev-release.yml to the new
path. Remove stale __pycache__/.
# Conflicts:
#	.github/workflows/dev-release.yml
#	CMakeLists.txt
#	cmake.toml
#	shared/sdk/Renderer.cpp
#	src/mods/VR.cpp
#	src/mods/tools/ObjectExplorer.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants