Skip to content

Commit a306f8c

Browse files
committed
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.
1 parent f171d00 commit a306f8c

2 files changed

Lines changed: 65 additions & 12 deletions

File tree

shared/sdk/RETypeDB.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
#include <cstdint>
55
#include <type_traits>
66

7+
#ifdef REFRAMEWORK_UNIVERSAL
8+
#include "GameIdentity.hpp"
9+
#endif
10+
711
// Forward decls
812
class RETypeDB;
913
class REClassInfo;
@@ -1846,6 +1850,35 @@ struct GenericListData : public sdk::tdb66::GenericListData {};
18461850
#else
18471851
static_assert(false, "TDB_VER is not defined");
18481852
#endif
1853+
1854+
// GenericListData bitfield dispatch.
1855+
// tdb67: definition_typeid:17, num:14 (1 bit padding)
1856+
// tdb69+: definition_typeid:19, num:13
1857+
// Universal build compiles the tdb84 layout, so direct ->num / ->definition_typeid
1858+
// reads are wrong on DMC5 (tdb67). These helpers dispatch at runtime.
1859+
#ifdef REFRAMEWORK_UNIVERSAL
1860+
namespace generic_list_accessor {
1861+
inline uint32_t get_num(const GenericListData* gd) {
1862+
if (sdk::GameIdentity::get().tdb_ver() < 69) {
1863+
return reinterpret_cast<const tdb67::GenericListData*>(gd)->num;
1864+
}
1865+
return gd->num;
1866+
}
1867+
inline uint32_t get_definition_typeid(const GenericListData* gd) {
1868+
if (sdk::GameIdentity::get().tdb_ver() < 69) {
1869+
return reinterpret_cast<const tdb67::GenericListData*>(gd)->definition_typeid;
1870+
}
1871+
return gd->definition_typeid;
1872+
}
1873+
inline uint32_t get_type_at(const GenericListData* gd, uint32_t index) {
1874+
if (sdk::GameIdentity::get().tdb_ver() < 69) {
1875+
return reinterpret_cast<const tdb67::GenericListData*>(gd)->types[index];
1876+
}
1877+
return gd->types[index];
1878+
}
1879+
}
1880+
#endif
1881+
18491882
} // namespace sdk
18501883

