Ctrl K
ComunidadGuías…

ParadoxRuntime/Networking.cpp

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * Original work Copyright (C) 2026 gwog :3 (SyST3MDeV/Undaunted)
 * Modified work Copyright (C) 2026 MysticFox / Pranav Karande (pranav158/Mystic-Paradox)
 *
 * Licensed under the GNU Affero General Public License v3.0.
 * You may obtain a copy of the License at the root of this repository.
 *
 * SPDX-License-Identifier: AGPL-3.0-only
 * Additional terms under AGPLv3 Section 7 apply. See ADDITIONAL_TERMS.md.
 */

#include "networking.h"
#include "P2PHooks.h"

#include <fstream>
#include <iostream>
#include <string>
#include <string_view>

using namespace SDK;

// [2026-10-11 1.14.7 P2P host MULTIHOME] Defined in dllmain.cpp (MpCarryMultihomeIntoEngineLine): the MULTIHOME the
// served engine FCommandLine carries, empty when none. Used by Listen to check a pinned listen address.
std::wstring MpEngineCommandLineMultihome();
// [2026-10-11 1.14.7 O3] Defined in dllmain.cpp (also declared in RuntimeShared.h, which this file does not include
// because its IsReadablePointer would clash with the one below): the shared log writer and the production log profile.
void MpLogRawPort(int Port, const std::string& Msg);
bool MpProductionLogProfile();

namespace Networking {
    UNetDriver* NetDriver = nullptr;

    static uintptr_t BaseAddress = 0x0;
    static int LastPort = 0; // Set once in Listen(); lets TickNetworking() (no Port param) log to the right per-port file.
    static void NetLog(int Port, const std::string& Msg);

    static bool IsReadablePointer(const void* Ptr, size_t Size = sizeof(void*)) {
        if (!Ptr || ((uintptr_t)Ptr & 0x7) != 0) {
            return false;
        }

        MEMORY_BASIC_INFORMATION Info{};
        if (!VirtualQuery(Ptr, &Info, sizeof(Info))) {
            return false;
        }

        if (Info.State != MEM_COMMIT || (Info.Protect & (PAGE_GUARD | PAGE_NOACCESS))) {
            return false;
        }

        uintptr_t Start = reinterpret_cast<uintptr_t>(Ptr);
        uintptr_t End = Start + Size;
        uintptr_t RegionEnd = reinterpret_cast<uintptr_t>(Info.BaseAddress) + Info.RegionSize;
        return End >= Start && End <= RegionEnd;
    }

    static bool IsSanePointerArray(void* Data, int32_t Num, int32_t Max, int32_t Limit) {
        if (Num < 0 || Max < 0 || Num > Max || Num > Limit) {
            return false;
        }

        if (Num == 0) {
            return true;
        }

        return IsReadablePointer(Data, static_cast<size_t>(Num) * sizeof(void*));
    }

    // Same file as dllmain's MpLog ("<exedir>\mysticparadox_dll_port<port>.log") so [TickNetLoop]
    // and [NetTickTrace] lines interleave in one per-port log. (The exe directory is resolved by dllmain's MpLogDir; the
    // local NetLogDir copy went with the per-line std::ofstream on 11 Oct.)
    static void NetLog(int Port, const std::string& Msg) {
        // [2026-10-11 1.14.7 O3] Through dllmain's writer (one open handle, one WriteFile per line; it also honours
        // MPLOG_OPEN_PER_LINE.flag and the DLL_PROCESS_DETACH rule) instead of a std::ofstream per line.
        MpLogRawPort(Port, Msg);
    }

    // [A/B 2026-07-16] NATIVE_REPLICATION_ONLY.flag — reversible diagnostic switch for the
    // two-players-in-one-instance join failure. When the flag file exists next to the game .exe
    // (exe-relative, VPS-safe), TickNetworking still runs driver maintenance + both counters
    // (+0x2AC, +0x418) but SKIPS the custom BuildConsiderList -> CreateChannelByName ->
    // ReplicateActor loop, leaving replication to the engine's native TickFlush/
    // ServerReplicateActors path (driven from dllmain via SafeManualTickDispatch/TickFlush).
    // This isolates whether the hand-rolled loop is what strands the late joiner. Default OFF
    // (absent flag = current custom-loop behaviour). Read once — set the flag before launch.
    static bool NativeReplicationOnly() {
        static int Cached = -1;
        if (Cached < 0) {
            Cached = 0;
            wchar_t ExePath[MAX_PATH];
            DWORD n = GetModuleFileNameW(nullptr, ExePath, MAX_PATH);
            if (n > 0 && n < MAX_PATH) {
                for (int i = (int)n - 1; i >= 0; --i) {
                    if (ExePath[i] == L'\\' || ExePath[i] == L'/') { ExePath[i + 1] = L'\0'; break; }
                }
                std::wstring FlagPath = std::wstring(ExePath) + L"NATIVE_REPLICATION_ONLY.flag";
                if (GetFileAttributesW(FlagPath.c_str()) != INVALID_FILE_ATTRIBUTES) { Cached = 1; }
            }
        }
        return Cached == 1;
    }

    // [2026-07-16] REVERSE_CONNECTION_ORDER.flag — reversible per-connection-starvation test. When
    // present next to the game .exe, TickNetworking iterates client connections back-to-front. If
    // the stuck late joiner then completes and the other player becomes starved, the manual loop
    // has first-connection/shared-frame state; if the same joiner stays stuck, the defect is a
    // specific init property/RPC queued behind the first connection. Read once (set before launch).
    static bool ReverseConnectionOrder() {
        static int Cached = -1;
        if (Cached < 0) {
            Cached = 0;
            wchar_t ExePath[MAX_PATH];
            DWORD n = GetModuleFileNameW(nullptr, ExePath, MAX_PATH);
            if (n > 0 && n < MAX_PATH) {
                for (int i = (int)n - 1; i >= 0; --i) {
                    if (ExePath[i] == L'\\' || ExePath[i] == L'/') { ExePath[i + 1] = L'\0'; break; }
                }
                std::wstring FlagPath = std::wstring(ExePath) + L"REVERSE_CONNECTION_ORDER.flag";
                if (GetFileAttributesW(FlagPath.c_str()) != INVALID_FILE_ATTRIBUTES) { Cached = 1; }
            }
        }
        return Cached == 1;
    }

    // [2026-07-16] HYBRID_REPLICATION.flag — ordering fix-test for the late-joiner actor graph. When
    // present next to the game .exe, the custom loop SKIPS PlayerController + Pawn actors, letting the
    // engine's native ServerReplicateActors (driven from dllmain's TickFlush) own the PC/pawn/ownership/
    // NetGUID ordering, while the custom loop keeps replicating only what native does not deliver here
    // (inventory/loadout/etc). Tests whether the custom loop racing the pawn ahead of the native
    // PlayerController/ownership channel is the late-join break. Read once (set before launch).
    static bool HybridReplication() {
        static int Cached = -1;
        if (Cached < 0) {
            Cached = 0;
            wchar_t ExePath[MAX_PATH];
            DWORD n = GetModuleFileNameW(nullptr, ExePath, MAX_PATH);
            if (n > 0 && n < MAX_PATH) {
                for (int i = (int)n - 1; i >= 0; --i) {
                    if (ExePath[i] == L'\\' || ExePath[i] == L'/') { ExePath[i + 1] = L'\0'; break; }
                }
                std::wstring FlagPath = std::wstring(ExePath) + L"HYBRID_REPLICATION.flag";
                if (GetFileAttributesW(FlagPath.c_str()) != INVALID_FILE_ATTRIBUTES) { Cached = 1; }
            }
        }
        return Cached == 1;
    }

    // [2026-07-16] LEVEL_VISIBILITY_GATE.flag — THE missing per-connection level-visibility check. The
    // custom loop was calling CreateChannelByName->SetChannelActor->ReplicateActor directly WITHOUT the
    // check native ServerReplicateActors does, so it shipped streamed-sublevel actors (e.g.
    // ramsgate_01_katplaza WorldSettings) before the client acked those levels -> client SerializeNewActor
    // refuses them (Actor: None) and streamed-sublevel interaction breaks for P1 too. When set, the loop
    // skips non-persistent-level actors EXCEPT the ownership-critical ones we must always deliver
    // (PlayerController / Pawn / inventory / loadout); native replication handles the streamed rest with
    // its real per-connection visibility check. Read once (set before launch).
    static bool LevelVisibilityGate() {
        static int Cached = -1;
        if (Cached < 0) {
            Cached = 0;
            wchar_t ExePath[MAX_PATH];
            DWORD n = GetModuleFileNameW(nullptr, ExePath, MAX_PATH);
            if (n > 0 && n < MAX_PATH) {
                for (int i = (int)n - 1; i >= 0; --i) {
                    if (ExePath[i] == L'\\' || ExePath[i] == L'/') { ExePath[i + 1] = L'\0'; break; }
                }
                std::wstring FlagPath = std::wstring(ExePath) + L"LEVEL_VISIBILITY_GATE.flag";
                if (GetFileAttributesW(FlagPath.c_str()) != INVALID_FILE_ATTRIBUTES) { Cached = 1; }
            }
        }
        return Cached == 1;
    }

