XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEWarframe

【WinUI】基于Warframe游戏本体的公开服务器以及安装器

公开
关注 0 Fork 0 Star 0
UTF-8
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; }
    }
}