Upscaler v2 merge#1732
Open
praydog wants to merge 29 commits into
Open
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges v2 into the upscaler branch