    // [A/B 2026-07-16] NATIVE_GRAPH_ONLY.flag — the perfectly-isolated native test. Once the real
    // ArchonReplicationGraph is installed (REPGRAPH_SELF_CONSTRUCT), the native driver + TickFlush own the
    // ENTIRE replication lifecycle — including the +0x2AC / +0x418 frame counters that NATIVE_REPLICATION_ONLY
    // still advanced manually (double-driving the graph's frame). When this flag is present, TickNetworking
    // does ONLY the minimal driver/world maintenance (World, ServerConnection, NetDriverName) and returns,
    // skipping the manual counters, BuildConsiderList, CreateChannelByName, SetChannelActor and ReplicateActor
    // entirely, so the installed graph is the sole replication path. Default OFF. Read once — set before launch.
    static bool NativeGraphOnly() {
        // PRODUCTION: native graph is the SOLE replication path (custom actor loop OFF) by default.
        // EMERGENCY_LEGACY_REPLICATION.flag reverts to the hand-written loop.
        static int Cached = -1;
        if (Cached < 0) {
            Cached = 1;   // default: native-graph-only
            wchar_t ExePath[MAX_PATH];
            DWORD n = GetModuleFileNameW(nullptr, ExePath, MAX_PATH);
            if (n > 0 && n < MAX_PATH) {
                for (int i = (int)n - 1; i >= 0; --i) {
                    if (ExePath[i] == L'\\' || ExePath[i] == L'/') { ExePath[i + 1] = L'\0'; break; }
                }
                std::wstring FlagPath = std::wstring(ExePath) + L"EMERGENCY_LEGACY_REPLICATION.flag";
                if (GetFileAttributesW(FlagPath.c_str()) != INVALID_FILE_ATTRIBUTES) { Cached = 0; }
            }
        }
        return Cached == 1;
    }

    // [2026-07-17 PRODUCTION] Per-tick RepGraph diagnostics ([GraphState]/[ActorsNoConn]) are OFF in
    // production; REPGRAPH_DIAG.flag re-enables them for development.
    static bool RepGraphDiagNet() {
        static int Cached = -1;
        if (Cached < 0) {
            Cached = 0;
            wchar_t ExePath[MAX_PATH];
            DWORD n = GetModuleFileNameW(nullptr, ExePath, MAX_PATH);
            if (n > 0 && n < MAX_PATH) {
                for (int i = (int)n - 1; i >= 0; --i) {
                    if (ExePath[i] == L'\\' || ExePath[i] == L'/') { ExePath[i + 1] = L'\0'; break; }
                }
                std::wstring FlagPath = std::wstring(ExePath) + L"REPGRAPH_DIAG.flag";
                if (GetFileAttributesW(FlagPath.c_str()) != INVALID_FILE_ATTRIBUTES) { Cached = 1; }
            }
        }
        return Cached == 1;
    }

    // ===== [1.14.7 PORT STATUS] =====
    // Ghidra verification for CL 647472 (2026-10-03):
    //   CreateNamedNetDriver: RVA 0x0425FDA0 (entry; failure/duplicate strings xref here).
    //   The old 1.12 RVA 0x04033D20 is inside FUN_144033CC0 at its loop compare, not a function entry.
    //   [2026-10-08 CORRECTION] The next five are 1.12 RVAs and are NOT function entries on 1.14.7
    //   (Ghidra 1.14.7 DB): ActorConsiderSetup 0x0394B400, PC_NetUpdate 0x03E9ABF0,
    //   CreateChannelByName 0x03D47AC0 (mid-instruction in the spline query FUN_143d47a90),
    //   SetChannelActor 0x03B80890 (mid-function), ReplicateActor 0x03B7B470.
    //   1.14.7 candidates by string xref, ABI unproven: CreateChannelByName 0x03F62E90,
    //   SetChannelActor 0x03D8FB20, ReplicateActor 0x03D8A2D0. No code path calls any of them now.
    // SKIPPED (direct assignment works):
    //   SetWorld: We directly set NetDriver->World at offset 0x140 (SDK confirmed) - function call not needed
    // UNVERIFIED (runtime-check): Actor VTable+0x150 (GetWorld), NetDriver vtable+0x280 (InitListen),
    //   NetDriver+0x2AC (counter), ActorChannel+0x90 (flags). Connection+0x134 (State) CONFIRMED unchanged.
    // NetworkNotify: UWorld+0x28 (unnamed in 1.12.0 SDK; layout stable). ================

    static std::vector<std::pair<AActor*, bool>> BuildConsiderList(UWorld* World, UNetDriver* Driver) {
        std::vector<std::pair<AActor*, bool>> Actors;

        // [2026-07-16] Persistent level for the level-visibility gate (UWorld->PersistentLevel @+0x30).
        // Persistent-level actors are always visible; streamed-sublevel actors must wait for the client
        // visibility ack, so the gate in TickNetworking skips non-persistent non-critical actors.
        ULevel* PersistentLevel = (World && IsReadablePointer(World, 0x38)) ? *reinterpret_cast<ULevel**>((uintptr_t)World + 0x30) : nullptr;

        for (ULevel* Level : World->Levels) {
            bool bPersistent = (Level == PersistentLevel);
            for (AActor* Actor : Level->Actors) {
                if (!Actor)
                    continue;

                if (Actor->RemoteRole == ENetRole::ROLE_None)
                    continue;

                if (Actor->bActorIsBeingDestroyed)
                    continue;

                // [1.12.0 FIX 2026-07-10] Native replication uses actor vtable +0x158 as early
                // filter (likely GetWorld() or IsNetRelevantFor()). Old +0x150 was unverified
                // and inherited from 1.4.4 (UE 4.25). UE 4.25 → 4.26 shifted this slot by one
                // 8-byte vtable entry. If +0x158 was wrong, actors would be silently filtered.
                if (!reinterpret_cast<UWorld * (*)(AActor*)>(*(void**)((uintptr_t)Actor->VTable + 0x158))(Actor)) {
                    continue;
                }

                reinterpret_cast<void(*)(AActor*, UNetDriver*)>(BaseAddress + 0x0394B400)(Actor, Driver);

                Actors.push_back({ Actor, bPersistent });
            }
        }

        /*
        for (int i = 0; i < SDK::UObject::GObjects->Num(); i++)
        {
            SDK::UObject* Obj = SDK::UObject::GObjects->GetByIndex(i);

            if (!Obj)
                continue;

            if (Obj->IsDefaultObject())
                continue;

            if (Obj->IsA(SDK::AActor::StaticClass()))
            {
                AActor* Actor = (AActor*)Obj;

                if (Actor->RemoteRole == ENetRole::ROLE_None)
                    continue;

                if (Actor->bActorIsBeingDestroyed)
                    continue;

                if (!reinterpret_cast<UWorld * (*)(AActor*)>(*(void**)((uintptr_t)Actor->VTable + 0x158))(Actor)) {
                    continue;
                }
                
                reinterpret_cast<void(*)(AActor*, UNetDriver*)>(BaseAddress + 0x0394B400)(Actor, Driver);

                Actors.push_back(Actor);
            }
        }
        */

        return Actors;
    }

    // [2026-10-11 1.14.7 O1/O2] One OpenChannels element, read under SEH. The walk used to call IsReadablePointer
    // (VirtualQuery) on every open channel - hundreds per connection - and it runs for every (role, connection) pair on
    // every connection-count change and every second for the churn diagnostic: ~roles x connections x channels system
    // calls per join at 30-40 players. A channel whose Class (+0x10) or Actor (+0x70, the last 8 bytes of the old
    // 0x78-byte check) cannot be read is skipped exactly as the unreadable one was; a misaligned pointer too.
    static bool ActorChannelMatchesGuarded(UChannel* Channel, const void* ActorChannelClass, const AActor* Actor) {
        if (!Channel || (reinterpret_cast<uintptr_t>(Channel) & 0x7) != 0) return false;
        __try {
            return Channel->Class == ActorChannelClass && ((UActorChannel*)Channel)->Actor == Actor;
        } __except (EXCEPTION_EXECUTE_HANDLER) {
            return false;
        }
    }

