Skip to content

Commit 09284e2

Browse files
praydogCopilot
andauthored
REFramework v2 (#1609)
* 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. …
1 parent 6216ec3 commit 09284e2

109 files changed

Lines changed: 9761 additions & 24903 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-pr.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@ env:
55
jobs:
66
build-pr:
77
runs-on: windows-latest
8-
strategy:
9-
matrix:
10-
target: [RE2, RE2_TDB66, RE3, RE3_TDB67, RE4, RE7, RE7_TDB49, RE8, RE9, DMC5, MHRISE, SF6, DD2, MHWILDS, PRAGMATA, MHSTORIES3, STARFORCE]
118
steps:
129
- name: Checkout
1310
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
1411
with:
1512
submodules: recursive
1613

14+
- name: Audit direct struct field access
15+
run: |
16+
pip install libclang
17+
python scripts/audit_direct_access_clang.py
18+
1719
- name: Configure CMake
1820
run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDEVELOPER_MODE=ON "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
1921

2022
- name: Build
21-
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target ${{matrix.target}}
23+
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target REFramework
2224

2325
- name: Upload artifacts
2426
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1
2527
with:
26-
name: ${{matrix.target}}
27-
path: ${{github.workspace}}/build/bin/${{matrix.target}}/dinput8.dll
28+
name: REFramework
29+
path: ${{github.workspace}}/build/bin/REFramework/dinput8.dll
2830
if-no-files-found: error
29-

.github/workflows/dev-release.yml

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ env:
55
jobs:
66
csharp-release:
77
runs-on: windows-latest
8-
strategy:
9-
matrix:
10-
target: [csharp-api]
118
steps:
129
- name: Set up Python
1310
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c
@@ -16,13 +13,13 @@ jobs:
1613

1714
- name: Checkout
1815
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
19-
16+
2017
- name: Configure CMake
2118
run: cmake -S ${{github.workspace}}/csharp-api -B ${{github.workspace}}/csharp-api/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
2219

2320
- name: Build
2421
run: cmake --build ${{github.workspace}}/csharp-api/build --config ${{env.BUILD_TYPE}} --target ALL_BUILD
25-
22+
2623
- name: Prepare release
2724
working-directory: ${{github.workspace}}/csharp-api
2825
run: |
@@ -32,20 +29,17 @@ jobs:
3229
- name: Compress release
3330
working-directory: ${{github.workspace}}/csharp-api
3431
run: |
35-
7z a ${{github.workspace}}/${{matrix.target}}.zip reframework
32+
7z a ${{github.workspace}}/csharp-api.zip reframework
3633
3734
- name: Upload artifacts
3835
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3
3936
with:
40-
name: ${{matrix.target}}
41-
path: ${{github.workspace}}/${{matrix.target}}.zip
37+
name: csharp-api
38+
path: ${{github.workspace}}/csharp-api.zip
4239
if-no-files-found: error
4340

4441
dev-release:
4542
runs-on: windows-latest
46-
strategy:
47-
matrix:
48-
target: [RE2, RE2_TDB66, RE3, RE3_TDB67, RE4, RE7, RE7_TDB49, RE8, RE9, DMC5, MHRISE, SF6, DD2, MHWILDS, PRAGMATA, MHSTORIES3, STARFORCE]
4943
steps:
5044
- name: Checkout
5145
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -54,31 +48,46 @@ jobs:
5448
fetch-depth: 0
5549
persist-credentials: false
5650

51+
- name: Audit direct struct field access
52+
run: |
53+
pip install libclang
54+
python scripts/audit_direct_access_clang.py
55+
5756
- name: Configure CMake
5857
run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDEVELOPER_MODE=ON "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
5958

6059
- name: Build
61-
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target ${{matrix.target}}
60+
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target REFramework
6261

63-
- name: Compress release
62+
- name: Compress REFramework
6463
run: |
6564
echo ${{github.sha}} > ${{github.workspace}}/reframework_revision.txt
65+
7z a ${{github.workspace}}/REFramework.zip ${{github.workspace}}/reframework_revision.txt
66+
7z a ${{github.workspace}}/REFramework.zip ${{github.workspace}}/build/bin/REFramework/dinput8.dll
67+
68+
- name: Compress VR prerequisites
69+
run: |
6670
echo none > ${{github.workspace}}/DELETE_OPENVR_API_DLL_IF_YOU_WANT_TO_USE_OPENXR
67-
7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/reframework_revision.txt
68-
7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/DELETE_OPENVR_API_DLL_IF_YOU_WANT_TO_USE_OPENXR
69-
7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/build/bin/${{matrix.target}}/dinput8.dll
70-
7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/dependencies/openvr/bin/win64/openvr_api.dll
71-
7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/build/_deps/openxr-build/src/loader/${{env.BUILD_TYPE}}/openxr_loader.dll
72-
7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/scripts
73-
7z rn ${{github.workspace}}/${{matrix.target}}.zip scripts reframework/autorun
71+
7z a ${{github.workspace}}/VR.zip ${{github.workspace}}/DELETE_OPENVR_API_DLL_IF_YOU_WANT_TO_USE_OPENXR
72+
7z a ${{github.workspace}}/VR.zip ${{github.workspace}}/dependencies/openvr/bin/win64/openvr_api.dll
73+
7z a ${{github.workspace}}/VR.zip ${{github.workspace}}/build/_deps/openxr-build/src/loader/${{env.BUILD_TYPE}}/openxr_loader.dll
74+
7z a ${{github.workspace}}/VR.zip ${{github.workspace}}/scripts
75+
7z rn ${{github.workspace}}/VR.zip scripts reframework/autorun
7476
75-
- name: Upload artifacts
77+
- name: Upload REFramework
7678
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1
7779
with:
78-
name: ${{matrix.target}}
79-
path: ${{github.workspace}}/${{matrix.target}}.zip
80+
name: REFramework
81+
path: ${{github.workspace}}/REFramework.zip
8082
if-no-files-found: error
81-
83+
84+
- name: Upload VR
85+
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1
86+
with:
87+
name: VR
88+
path: ${{github.workspace}}/VR.zip
89+
if-no-files-found: error
90+
8291
nightly-push:
8392
runs-on: windows-latest
8493
needs: [dev-release, csharp-release]
@@ -112,4 +121,3 @@ jobs:
112121
makeLatest: true
113122
bodyFile: ${{github.workspace}}/nightly-body.md
114123
allowUpdates: true
115-

0 commit comments

Comments
 (0)