using System.Runtime.InteropServices; namespace XFEWarframe.Bridge; internal static unsafe partial class ExperimentalAbilityRuntime { // Exercises the production callbacks across the real unmanaged calling boundary. // The fixture models the audited layouts; this does not substitute for gameplay validation. internal static uint SelfTest() { if (Available) return 10; // Only run in the standalone installer self-test process. var memory = (nint)NativeMemory.AllocZeroed(0x9000); try { var reader = memory; var table = memory + 0x100; var a = memory + 0x400; var b = memory + 0x800; var c = memory + 0xC00; var suit = memory + 0x1000; *(nint*)reader = table; *(byte*)(reader + 10) = 1; *(nint*)(table + 0x138) = (nint)(delegate* unmanaged)&TestName; *(nint*)(table + 0x158) = (nint)(delegate* unmanaged)&TestType; *(nint*)(table + 0x48) = (nint)(delegate* unmanaged)&TestExists; *(nint*)(table + 0x58) = (nint)(delegate* unmanaged)&TestBegin; *(nint*)(table + 0x60) = (nint)(delegate* unmanaged)&TestEnd; *(nint*)(table + 0xD0) = (nint)(delegate* unmanaged)&TestKing; serialize = (nint)(delegate* unmanaged)&TestSerialize; typeSerialize = (nint)(delegate* unmanaged)&TestReadAbility; validate = (nint)(delegate* unmanaged)&TestValidate; copy = move = assign = moveAssign = (nint)(delegate* unmanaged)&TestCopy; destroy = suitDestroy = (nint)(delegate* unmanaged)&TestDestroy; buildEquippedItem = (nint)(delegate* unmanaged)&TestBuildEquippedItem; set = (nint)(delegate* unmanaged)&TestSet; apply = (nint)(delegate* unmanaged)&TestApply; initialize = (nint)(delegate* unmanaged)&TestInitialize; rank = (nint)(delegate* unmanaged)&TestRank; *(nint*)(a + OverrideOffset) = 101; *(byte*)(a + OverrideOffset + 8) = 0; for (var i = 0; i < 4; i++) *(nint*)(reader + 32 + i * 8) = 101 + i; ((delegate* unmanaged)&Serialize)(a, reader); if (!Configs.TryGetValue(a, out var plan) || plan.Abilities[3] != 104 || *(int*)(reader + 24) != 0) return 11; *(byte*)(reader + 96) = 1; ((delegate* unmanaged)&Serialize)(a, reader); if (!KingConfigs.Contains(a)) return 31; // Configs are also cloned through serializers, not only C++ copy constructors. *(byte*)(reader + 10) = 0; for (var i = 0; i < 4; i++) *(nint*)(reader + 32 + i * 8) = 0; ((delegate* unmanaged)&Serialize)(a, reader); if (*(nint*)(reader + 32) != 101 || *(nint*)(reader + 56) != 104 || *(int*)(reader + 24) != 0) return 20; *(nint*)(b + OverrideOffset) = 101; *(byte*)(b + OverrideOffset + 8) = 0; *(byte*)(reader + 10) = 1; ((delegate* unmanaged)&Serialize)(b, reader); if (!Configs.TryGetValue(b, out var roundTrip) || !roundTrip.Abilities.SequenceEqual(plan.Abilities)) return 21; ((delegate* unmanaged)&Copy)(b, a); ((delegate* unmanaged)&Move)(c, b); if (!Configs.ContainsKey(a) || !Configs.ContainsKey(c) || Configs.ContainsKey(b)) return 12; if (!KingConfigs.Contains(a) || !KingConfigs.Contains(c) || KingConfigs.Contains(b)) return 32; ((delegate* unmanaged)&Assign)(b, c); ((delegate* unmanaged)&MoveAssign)(c, c); if (!Configs.ContainsKey(b) || !Configs.ContainsKey(c)) return 13; var types = memory + 0x6000; var reference = memory + 0x6100; var instances = memory + 0x6200; *(nint*)reference = 1; for (var i = 0; i < 4; i++) { *(nint*)(types + i * 16) = reference; *(nint*)(types + i * 16 + 8) = 11 + i; } *(nint*)(suit + 0x3418) = types; *(int*)(suit + 0x3420) = 64; *(nint*)(suit + 0x3448) = instances; ((delegate* unmanaged)&Set)(suit, c + OverrideOffset); ((delegate* unmanaged)&Initialize)(suit); // Rank processing must observe all four replacements, not the defaults. if (*(int*)(suit + 0x4000) != 410 || *(nint*)(suit + SuitOverrideOffset) != 101) return 14; for (var i = 0; i < 4; i++) if (*(nint*)(instances + i * 8) != 101 + i) return 15; // Switching to another config with the SAME primary must clear the extra slots. Configs.Remove(b); ((delegate* unmanaged)&Set)(suit, b + OverrideOffset); if (*(nint*)instances != 101 || *(nint*)(instances + 8) != 12 || *(nint*)(instances + 24) != 14 || Suits.ContainsKey(suit)) return 16; ((delegate* unmanaged)&Set)(suit, c + OverrideOffset); if (*(nint*)(instances + 24) != 104) return 17; // Actual game loadouts merge MOD and appearance configs field by field. // A and C deliberately share the same primary, and appearance stays on C. var items = memory + 0x6500; var sourceItem = memory + 0x6600; var equipped = memory + 0x6800; var sourceConfigs = memory + 0x7000; var merged = memory + 0x7C00; *(nint*)items = sourceItem; *(uint*)(items + 8) = 0x130; *(ulong*)(sourceItem + 0x20) = *(ulong*)(equipped + 0x20) = 1234; *(uint*)(sourceItem + 0x28) = *(uint*)(equipped + 0x28) = 5678; *(nint*)(sourceItem + 0x68) = sourceConfigs; *(uint*)(sourceItem + 0x70) = 3 * 0x330; *(nint*)(equipped + 0x68) = merged; *(nuint*)(equipped + 0x138) = 2; *(nint*)(sourceConfigs + OverrideOffset) = *(nint*)(sourceConfigs + 2 * 0x330 + OverrideOffset) = 101; Configs[sourceConfigs] = plan with { Abilities = [101, 102, 0, 0] }; Configs[sourceConfigs + 2 * 0x330] = plan; for (nuint i = 0; i < 3; i++) { *(nuint*)(equipped + 0x130) = i; ((delegate* unmanaged)&BuildEquippedItem) (17, 18, 19, 0, 0, items, 20, equipped, 1, 1); var expected = i == 0 ? 2 : i == 1 ? 0 : 4; if ((Configs.TryGetValue(merged, out var selected) ? selected.Abilities.Count(x => x != 0) : 0) != expected || *(int*)(equipped + 0x150) != 1) return 22; } // Wrong ID or Conclave must never recover a plan based on the shared primary. *(uint*)(equipped + 0x28) = 999; if (SelectedPlan(items, equipped, 0) is not null) return 23; *(uint*)(equipped + 0x28) = 5678; if (SelectedPlan(items, equipped, 3) is not null) return 24; Configs.Remove(sourceConfigs); Configs.Remove(sourceConfigs + 2 * 0x330); Configs.Remove(merged); // Four banks, three choices each, across the production JSON callbacks. *(int*)(reader + 64) = 1; *(nint*)(reader + 80) = memory + 0x8500; for (var slot = 0; slot < 4; slot++) for (var choice = 0; choice < 3; choice++) *(nint*)(memory + 0x8500 + (slot * 3 + choice) * 8) = 101 + slot + choice * 10; ((delegate* unmanaged)&Serialize)(c, reader); if (!Configs.TryGetValue(c, out var bankPlan) || bankPlan.Banks?[3].Length != 3 || bankPlan.Banks[3][2] != 124) return 25; new Span((void*)(memory + 0x8500), 96).Clear(); *(byte*)(reader + 10) = 0; ((delegate* unmanaged)&Serialize)(c, reader); if (*(nint*)(memory + 0x8500 + 11 * 8) != 124 || *(int*)(reader + 24) != 0) return 26; *(byte*)(reader + 10) = 1; ((delegate* unmanaged)&Serialize)(b, reader); ((delegate* unmanaged)&Assign)(a, b); if (!Configs.TryGetValue(a, out var copiedBanks) || copiedBanks.Banks?[0][1] != 111) return 27; // A missing middle choice must not silently renumber the later skill. *(nint*)(memory + 0x8500 + 8) = 0; ((delegate* unmanaged)&Serialize)(c, reader); if (Configs.ContainsKey(c) || *(int*)(reader + 24) != 0) return 28; *(int*)(reader + 64) = 0; *(byte*)(reader + 96) = 0; Configs.Remove(b); // Reusing a config address without the extension must erase its cached plan. *(nint*)(reader + 32) = 0; ((delegate* unmanaged)&Serialize)(c, reader); if (Configs.ContainsKey(c) || *(int*)(reader + 24) != 0) return 18; ((delegate* unmanaged)&Destroy)(a); ((delegate* unmanaged)&SuitDestroy)(suit); if (Configs.Count != 0 || Suits.Count != 0) return 19; return 0; } finally { Configs.Clear(); Suits.Clear(); BankStates.Clear(); KingConfigs.Clear(); KingSuits.Clear(); NativeMemory.Free((void*)memory); serialize = copy = move = assign = moveAssign = destroy = buildEquippedItem = set = apply = initialize = rank = typeSerialize = validate = suitDestroy = 0; } } [UnmanagedCallersOnly] private static void TestName(nint reader, nint name) => *(nint*)(reader + 16) = name; [UnmanagedCallersOnly] private static void TestType(nint reader, byte type) { } [UnmanagedCallersOnly] private static void TestKing(nint reader, byte* value) { if (*(byte*)(reader + 10) == 1) *value = *(byte*)(reader + 96); else *(byte*)(reader + 96) = *value; } [UnmanagedCallersOnly] private static byte TestExists(nint reader) => (byte)((*(nint*)(reader + 16) == Names["XFEAbilityOverrides"] && *(nint*)(reader + 32) != 0) || (*(int*)(reader + 64) != 0 && (*(nint*)(reader + 16) == Names["XFEAbilityBanks"] || *(int*)(reader + 72) != 0)) ? 1 : 0); [UnmanagedCallersOnly] private static void TestBegin(nint reader) { ++*(int*)(reader + 24); if (*(nint*)(reader + 16) == Names["XFEAbilityBanks"]) *(int*)(reader + 72) = 1; else if (*(int*)(reader + 72) != 0) *(int*)(reader + 68) = **(byte**)(*(nint*)(reader + 16) + 8) - (byte)'0'; } [UnmanagedCallersOnly] private static void TestEnd(nint reader) { if (--*(int*)(reader + 24) == 0) *(int*)(reader + 72) = 0; } [UnmanagedCallersOnly] private static void TestSerialize(nint config, nint reader) { } [UnmanagedCallersOnly] private static void TestReadAbility(nint* value, nint reader, nint root) { var name = *(byte**)(*(nint*)(reader + 16) + 8); var slot = *name - (byte)'0'; if (slot is not (>= 0 and < 4)) { *value = 0; return; } var address = *(int*)(reader + 72) == 0 ? reader + 32 + slot * 8 : *(nint*)(reader + 80) + (*(int*)(reader + 68) * 3 + slot) * 8; if (*(byte*)(reader + 10) == 0) *(nint*)address = *value; else *value = *(nint*)address; } [UnmanagedCallersOnly] private static nint TestValidate(nint* value) => (nint)value; [UnmanagedCallersOnly] private static nint TestCopy(nint destination, nint source) { *(nint*)(destination + OverrideOffset) = *(nint*)(source + OverrideOffset); *(byte*)(destination + OverrideOffset + 8) = *(byte*)(source + OverrideOffset + 8); return destination; } [UnmanagedCallersOnly] private static void TestDestroy(nint value) { } [UnmanagedCallersOnly] private static void TestBuildEquippedItem(nint profile, nint loadout, nint inventory, byte mode, byte slot, nint items, nint preset, nint equipped, byte force, byte option) { // Intentionally bypass the copy callback, as the game's flattening code does. var source = *(nint*)(*(nint*)items + 0x68) + (nint)(*(nuint*)(equipped + 0x130) * 0x330); var destination = *(nint*)(equipped + 0x68); *(nint*)(destination + OverrideOffset) = *(nint*)(source + OverrideOffset); *(byte*)(destination + OverrideOffset + 8) = *(byte*)(source + OverrideOffset + 8); *(uint*)(equipped + 0x70) = 0x330; *(int*)(equipped + 0x150) = profile == 17 && loadout == 18 && inventory == 19 && mode == 0 && slot == 0 && preset == 20 && force == 1 && option == 1 ? 1 : 0; } [UnmanagedCallersOnly] private static void TestSet(nint suit, nint value) { *(nint*)(suit + SuitOverrideOffset) = *(nint*)value; *(byte*)(suit + SuitOverrideOffset + 8) = *(byte*)(value + 8); if (*(int*)(suit + 0x3450) != 0) ((delegate* unmanaged)&TestApply)(suit); } [UnmanagedCallersOnly] private static void TestApply(nint suit) { var slot = *(byte*)(suit + SuitOverrideOffset + 8); if (slot < 4) *(nint*)(*(nint*)(suit + 0x3448) + slot * 8) = *(nint*)(suit + SuitOverrideOffset); } [UnmanagedCallersOnly] private static void TestInitialize(nint suit) { if (*(int*)(suit + 0x3450) == 0) { for (var i = 0; i < 4; i++) *(nint*)(*(nint*)(suit + 0x3448) + i * 8) = 11 + i; *(int*)(suit + 0x3450) = 32; ((delegate* unmanaged)&TestApply)(suit); } ((delegate* unmanaged)&BeforeRanks)(suit); } [UnmanagedCallersOnly] private static int TestRank(nint suit) { var total = 0; for (var i = 0; i < 4; i++) total += (int)*(nint*)(*(nint*)(suit + 0x3448) + i * 8); *(int*)(suit + 0x4000) = total; return 30; } }