    static UActorChannel* GetActorChannelForConnectionAndActor(UNetConnection* Connection, AActor* Actor) {
        if (!IsReadablePointer(Connection, 0x80)) {
            return nullptr;
        }

        UChannel** Channels = *reinterpret_cast<UChannel***>((uintptr_t)Connection + 0x70);
        int32_t ChannelCount = *reinterpret_cast<int32_t*>((uintptr_t)Connection + 0x78);
        int32_t ChannelMax = *reinterpret_cast<int32_t*>((uintptr_t)Connection + 0x7C);

        if (!IsSanePointerArray(Channels, ChannelCount, ChannelMax, 4096)) {
            NetLog(LastPort, "[GetActorChannel] Invalid OpenChannels array; skipping");
            return nullptr;
        }

        const void* ActorChannelClass = UActorChannel::StaticClass();
        for (int32_t i = 0; i < ChannelCount; ++i) {
            UChannel* Channel = Channels[i];
            if (ActorChannelMatchesGuarded(Channel, ActorChannelClass, Actor)) {
                return ((UActorChannel*)Channel);
            }
        }

        return nullptr;
    }

    // See the comment on the declaration in Networking.h. Pure read: walks the same OpenChannels
    // array GetActorChannelForConnectionAndActor uses and reports the ChIndex, never creating,
    // closing or touching a channel. UChannel::ChIndex sits at +0x20 on this build (the slot
    // between Connection@0x28 and the class pointer, confirmed against the OpenChannels walk).
    // [2026-07-25] Returns 1 when an open actor channel exists for this pair, -1 when it does not,
    // -2 when the inputs are unusable. Deliberately reports PRESENCE, not ChIndex: the first cut
    // read a guessed UChannel::ChIndex offset and produced garbage (repro 11 logged
    // chIndex=-173321872), which made every sample look "closed" and suppressed every transition.
    // Presence is what the churn question actually needs and it requires no unverified offset.
    int GetActorChannelIndexForConnection(AActor* Actor, UNetConnection* Connection) {
        if (!Actor || !IsReadablePointer(Connection, 0x80)) return -2;
        return GetActorChannelForConnectionAndActor(Connection, Actor) ? 1 : -1;
    }

    static bool IsActorChannelBootstrapSafe(AActor* Actor) {
        if (!Actor || !IsReadablePointer(Actor, 0x60)) return false;
        __try {
            constexpr uint32_t kRF_BeginDestroyed = 0x00008000u;
            constexpr uint32_t kRF_FinishDestroyed = 0x00010000u;
            const uintptr_t Address = reinterpret_cast<uintptr_t>(Actor);
            const uint32_t ObjectFlags = *reinterpret_cast<const uint32_t*>(Address + 0x08);
            const uint8_t ActorFlags5C = *reinterpret_cast<const uint8_t*>(Address + 0x5C);
            return (ActorFlags5C & 0x08u) == 0
                && (ObjectFlags & (kRF_BeginDestroyed | kRF_FinishDestroyed)) == 0;
        } __except (EXCEPTION_EXECUTE_HANDLER) {
            return false;
        }
    }

    int BootstrapActorChannel(AActor* Actor, UNetConnection* Connection,
                              ActorChannelPushDiag* OutDiag) {
        if (OutDiag) *OutDiag = ActorChannelPushDiag{};
        if (!IsActorChannelBootstrapSafe(Actor) || !Connection
            || !IsReadablePointer(Connection, 0x140)) {
            return 0;
        }

        UActorChannel* ActorChannel = GetActorChannelForConnectionAndActor(Connection, Actor);

        // [1.14.7 FIX 2026-10-08] Never create a channel here. This used the 1.12 RVAs
        // CreateChannelByName 0x03D47AC0 and SetChannelActor 0x03B80890. On 1.14.7, 0x03D47AC0 is one
        // byte inside `lea r9,[rip+..]` in the spline direction query FUN_143d47a90: entered there it
        // runs `lea ecx,[rip+..]` and calls the curve evaluator with a 32-bit image address as the
        // curve. That was the whole "hub spline crash" - every crash carried this call's own arguments
        // (r8=2, r9=0xFFFFFFFF), rax=base+0x3D47AC0, and rcx=low32(base+0x6DF33F8). 0x03B80890 is
        // mid-function on 1.14.7. Ghidra 1.14.7 candidates (string xrefs, ABI not yet proven):
        // CreateChannelByName 0x03F62E90, SetChannelActor 0x03D8FB20. The driven Archon graph opens
        // this actor's channel itself, so the bootstrap only reports whether one exists.
        if (!ActorChannel || ActorChannel->Actor != Actor) {
            // [2026-10-11 1.14.7 O3] Off with PRODUCTION_LOG_PROFILE.flag (one line per role x connection per edge).
            if (!MpProductionLogProfile()) NetLog(LastPort, "[PlayerRoleDirectChannel] actor="
                + std::to_string(reinterpret_cast<uintptr_t>(Actor)) + " conn="
                + std::to_string(reinterpret_cast<uintptr_t>(Connection))
                + " result=NO_CHANNEL (creation disabled on 1.14.7; the replication graph opens it)");
            return 0;
        }

        // Snapshot the exact gates UActorChannel::ReplicateActor tests in its prologue, immediately
        // before we call it. Without this, wroteData=0 is ambiguous between "could not try" and
        // "nothing was dirty" - see ActorChannelPushDiag in Networking.h for the verified offsets.
        ActorChannelPushDiag Diag{};
        Diag.Channel = ActorChannel;
        Diag.ExistingChannel = true;
        if (IsReadablePointer(ActorChannel, 0x94)) {
            const uintptr_t ChannelAddr = reinterpret_cast<uintptr_t>(ActorChannel);
            Diag.PendingDormancy =
                (*reinterpret_cast<uint32_t*>(ChannelAddr + 0x30) & 0x100u) != 0;
            Diag.ReplicatingReentrant =
                (*reinterpret_cast<uint32_t*>(ChannelAddr + 0x90) & 0x4u) != 0;
            Diag.Valid = true;
        }
        if (IsReadablePointer(Connection, 0x1511)) {
            Diag.ResendAllDataState = *reinterpret_cast<uint8_t*>(
                reinterpret_cast<uintptr_t>(Connection) + 0x1510);
        }

        // [1.14.7 FIX 2026-10-08] 0x03B7B470 is the 1.12 ReplicateActor RVA; on 1.14.7 no function starts
        // there. The 1.14.7 ReplicateActor is FUN_143d8a2d0 (RVA 0x03D8A2D0, "ReplicateActor called while
        // already replicating!" xref) but its ABI is unproven, so the push is skipped and the graph drive
        // replicates the channel on its own schedule.
        const bool WroteData = false;
        Diag.WroteData = WroteData;
        if (OutDiag) *OutDiag = Diag;
        if (!MpProductionLogProfile()) NetLog(LastPort, "[PlayerRoleDirectChannel] actor="   // [2026-10-11 O3]
            + std::to_string(reinterpret_cast<uintptr_t>(Actor)) + " conn="
            + std::to_string(reinterpret_cast<uintptr_t>(Connection)) + " channel="
            + std::to_string(reinterpret_cast<uintptr_t>(ActorChannel))
            + " result=EXISTING pushSkipped=1 (1.14.7 ReplicateActor ABI unproven)"
            + " wroteData=" + std::to_string(WroteData ? 1 : 0)
            + " gateRead=" + std::to_string(Diag.Valid ? 1 : 0)
            + " pendingDormancy=" + std::to_string(Diag.PendingDormancy ? 1 : 0)
            + " reentrant=" + std::to_string(Diag.ReplicatingReentrant ? 1 : 0)
            + " resendAllData=" + std::to_string(Diag.ResendAllDataState)
            + " blocked=" + std::to_string(Diag.Blocked() ? 1 : 0));
        return 1;   // channel exists, nothing written (2/3 meant "wrote data" while the push ran)
    }

    static FWorldContext* ResolveWorldContextFromWorld(UEngine* Engine, UWorld* World) {
        if (!Engine || !World) {
            return nullptr;
        }

        FWorldContext** WorldList = *reinterpret_cast<FWorldContext***>((uintptr_t)Engine + 0xC38);
        int32_t WorldListCount = *reinterpret_cast<int32_t*>((uintptr_t)Engine + 0xC40);

        for (int32_t i = 0; i < WorldListCount; ++i) {
            FWorldContext* Context = WorldList[i];
            if (!Context) {
                continue;
            }

            UWorld* ContextWorld = *reinterpret_cast<UWorld**>((uintptr_t)Context + 0x280);
            if (ContextWorld == World) {
                return Context;
            }
        }

        return nullptr;
    }

