Skip to content

Hook the right Class::GetDefaultFieldValue on non-Windows clang builds - #290

Open
Ketlark wants to merge 1 commit into
BepInEx:masterfrom
Ketlark:macos-field-default-value
Open

Ketlark wants to merge 1 commit into
BepInEx:masterfrom
Ketlark:macos-field-default-value

Conversation

@Ketlark

@Ketlark Ketlark commented Oct 5, 2026

Copy link
Copy Markdown

When the signatures don't match, Class_GetFieldDefaultValue_Hook finds Class::GetDefaultFieldValue by walking xrefs into Field::StaticGetValueInternal, then picks the last of three jump targets or the first one otherwise. That order comes from MSVC builds. In the clang-built macOS GameAssembly.dylib of Dofus 3 (Unity 6000.3.16f1, x86-64 slice), the targets are:

target function
+0x4a0c80 another internal call (not identified)
+0x4a4610 Class::GetDefaultFieldValue(field, &type)
+0x464fa0 BlobReader::GetConstantValueFromBlob(...)

So the hook is installed on BlobReader::GetConstantValueFromBlob. Its arguments don't match the detour's signature, so reading any constant through reflection (FieldInfo.GetValue on a literal field) gets a garbage result. In the game this shows up as random SIGSEGVs inside Class::IsAssignableFrom (fault address 0x8bc) once the options manager serializes its settings through reflection.

The fix applies on non-Windows only. Before the position-based choice, it decodes Field::StaticGetValueInternal up to its first ret and looks for the single direct call whose second System V argument (rsi) was just set with lea rsi, [rsp/rbp+x], which is the &type out parameter. If there is exactly one such call, its target is used. Otherwise the existing heuristic runs unchanged, and Windows behaviour is untouched because I can't verify the MSVC layout.

Related: #284 (a false signature match for the same hook on Windows).

Validation

  • Repro mod: calls the game's CachedDataService.SaveAllOptionsManager() every frame and Il2CppSystem.GC.Collect() every 30 frames.
  • Before: SIGSEGV on the first save, every run.
  • After: the hook resolves to +0x4a4610, and 3,000 saves ran with no crash. The game was then played normally for 10+ minutes (moving around, fights) without the crashes it had before.
  • Build: Il2CppInterop.Runtime builds with no new warnings.

Limits: Only tested on macOS x86-64 (Rosetta). Linux clang builds probably have the same layout, but I didn't check one.

On clang builds Field::StaticGetValueInternal calls Class::GetDefaultFieldValue(field, &type) followed by BlobReader::GetConstantValueFromBlob, so the 'last of three targets' heuristic hooks the blob reader. Find the direct call whose second argument is the address of a stack slot instead, and keep the old heuristic as the fallback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant