using System.Runtime.InteropServices;
using XFEWarframe.Client;
namespace XFEWarframe.Bridge;
/// <summary>
/// Extends config serialization and follows its C++ copy/move/destruction lifecycle.
/// All engine calls occur synchronously on the engine's original calling thread.
/// Native structs and slot indices retain their original size and values.
/// </summary>
internal static unsafe partial class ExperimentalAbilityRuntime
{
private const int OverrideOffset = 0x38, SuitOverrideOffset = 0x36C0;
private sealed record Plan(nint Primary, byte Slot, nint[] Abilities)
{
public nint[][]? Banks { get; init; }
}
private static readonly object Gate = new();
private static readonly Dictionary<nint, Plan> Configs = [];
private static readonly Dictionary<nint, Plan> Suits = [];
private static readonly Dictionary<string, nint> Names = new[] { "XFEAbilityOverrides", "XFEAbilityBanks", "XFEInfestationKing", "0", "1", "2", "3" }.ToDictionary(x => x, Literal);
private static nint serialize, copy, move, assign, moveAssign, destroy, buildEquippedItem, initialize, set, suitDestroy, apply, typeSerialize, validate, typeBase, rank;
private static Action<string> log = _ => { };
private static bool trace;
private static int transferTraceCount, setTraceCount, initializeTraceCount, serializeTraceCount;
[ThreadStatic] private static bool applying;
[ThreadStatic] private static nint initializingSuit;
public static bool Available { get; private set; }
public static void Prepare(nint image, ClientConfiguration configuration, List<NativeHook> hooks, Action<string> writeLog)
{
log = writeLog;
trace = configuration.EnableGameTestControl;
if (!configuration.GameSha256.Equals(ExperimentalAbilityCompatibility.ExecutableSha256, StringComparison.OrdinalIgnoreCase))
{ log("Multiple abilities unavailable: executable is outside the experimental ABI profile."); return; }
foreach (var entry in ExperimentalAbilityCompatibility.Entries)
if (!new ReadOnlySpan<byte>((void*)(image + entry.Rva), entry.Bytes.Length / 2).SequenceEqual(Convert.FromHexString(entry.Bytes)))
{ log("Multiple abilities unavailable: ABI check failed for " + entry.Name); return; }
nint Address(string name) => image + ExperimentalAbilityCompatibility.Entries.Single(e => e.Name == name).Rva;
nint Hook(string name, nint callback)
{
var hook = new NativeHook(Address(name), callback); hooks.Add(hook); return hook.Trampoline;
}
typeBase = image + ExperimentalAbilityCompatibility.TypeBaseRva;
typeSerialize = Address("TypeSerialize"); validate = Address("AbilityValidate"); apply = Address("OverrideApply");
serialize = Hook("ConfigSerialize", (nint)(delegate* unmanaged<nint, nint, void>)&Serialize);
copy = Hook("ConfigCopy", (nint)(delegate* unmanaged<nint, nint, nint>)&Copy);
move = Hook("ConfigMove", (nint)(delegate* unmanaged<nint, nint, nint>)&Move);
assign = Hook("ConfigAssign", (nint)(delegate* unmanaged<nint, nint, nint>)&Assign);
moveAssign = Hook("ConfigMoveAssign", (nint)(delegate* unmanaged<nint, nint, nint>)&MoveAssign);
destroy = Hook("ConfigDestroy", (nint)(delegate* unmanaged<nint, void>)&Destroy);
buildEquippedItem = Hook("BuildEquippedItem", (nint)(delegate* unmanaged<nint, nint, nint, byte, byte, nint, nint, nint, byte, byte, void>)&BuildEquippedItem);
initialize = Hook("SuitInitialize", (nint)(delegate* unmanaged<nint, void>)&Initialize);
var rankHook = NativeHook.RedirectCall(Address("SuitBeforeRanks"), (nint)(delegate* unmanaged<nint, int>)&BeforeRanks);
hooks.Add(rankHook); rank = rankHook.Trampoline;
set = Hook("OverrideSet", (nint)(delegate* unmanaged<nint, nint, void>)&Set);
suitDestroy = Hook("SuitDestroy", (nint)(delegate* unmanaged<nint, void>)&SuitDestroy);
PrepareBanks(Address, Hook, hooks);
PrepareDependencies(image, Address, Hook);
_ = AbilityHudCatalog.Entries; // Parse the embedded resource catalog before installing hooks.
Available = true;
log("Multiple abilities: experimental ABI bindings prepared (2026.08.19.11.06).");
}
[UnmanagedCallersOnly] private static void Serialize(nint config, nint reader)
{
((delegate* unmanaged<nint, nint, void>)serialize)(config, reader);
var mode = *(byte*)(reader + 10); var binary = *(byte*)(reader + 11) != 0;
if (trace && *(nint*)(config + OverrideOffset) != 0 && Interlocked.Increment(ref serializeTraceCount) <= 80)
log($"Multiple abilities: serialize config=0x{config:X}, mode={mode}, binary={binary}.");
if (binary || mode is not (0 or 1)) return;
try
{
SerializeKing(config, reader, mode);
if (mode == 0)
{
Plan? saved; lock (Gate) Configs.TryGetValue(config, out saved);
if (saved is null || saved.Primary != *(nint*)(config + OverrideOffset) || saved.Slot != *(byte*)(config + OverrideOffset + 8)) return;
Name(reader, "XFEAbilityOverrides");
((delegate* unmanaged<nint, byte, void>)Virtual(reader, 0x158))(reader, 12);
((delegate* unmanaged<nint, void>)Virtual(reader, 0x58))(reader);
try
{
for (var slot = 0; slot < 4; slot++)
{
var ability = saved.Abilities[slot];
if (ability == 0) continue;
Name(reader, slot.ToString(System.Globalization.CultureInfo.InvariantCulture));
((delegate* unmanaged<nint*, nint, nint, void>)typeSerialize)(&ability, reader, typeBase);
}
}
finally { ((delegate* unmanaged<nint, void>)Virtual(reader, 0x60))(reader); }
if (saved.Banks is not null) WriteBanks(reader, saved.Banks);
if (trace) log("Multiple abilities: wrote extended JSON configuration.");
return;
}
lock (Gate) Configs.Remove(config);
Name(reader, "XFEAbilityOverrides");
((delegate* unmanaged<nint, byte, void>)Virtual(reader, 0x158))(reader, 12);
if (((delegate* unmanaged<nint, byte>)Virtual(reader, 0x48))(reader) == 0) return;
((delegate* unmanaged<nint, void>)Virtual(reader, 0x58))(reader);
var abilities = new nint[4];
try
{
for (var slot = 0; slot < 4; slot++)
{
Name(reader, slot.ToString(System.Globalization.CultureInfo.InvariantCulture));
nint ability = 0;
((delegate* unmanaged<nint*, nint, nint, void>)typeSerialize)(&ability, reader, typeBase);
if (ability != 0 && *(nint*)((delegate* unmanaged<nint*, nint>)validate)(&ability) == 0)
throw new InvalidDataException("An experimental override is not a player ability.");
abilities[slot] = ability;
}
}
finally { ((delegate* unmanaged<nint, void>)Virtual(reader, 0x60))(reader); }
var banks = ReadBanks(reader);
var primary = *(nint*)(config + OverrideOffset); var index = *(byte*)(config + OverrideOffset + 8);
if (index > 3 || primary == 0 || abilities[index] != primary ||
(banks is null && abilities.Count(a => a != 0) < 2)) return;
if (banks is not null && Enumerable.Range(0, 4).Any(i => banks[i].FirstOrDefault() != abilities[i]))
throw new InvalidDataException("Ability bank does not match its compatibility projection.");
lock (Gate)
{
if (Configs.Count >= 65536) throw new InvalidDataException("Experimental config cache limit reached.");
Configs[config] = new(primary, index, abilities) { Banks = banks };
}
if (trace) log($"Multiple abilities: read configuration 0x{config:X} with {abilities.Count(a => a != 0)} replacements; primary=0x{primary:X}, slot={index}.");
}
catch (Exception ex) { log("Multiple abilities: configuration ignored (" + ex.GetType().Name + ")."); }
}
private static nint Virtual(nint instance, int offset) => *(nint*)(*(nint*)instance + offset);
private static void Name(nint reader, string name)
{
((delegate* unmanaged<nint, nint, void>)Virtual(reader, 0x138))(reader, Names[name]);
}
// The native reader can retain the selected property's literal view. These five small
// allocations intentionally live for the entire injected module lifetime, like native literals.
private static nint Literal(string text)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(text + '\0');
var memory = Marshal.AllocHGlobal(16 + bytes.Length);
*(ulong*)memory = 0xC000000000000000;
*(nint*)(memory + 8) = memory + 16;
Marshal.Copy(bytes, 0, memory + 16, bytes.Length);
return memory;
}
private static nint Transfer(nint original, nint destination, nint source, bool moving)
{
Plan? plan; lock (Gate) Configs.TryGetValue(source, out plan);
bool king; lock (Gate) king = KingConfigs.Contains(source);
var result = ((delegate* unmanaged<nint, nint, nint>)original)(destination, source);
lock (Gate)
{
Configs.Remove(destination);
if (moving && source != destination) Configs.Remove(source);
KingConfigs.Remove(destination);
if (moving && source != destination) KingConfigs.Remove(source);
if (king) KingConfigs.Add(destination);
if (plan is not null && Configs.Count < 65536 && *(nint*)(destination + OverrideOffset) == plan.Primary && *(byte*)(destination + OverrideOffset + 8) == plan.Slot)
Configs[destination] = plan;
}
if (trace && plan is not null && Interlocked.Increment(ref transferTraceCount) <= 80)
log($"Multiple abilities: {(moving ? "move" : "copy")} 0x{source:X} -> 0x{destination:X}; primary matches={*(nint*)(destination + OverrideOffset) == plan.Primary}.");
return result;
}
[UnmanagedCallersOnly] private static nint Copy(nint d, nint s) => Transfer(copy, d, s, false);
[UnmanagedCallersOnly] private static nint Move(nint d, nint s) => Transfer(move, d, s, true);
[UnmanagedCallersOnly] private static nint Assign(nint d, nint s) => Transfer(assign, d, s, false);
[UnmanagedCallersOnly] private static nint MoveAssign(nint d, nint s) => Transfer(moveAssign, d, s, true);
[UnmanagedCallersOnly] private static void Destroy(nint config)
{ lock (Gate) { Configs.Remove(config); KingConfigs.Remove(config); } ((delegate* unmanaged<nint, void>)destroy)(config); }
[UnmanagedCallersOnly] private static void SuitDestroy(nint suit)
{
try { ReleaseBanks(suit); RestoreDonorAnimations(suit, true); }
catch (Exception ex) { log("Ability animation cleanup: " + ex.GetType().Name); }
lock (Gate) { Suits.Remove(suit); BankStates.Remove(suit); KingSuits.Remove(suit); }
((delegate* unmanaged<nint, void>)suitDestroy)(suit);
}
// The engine flattens the selected MOD config and the independently selected cosmetic
// config into a new config. That path copies AbilityOverride inline, bypassing every
// config copy constructor. Match the source item ID and MOD index before flattening;
// never infer a plan from the primary ability (A and C can share that ability).
[UnmanagedCallersOnly]
private static void BuildEquippedItem(nint profile, nint loadout, nint inventory, byte mode, byte slot,
nint items, nint preset, nint equipped, byte force, byte option)
{
var selected = SelectedPlan(items, equipped, mode);
bool king; lock (Gate) king = KingConfigs.Contains(SelectedConfig(items, equipped, mode));
((delegate* unmanaged<nint, nint, nint, byte, byte, nint, nint, nint, byte, byte, void>)buildEquippedItem)
(profile, loadout, inventory, mode, slot, items, preset, equipped, force, option);
var config = *(nint*)(equipped + 0x68);
if (config == 0 || *(uint*)(equipped + 0x70) < 0x330) return;
lock (Gate)
{
KingConfigs.Remove(config);
if (king) KingConfigs.Add(config);
Configs.Remove(config);
if (selected is not null && Configs.Count < 65536 &&
*(nint*)(config + OverrideOffset) == selected.Primary && *(byte*)(config + OverrideOffset + 8) == selected.Slot)
{
Configs[config] = selected;
if (trace) log($"Multiple abilities: merged MOD config {*(nuint*)(equipped + 0x130)} into equipped config 0x{config:X} ({selected.Abilities.Count(a => a != 0)} slots).");
}
}
}
private static Plan? SelectedPlan(nint items, nint equipped, byte mode)
{
var config = SelectedConfig(items, equipped, mode);
if (config == 0) return null;
lock (Gate)
return Configs.TryGetValue(config, out var plan) && plan.Primary == *(nint*)(config + OverrideOffset) &&
plan.Slot == *(byte*)(config + OverrideOffset + 8) ? plan : null;
}
private static nint SelectedConfig(nint items, nint equipped, byte mode)
{
// Conclave builds deliberately omit the native override too.
if (mode == 3 || items == 0 || equipped == 0) return 0;
var first = *(nint*)items; var bytes = *(uint*)(items + 8);
if (first == 0 || bytes > 0x130 * 65536 || bytes % 0x130 != 0) return 0;
var index = *(nuint*)(equipped + 0x130);
for (nuint offset = 0; offset < bytes; offset += 0x130)
{
var item = first + (nint)offset;
if (*(ulong*)(item + 0x20) != *(ulong*)(equipped + 0x20) ||
*(uint*)(item + 0x28) != *(uint*)(equipped + 0x28)) continue;
var configs = *(nint*)(item + 0x68); var configBytes = *(uint*)(item + 0x70);
if (configs == 0 || index >= configBytes / 0x330) return 0;
return configs + (nint)(index * 0x330);
}
return 0;
}
[UnmanagedCallersOnly] private static void Set(nint suit, nint incoming)
{
ReleaseBanks(suit);
RestoreDonorAnimations(suit, true);
Plan? next; Plan? previous;
lock (Gate)
{
KingSuits.Remove(suit);
if (KingConfigs.Contains(incoming - OverrideOffset)) KingSuits.Add(suit);
Configs.TryGetValue(incoming - OverrideOffset, out next);
if (next is not null && (next.Primary != *(nint*)incoming || next.Slot != *(byte*)(incoming + 8))) next = null;
Suits.TryGetValue(suit, out previous);
Suits.Remove(suit);
BankStates.Remove(suit);
if (next is not null) Suits[suit] = next;
}
if (trace && Interlocked.Increment(ref setTraceCount) <= 80)
{
string candidates;
lock (Gate) candidates = string.Join(",", Configs.Where(p => p.Value.Primary == *(nint*)incoming).Take(8).Select(p => $"0x{p.Key:X}:{p.Value.Abilities.Count(a => a != 0)}"));
log($"Multiple abilities: set suit=0x{suit:X}, config=0x{incoming - OverrideOffset:X}, plan={next is not null}, instances={*(int*)(suit + 0x3450)}, candidates=[{candidates}].");
}
if (!applying && previous is not null) RestoreDefaults(suit, previous);
((delegate* unmanaged<nint, nint, void>)set)(suit, incoming);
ApplyPlan(suit, next);
}
[UnmanagedCallersOnly] private static void Initialize(nint suit)
{
var previous = initializingSuit;
if (trace && Interlocked.Increment(ref initializeTraceCount) <= 40)
{
bool hasPlan; lock (Gate) hasPlan = Suits.ContainsKey(suit);
log($"Multiple abilities: initialize suit=0x{suit:X}, plan={hasPlan}, instances={*(int*)(suit + 0x3450)}.");
}
if (*(int*)(suit + 0x3450) == 0) initializingSuit = suit;
try { ((delegate* unmanaged<nint, void>)initialize)(suit); }
finally { initializingSuit = previous; }
}
[UnmanagedCallersOnly] private static int BeforeRanks(nint suit)
{
Plan? plan; lock (Gate) Suits.TryGetValue(suit, out plan);
// Default instances already exist here; ranks, augments and scripts are
// initialized next by the engine, using the final replaced instances.
if (initializingSuit == suit) { initializingSuit = 0; ApplyPlan(suit, plan); }
return ((delegate* unmanaged<nint, int>)rank)(suit);
}
private static void ApplyPlan(nint suit, Plan? plan)
{
if (applying || plan is null || *(int*)(suit + 0x3450) == 0) return;
applying = true;
try
{
for (byte slot = 0; slot < 4; slot++)
if (plan.Abilities[slot] != 0 && slot != plan.Slot) ApplyOne(suit, plan.Abilities[slot], slot);
if (trace) log("Multiple abilities: applied " + plan.Abilities.Count(a => a != 0) + " slots to one suit configuration.");
}
finally { applying = false; }
}
private static void RestoreDefaults(nint suit, Plan previous)
{
if (*(int*)(suit + 0x3450) == 0) return;
var types = *(nint*)(suit + 0x3418); var bytes = *(uint*)(suit + 0x3420);
if (types == 0 || bytes < 64 || bytes > 1024) return;
for (byte slot = 0; slot < 4; slot++)
{
if (previous.Abilities[slot] == 0) continue;
var reference = *(nint*)(types + slot * 16);
if (reference != 0 && *(nint*)reference != 0) ApplyOne(suit, *(nint*)(types + slot * 16 + 8), slot);
}
}
private static void ApplyOne(nint suit, nint ability, byte slot)
{
var primary = *(nint*)(suit + SuitOverrideOffset); var index = *(byte*)(suit + SuitOverrideOffset + 8);
try
{
*(nint*)(suit + SuitOverrideOffset) = ability; *(byte*)(suit + SuitOverrideOffset + 8) = slot;
((delegate* unmanaged<nint, void>)apply)(suit);
}
finally { *(nint*)(suit + SuitOverrideOffset) = primary; *(byte*)(suit + SuitOverrideOffset + 8) = index; }
}
}
using System.Runtime.InteropServices;
using XFEWarframe.Client;
namespace XFEWarframe.Bridge;
/// <summary>
/// Extends config serialization and follows its C++ copy/move/destruction lifecycle.
/// All engine calls occur synchronously on the engine's original calling thread.
/// Native structs and slot indices retain their original size and values.
/// </summary>
internal static unsafe partial class ExperimentalAbilityRuntime
{
private const int OverrideOffset = 0x38, SuitOverrideOffset = 0x36C0;
private sealed record Plan(nint Primary, byte Slot, nint[] Abilities)
{
public nint[][]? Banks { get; init; }
}
private static readonly object Gate = new();
private static readonly Dictionary<nint, Plan> Configs = [];
private static readonly Dictionary<nint, Plan> Suits = [];
private static readonly Dictionary<string, nint> Names = new[] { "XFEAbilityOverrides", "XFEAbilityBanks", "XFEInfestationKing", "0", "1", "2", "3" }.ToDictionary(x => x, Literal);
private static nint serialize, copy, move, assign, moveAssign, destroy, buildEquippedItem, initialize, set, suitDestroy, apply, typeSerialize, validate, typeBase, rank;
private static Action<string> log = _ => { };
private static bool trace;
private static int transferTraceCount, setTraceCount, initializeTraceCount, serializeTraceCount;
[ThreadStatic] private static bool applying;
[ThreadStatic] private static nint initializingSuit;
public static bool Available { get; private set; }
public static void Prepare(nint image, ClientConfiguration configuration, List<NativeHook> hooks, Action<string> writeLog)
{
log = writeLog;
trace = configuration.EnableGameTestControl;
if (!configuration.GameSha256.Equals(ExperimentalAbilityCompatibility.ExecutableSha256, StringComparison.OrdinalIgnoreCase))
{ log("Multiple abilities unavailable: executable is outside the experimental ABI profile."); return; }
foreach (var entry in ExperimentalAbilityCompatibility.Entries)
if (!new ReadOnlySpan<byte>((void*)(image + entry.Rva), entry.Bytes.Length / 2).SequenceEqual(Convert.FromHexString(entry.Bytes)))
{ log("Multiple abilities unavailable: ABI check failed for " + entry.Name); return; }
nint Address(string name) => image + ExperimentalAbilityCompatibility.Entries.Single(e => e.Name == name).Rva;
nint Hook(string name, nint callback)
{
var hook = new NativeHook(Address(name), callback); hooks.Add(hook); return hook.Trampoline;
}
typeBase = image + ExperimentalAbilityCompatibility.TypeBaseRva;
typeSerialize = Address("TypeSerialize"); validate = Address("AbilityValidate"); apply = Address("OverrideApply");
serialize = Hook("ConfigSerialize", (nint)(delegate* unmanaged<nint, nint, void>)&Serialize);
copy = Hook("ConfigCopy", (nint)(delegate* unmanaged<nint, nint, nint>)&Copy);
move = Hook("ConfigMove", (nint)(delegate* unmanaged<nint, nint, nint>)&Move);
assign = Hook("ConfigAssign", (nint)(delegate* unmanaged<nint, nint, nint>)&Assign);
moveAssign = Hook("ConfigMoveAssign", (nint)(delegate* unmanaged<nint, nint, nint>)&MoveAssign);
destroy = Hook("ConfigDestroy", (nint)(delegate* unmanaged<nint, void>)&Destroy);
buildEquippedItem = Hook("BuildEquippedItem", (nint)(delegate* unmanaged<nint, nint, nint, byte, byte, nint, nint, nint, byte, byte, void>)&BuildEquippedItem);
initialize = Hook("SuitInitialize", (nint)(delegate* unmanaged<nint, void>)&Initialize);
var rankHook = NativeHook.RedirectCall(Address("SuitBeforeRanks"), (nint)(delegate* unmanaged<nint, int>)&BeforeRanks);
hooks.Add(rankHook); rank = rankHook.Trampoline;
set = Hook("OverrideSet", (nint)(delegate* unmanaged<nint, nint, void>)&Set);
suitDestroy = Hook("SuitDestroy", (nint)(delegate* unmanaged<nint, void>)&SuitDestroy);
PrepareBanks(Address, Hook, hooks);
PrepareDependencies(image, Address, Hook);
_ = AbilityHudCatalog.Entries; // Parse the embedded resource catalog before installing hooks.
Available = true;
log("Multiple abilities: experimental ABI bindings prepared (2026.08.19.11.06).");
}
[UnmanagedCallersOnly] private static void Serialize(nint config, nint reader)
{
((delegate* unmanaged<nint, nint, void>)serialize)(config, reader);
var mode = *(byte*)(reader + 10); var binary = *(byte*)(reader + 11) != 0;
if (trace && *(nint*)(config + OverrideOffset) != 0 && Interlocked.Increment(ref serializeTraceCount) <= 80)
log($"Multiple abilities: serialize config=0x{config:X}, mode={mode}, binary={binary}.");
if (binary || mode is not (0 or 1)) return;
try
{
SerializeKing(config, reader, mode);
if (mode == 0)
{
Plan? saved; lock (Gate) Configs.TryGetValue(config, out saved);
if (saved is null || saved.Primary != *(nint*)(config + OverrideOffset) || saved.Slot != *(byte*)(config + OverrideOffset + 8)) return;
Name(reader, "XFEAbilityOverrides");
((delegate* unmanaged<nint, byte, void>)Virtual(reader, 0x158))(reader, 12);
((delegate* unmanaged<nint, void>)Virtual(reader, 0x58))(reader);
try
{
for (var slot = 0; slot < 4; slot++)
{
var ability = saved.Abilities[slot];
if (ability == 0) continue;
Name(reader, slot.ToString(System.Globalization.CultureInfo.InvariantCulture));
((delegate* unmanaged<nint*, nint, nint, void>)typeSerialize)(&ability, reader, typeBase);
}
}
finally { ((delegate* unmanaged<nint, void>)Virtual(reader, 0x60))(reader); }
if (saved.Banks is not null) WriteBanks(reader, saved.Banks);
if (trace) log("Multiple abilities: wrote extended JSON configuration.");
return;
}
lock (Gate) Configs.Remove(config);
Name(reader, "XFEAbilityOverrides");
((delegate* unmanaged<nint, byte, void>)Virtual(reader, 0x158))(reader, 12);
if (((delegate* unmanaged<nint, byte>)Virtual(reader, 0x48))(reader) == 0) return;
((delegate* unmanaged<nint, void>)Virtual(reader, 0x58))(reader);
var abilities = new nint[4];
try
{
for (var slot = 0; slot < 4; slot++)
{
Name(reader, slot.ToString(System.Globalization.CultureInfo.InvariantCulture));
nint ability = 0;
((delegate* unmanaged<nint*, nint, nint, void>)typeSerialize)(&ability, reader, typeBase);
if (ability != 0 && *(nint*)((delegate* unmanaged<nint*, nint>)validate)(&ability) == 0)
throw new InvalidDataException("An experimental override is not a player ability.");
abilities[slot] = ability;
}
}
finally { ((delegate* unmanaged<nint, void>)Virtual(reader, 0x60))(reader); }
var banks = ReadBanks(reader);
var primary = *(nint*)(config + OverrideOffset); var index = *(byte*)(config + OverrideOffset + 8);
if (index > 3 || primary == 0 || abilities[index] != primary ||
(banks is null && abilities.Count(a => a != 0) < 2)) return;
if (banks is not null && Enumerable.Range(0, 4).Any(i => banks[i].FirstOrDefault() != abilities[i]))
throw new InvalidDataException("Ability bank does not match its compatibility projection.");
lock (Gate)
{
if (Configs.Count >= 65536) throw new InvalidDataException("Experimental config cache limit reached.");
Configs[config] = new(primary, index, abilities) { Banks = banks };
}
if (trace) log($"Multiple abilities: read configuration 0x{config:X} with {abilities.Count(a => a != 0)} replacements; primary=0x{primary:X}, slot={index}.");
}
catch (Exception ex) { log("Multiple abilities: configuration ignored (" + ex.GetType().Name + ")."); }
}
private static nint Virtual(nint instance, int offset) => *(nint*)(*(nint*)instance + offset);
private static void Name(nint reader, string name)
{
((delegate* unmanaged<nint, nint, void>)Virtual(reader, 0x138))(reader, Names[name]);
}
// The native reader can retain the selected property's literal view. These five small
// allocations intentionally live for the entire injected module lifetime, like native literals.
private static nint Literal(string text)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(text + '\0');
var memory = Marshal.AllocHGlobal(16 + bytes.Length);
*(ulong*)memory = 0xC000000000000000;
*(nint*)(memory + 8) = memory + 16;
Marshal.Copy(bytes, 0, memory + 16, bytes.Length);
return memory;
}
private static nint Transfer(nint original, nint destination, nint source, bool moving)
{
Plan? plan; lock (Gate) Configs.TryGetValue(source, out plan);
bool king; lock (Gate) king = KingConfigs.Contains(source);
var result = ((delegate* unmanaged<nint, nint, nint>)original)(destination, source);
lock (Gate)
{
Configs.Remove(destination);
if (moving && source != destination) Configs.Remove(source);
KingConfigs.Remove(destination);
if (moving && source != destination) KingConfigs.Remove(source);
if (king) KingConfigs.Add(destination);
if (plan is not null && Configs.Count < 65536 && *(nint*)(destination + OverrideOffset) == plan.Primary && *(byte*)(destination + OverrideOffset + 8) == plan.Slot)
Configs[destination] = plan;
}
if (trace && plan is not null && Interlocked.Increment(ref transferTraceCount) <= 80)
log($"Multiple abilities: {(moving ? "move" : "copy")} 0x{source:X} -> 0x{destination:X}; primary matches={*(nint*)(destination + OverrideOffset) == plan.Primary}.");
return result;
}
[UnmanagedCallersOnly] private static nint Copy(nint d, nint s) => Transfer(copy, d, s, false);
[UnmanagedCallersOnly] private static nint Move(nint d, nint s) => Transfer(move, d, s, true);
[UnmanagedCallersOnly] private static nint Assign(nint d, nint s) => Transfer(assign, d, s, false);
[UnmanagedCallersOnly] private static nint MoveAssign(nint d, nint s) => Transfer(moveAssign, d, s, true);
[UnmanagedCallersOnly] private static void Destroy(nint config)
{ lock (Gate) { Configs.Remove(config); KingConfigs.Remove(config); } ((delegate* unmanaged<nint, void>)destroy)(config); }
[UnmanagedCallersOnly] private static void SuitDestroy(nint suit)
{
try { ReleaseBanks(suit); RestoreDonorAnimations(suit, true); }
catch (Exception ex) { log("Ability animation cleanup: " + ex.GetType().Name); }
lock (Gate) { Suits.Remove(suit); BankStates.Remove(suit); KingSuits.Remove(suit); }
((delegate* unmanaged<nint, void>)suitDestroy)(suit);
}
// The engine flattens the selected MOD config and the independently selected cosmetic
// config into a new config. That path copies AbilityOverride inline, bypassing every
// config copy constructor. Match the source item ID and MOD index before flattening;
// never infer a plan from the primary ability (A and C can share that ability).
[UnmanagedCallersOnly]
private static void BuildEquippedItem(nint profile, nint loadout, nint inventory, byte mode, byte slot,
nint items, nint preset, nint equipped, byte force, byte option)
{
var selected = SelectedPlan(items, equipped, mode);
bool king; lock (Gate) king = KingConfigs.Contains(SelectedConfig(items, equipped, mode));
((delegate* unmanaged<nint, nint, nint, byte, byte, nint, nint, nint, byte, byte, void>)buildEquippedItem)
(profile, loadout, inventory, mode, slot, items, preset, equipped, force, option);
var config = *(nint*)(equipped + 0x68);
if (config == 0 || *(uint*)(equipped + 0x70) < 0x330) return;
lock (Gate)
{
KingConfigs.Remove(config);
if (king) KingConfigs.Add(config);
Configs.Remove(config);
if (selected is not null && Configs.Count < 65536 &&
*(nint*)(config + OverrideOffset) == selected.Primary && *(byte*)(config + OverrideOffset + 8) == selected.Slot)
{
Configs[config] = selected;
if (trace) log($"Multiple abilities: merged MOD config {*(nuint*)(equipped + 0x130)} into equipped config 0x{config:X} ({selected.Abilities.Count(a => a != 0)} slots).");
}
}
}
private static Plan? SelectedPlan(nint items, nint equipped, byte mode)
{
var config = SelectedConfig(items, equipped, mode);
if (config == 0) return null;
lock (Gate)
return Configs.TryGetValue(config, out var plan) && plan.Primary == *(nint*)(config + OverrideOffset) &&
plan.Slot == *(byte*)(config + OverrideOffset + 8) ? plan : null;
}
private static nint SelectedConfig(nint items, nint equipped, byte mode)
{
// Conclave builds deliberately omit the native override too.
if (mode == 3 || items == 0 || equipped == 0) return 0;
var first = *(nint*)items; var bytes = *(uint*)(items + 8);
if (first == 0 || bytes > 0x130 * 65536 || bytes % 0x130 != 0) return 0;
var index = *(nuint*)(equipped + 0x130);
for (nuint offset = 0; offset < bytes; offset += 0x130)
{
var item = first + (nint)offset;
if (*(ulong*)(item + 0x20) != *(ulong*)(equipped + 0x20) ||
*(uint*)(item + 0x28) != *(uint*)(equipped + 0x28)) continue;
var configs = *(nint*)(item + 0x68); var configBytes = *(uint*)(item + 0x70);
if (configs == 0 || index >= configBytes / 0x330) return 0;
return configs + (nint)(index * 0x330);
}
return 0;
}
[UnmanagedCallersOnly] private static void Set(nint suit, nint incoming)
{
ReleaseBanks(suit);
RestoreDonorAnimations(suit, true);
Plan? next; Plan? previous;
lock (Gate)
{
KingSuits.Remove(suit);
if (KingConfigs.Contains(incoming - OverrideOffset)) KingSuits.Add(suit);
Configs.TryGetValue(incoming - OverrideOffset, out next);
if (next is not null && (next.Primary != *(nint*)incoming || next.Slot != *(byte*)(incoming + 8))) next = null;
Suits.TryGetValue(suit, out previous);
Suits.Remove(suit);
BankStates.Remove(suit);
if (next is not null) Suits[suit] = next;
}
if (trace && Interlocked.Increment(ref setTraceCount) <= 80)
{
string candidates;
lock (Gate) candidates = string.Join(",", Configs.Where(p => p.Value.Primary == *(nint*)incoming).Take(8).Select(p => $"0x{p.Key:X}:{p.Value.Abilities.Count(a => a != 0)}"));
log($"Multiple abilities: set suit=0x{suit:X}, config=0x{incoming - OverrideOffset:X}, plan={next is not null}, instances={*(int*)(suit + 0x3450)}, candidates=[{candidates}].");
}
if (!applying && previous is not null) RestoreDefaults(suit, previous);
((delegate* unmanaged<nint, nint, void>)set)(suit, incoming);
ApplyPlan(suit, next);
}
[UnmanagedCallersOnly] private static void Initialize(nint suit)
{
var previous = initializingSuit;
if (trace && Interlocked.Increment(ref initializeTraceCount) <= 40)
{
bool hasPlan; lock (Gate) hasPlan = Suits.ContainsKey(suit);
log($"Multiple abilities: initialize suit=0x{suit:X}, plan={hasPlan}, instances={*(int*)(suit + 0x3450)}.");
}
if (*(int*)(suit + 0x3450) == 0) initializingSuit = suit;
try { ((delegate* unmanaged<nint, void>)initialize)(suit); }
finally { initializingSuit = previous; }
}
[UnmanagedCallersOnly] private static int BeforeRanks(nint suit)
{
Plan? plan; lock (Gate) Suits.TryGetValue(suit, out plan);
// Default instances already exist here; ranks, augments and scripts are
// initialized next by the engine, using the final replaced instances.
if (initializingSuit == suit) { initializingSuit = 0; ApplyPlan(suit, plan); }
return ((delegate* unmanaged<nint, int>)rank)(suit);
}
private static void ApplyPlan(nint suit, Plan? plan)
{
if (applying || plan is null || *(int*)(suit + 0x3450) == 0) return;
applying = true;
try
{
for (byte slot = 0; slot < 4; slot++)
if (plan.Abilities[slot] != 0 && slot != plan.Slot) ApplyOne(suit, plan.Abilities[slot], slot);
if (trace) log("Multiple abilities: applied " + plan.Abilities.Count(a => a != 0) + " slots to one suit configuration.");
}
finally { applying = false; }
}
private static void RestoreDefaults(nint suit, Plan previous)
{
if (*(int*)(suit + 0x3450) == 0) return;
var types = *(nint*)(suit + 0x3418); var bytes = *(uint*)(suit + 0x3420);
if (types == 0 || bytes < 64 || bytes > 1024) return;
for (byte slot = 0; slot < 4; slot++)
{
if (previous.Abilities[slot] == 0) continue;
var reference = *(nint*)(types + slot * 16);
if (reference != 0 && *(nint*)reference != 0) ApplyOne(suit, *(nint*)(types + slot * 16 + 8), slot);
}
}
private static void ApplyOne(nint suit, nint ability, byte slot)
{
var primary = *(nint*)(suit + SuitOverrideOffset); var index = *(byte*)(suit + SuitOverrideOffset + 8);
try
{
*(nint*)(suit + SuitOverrideOffset) = ability; *(byte*)(suit + SuitOverrideOffset + 8) = slot;
((delegate* unmanaged<nint, void>)apply)(suit);
}
finally { *(nint*)(suit + SuitOverrideOffset) = primary; *(byte*)(suit + SuitOverrideOffset + 8) = index; }
}
}