    static UNetDriver* ResolveNamedNetDriver(FWorldContext* Context, const FName& DriverName) {
        if (!Context) {
            return nullptr;
        }

        auto ActiveNetDrivers = *reinterpret_cast<uint8_t**>((uintptr_t)Context + 0x220);
        int32_t ActiveNetDriverCount = *reinterpret_cast<int32_t*>((uintptr_t)Context + 0x228);
        int32_t ActiveNetDriverMax = *reinterpret_cast<int32_t*>((uintptr_t)Context + 0x22C);

        if (ActiveNetDriverCount < 0 || ActiveNetDriverCount > ActiveNetDriverMax || ActiveNetDriverMax > 128) {
            NetLog(LastPort, "[ResolveNamedNetDriver] Invalid ActiveNetDrivers array metadata");
            return nullptr;
        }

        if (ActiveNetDriverCount > 0 && !IsReadablePointer(ActiveNetDrivers, static_cast<size_t>(ActiveNetDriverCount) * 0x10)) {
            NetLog(LastPort, "[ResolveNamedNetDriver] ActiveNetDrivers data is not readable");
            return nullptr;
        }

        for (int32_t i = 0; i < ActiveNetDriverCount; ++i) {
            UNetDriver* Candidate = *reinterpret_cast<UNetDriver**>(ActiveNetDrivers + (static_cast<size_t>(i) * 0x10));
            if (!IsReadablePointer(Candidate, 0x2B0)) {
                continue;
            }

            if (!Candidate->IsA(SDK::UNetDriver::StaticClass())) {
                continue;
            }

            if (Candidate->NetDriverName == DriverName) {
                return Candidate;
            }
        }

        return nullptr;
    }

    static UNetDriver* FindNamedNetDriverInGObjects(const FName& DriverName) {
        for (int i = 0; i < SDK::UObject::GObjects->Num(); i++)
        {
            SDK::UObject* Obj = SDK::UObject::GObjects->GetByIndex(i);

            if (!Obj || Obj->IsDefaultObject()) {
                continue;
            }

            if (Obj->IsA(SDK::UNetDriver::StaticClass()))
            {
                UNetDriver* Candidate = (UNetDriver*)Obj;
                if (!IsReadablePointer(Candidate, 0x2B0)) {
                    continue;
                }

                if (Candidate->NetDriverName == DriverName) {
                    return Candidate;
                }
            }
        }

        return nullptr;
    }

    bool Listen(UEngine* Engine, int Port) {
        std::cout << "[Networking::Listen] Entry" << std::endl;
        NetLog(Port, "[Networking::Listen] Entry");
        LastPort = Port;
        BaseAddress = (uintptr_t)GetModuleHandleA(nullptr);

        FName GameNetDriver = UKismetStringLibrary::Conv_StringToName(L"GameNetDriver");
        std::cout << "[Networking::Listen] Creating NetDriver..." << std::endl;
        NetLog(Port, "[Networking::Listen] Creating NetDriver...");

        UWorld* World = UWorld::GetWorld();
        FWorldContext* WorldContext = ResolveWorldContextFromWorld(Engine, World);
        if (!WorldContext) {
            std::cout << "[Networking::Listen] ERROR: FWorldContext not found!" << std::endl;
            NetLog(Port, "[Networking::Listen] ERROR: FWorldContext not found");
            return false;
        }

        using CreateNamedNetDriverFn = bool (*)(UEngine*, FWorldContext*, FName, FName);
        bool Created = reinterpret_cast<CreateNamedNetDriverFn>(BaseAddress + 0x0425FDA0)(
            Engine,
            WorldContext,
            GameNetDriver,
            GameNetDriver
        );
        std::cout << "Net driver create: " << Created << std::endl;
        NetLog(Port, std::string("[Networking::Listen] CreateNamedNetDriver returned ") + (Created ? "true" : "false"));

        NetDriver = ResolveNamedNetDriver(WorldContext, GameNetDriver);
        if (!NetDriver) {
            std::cout << "[Networking::Listen] ActiveNetDrivers lookup failed; searching GObjects for named NetDriver..." << std::endl;
            NetLog(Port, "[Networking::Listen] ActiveNetDrivers lookup failed; searching GObjects for named NetDriver");
            NetDriver = FindNamedNetDriverInGObjects(GameNetDriver);
        }

        if (!NetDriver) {
            std::cout << "[Networking::Listen] ERROR: named NetDriver not found!" << std::endl;
            NetLog(Port, "[Networking::Listen] ERROR: named NetDriver not found");
            return false;
        }

        std::cout << "[Networking::Listen] NetDriver found: " << NetDriver << std::endl;
        NetLog(Port, "[Networking::Listen] NetDriver found at 0x" + std::to_string((uintptr_t)NetDriver));
        NetDriver->NetDriverName = GameNetDriver;
        NetDriver->ServerConnection = nullptr;

        std::cout << "[Networking::Listen] Setting World directly (offset 0x140)..." << std::endl;
        NetLog(Port, "[Networking::Listen] Setting World");

        // SetWorld is a simple function that just stores World at NetDriver+0x140
        // We do it directly instead of calling the function with wrong RVA
        NetDriver->World = UWorld::GetWorld();

        std::cout << "[Networking::Listen] Creating URL..." << std::endl;
        FURL url = FURL();

        url.Port = Port;

        // The optional player-hosted mode may refuse the listen or pin its address (P2PHooks.h).
        // ListenHost outlives the Listen call below, which reads url.Host.
        std::wstring ListenHost;
        if (!P2PHooks::PrepareListen(ListenHost)) {
            return false;
        }
        if (!ListenHost.empty()) {
            // [2026-10-11 1.14.7 P2P host MULTIHOME] url.Host does not choose the socket's address: the net driver binds
            // GetLocalBindAddr (0x032AE2C0), i.e. MULTIHOME= from the engine's FCommandLine (GetMultihomeAddress
            // 0x032AE4B0), else every interface. A pinned address is honoured only when the served engine line carries
            // exactly that MULTIHOME (dllmain's HookFCommandLineGet carries it from the OS command line), so refuse the
            // listen otherwise - for example when the FCommandLine::Get hook failed to install. Fails closed: the caller
            // then fences the host (P2PHooks::OnListenFailed).
            const std::wstring EngineMultihome = MpEngineCommandLineMultihome();
            std::string HostText;
            for (wchar_t Ch : ListenHost) HostText.push_back(static_cast<char>(Ch));
            if (EngineMultihome.empty() || _wcsicmp(EngineMultihome.c_str(), ListenHost.c_str()) != 0) {
                std::string EngineText;
                for (wchar_t Ch : EngineMultihome) EngineText.push_back(static_cast<char>(Ch));
                NetLog(Port, "[Networking::Listen] pinned listen address " + HostText + " is not the engine's MULTIHOME ("
                    + (EngineText.empty() ? std::string("none - the engine would bind every interface") : EngineText)
                    + "); refusing listen");
                return false;
            }
            NetLog(Port, "[Networking::Listen] engine MULTIHOME matches the pinned listen address " + HostText);
            url.Host = FString(ListenHost.c_str());
        }

        FString empy = FString();

        std::cout << "[Networking::Listen] Calling Listen..." << std::endl;
        NetLog(Port, "[Networking::Listen] Calling Listen...");
        bool ListenStatus = (*(reinterpret_cast<bool(**)(UNetDriver*, void*, FURL*, bool, FString*)>(*(__int64*)NetDriver + 0x290)))(NetDriver, reinterpret_cast<void*>((uintptr_t)UWorld::GetWorld() + 0x28), &url, false, &empy);
        std::cout << "Listen Status: " << ListenStatus << std::endl;
        NetLog(Port, std::string("[Networking::Listen] Listen returned ") + (ListenStatus ? "true" : "false"));

        std::string ListenError = empy.ToString();
        if (!ListenError.empty()) {
            NetLog(Port, "[Networking::Listen] Error: " + ListenError);
        }

        if (!ListenStatus) {
            NetLog(Port, "[Networking::Listen] ERROR: InitListen returned false");
            return false;
        }

        // SetWorld again after Listen (direct assignment)
        NetDriver->World = UWorld::GetWorld();
        NetDriver->NetDriverName = GameNetDriver;
        NetDriver->ServerConnection = nullptr;

        // [1.14.7 FIX 2026-10-04] DIRECT poke of UWorld::NetDriver (UWorld+0x38).
        //
        // The previous comment relied on level-collection registration propagating the driver into
        // UWorld->NetDriver. That path is rejected in this build:
        //   [CollectionFix] registration REJECTED (driver not in WorldContext; would re-null)
        // so UWorld->NetDriver kept pointing at the game's own driver and the engine's tick never drove OURS.
        // Measured consequence: with the manual drive off, the hub made ZERO receive calls while the client
        // sent "sendto -> 127.0.0.1:8790 len=29" once a second for 20s - the socket was simply never read.
        //
        // Pointing the world at our named driver makes the engine's own tick dispatch it exactly once, which
        // is what removes the need for the manual second engine tick (and its null+0x88 fault).
        // [1.14.7 FIX 2026-10-04] Poke EVERY world candidate, not just UWorld::GetWorld().
        //
        // UWorld::GetWorld() returns the GWorld global, but the engine may be ticking a different UWorld
        // object - and if that one's NetDriver (+0x38) stays null, anything that consults the net driver
        // through it reads [NULL + 0x88] = UNetDriver::ServerConnection, which is exactly the fault signature
        // measured inside the manual UIpNetDriver::TickDispatch call:
        //     ManualTickDispatchSEH code=0xC0000005 params=0x0,0x88 regs{rax=0x0 ...}
        // So cover all of them: UWorld::GetWorld(), the direct GWorld global, and every UWorld found in
        // GObjects. Each is logged with its before/after driver so the log shows which world the net path
        // actually uses.
        // [1.14.7 CORRECTION 2026-10-04] Assign the driver to the world that OWNS it - not to every UWorld.
        //
        // The previous version poked UWorld::GetWorld(), the GWorld global, and up to six further UWorld
        // objects found in GObjects. That is wrong on its own terms: a UNetDriver belongs to exactly ONE world
        // (and NetDriver->World is set to UWorld::GetWorld() just above), so fanning one driver across eight
        // worlds corrupts the ownership relation rather than establishing it - and the engine's world never
        // got the driver it actually needed.
        //
        // The authoritative owner is the FWorldContext that CreateNamedNetDriver was given. Resolve the world
        // from THAT (FWorldContext+0x280, the same offset ResolveWorldContextFromWorld uses in reverse) and
        // poke only it.
        UWorld* OwningWorld = nullptr;
        if (WorldContext && IsReadablePointer(WorldContext, 0x288)) {
            OwningWorld = *reinterpret_cast<UWorld**>(reinterpret_cast<uintptr_t>(WorldContext) + 0x280);
        }
        if (OwningWorld != nullptr && IsReadablePointer(OwningWorld, 0x40)) {
            UNetDriver** Slot = reinterpret_cast<UNetDriver**>(reinterpret_cast<uintptr_t>(OwningWorld) + 0x38);
            UNetDriver* Previous = *Slot;
            if (Previous != NetDriver) {
                *Slot = NetDriver;
                NetLog(Port, std::string("[Networking::Listen] set UWorld::NetDriver on the WorldContext's world ")
                    + (Previous ? "(replaced a previous driver)" : "(was null)"));
            }
            else {
                NetLog(Port, "[Networking::Listen] the WorldContext's world already points at our named driver");
            }

            // Validate the ownership relation in the other direction too: the driver's own World must agree
            // with the world it has just been assigned to.
            if (NetDriver->World != OwningWorld) {
                NetLog(Port, "[Networking::Listen] driver World differed from the WorldContext world; aligning them");
                NetDriver->World = OwningWorld;
            }
        }
        else {
            NetLog(Port, "[Networking::Listen] WARNING: could not resolve the WorldContext's world; the engine will not drive this driver");
        }
        std::cout << "[Networking::Listen] Complete!" << std::endl;
        NetLog(Port, "[Networking::Listen] Complete");

        // DeployServer refuses to advertise this process until this exact marker.
        // It is emitted only after UNetDriver::Listen returned true, not at process spawn.
        // The optional player-hosted mode reports to its launcher instead (P2PHooks.h).
        if (P2PHooks::ReportsReadiness()) {
            return P2PHooks::ReportReady(Port);
        }

        char LaunchId[128]{};
        const DWORD LaunchIdLength = GetEnvironmentVariableA("MYSTICPARADOX_GAMESERVER_LAUNCH_ID", LaunchId, sizeof(LaunchId));
        if (LaunchIdLength > 0 && LaunchIdLength < sizeof(LaunchId)) {
            std::cout << "MYSTICPARADOX_GAMESERVER_READY launchId=" << LaunchId << " port=" << Port << std::endl;
            NetLog(Port, std::string("[Networking::Listen] Ready marker emitted launchId=") + LaunchId);
        }
        return true;
    }