18511884
namespace sdk {

shared/sdk/RETypeDefinition.cpp

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ std::string RETypeDefinition::get_full_name() const {
258258
}
259259

260260
auto generate_full_name_via_reflection = [&]() {
261+
// system_runtime_type may be null on older TDB versions (e.g. DMC5/TDB67)
262+
// where System.RuntimeType doesn't hash to this FQN, or when reflection isn't
263+
// available yet. Fall through silently — full_name already contains the
264+
// string-constructed version above.
265+
if (system_runtime_type == nullptr) {
266+
return;
267+
}
268+
261269
struct FakeRuntimeType : public ::REManagedObject {
262270
const sdk::RETypeDefinition* t{nullptr};
263271
uint32_t unk{0};
@@ -268,8 +276,11 @@ std::string RETypeDefinition::get_full_name() const {
268276

269277
static auto get_full_name_method = system_runtime_type->get_method("get_FullName");
270278

271-
auto full_name_obj = seh_call_get_full_name(get_full_name_method, &fake_type);
279+
if (get_full_name_method == nullptr) {
280+
return;
281+
}
272282

283+
auto full_name_obj = seh_call_get_full_name(get_full_name_method, &fake_type);
273284
if (full_name_obj != nullptr) {
274285
full_name = utility::re_string::get_string(full_name_obj);
275286

@@ -281,16 +292,17 @@ std::string RETypeDefinition::get_full_name() const {
281292
#if TDB_VER > 49
282293
const auto generics = this->get_generic_data();
283294

284-
if (generics != nullptr && generics->num > 0) {
295+
if (generics != nullptr && sdk::generic_list_accessor::get_num(generics) > 0) {
285296
// The base type that isn't inflated.
286297
if (this->is_generic_type_definition()) {
287298
generate_full_name_via_reflection();
288299
} else {
289300
// We COULD use generate_full_name_via_reflection, but that's API breaking because it removes the spaces we add manually here.
290301
full_name += "<";
291302

292-
for (uint32_t f = 0; f < generics->num; ++f) {
293-
auto gtypeid = generics->types[f];
303+
const auto num_generics = sdk::generic_list_accessor::get_num(generics);
304+
for (uint32_t f = 0; f < num_generics; ++f) {
305+
auto gtypeid = sdk::generic_list_accessor::get_type_at(generics, f);
294306

295307
if (gtypeid > 0 && gtypeid < tdb->get_num_types()) {
296308
auto& generic_type = *tdb->get_type(gtypeid);
@@ -299,7 +311,7 @@ std::string RETypeDefinition::get_full_name() const {
299311
full_name += "";
300312
}
301313

302-
if (generics->num > 1 && f < generics->num - 1) {
314+
if (num_generics > 1 && f < num_generics - 1) {
303315
full_name += ",";
304316
}
305317
}
@@ -429,6 +441,13 @@ sdk::RETypeDefinition* RETypeDefinition::get_underlying_type() const {
429441
if (underlying_type != nullptr) {
430442
static const auto get_name_method = system_runtime_type_type->get_method("get_FullName");
431443

444+
if (get_name_method == nullptr) {
445+
std::unique_lock _{ g_underlying_mtx };
446+
g_underlying_types[this] = nullptr;
447+
return nullptr;
448+
}
449+
450+
432451
const auto full_name = get_name_method->call<::REManagedObject*>(sdk::get_thread_context(), underlying_type);
433452

434453
if (full_name != nullptr) {
@@ -468,7 +487,7 @@ sdk::RETypeDefinition* RETypeDefinition::get_generic_type_definition() const {
468487
const auto tdb = sdk::RETypeDB::get();
469488
auto generics = tdb->get_data<sdk::GenericListData>(TDEF_FIELD(this, generics));
470489

471-
const auto id = generics->definition_typeid;
490+
const auto id = sdk::generic_list_accessor::get_definition_typeid(generics);
472491

473492
if (id > 0) {
474493
return tdb->get_type(id);
@@ -625,11 +644,12 @@ std::vector<sdk::RETypeDefinition*> RETypeDefinition::get_generic_argument_types
625644
#if TDB_VER > 49
626645
const auto generics = get_generic_data();
627646

628-
if (generics != nullptr && generics->num > 0) {
647+
if (generics != nullptr && sdk::generic_list_accessor::get_num(generics) > 0) {
629648
const auto tdb = sdk::RETypeDB::get();
630649

631-
for (uint32_t f = 0; f < generics->num; ++f) {
632-
auto gtypeid = generics->types[f];
650+
const auto num_generics = sdk::generic_list_accessor::get_num(generics);
651+
for (uint32_t f = 0; f < num_generics; ++f) {
652+
auto gtypeid = sdk::generic_list_accessor::get_type_at(generics, f);
633653

634654
if (gtypeid > 0 && gtypeid < tdb->get_num_types()) {
635655
out.push_back(tdb->get_type(gtypeid)); // This COULD be null. we aren't going to skip it because it's important to know the index of the generic type
@@ -917,7 +937,7 @@ bool RETypeDefinition::is_primitive() const {
917937

918938
bool RETypeDefinition::is_generic_type_definition() const {
919939
#if TDB_VER > 49
920-
if (const auto gd = get_generic_data(); gd != nullptr && gd->definition_typeid == this->get_index()) {
940+
if (const auto gd = get_generic_data(); gd != nullptr && sdk::generic_list_accessor::get_definition_typeid(gd) == this->get_index()) {
921941
return true;
922942
}
923943
#endif
@@ -965,7 +985,7 @@ bool RETypeDefinition::has_attribute(::REManagedObject* attribute_runtime_type,
965985
uint32_t RETypeDefinition::get_crc_hash() const {
966986
#if TDB_VER > 49
967987
const auto t = get_type();
968-
return t != nullptr ? utility::re_type_accessor::get_typeCRC(t) : this->type_crc;
988+
return t != nullptr ? utility::re_type_accessor::get_typeCRC(t) : TDEF_FIELD(this, type_crc);
969989
#else
970990
const auto t = (regenny::via::typeinfo::TypeInfo*)get_type();
971991

@@ -1220,7 +1240,7 @@ ::REObjectInfo* RETypeDefinition::get_managed_vt() const {
12201240
REObjectInfo *target_managed_vt = TDEF_FIELD(this, managed_vt);
12211241
const int ABSTRACT_TYPE_FLAG = 128;
12221242
if (TDEF_FIELD(this, managed_vt) == nullptr) {
1223-
if (this->type_flags & ABSTRACT_TYPE_FLAG) {
1243+
if (TDEF_FIELD(this, type_flags) & ABSTRACT_TYPE_FLAG) {
12241244
auto parent_type_def = this->get_parent_type();
12251245
while (parent_type_def != nullptr) {
12261246
target_managed_vt = TDEF_FIELD(parent_type_def, managed_vt);

0 commit comments

Comments
 (0)