    // [1.14.7 2026-10-03] See Networking.h. Reuses the identical marker and the same
    // MYSTICPARADOX_GAMESERVER_LAUNCH_ID contract as the manual path, so the DeployServer cannot tell the
    // difference and the harness check stays valid.
    void AnnounceEngineReady(int Port) {
        if (P2PHooks::ReportsReadiness()) {
            // [2026-10-11 1.14.7 P2P host] The player-hosted server no longer takes this path (dllmain refuses
            // MAP_URL_LISTEN.flag there - MpMapUrlListenEnabled); should it ever, a failed readiness report fences the
            // host like the manual path does instead of being ignored.
            if (!P2PHooks::ReportReady(Port)) {
                NetLog(Port, "[EngineListen] player-host readiness report failed; fencing the host (OnListenFailed)");
                P2PHooks::OnListenFailed();
            }
            return;
        }

        char LaunchId[128]{};
        const DWORD LaunchIdLength = GetEnvironmentVariableA("MYSTICPARADOX_GAMESERVER_LAUNCH_ID", LaunchId, sizeof(LaunchId));
        if (LaunchIdLength > 0 && LaunchIdLength < sizeof(LaunchId)) {
            std::cout << "MYSTICPARADOX_GAMESERVER_READY launchId=" << LaunchId << " port=" << Port << std::endl;
            NetLog(Port, std::string("[EngineListen] Ready marker emitted launchId=") + LaunchId);
        }
    }

    void TickNetworking() {
        UWorld* World = UWorld::GetWorld();
        if (!World || !IsReadablePointer(NetDriver, 0x2B0)) {
            NetLog(LastPort, "[TickNetworking] Invalid World or NetDriver; skipping");
            return;
        }

        // World->NetDriver is maintained via level-collection registration (engine propagates it); no direct poke.
        NetDriver->World = World;
        NetDriver->ServerConnection = nullptr;

        static FName name = FName();
        static bool nameInit = false;

        if (!nameInit) {
            nameInit = true;
            name = UKismetStringLibrary::Conv_StringToName(L"Actor");
        }

        static FName gameNetDriverName = FName();
        static bool gameNetDriverNameInit = false;

        if (!gameNetDriverNameInit) {
            gameNetDriverNameInit = true;
            gameNetDriverName = UKismetStringLibrary::Conv_StringToName(L"GameNetDriver");
        }

        NetDriver->NetDriverName = gameNetDriverName;

        // [GraphState 2026-07-16] Periodic read-only dump of the installed native ReplicationDriver
        // (UNetDriver::ReplicationDriver @ +0x6E8). Shows whether the graph initialized its nodes
        // (GlobalGraphNodes>0 => InitForNetDriver/InitGlobalGraphNodes ran) and whether it received the
        // client connection(s) (Connections grows on join => AddClientConnection ran). Runs in ALL modes
        // (before the NATIVE_GRAPH_ONLY return). Throttled ~2s. Offsets from SDK ReplicationGraph_classes.
        {
            static uint64_t s_graphDumpMs = 0;
            static int s_lastLiveConns = -2;
            int liveConns = (IsReadablePointer(reinterpret_cast<void*>((uintptr_t)NetDriver + 0x98), 4))
                            ? *reinterpret_cast<int32_t*>((uintptr_t)NetDriver + 0x98) : -1;
            uint64_t gnow = static_cast<uint64_t>(GetTickCount64());
            bool connEdge = (liveConns != s_lastLiveConns);
            if (RepGraphDiagNet() && (connEdge || (gnow - s_graphDumpMs > 2000))) {
                s_graphDumpMs = gnow;
                s_lastLiveConns = liveConns;
                void* graph = (IsReadablePointer(reinterpret_cast<void*>((uintptr_t)NetDriver + 0x6F0), 8))
                              ? *reinterpret_cast<void**>((uintptr_t)NetDriver + 0x6E8) : nullptr;
                void* ndWorld = (IsReadablePointer(reinterpret_cast<void*>((uintptr_t)NetDriver + 0x148), 8))
                                ? *reinterpret_cast<void**>((uintptr_t)NetDriver + 0x140) : nullptr;
                // [ServerReplicateActors gate] TickFlush only calls ServerReplicateActors (driver vtable +0x2A8)
                // when NetDriver+0x108 (param_1[0x21]) is non-null. If this is null the graph's replicate tick
                // (which re-evaluates ActorsWithoutNetConnection) never runs -> actors stay stuck.
                void* replGate = (IsReadablePointer(reinterpret_cast<void*>((uintptr_t)NetDriver + 0x110), 8))
                                 ? *reinterpret_cast<void**>((uintptr_t)NetDriver + 0x108) : nullptr;
                if (graph && IsReadablePointer(graph, 0xB8)) {
                    void* gClass = *reinterpret_cast<void**>((uintptr_t)graph + 0x10);
                    std::string gcn = (gClass && IsReadablePointer(gClass, 0x20)) ? reinterpret_cast<UObject*>(gClass)->GetName() : "(null)";
                    void* gDriver    = *reinterpret_cast<void**>((uintptr_t)graph + 0x30);
                    void* connMgrCls = *reinterpret_cast<void**>((uintptr_t)graph + 0x28);
                    int globalNodes  = *reinterpret_cast<int*>((uintptr_t)graph + 0xA0);
                    int prepNodes    = *reinterpret_cast<int*>((uintptr_t)graph + 0xB0);
                    int connMgrs     = *reinterpret_cast<int*>((uintptr_t)graph + 0x40);
                    int pendConns    = *reinterpret_cast<int*>((uintptr_t)graph + 0x50);
                    std::string sub = "";
                    if (IsReadablePointer(graph, 0x4C8)) {   // Archon subclass fields (UBasicReplicationGraph layout proxy)
                        void* gridNode = *reinterpret_cast<void**>((uintptr_t)graph + 0x498);
                        void* alwaysRel = *reinterpret_cast<void**>((uintptr_t)graph + 0x4A0);
                        int arfc       = *reinterpret_cast<int*>((uintptr_t)graph + 0x4B0);
                        int actorsNoC  = *reinterpret_cast<int*>((uintptr_t)graph + 0x4C0);
                        sub = " GridNode=" + std::to_string((uintptr_t)gridNode)
                            + " AlwaysRelevantNode=" + std::to_string((uintptr_t)alwaysRel)
                            + " ARFCList=" + std::to_string(arfc)
                            + " ActorsNoConn=" + std::to_string(actorsNoC);
                    }
                    NetLog(LastPort, std::string("[GraphState]") + (connEdge ? " (CONN-EDGE)" : "")
                        + " driver=" + std::to_string((uintptr_t)graph) + " (" + gcn + ")"
                        + " graph.NetDriver=" + std::to_string((uintptr_t)gDriver)
                        + " ConnMgrClass=" + std::to_string((uintptr_t)connMgrCls)
                        + " GlobalGraphNodes=" + std::to_string(globalNodes)
                        + " PrepareNodes=" + std::to_string(prepNodes)
                        + " ConnMgrs=" + std::to_string(connMgrs)
                        + " PendingConns=" + std::to_string(pendConns)
                        + sub
                        + " | NetDriver.World=" + std::to_string((uintptr_t)ndWorld)
                        + " ClientConns=" + std::to_string(liveConns)
                        + " replGate(+0x108)=" + std::to_string((uintptr_t)replGate));

                    // [ActorsNoConn enum 2026-07-17] Enumerate the graph's stuck ActorsWithoutNetConnection
                    // (+0x4B8 TArray data, +0x4C0 num). For each: class/full-name, owner chain, and the
                    // owning PlayerController's NetConnection (+0x418) resolved by walking Owner (+0xE0).
                    //   resolvedConn != 0 while stuck  => the actor HAS a connection but the graph's reroute/
                    //                                     PrepareForReplication migration isn't running.
                    //   resolvedConn == 0              => ownership/net-connection resolves too late; actors
                    //                                     must be re-routed after possession.
                    if (IsReadablePointer(graph, 0x4C8)) {
                        void** awncData = *reinterpret_cast<void***>((uintptr_t)graph + 0x4B8);
                        int awncNum = *reinterpret_cast<int*>((uintptr_t)graph + 0x4C0);
                        if (awncData && awncNum > 0 && awncNum < 4096) {
                            int cap = awncNum < 8 ? awncNum : 8;
                            for (int i = 0; i < cap; ++i) {
                                if (!IsReadablePointer(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(awncData) + (size_t)i * 8), 8)) break;
                                AActor* a = reinterpret_cast<AActor*>(awncData[i]);
                                if (!a || !IsReadablePointer(a, 0x420)) continue;
                                std::string acn = a->Class ? a->Class->GetName() : "(null-cls)";
                                std::string afn = a->GetFullName();
                                void* conn = nullptr;
                                std::string chain;
                                AActor* cur = a;
                                for (int d = 0; d < 5 && cur && IsReadablePointer(cur, 0x420); ++d) {
                                    if (cur->IsA(APlayerController::StaticClass())) {
                                        conn = *reinterpret_cast<void**>((uintptr_t)cur + 0x418);
                                        chain += "->PC";
                                        break;
                                    }
                                    AActor* own = *reinterpret_cast<AActor**>((uintptr_t)cur + 0xE0);
                                    chain += (own && IsReadablePointer(own, 0x20) && own->Class) ? ("->" + own->Class->GetName()) : "->(null)";
                                    cur = own;
                                }
                                NetLog(LastPort, "[ActorsNoConn #" + std::to_string(i) + "/" + std::to_string(awncNum) + "] "
                                    + acn + " (" + afn + ") ownerChain=" + chain
                                    + " resolvedConn=" + std::to_string((uintptr_t)conn));
                            }
                        }
                    }
                } else {
                    NetLog(LastPort, std::string("[GraphState]") + (connEdge ? " (CONN-EDGE)" : "")
                        + " ReplicationDriver=" + std::to_string((uintptr_t)graph) + " (null/unreadable)"
                        + " | NetDriver.World=" + std::to_string((uintptr_t)ndWorld)
                        + " ClientConns=" + std::to_string(liveConns));
                }
            }
        }

        // [NATIVE_GRAPH_ONLY 2026-07-16] Perfectly-isolated native test: skip the manual replication-frame/
        // counter advancement (+0x2AC, +0x418) AND the whole actor-replication loop below, leaving only the
        // driver/world maintenance above. The installed native ArchonReplicationGraph + TickFlush own the full
        // lifecycle (counters, channels, visibility). Distinct from NATIVE_REPLICATION_ONLY, which still bumped
        // the counters and thus double-drove the graph's frame lifecycle.
        if (NativeGraphOnly()) {
            static bool s_loggedGraphOnly = false;
            if (!s_loggedGraphOnly) {
                s_loggedGraphOnly = true;
                NetLog(LastPort, "[NATIVE_GRAPH_ONLY] manual counters + actor-replication loop skipped "
                    "(driver/world maintenance only; native ArchonReplicationGraph + TickFlush own replication)");
            }
            return;
        }

        ++ * (uint32_t*)((uintptr_t)NetDriver + 0x2AC);

        // [Phase 0i / release] Advance the driver's replication frame (NetDriver+0x418) once per networking
        // maintenance tick. The RepLayout changelist compare (FUN_143f11cc0) early-returns (no property write)
        // unless this frame advances; standard UNetDriver::ServerReplicateActors bumps it every tick, so our
        // manual loop must too — otherwise every post-initial property change on every actor is skipped
        // (loadout Num 0->2 never re-detected, and every replicated actor is affected). Unconditional +
        // wrap-safe: skip 0, which RepLayout changelists treat as "never compared".
        {
            uint32_t* repFrame = reinterpret_cast<uint32_t*>((uintptr_t)NetDriver + 0x418);
            if (++(*repFrame) == 0) *repFrame = 1;
        }

        // [A/B 2026-07-16] NATIVE_REPLICATION_ONLY: driver maintenance + both counters above have
        // run this tick; stop before the custom actor-replication loop so the engine's native
        // TickFlush/ServerReplicateActors (driven from dllmain) is the sole replication path.
        // Log once so the mode is obvious in the per-port log.
        if (NativeReplicationOnly()) {
            static bool s_loggedNativeOnly = false;
            if (!s_loggedNativeOnly) {
                s_loggedNativeOnly = true;
                NetLog(LastPort, "[NATIVE_REPLICATION_ONLY] custom actor-replication loop skipped "
                    "(driver maintenance + counters still run; native TickDispatch/TickFlush drive replication)");
            }
            return;
        }

        // [Phase 0j v2 2026-07-12] Throttle ONLY the actor-replication work below (BuildConsiderList +
        // the per-connection ReplicateActor loop). Driver maintenance (ServerConnection=nullptr, World,
        // NetDriverName) and BOTH counters (+0x2AC and the +0x418 replication frame) above run EVERY tick.
        // The v1 gate at the top of TickNetworking wrongly skipped those and broke the join handshake.
        // When debug\THROTTLE_REP.flag is present, cap the actor loop to ~20Hz. Default = unthrottled.
        {
            // [PROD] Preserve the validated known-good config (THROTTLE_REP.flag was present in
            // debug\): cap actor replication to ~20Hz permanently. Previously flag-gated.
            static uint64_t s_lastRepMs = 0;
            uint64_t nowT = static_cast<uint64_t>(GetTickCount64());
            if (nowT - s_lastRepMs < 50) return;   // ~20Hz cap on actor replication only (counters already ticked)
            s_lastRepMs = nowT;
        }

        // [1.14.7 FIX 2026-10-08] The hand-written actor loop below (EMERGENCY_LEGACY_REPLICATION.flag only)
        // still calls 1.12 RVAs that are not function entries on 1.14.7: 0x0394B400, 0x03E9ABF0,
        // 0x03D47AC0, 0x03B80890, 0x03B7B470. Running it would jump into the middle of unrelated code, so
        // it is refused until each target is remapped and its ABI verified.
        // tools/rva-port-audit.py (static signature transfer through the 1.12 exe): 0x03D47AC0 -> 0x03F62E90
        // and 0x03B7B470 -> 0x03D8A2D0 (both bidirectionally verified); 0x03B80890 -> function 0x03D8FB20
        // (interior anchor, unverified); 0x0394B400 and 0x03E9ABF0 not found. The graph drive replaced this loop.
        {
            static bool s_loggedLegacyRefused = false;
            if (!s_loggedLegacyRefused) {
                s_loggedLegacyRefused = true;
                NetLog(LastPort, "[LegacyReplication] refused on 1.14.7: the custom actor loop still uses "
                    "unmapped 1.12 RVAs (0x0394B400, 0x03E9ABF0, 0x03D47AC0, 0x03B80890, 0x03B7B470)");
            }
            return;
        }

        std::vector<std::pair<AActor*, bool>> Actors = BuildConsiderList(World, NetDriver);

        UNetConnection** Connections = *reinterpret_cast<UNetConnection***>((uintptr_t)NetDriver + 0x90);
        int32_t ConnectionCount = *reinterpret_cast<int32_t*>((uintptr_t)NetDriver + 0x98);
        int32_t ConnectionMax = *reinterpret_cast<int32_t*>((uintptr_t)NetDriver + 0x9C);

        if (!IsSanePointerArray(Connections, ConnectionCount, ConnectionMax, 1024)) {
            NetLog(LastPort, "[TickNetworking] Invalid ClientConnections array; skipping");
            return;
        }

        // [2026-07-16] REVERSE_CONNECTION_ORDER: iterate connections back-to-front when set.
        const bool ReverseConns = ReverseConnectionOrder();
        { static bool s_loggedRepOrder = false; if (!s_loggedRepOrder) { s_loggedRepOrder = true; NetLog(LastPort, std::string("[RepOrder] reverse=") + (ReverseConns ? "1" : "0")); } }
        for (int32_t ci = 0; ci < ConnectionCount; ++ci) {
            int32_t ConnectionIndex = ReverseConns ? (ConnectionCount - 1 - ci) : ci;
            UNetConnection* Connection = Connections[ConnectionIndex];
            if (!IsReadablePointer(Connection, 0x140)) {
                continue;
            }

            if (!Connection->OwningActor || *(uint32_t*)((uintptr_t)Connection + 0x134) != 3)
                continue;

            // [2026-07-16] Per-connection [RepActor] logging throttle (~1/sec per connection index).
            bool doRepActorLog = false;
            {
                static uint64_t s_repActorLogMs[16] = { 0 };
                uint64_t rn = static_cast<uint64_t>(GetTickCount64());
                int slotIdx = (ConnectionIndex >= 0 && ConnectionIndex < 16) ? ConnectionIndex : 0;
                if (rn - s_repActorLogMs[slotIdx] > 1000) { s_repActorLogMs[slotIdx] = rn; doRepActorLog = true; }
            }

            // [1.12.0 DIAG 2026-07-10] Actor-loop instrumentation. Only log once per second.
            static std::atomic<uint64_t> s_lastLoopLogMs{0};
            uint64_t nowMs = static_cast<uint64_t>(GetTickCount64());
            bool doLoopLog = (nowMs - s_lastLoopLogMs.load(std::memory_order_relaxed)) > 1000;
            if (doLoopLog) s_lastLoopLogMs.store(nowMs, std::memory_order_relaxed);

            if (doLoopLog) {
                NetLog(LastPort, "[TickNetLoop] conn=" + std::to_string((uintptr_t)Connection)
                    + " owningActor=" + std::to_string((uintptr_t)Connection->OwningActor)
                    + " actors.size=" + std::to_string(Actors.size()));
            }

            int actorsProcessed = 0;
            int channelsFound = 0;
            int channelsCreated = 0;
            int channelsFailed = 0;
            int channelsWithActor = 0;

            for (auto& _considerEntry : Actors) {
                AActor* Actor = _considerEntry.first;
                bool bActorPersistent = _considerEntry.second;
                actorsProcessed++;

                // [2026-07-16] LEVEL_VISIBILITY_GATE: skip streamed-sublevel actors the client has not
                // acked visible (native ServerReplicateActors does this; the manual loop did not, so it
                // shipped e.g. ramsgate_01_katplaza WorldSettings early -> client SerializeNewActor
                // Actor:None + broken P1 sublevel interaction). Always keep the ownership-critical actors
                // (PlayerController / Pawn / inventory / loadout); native handles the streamed rest.
                if (LevelVisibilityGate() && !bActorPersistent && Actor->Class) {
                    std::string _cn = Actor->Class->GetName();
                    bool _critical = Actor->IsA(APlayerController::StaticClass()) || Actor->IsA(APawn::StaticClass())
                        || _cn.find("inventory") != std::string::npos || _cn.find("loadout") != std::string::npos;
                    if (!_critical) {
                        static std::atomic<int> s_skipLog{ 0 };
                        if (s_skipLog.fetch_add(1, std::memory_order_relaxed) < 80) {
                            NetLog(LastPort, "[RepSkipInvisible] connIdx=" + std::to_string(ConnectionIndex)
                                + " conn=" + std::to_string((uintptr_t)Connection)
                                + " class=" + _cn + " actor=" + Actor->GetFullName());
                        }
                        continue;
                    }
                }

                // [2026-07-16] HYBRID_REPLICATION: let native ServerReplicateActors own PlayerController
                // + Pawn (correct ownership/NetGUID ordering for the late joiner); the custom loop keeps
                // only the actors native does not deliver here (inventory/loadout/etc).
                if (HybridReplication() && (Actor->IsA(APlayerController::StaticClass()) || Actor->IsA(APawn::StaticClass()))) {
                    continue;
                }
                if ((Actor->IsA(APlayerController::StaticClass()))) {
                    if (Actor != Connection->OwningActor) {
                        continue;
                    }
                    else {
                        //*(uint8_t*)((uintptr_t)Actor + 0xA14) = 0x1;
                        Connection->ViewTarget = ((APlayerController*)Actor)->GetViewTarget();

                        if (!Connection->ViewTarget)
                            std::cout << "NULL VIEWTARGET BAD THINGS WILL HAPPEN" << std::endl;

                        reinterpret_cast<void(*)(APlayerController*)>(BaseAddress + 0x03E9ABF0)((APlayerController*)Actor);
                    }
                }

                //

                UActorChannel* ActorChannel = GetActorChannelForConnectionAndActor(Connection, Actor);
                if (ActorChannel) channelsFound++;

                bool bJustCreated = false;
                if (!ActorChannel) {
                    // [1.12.0 FIX 2026-07-10] RVA was 0x0236AF60 which is FUN_14236AF60 =
                    // TaskGraph wait routine (verified via string xref "Recursive waits are not
                    // allowed in single threaded mode." at 0x1455599D0). Called with
                    // (Connection, &name, ...) as if it were CreateChannelByName — Connection
                    // landed in TaskGraph 'this', RAX returned garbage read back as
                    // UActorChannel*. Every pawn channel-create failed silently.
                    //
                    // Real UNetConnection::CreateChannelByName is at RVA 0x03D47AC0 (verified
                    // via string xref "No free channel could be found in the channel list ...
                    // net.MaxChannelSize." at 0x145A92800, inside FUN_143D47AC0).
                    //
                    // Flag = 2 (verified via native callsite disassembly at 143f05d66:
                    //   LEA R8D, [RDI + 0x2]     ← R8 (3rd arg / flag) = 2
                    //   CALL 0x143d47ac0         ← CreateChannelByName
                    // Earlier attempt using 1<<0 = 1 was wrong. Native uses 2.
                    ActorChannel = reinterpret_cast<UActorChannel * (*)(UNetConnection*, FName*, unsigned int, int)>(BaseAddress + 0x03D47AC0)(Connection, &name, 1 << 1, -1);
                    if (ActorChannel) { channelsCreated++; bJustCreated = true; }
                    else channelsFailed++;

                    if (ActorChannel) {
                        reinterpret_cast<void(*)(UActorChannel*, AActor*, unsigned int)>(BaseAddress + 0x03B80890)(ActorChannel, Actor, 0);
                    }
                }

                if (ActorChannel && ActorChannel->Actor) {
                    channelsWithActor++;
                    // [1.12.0 FIX 2026-07-10] Removed manual "ActorChannel+0x90 |= 2" write.
                    // Native replication does not do this. The flag is set by proper channel
                    // construction via CreateChannelByName. Manually forcing it after the fact
                    // could put the channel in an incorrect state (e.g., set bClosing or a
                    // similar dirty-flag that suppresses replication).
                    //
                    // ReplicateActor: 0x03B7B470 (verified via string xref "ReplicateActor
                    // called while already replicating!" at 0x145A2C140).
                    // [Phase 0g 2026-07-12] The manual loop drives ReplicateActor, where bNetOwner is
                    // computed as (Actor->GetNetConnection()[vtable+0x4C0] == channel->Connection). Log the
                    // ACTUAL input for the loadout: if GetNetConnection() != this connection, COND_OwnerOnly
                    // is filtered right here -> trace AArchonLoadout::GetNetConnection resolution.
                    if (Actor->Class && Actor->Class->GetName() == "bp_archon_loadout_C") {
                        static std::atomic<uint64_t> s_loLast{ 0 };
                        uint64_t lm = static_cast<uint64_t>(GetTickCount64());
                        if (lm - s_loLast.load(std::memory_order_relaxed) > 1000) {
                            s_loLast.store(lm, std::memory_order_relaxed);
                            void* netConn = nullptr;
                            uintptr_t vt = *reinterpret_cast<uintptr_t*>(Actor);
                            if (IsReadablePointer(reinterpret_cast<void*>(vt + 0x4C0), 8))
                                netConn = reinterpret_cast<void*(*)(void*)>(*reinterpret_cast<void**>(vt + 0x4C0))(Actor);
                            void* chanConn = *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(ActorChannel) + 0x28);
                            NetLog(LastPort, "[LoadoutRepFlags] loadout=" + Actor->GetName()
                                + " GetNetConn(+4C0)=" + std::to_string(reinterpret_cast<uintptr_t>(netConn))
                                + " thisConn=" + std::to_string(reinterpret_cast<uintptr_t>(Connection))
                                + " chanConn(+28)=" + std::to_string(reinterpret_cast<uintptr_t>(chanConn))
                                + " bNetOwner=" + std::to_string(netConn == reinterpret_cast<void*>(Connection) ? 1 : 0));

                            // [2026-07-16] Server loadout state snapshot: is this player's loadout FINAL
                            // before P1 disconnects, or does it only complete at the disconnect edge?
                            // (splits server-producer vs replication). Offsets: LoadoutSlotDataArray
                            // TArray @+0x670 (Data / Num@+0x678 / Max@+0x67C), ActiveLoadoutSlotIndex int32 @+0x680.
                            if (IsReadablePointer(Actor, 0x688)) {
                                uintptr_t lo = reinterpret_cast<uintptr_t>(Actor);
                                NetLog(LastPort, "[LoadoutState] loadout=" + Actor->GetName()
                                    + " conn=" + std::to_string(reinterpret_cast<uintptr_t>(Connection))
                                    + " bNetOwner=" + std::to_string(netConn == reinterpret_cast<void*>(Connection) ? 1 : 0)
                                    + " slotData=" + std::to_string(*reinterpret_cast<uintptr_t*>(lo + 0x670))
                                    + " slotNum=" + std::to_string(*reinterpret_cast<int32_t*>(lo + 0x678))
                                    + " slotMax=" + std::to_string(*reinterpret_cast<int32_t*>(lo + 0x67C))
                                    + " activeIdx=" + std::to_string(*reinterpret_cast<int32_t*>(lo + 0x680)));
                            }
                        }
                    }
                    bool wroteData = reinterpret_cast<bool(*)(UActorChannel*)>(BaseAddress + 0x03B7B470)(ActorChannel);
                    // [2026-07-16] Log EVERY newly-created channel (initial bunch) for this connection,
                    // regardless of class, edge-triggered on creation (bounded). wroteData=0 on a
                    // just-created channel = the server opened the channel but wrote NO initial bunch ->
                    // client SerializeNewActor "Actor: None". Also lets us compare P1 vs P2 channel sets
                    // (an actor P1 gets but P2 never does breaks P2's Owner/PC references). Send-side split,
                    // no ChIndex/NetGUID needed.
                    if (bJustCreated && Actor->Class) {
                        void* ownerPtr = nullptr; std::string ownerName = "null";
                        if (IsReadablePointer(Actor, 0xE8)) {
                            ownerPtr = *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(Actor) + 0x00E0);
                            if (ownerPtr && IsReadablePointer(ownerPtr, 0x40)) ownerName = reinterpret_cast<AActor*>(ownerPtr)->GetName();
                        }
                        NetLog(LastPort, "[ChannelCreate] connIdx=" + std::to_string(ConnectionIndex)
                            + " conn=" + std::to_string((uintptr_t)Connection)
                            + " class=" + Actor->Class->GetName()
                            + " wroteData=" + std::to_string(wroteData ? 1 : 0)
                            + " owner=" + std::to_string((uintptr_t)ownerPtr) + "/" + ownerName
                            + " actor=" + Actor->GetFullName());
                    }
                    // [2026-07-16] Per-connection ReplicateActor result for join-relevant actors, keyed by
                    // connection + object. Answers: does the stuck joiner's connection get wroteData=1 for its
                    // PlayerController / pawn / inventory / loadout before the other player leaves? Throttled
                    // ~1/sec per connection. initial=channel-created-this-pass; owner=GetNetConnection==thisConn.
                    if (doRepActorLog && Actor->Class) {
                        std::string cn = Actor->Class->GetName();
                        if (cn.find("player_controller") != std::string::npos || cn.find("PlayerCharacter") != std::string::npos
                            || cn.find("inventory") != std::string::npos || cn.find("loadout") != std::string::npos) {
                            void* netConn = nullptr;
                            uintptr_t vt = *reinterpret_cast<uintptr_t*>(Actor);
                            if (IsReadablePointer(reinterpret_cast<void*>(vt + 0x4C0), 8))
                                netConn = reinterpret_cast<void*(*)(void*)>(*reinterpret_cast<void**>(vt + 0x4C0))(Actor);
                            NetLog(LastPort, "[RepActor] connIdx=" + std::to_string(ConnectionIndex)
                                + " conn=" + std::to_string((uintptr_t)Connection)
                                + " actor=" + Actor->GetName() + " class=" + cn
                                + " initial=" + std::to_string(bJustCreated ? 1 : 0)
                                + " owner=" + std::to_string(netConn == reinterpret_cast<void*>(Connection) ? 1 : 0)
                                + " wroteData=" + std::to_string(wroteData ? 1 : 0));
                        }
                    }
                }
            }

            if (doLoopLog) {
                NetLog(LastPort, "[TickNetLoop] processed=" + std::to_string(actorsProcessed)
                    + " existing=" + std::to_string(channelsFound)
                    + " created=" + std::to_string(channelsCreated)
                    + " failed=" + std::to_string(channelsFailed)
                    + " withActor=" + std::to_string(channelsWithActor));
            }
        }
    }
}