Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
SWBF2-Kyber-Research
#1


first fun decided I'm going to reverse-engineer and released publicly a method that someone made for turning frostbite game clients into Game Servers. so im doing a step-by-step breakdown of disassembly and reverse engineering of the Star Wars Battlefront hook for server and to client by BattleDash


First Step acquiring decrypted and unpacked dll - done and fixed in Port tables and PE header
DLL: https://github.com/g91/SWBF2-Kyber-Resea...r_dump.dll

and ida pro disassembly to c of the dll please note that this is pseudocode
https://github.com/g91/SWBF2-Kyber-Resea...dump.dll.c

interesting code Snippets that I found so far
Code:
    sub_7FFD190FD630(0x140238C60i64, (__int64)sub_7FFD190E7950);
    sub_7FFD190FD630(0x140A874C0i64, (__int64)sub_7FFD190E6A90);
    sub_7FFD190FD630(0x1454D5900i64, (__int64)sub_7FFD190E6B30);
    sub_7FFD190FD630(0x1454D59E0i64, (__int64)sub_7FFD190E6BD0);
    sub_7FFD190FD630(0x141C1EFD0i64, (__int64)sub_7FFD190E7510);
    sub_7FFD190FD630(0x1484A9320i64, (__int64)sub_7FFD190E84A0);
    sub_7FFD190FD630(0x140BE9C10i64, (__int64)sub_7FFD190E76B0);
    sub_7FFD190FD630(0x14193DA20i64, (__int64)sub_7FFD190E69D0);
    sub_7FFD190FD630(0x1401F7BD0i64, (__int64)sub_7FFD190E8D60);
    sub_7FFD190FD180();
   
   
    sub_7FFD190E5180("[Kyber] [%s] Initialized Game Hooks.\n", "Info");



    if ( *(_DWORD *)qword_7FFD193E4230 == 1 )
    {
      v29 = 441;
      v30 = 0;
      sub_7FFD19104880(0x140A92F71i64, &v29, 5i64);
      v22[0] = -112;
      sub_7FFD19104880(0x140A92F76i64, v22, 1i64);
      sub_7FFD19104880(0x140A92F77i64, v22, 1i64);
    }
    sub_7FFD190E5180("[Kyber] [%s] Initialized Game Patches.\n", "Info");
    *(_BYTE *)(qword_7FFD193E4230 + 16) = 1;
[Image: FiHzeWA.jpg]
Reply
#2
info from BattleDash BattleDashDattfdhfgdhjBattleDash

Code:
The gist is that you need to hook fb::Server::<cinit> and fb::Server::start, and do a byte patch in Client::startServer.

In the Server ctor hook you set isLocalHost to false on ServerSpawnInfo, and in the Server start hook you replace the fb::ISocketManager instance with a new instantiation of your own recreated implementation of fb::SocketManager, as the client by default uses fb::MemorySocketManager which won't even listen on a port.

Then you just set Game.Level and Game.DefaultLayerInclusion and put the client in ClientState_Startup

initNetwork isn't needed, just Server::start and the Server constructor. initNetwork has other uses. there's no easy way to share fb::SocketManager, you need to recreate the entire structure of it as well as UDPSocket, both of which are quite large. You may be able to do it from the bf3 pdbs, but it'd be easier to take a look through the Frostbite source


manu157
Code:
in bf4 game Server::start = 0x1409D72

void __fastcall Hooked_sub_1409D72(ServerSpawnInfo& info, ServerSpawnOverrides& spawnOverrides, ISocketManager* socketManager)
// info.isLocalHost
// info.isMenu

fb::Client::initNetwork = 0x1408217
fb::Client::initNetwork(fb::Client *this, bool isSingleplayer, bool localHost, bool isCoop, bool isHosted)
[Image: FiHzeWA.jpg]
Reply
#3
SocketManager cpp
Code:
namespace fb
{

SocketManager::SocketManager(MemoryArena& arena)
:    m_sockets(arena)
{
}

SocketManager::~SocketManager()
{
}

void SocketManager::destroy()
{
    delete this;
}

void SocketManager::close( IUDPSocket* socket )
{
    if (!m_sockets.empty())
        m_sockets.remove(socket);
}


ISocket*
SocketManager::listen(const char* name, bool blocking)
{
    ScopedPtr<UDPSocket> sock(new (getArena()) UDPSocket(this));

    ISocketAddress address;
    if (!networkNameToAddress(name, &address))
    {
        FB_WARNING_FORMAT("Failed to convert '%s' to an address.", name);
        return nullptr;
    }
    
    if (!sock->listen(address, blocking))
    {
        FB_WARNING_FORMAT("Couldn't listen to '%s'", name);
        return nullptr;
    }

    m_sockets.push_back(sock);
    return sock.detach();
}

ISocket *
SocketManager::connect(const char* addressString, bool blocking)
{
    if (addressString[0] == '\0')
        return nullptr;

    ScopedPtr<UDPSocket> sock(new (getArena()) UDPSocket(this));

    ISocketAddress address;
    if (!networkNameToAddress(addressString, &address))
        return nullptr;

    if (!sock->connect(address, blocking))
        return nullptr;

    m_sockets.push_back(sock);
    return sock.detach();
}


ISocket*
SocketManager::createSocket()
{
    ScopedPtr<UDPSocket> sock(new (getArena()) UDPSocket(this));

    if (!sock->create())
    {
        return nullptr;
    }

    m_sockets.push_back(sock);
    return sock.detach();
}

SocketManager h
Code:
namespace fb
{

/// @addtogroup Network
/// @ingroup Core
/// @{

class IUDPSocket;

class SocketManager : public ISocketManager, public IUDPSocketCreator
{
public:

    FB_CORE_API SocketManager(MemoryArena& arena);
    __forceinline MemoryArena& getArena() { return *m_sockets.get_allocator().get_arena(); }

    virtual void destroy() override;

    // IUDPSocketCreator
    virtual void close(IUDPSocket* socket) override;

    // UDP
    /// Creates a socket, bind it to a free port and
    /// makes a connection to the specified peer (ip + port)
    /// returns a pointer to the IUDPSocket interface if succeded or
    /// returns 0 if not
    virtual ISocket* connect(const char* address, bool blocking = false) override;
    virtual ISocket* listen(const char* address, bool blocking = false) override;
    virtual ISocket* createSocket() override;

protected:

    FB_CORE_API virtual ~SocketManager();

private:

    typedef eastl::list<IUDPSocket*> SocketList;
    SocketList m_sockets;
};

/// @}

}
[Image: FiHzeWA.jpg]
Reply
#4
MemorySocketManager cpp
Code:
namespace fb
{

MemorySocketManager::MemorySocketManager(MemoryArena& arena)
:    m_sockets(arena)
{
}

MemorySocketManager::~MemorySocketManager()
{
    FB_FATAL_ASSERT(m_sockets.empty());
}

void MemorySocketManager::destroy()
{
    delete this;
}

void MemorySocketManager::close(IUDPSocket* socket)
{
    if (!m_sockets.empty())
        m_sockets.remove( socket );
}

ISocket* MemorySocketManager::listen(const char* name, bool blocking)
{
    ScopedPtr<MemorySocket> sock(new (getArena()) MemorySocket(this));

    ISocketAddress address;
    if (!networkNameToAddress(name, &address))
    {
        FB_WARNING_FORMAT("Failed to convert '%s' to an address.", name);
        return nullptr;
    }

    if (!sock->listen(address, blocking))
    {
        FB_WARNING_FORMAT("Couldn't listen to '%s'", name);
        return nullptr;
    }

    m_sockets.push_back(sock);
    return sock.detach();
}

ISocket* MemorySocketManager::connect(const char* addressString, bool blocking)
{
    if (addressString[0] == '\0')
        return nullptr;

    ScopedPtr<MemorySocket> sock(new (getArena()) MemorySocket(this));

    ISocketAddress address;
    if (!networkNameToAddress(addressString, &address))
        return nullptr;

    if (!sock->connect(address, blocking))
        return nullptr;

    m_sockets.push_back(sock);
    return sock.detach();
}

ISocket* MemorySocketManager::createSocket()
{
    ScopedPtr<MemorySocket> sock(new (getArena()) MemorySocket(this));

    if (!sock->create())
        return nullptr;

    m_sockets.push_back(sock);
    return sock.detach();
}

}

MemorySocketManager h
Code:
namespace fb
{

class IUDPSocket;


class MemorySocketManager : public ISocketManager, public IUDPSocketCreator
{
public:
    FB_CORE_API MemorySocketManager(MemoryArena& arena);
    __forceinline MemoryArena& getArena() { return *m_sockets.get_allocator().get_arena(); }

    virtual void destroy() override;

    // IUDPSocketCreator
    virtual void close(IUDPSocket* socket) override;

    virtual ISocket* connect(const char* address, bool blocking = false) override;
    virtual ISocket* listen(const char* address, bool blocking = false) override;
    virtual ISocket* createSocket() override;

protected:
    virtual ~MemorySocketManager();

private:
    typedef eastl::list<IUDPSocket*> SocketList;
    SocketList m_sockets;
};

}
[Image: FiHzeWA.jpg]
Reply
#5
Server cpp
Code:
namespace fb {

class DefaultServerConnectionCreator : public IServerConnectionCreator
{
public:
    virtual ~DefaultServerConnectionCreator() = default;

private:
    virtual ServerConnection* createServerConnection(const ServerConnectionCreationInfo& info) override
    {
        return new (FB_SERVER_ARENA) ServerConnection(info);
    }

    virtual void destroyServerConnection(ServerConnection* conn) override
    {
        deleteObject(FB_SERVER_ARENA, conn);
    }
};

class DefaultServerPeerCreator : public IServerPeerCreator
{
public:
    virtual ~DefaultServerPeerCreator() = default;

private:
    virtual ServerPeer* createServerPeer(const ServerPeerCreationInfo& info) override
    {
        return new (FB_SERVER_ARENA) ServerPeer(info);
    }

    virtual void destroyServerPeer(ServerPeer* peer) override
    {
        deleteObject(FB_SERVER_ARENA, peer);
    }
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

ServerSpawnOverrides::ServerSpawnOverrides(LevelSetup& ls)
:    levelSetup(ls)
,    socketManager(nullptr)
,   connectionCreator(nullptr)
,   peerCreator(nullptr)
{}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//$PONG: where how to init creators?
void
ServerCallbacks::createNetObjectCreators()
{
#if FB_USING(FB_NET_PONG_ENABLED)
    createGhostCreators(Realm_Server);
#endif
}
void
ServerCallbacks::destroyNetObjectCreators()
{
#if FB_USING(FB_NET_PONG_ENABLED)
    destroyGhostCreators(Realm_Server);
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
ServerCallbacks::destroyServer(Server* server)
{
    deleteObject(FB_SERVER_ARENA, server);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Server::Server(ServerSpawnInfo& info, ISocketManager* socketManager)
: Common(info.keepResources)
, m_playerManager(info.playerManager)
, m_loadInfo(static_cast<ServerLevelLoadInfo*>(info.loadInfo.get()))
, m_isLoading(false)
, m_destroyLevel(false)
, m_loadNextLevelRequested(false)
, m_isSinglePlayer(info.isSinglePlayer)
, m_isDedicated(info.isDedicated)
, m_peer(nullptr)
, m_destructionManager(nullptr)
, m_serverCallbacks(info.serverCallbacks)
, m_gameContext(nullptr)
, m_keepResources(info.keepResources)
, m_waitingToCleanResources(false)
, m_shouldQuit(false)
, m_level(nullptr)
, m_gameTime(nullptr)
, m_serverPort(0) // will be set in listen()
, m_serverPortRange(16)
, m_validLocalPlayersMask(info.validLocalPlayersMask)
, m_clientLoadingScreenReady(m_isDedicated)
, m_clientLoadingLevelDone(m_isDedicated)
, m_clientDestroyLevelDone(m_isDedicated)
, m_shouldLoadDoneDeferredRun(false)
, m_defaultSocketManager(nullptr)
, m_defaultConnectionCreator(nullptr)
, m_defaultPeerCreator(nullptr)
, m_peerCreator(nullptr)
#if FB_USING(FB_PROFILER)
, m_profiler(nullptr)
#endif
#ifdef FB_HAS_DEDICATED_SERVER
, m_serverSidePatchData(nullptr)
#endif
#if !defined(FB_RETAIL)
, m_numTickFrames(0)
, m_tickSumDeltaTime(0.f)
, m_maxTickDeltaTime(0.f)
#endif
{
    #if FB_USING(FB_ENTITY_TWEAK_MANAGER)
        LevelTweaker::allowTweaks(this, false);
    #endif

    FB_MEMORYTRACKER_SCOPE("server");

    FB_INFO_FORMAT("Creating server...");

    m_serverSettings = Settings<ServerSettings>();

    if (m_isDedicated)
    {
#ifdef FB_WIN32
        // Prevent server from swapping processors. This makes sure QueryPerformanceCounter
        // won't go backwards from call to call.
        // See http://msdn2.microsoft.com/en-us/library/bb173458.aspx for more info.
        uint affinityMask = m_serverSettings->getDedicatedServerCpu();
        if (affinityMask != ~0U)
        {
            uint defaultAffinityMask = 1U << Environment::getCurrentProcessorNumber();
            affinityMask = (1U << affinityMask);
            if (!Thread::setThreadAffinity(affinityMask))
                Thread::setThreadAffinity(defaultAffinityMask);
        }
#endif

        // This looks a bit weird, but if you are running a dedicated server these resources
        // should not be in the data, however if you run Win32 data you need to ignore these.
        const Platform targetPlatform = getPlatformFromName(getDataPlatformPathName());
        if (targetPlatform != DedicatedServer)
            RESOURCEMANAGER_ADDIGNOREDRESOURCETYPE_NAME("ImpulseResponse");

        // Load the game configuration's bundle.
        const char* staticBundle = g_runtimeSettings.getStaticBundleName().c_str();
        FB_FATAL_ASSERT(*staticBundle);

        FB_INFO_FORMAT("Loading bundle %s", staticBundle);
        AsyncResultHandle result = ResourceManager_beginLoadData(ResourceCompartment_Static, &staticBundle, 1);
        // force load static animation data for dedicated servers

        // Wait 20s before asserting
        uint retriesLeft = 4000;
        while (!ResourceManager_waitBundleOperation(result, 5))
        {
            // We need to beat main heartbeat here to not have monitor timeouts (I don't like this loop, should be part of state machine instead)
            HeartbeatMonitor::beat("Main");
            FB_ASSERT_FORMAT(retriesLeft--, "Stuck on waiting for bundle load to complete (%s). It's likely somebody is trying to pull in chunks. That is not supported at this stage.", staticBundle); (void)retriesLeft;
            ResourceManager_forceUpdate();
        }

        if (!ResourceManager_endLoadData(result))
            return;

        Level::staticPostStaticBundleLoad();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // ONLY PUT STATIC SERVICES THAT RUN REGARDLESS IF THE SERVER IS STARTED OR NOT HERE
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    m_gameContext = ServerGameContext::context();
    GameContextScope gameContextScope(m_gameContext);
    m_gameContext->setServer(this);
#ifdef FB_HAS_DEDICATED_SERVER
    loadServerSidePatchData(m_gameContext);
#endif

    createSchematicsInstance(m_gameContext, FB_SERVER_ARENA);

    m_gameContext->messageManager()->registerMessageListener(MessageCategory_Server, this);
    m_gameContext->messageManager()->registerMessageListener(MessageCategory_ServerPeer, this);

    m_gameContext->setServerPlayerManager(m_playerManager);

    m_destructionManager = new (FB_SERVER_ARENA)ServerDestructionStateManager(FB_SERVER_ARENA, false);
    m_gameContext->setDestructionManager(m_destructionManager);
    if (info.damageArbitrator)
    {
        m_gameContext->setDamageArbitrator(info.damageArbitrator);
        if (!info.isSinglePlayer)
            m_gameContext->damageArbitrator()->enableArbitration();
    }

    m_activeDamageArbitrator = info.damageArbitrator;

    const char* serverName = 0;
    const String& serverNameSettings = Settings<ServerSettings>()->getServerName();
    serverName = serverNameSettings.empty()?Environment::getFullHostIdentifier():serverNameSettings.c_str();
    setServerName(ExecutionContext::getOptionValue("name", serverName));

    LevelSetup firstLevelSetup;
    firstLevelSetup.setInclusionOptions(Settings<GameSettings>()->getDefaultLayerInclusion().c_str());

    if (!info.levelSetup.getName().empty())
        firstLevelSetup = info.levelSetup;
    else
        applyCmdline(firstLevelSetup);

    ServerSpawnOverrides overrides(firstLevelSetup);

    for (RuntimeModuleVec::iterator i = info.runtimeModules->begin(), e = info.runtimeModules->end(); i != e; ++i)
    {
        RuntimeModule* runtimeModule = *i;
        if (runtimeModule->isKindOf<GameModule>())
        {
            static_cast<GameModule*>(runtimeModule)->applyServerSpawnOverrides(info, &overrides);
        }
    }

    // take over any savegame data
    m_nextSaveData.swap(info.saveData);
    
    if (m_nextSaveData.empty())
        FB_ASSERT(!firstLevelSetup.getIsSaveGame() && !firstLevelSetup.getHasPersistentSave());
    else
        FB_ASSERT(firstLevelSetup.getIsSaveGame() || firstLevelSetup.getHasPersistentSave());

    const char* saveGame = ExecutionContext::getOptionValue("saveGame", 0);
    if (saveGame)
        g_serverSaveGameManager.debugLoad(saveGame, firstLevelSetup, false);

    m_nextLevelSetup = firstLevelSetup;

    if (!overrides.connectionCreator)
    {
        m_defaultConnectionCreator = new (FB_SERVER_ARENA) DefaultServerConnectionCreator();
        overrides.connectionCreator = m_defaultConnectionCreator;
    }

    if (!overrides.peerCreator)
    {
        m_defaultPeerCreator = new (FB_SERVER_ARENA) DefaultServerPeerCreator();
        overrides.peerCreator = m_defaultPeerCreator;
    }

    m_peerCreator = overrides.peerCreator;

    //$PONG: setup NetObjectCreators
    m_serverCallbacks->createNetObjectCreators();

    start(info, overrides, socketManager);

#if FB_USING(FB_PROFILER)
    if (!m_isDedicated)
        m_profiler = new (FB_SERVER_ARENA) Profiler(false, "Server");
#endif

    FB_INFO_FORMAT("Server created");
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Server::~Server()
{
    FB_PROFILER_SCOPE_TIMER(Server_ServerDtor);
    FB_INFO_FORMAT("Destroying Server...");

#if FB_USING(FB_PROFILER)
    deleteObject(FB_SERVER_ARENA, m_profiler);
    m_profiler = nullptr;
#endif

    destroySchematicsInstance(m_gameContext, FB_SERVER_ARENA);

    bool hadLevel = m_level != nullptr;
    if (hadLevel)
    {
        g_serverGameWorld->endAsynchronousQueries();
        g_serverGameWorld->resetAsynchronousQueries();
    }

    // Make sure that this gets done first of all...
    m_peer->clearConnections(true);

    if (m_loadLevel)
    {
#if defined(FB_DEBUGMENU_ENABLE)
        g_inGameMenu->serverDestroyLevel();
#endif
        internalDestroyLoadLevel();
    }

    FB_FATAL_ASSERT(m_loadInfo);
    m_loadInfo->reset();

    if (m_level)
    {
#if defined(FB_DEBUGMENU_ENABLE)
        g_inGameMenu->serverDestroyLevel();
#endif
        m_serverCallbacks->serverIsDestroyingLevel();
        m_level->destroy();
        FB_ASSERT(m_level->refCount() == 1);
        m_level = nullptr;
        m_serverCallbacks->serverHasDestroyedLevel();
    }

    deleteObject(FB_SERVER_ARENA, m_gameTime);

    m_playerManager->unregisterNetObject(m_peer->serverGhostManager());
    m_gameContext->setServerPlayerManager(nullptr);
    deleteObject(FB_SERVER_ARENA, m_playerManager);

    m_gameContext->setDamageArbitrator(nullptr);

    deleteObject(FB_SERVER_ARENA, m_destructionManager);
    m_gameContext->setDestructionManager(nullptr);
    m_destructionManager = nullptr;

    m_peerCreator->destroyServerPeer(m_peer);
    m_gameContext->setServerPeer(nullptr);

    deleteObject(FB_SERVER_ARENA, m_defaultConnectionCreator);
    deleteObject(FB_SERVER_ARENA, m_defaultPeerCreator);

    //$PONG: destroy creators
    m_serverCallbacks->destroyNetObjectCreators();

    if (m_defaultSocketManager)
    {
        m_defaultSocketManager->destroy();
        m_defaultSocketManager = nullptr;
    }

    m_gameContext->messageManager()->unregisterMessageListener(MessageCategory_ServerPeer, this);
    m_gameContext->messageManager()->unregisterMessageListener(MessageCategory_Server, this);

    m_gameContext->messageManager()->clearMessageQueue();

    #if FB_USING(FB_ENTITY_TWEAK_MANAGER)
    LevelTweaker::allowTweaks(this, true);
    #endif

#if defined(FB_HAS_DEDICATED_SERVER)
    if (m_serverSidePatchData)
    {
        m_serverSidePatchData.release();
        m_gameContext->setServerSidePatchData(nullptr);
    }
#endif

    FB_INFO_FORMAT("Server destroyed");
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::start(ServerSpawnInfo& info, ServerSpawnOverrides& spawnOverrides, ISocketManager* socketManager)
{    
    // we don't own the socketManager passed in the ctor. Clear it here    
    if (!socketManager)
    {
        socketManager = spawnOverrides.socketManager;
    }    
    
    if (!socketManager)
    {
        FB_WARNING_FORMAT("No socket manager was provided by game modules -- defaulting to memory sockets");
        m_defaultSocketManager = new (FB_SERVER_ARENA) MemorySocketManager(FB_SERVER_ARENA);
        socketManager = m_defaultSocketManager;
    }

    bool isLocalOnly = m_isSinglePlayer || info.isLocalHost || isGameRunningInGameView();

    ServerPeerCreationInfo sci = {m_isSinglePlayer, m_isDedicated, false, isLocalOnly, info.isMenu, spawnOverrides.connectionCreator, m_validLocalPlayersMask, m_destructionManager };
    m_peer = m_peerCreator->createServerPeer(sci);

    const String& serverPassword = m_serverSettings->getServerPassword();
    if (!serverPassword.empty())
    {
        m_peer->setPassword(serverPassword.c_str());
    }

    listen(ExecutionContext::getOptionValue("listen", ""));

    bool useSocketDebug = false;
#ifndef FB_RETAIL
    useSocketDebug = !m_isSinglePlayer && m_serverSettings->getUseDebugSocket() && !isGameRunningInGameView();
#endif

    bool ok = true;
    uint tries = 0;
    uint port = m_serverPort;
    FB_ASSERT_DESC(port != 0, "Must set a specific port for the server to use.");

    if (m_serverAddress.empty() && isLocalOnly)
        m_serverAddress = "127.0.0.1";

    do
    {
        StringBuilder<256> fullAddress;
        fullAddress << m_serverAddress << ":" << port;

        FB_INFO_FORMAT("Attempting listen to %s", fullAddress.c_str());

        uint titleId = fb_lexical_cast<uint>(Settings<NetworkSettings>()->getTitleId());

        m_serverPort = port;
        FB_INFO_FORMAT("Network.ProtocolVersion: %u gameProtocol: %u", Settings<NetworkSettings>()->getProtocolVersion(), getGameProtocolVersion());
        ok = m_peer->init(
            socketManager,
            fullAddress.c_str(),
            titleId,
            getGameProtocolVersion(),
            useSocketDebug);

        if (!ok)
        {
            ++tries;
            if (Settings<NetworkSettings>()->getIncrementServerPortOnFail())
            {
                ++port;
            }
            else
            {
                // if we are forced to use a port we sleep awhile between tries to give the port time to free up
                Thread::sleep(100);
            }
        }


    } while (!ok && tries < m_serverPortRange);

    FB_INFO_FORMAT("Successful.");

    if (!ok)
    {
        FB_ERROR_FORMAT("Failed trying to listen to %s:%u. Cannot recover from this.", m_serverAddress.c_str(), m_serverPort);
        FB_BREAK;
    }

    m_peer->postInit();
    m_gameContext->setServerPeer(m_peer);

    m_playerManager->registerNetObject(m_peer->serverGhostManager());

    m_gameTime = new (FB_SERVER_ARENA) GameTime(info.tickFrequency);
    m_gameContext->setGameTime(m_gameTime);

    m_peer->listen(true);
    m_peer->host(true);

    ServerStartedMessage startedMsg;
    startedMsg.setIsDedicated(m_isDedicated);
    m_gameContext->messageManager()->executeMessage(startedMsg);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::staticInit(bool isDedicatedServerData)
{
    FB_PROFILER_SCOPE_TIMER(Server_staticInit);


    // Finalize set of networkable messages registered during static initialization.
    freezeNetworkableMessages();

    // This freezes registration of ghost factory functions registered during static initialization:
    freezeGhostTypes();

    freezeGhostCreators();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::staticExit()
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::create(ServerSpawnInfo& info)
{
    g_serverSaveGameManager.initialize(getProjectSaveVersion(), getProjectMinimumSaveVersion());
    getServerSubLevelManager().startServer();
    requestStartLoadLevel();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void
Server::shutdown()
{
    FB_INFO_FORMAT("Server::shutdown()");

    if (m_loadLevel)
    {
        FB_FATAL_ASSERT(m_loadInfo != nullptr);
        m_loadLevel->abortLoad(*m_loadInfo);
    }

    if (m_isDedicated)
        requestStop();
    else
        m_shouldQuit = true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::destroy()
{
    FB_FATAL_ASSERT(m_keepResources || !ResourceManager_isInUse(ResourceCompartment_Game));

    //    Ensure the ServerSaveGame manager can clear the message listeners that exist on the this, the
    //    ServerGameContext, before it is destroyed!
    g_serverSaveGameManager.deinitialize();

    getServerSubLevelManager().destroyServer();

    ServerGameContext::context()->messageManager()->executeMessage(ServerStoppedMessage());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::update(const UpdateParameters& params)
{
    // Set update thread to allow write access to entity system
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);

    // Server update runs single-threaded so there should be no issue in granting a
    // global WorldWrite access on the server world to its update.
    PhysicsAccessScope physicsAccessScope(Realm_Server, PhysicsAccessScope::WorldWrite);

    GameContextScope contextScope(ServerGameContext::context());
    updateGame(params);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePassPreFrame(const UpdateParameters& params)
{
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);
#if FB_USING(FB_PROFILER)
    if (m_profiler)
        m_profiler->startGlobalTimer();
#endif

    m_gameTime->advance(params.simulationDeltaTime.toSecondsAsFloat(), params.virtualDeltaTicks);
    const float deltaTime = params.simulationDeltaTime.toSecondsAsFloat();
    FB_ASSERT_FORMAT(deltaTime >= 0.0f, "deltaTime is %f", deltaTime);

    constructSimUpdateInfo(params);
    
    g_dilationManager[Realm_Server]->computeDilationValues(deltaTime, m_gameContext->gameTime()->time(), m_gameContext->gameTime()->ticks());

    m_gameContext->messageManager()->executeMessageQueue(params.simulationDeltaTime);

    g_serverSaveGameManager.update();

    FB_CPU_SCOPE_TIMER("serverUpdate", Color32(255,128,0));
    FB_MEMORYTRACKER_SCOPE("serverUpdate");

    updateGameModulesPreFrame(params);
    updatePostGameModulesPreFrame(params);

    updateLevelPreFrame(params);

    m_peer->pulse(deltaTime, params.wallDeltaTime.toSecondsAsFloat());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updateGame(const UpdateParameters& params)
{
    updatePassPreFrame(params);
    
    // FIFA_BEGIN | DIVERGENCE | kperry | 15-02-2016 | Allow server to run single threaded on multiple job threads. This is safe for us. Not sure of other games. If it is then it should probably be sendback.
    PhysicsThreadMemoryScope scope(HavokSimulationThread);
    // FIFA_END

    updatePassPreSimUpdate(params);

    if (m_level)
    {
        m_level->getGameWorld()->simUpdate(m_simUpdateInfo);
    }

    updatePassPostSimUpdate(params);

    updatePassLevelPostFrame(params);
    updatePassPostFrame(params);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePassLevelPostFrame(const UpdateParameters& params)
{
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);
    updateLevelPostFrame(params);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePassPostFrame(const UpdateParameters& params)
{    
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);
    // transmit
    m_peer->sendPulse();

    updateLua();

    updateDedicatedServerStatus(params);

    updateStats(params);

    updateGameModulesPostFrame(params);
    updatePostGameModulesPostFrame(params);

    getServerSubLevelManager().updatePost(params.wallDeltaTime.toSecondsAsFloat());

    // TODO: This should move to GameModule inside Engine.Ant instead bug since GameModule is in Engine.Game.Common we can't
    if (ant::AnimationManager* serverAnimMgr = ServerGameContext::context()->animationManager())
        serverAnimMgr->postTick();

#if FB_USING(FB_PROFILER)
    if (m_profiler)
        m_profiler->stopGlobalTimer();
#endif

    const bool hadLevel = m_level != nullptr; //keep before updateLoadLevel call as this sets the level pointer

    updateLoadLevel(params);

    updateDestroyLevel(params);

    // game time reset must be performed exactly while the game clocks produce variable delta time ticks during loading
    if (!hadLevel)
        m_gameTime->set(0.0, 0);

    if (!shouldRunAsJob())
        processAsyncQueries();

    // If the server level hasn't loaded and we are running in single threaded mode
    // manually tick the resource system on the main thread.
    if (!isLoaded() && !shouldRunAsJob())
        ResourceManager_forceUpdate();

    Frame::end(FrameType_ServerSim);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePostGameModulesPreFrame(const UpdateParameters& params)
{
    FB_CPU_SCOPE_TIMER("srvPostGameModPreFrameUpd", Color32(255, 128, 0));
    onUpdatePostGameModulesPreFrame(params);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePostGameModulesPostFrame(const UpdateParameters& params)
{
    FB_CPU_SCOPE_TIMER("srvPostGameModPostFrameUpd", Color32(255, 128, 64));
    onUpdatePostGameModulesPostFrame(params);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Server::internalDestroyLoadLevel()
{
    m_serverCallbacks->serverIsDestroyingLevel();
    m_loadLevel->destroy();
    FB_ASSERT(m_loadLevel->refCount() == 1);
    m_loadLevel = nullptr;
    m_serverCallbacks->serverHasDestroyedLevel();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Extension point for derived classes
///
void
Server::onUpdatePostGameModulesPreFrame(const UpdateParameters& params)
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Extension point for derived classes
///
void
Server::onUpdatePostGameModulesPostFrame(const UpdateParameters& params)
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updateLoadLevel(const UpdateParameters& params)
{
    if (m_loadNextLevelRequested && !getServerSubLevelManager().isLoading() && !m_waitingToCleanResources && !m_destroyLevel && m_serverCallbacks->fromServerTryLoadLevel())
    {
        if (!ExecutionContext::getPatchFramework().getLCUService().isManualMode())
            ExecutionContext::getPatchFramework().getLCUService().patch(true);

        m_loadNextLevelRequested = false;
        startLoadLevel(params);
    }

    if (m_loadLevel)
    {
        FB_FATAL_ASSERT(m_level == nullptr);
        LoadProgress progress = m_loadLevel->updateLoad(*m_loadInfo);

        if (progress == LoadProgress_Aborted || progress == LoadProgress_Fail)
        {
            // if the load data can't be destroyed just yet, prevent cleanup (we should be allowed to reset next frame)
            if (m_loadInfo->loadSession && !m_loadInfo->loadSession->isResetAllowed())
                return;

            m_destroyLevel = true;
            m_keepResources = false;
            m_waitingToCleanResources = true;
            m_shouldLoadDoneDeferredRun = false;
            m_loadInfo->reset();
            
            internalDestroyLoadLevel();
            FB_INFO_FORMAT("ServerLevel load aborted.");
            return;
        }

        if (progress == LoadProgress_Active)
            return;

        m_isLoading = !m_loadLevel->data();

        finalizeLoadLevel();

        if (m_shouldQuit)
        {
            m_destroyLevel = true;
            m_waitingToCleanResources = !m_keepResources;
        }
    }

    if (ServerSaveGameLoadSession* loadSession = m_loadInfo->loadSession)
    {
        if (loadSession->updateLoad())
        {
            FB_ASSERT(m_shouldLoadDoneDeferredRun);
            m_shouldLoadDoneDeferredRun = false;
            m_level->loadDone(*m_loadInfo);
            m_loadInfo->loadSession->levelFinalized();
            m_loadInfo->reset();
        }
    }
}

void
Server::updateDestroyLevel(const UpdateParameters& params)
{    
    // don't destroy level if we're in the middle of saving
    if (m_destroyLevel && g_serverSaveGameManager.isSaving())
        return;


    if (m_destroyLevel && m_level && m_clientLoadingLevelDone)
    {
        Malloc::setAllocationMode(MallocAllocator::BestFit);

        #if FB_USING(FB_ENTITY_TWEAK_MANAGER)
        LevelTweaker::allowTweaks(this, false);
        #endif

        m_gameContext->messageManager()->executeMessage(ServerUnloadLevelMessage());

        #if defined(FB_DEBUGMENU_ENABLE)
        if (g_inGameMenu)
            g_inGameMenu->serverDestroyLevel();
        #endif

        m_serverCallbacks->serverIsDestroyingLevel();
        m_level->destroy();

        #if !defined(FB_FINAL)
        FB_FATAL_ASSERT_DESC(m_level->refCount() == 1, "Level has higher refcount than 1(" << m_level->refCount() << "), something is fishy. Buses left in sublevel " << EntityBusDebug(Realm_Server, m_level) << ". Entities left in Level " << EntityDebug(Realm_Server, m_level) << ". SmartRefs: "  << EntityOwnerDebugRefCount(m_level));
        #else
        FB_FATAL_ASSERT(m_level->refCount() == 1);
        #endif

        m_level = nullptr;
        m_serverCallbacks->serverHasDestroyedLevel();
        FB_SERVER_LOAD_TITLE("Level destroyed");
        m_clientLoadingScreenReady = m_isDedicated;

        m_gameContext->messageManager()->executeMessage(ServerLevelUnloadedMessage());
    }

    BundleManager& bundleMgr = getServerSubLevelManager().getBundleManager();

    if (m_destroyLevel && !m_level)
    {
        FB_SERVER_LOAD_TITLE("Waiting for game modules to clear");
        // If any module isn't done with the level yet, then wait until it is.
        FB_SERVER_LOAD_TITLE("Waiting for game modules");
        for (RuntimeModuleVec::iterator i = params.runtimeModules->begin(), e = params.runtimeModules->end(); i != e; ++i)
        {
            RuntimeModule* runtimeModule = *i;
            if (runtimeModule->isKindOf<GameModule>())
            {
                if (!static_cast<GameModule*>(runtimeModule)->isOkToUnloadServerLevelData())
                    return;
            }
        }
        
        // release all level specifics
        eastl::vector<ServerConnection*>::const_iterator connectionIt = m_peer->connections().begin();
        for (; connectionIt != m_peer->connections().end(); ++connectionIt)
            if (ServerConnection* connection = *connectionIt)
                connection->clearLevelDependencies();

        FB_SERVER_LOAD_TITLE("Waiting for client destroy");
        if (!m_clientDestroyLevelDone)
            return;

        FB_SERVER_LOAD_TITLE("Waiting for BundleManager to become empty");
        if (!bundleMgr.isEmpty())
            return;

        m_destroyLevel = false;

        // quitting during an unload needs to also clean kept resources
        if (m_shouldQuit)
            m_waitingToCleanResources = !m_keepResources;

        if (!m_waitingToCleanResources)
        {
            m_activeLevel.clear();
            FB_SERVER_LOAD_TITLE("Waiting for client(s) to be ready to load")
        }
    }

    if (!m_destroyLevel && m_waitingToCleanResources)
    {
        FB_ASSERT(m_destroyLevel == false);
        if (!m_clientDestroyLevelDone)
            return;
            
        if (m_shouldQuit)
        {
            FB_ASSERT_DESC(!m_keepResources, "Cannot keep resources if asked to wait for cleaning of resources.");

            // pinned bundles may also need to be unloaded as well
            bundleMgr.unpinAllBundles(true);
            FB_SERVER_LOAD_TITLE("Clearing pinned bundle data");

            if (!bundleMgr.isEmpty())
                return;
        }

        FB_SERVER_LOAD_TITLE("Destroying level data");
        CommonLevelDataParams destroyParams;
        destroyParams.levelName = m_activeLevel.c_str();
        destroyParams.needUI = !m_isDedicated;
        destroyParams.runtimeModules = params.runtimeModules;

        if (!destroyLevelData(destroyParams))
            return;

        m_activeLevel.clear();
        FB_SERVER_LOAD_TITLE("Waiting for client(s) to be ready to load");
        m_waitingToCleanResources = ResourceManager_isInUse(ResourceCompartment_Game);
        m_isLoading = false;
    }
}

void
Server::updateLevelPreFrame(const UpdateParameters& params)
{
    FB_CPU_SCOPE_TIMER("Server_updateLevelPreFrame", Color32::Orange);
    if (g_serverGameWorld)
    {
        FB_PROFILER_SCOPE_TIMER(Server_endAsynchronousQueries);
        g_serverGameWorld->endAsynchronousQueries();
        g_serverGameWorld->resetAsynchronousQueries();
    }

    if (m_level)
    {
        FrameUpdateInfo updateInfo;
        updateInfo.deltaTime = params.simulationDeltaTime;
        updateInfo.realDeltaTime = params.wallDeltaTime;
        m_level->preFrameUpdate(updateInfo);
    }
}

void
Server::updateLevelPostFrame(const UpdateParameters& params)
{
    FB_CPU_SCOPE_TIMER("Server_updateLevelPostFrame", Color32::LightGreen);
    if (!m_level)
        return;

    // not finished with deferred loading yet, so don't bother doing game update
    if (m_level->isLoadDoneDeferred())
        return;

    FrameUpdateInfo updateInfo;
    updateInfo.deltaTime = params.simulationDeltaTime;
    updateInfo.realDeltaTime = params.wallDeltaTime;
    m_level->postFrameUpdate(updateInfo);

    // processDamageEvents
    if (auto damageArbitrator = m_gameContext->damageArbitrator())
        damageArbitrator->update();
}

void Server::processAsyncQueries()
{
    FB_CPU_SCOPE_TIMER("Server_processAsyncQueries", Color32::Cyan);
    if (m_level && !m_level->isLoadDoneDeferred())
    {
        // not finished with deferred loading yet, so don't bother doing game update
        FB_ASSERT(g_serverGameWorld);
        FB_PROFILER_SCOPE_TIMER(Server_beginAsynchronousQueries);
        g_serverGameWorld->beginAsynchronousQueries();
    }
}

void Server::cleanAsyncQueries()
{
    FB_ASSERT(g_serverGameWorld);
    if (g_serverGameWorld)
    {
        g_serverGameWorld->endAsynchronousQueries();
        g_serverGameWorld->resetAsynchronousQueries();
    }
}

void
Server::setIsUsingFixedSimTickDeltaTimes(bool isUsingFixedSimTickDeltaTimes)
{
    if (m_peer)
        m_peer->setIsUsingFixedSimTickDeltaTimes(isUsingFixedSimTickDeltaTimes);
}

void
Server::updateLua()
{
#if defined(FB_ENABLE_LUA_BINDINGS)
    FB_CPU_SCOPE_TIMER("Server_updateLua", Color32::LightGreen);
    // Send update pulse to script interpreter
    FB_PROFILER_SCOPE_TIMER(Server_updateLua);
    m_gameContext->messageManager()->executeMessage(ServerScriptTickMessage());
#endif
}

void
Server::updateDedicatedServerStatus(const UpdateParameters& params)
{
#if defined(FB_HAS_DEDICATED_SERVER)
    if (!m_isDedicated)
        return;
    FB_CPU_SCOPE_TIMER("Server_updateDedicatedServerStatus", Color32::Cyan);
    FB_PROFILER_SCOPE_TIMER(Server_updateDedicatedServerStatus);

    ServerCallbacks::ServerStatusList statusList;
    updateStatus(params.wallCalculationTime.toSecondsAsFloat(), statusList);
    for (RuntimeModule* runtimeModule : *params.runtimeModules)
        if (auto gameModule = fb_dynamic_cast<GameModule*>(runtimeModule))
            gameModule->serverUpdateStatus(statusList);

    m_serverCallbacks->fromServerSetStatus(statusList);
    m_serverCallbacks->fromServerSetTitle(FormattedStringBuffer<>("Server - Name %s - Port %d", Settings<ServerSettings>()->getServerName().c_str(), m_serverPort).c_str());
    onUpdateDedicatedServerStatus(statusList);
#endif
}

///
/// Extension point for derived classes
///
void
Server::onUpdateDedicatedServerStatus(ServerCallbacks::ServerStatusList& statusList)
{

}

void
Server::updateStats(const UpdateParameters& params)
{
#if FB_USING(FB_PERFORMANCETRACKER) || !defined(FB_RETAIL)
    FB_CPU_SCOPE_TIMER("Server_updateStats", Color32::LightRed);
    FB_PROFILER_SCOPE_TIMER(Server_updateStats);

#if !defined(FB_RETAIL)
    if (m_level)
    {
        const eastl::vector<ServerConnection*>& connections = m_peer->connections();
        const ServerConnection* conn = (connections.empty() ? nullptr : connections.front());
        float latency = (conn ? conn->getPlayerConnections()[0]->getClientLatency() : 0.0f);

        m_level->debugRender(latency);
    }

    if (m_serverSettings->getDrawActivePhysicsObjects())
        m_level->debugRender(0.0f);

    {
        const float tickDeltaTime = params.wallCalculationTime.toSecondsAsFloat();
        m_tickSumDeltaTime += tickDeltaTime;
        m_maxTickDeltaTime = max(m_maxTickDeltaTime, tickDeltaTime);
        ++m_numTickFrames;
        if (m_numTickFrames >= m_gameTime->tickFrequency()) // Update once every second for now
        {
            const float averageTickDeltaTime = m_tickSumDeltaTime / (float)m_numTickFrames;

            NetworkServerDiagnosticsMessage message;
            message.setAverageTickDeltaTime((u8)clamp(floor(averageTickDeltaTime * 1000.f + 0.5f), 1.f, 255.f));
            message.setMaxTickDeltaTime((u8)clamp(floor(m_maxTickDeltaTime * 1000.f + 0.5f), 1.f, 255.f));
            m_peer->sendMessage(message);

            m_tickSumDeltaTime = 0.f;
            m_numTickFrames = 0;
            m_maxTickDeltaTime = 0.f;
        }
    }

#endif // !defined(FB_RETAIL)
#endif
}

void
Server::updateGameModulesPreFrame(const UpdateParameters& params)
{
    FB_CPU_SCOPE_TIMER("Server_updateGameModulesPreFrame", Color32::Cyan);
    FB_PROFILER_SCOPE_TIMER(Server_updateGameModulesPreFrame);
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);
    for (RuntimeModuleVec::iterator i = params.runtimeModules->begin(), e = params.runtimeModules->end(); i != e; ++i)
    {
        RuntimeModule* runtimeModule = *i;
        if (runtimeModule->isKindOf<GameModule>())
        {
            static_cast<GameModule*>(runtimeModule)->serverPreFrameUpdate(params);
        }
    }
}

void
Server::updateGameModulesPostFrame(const UpdateParameters& params)
{
    //FB_PROFILER_SCOPE_TIMER(Server_updateGameModulesPostFrame);
    FB_CPU_SCOPE_TIMER("Server_updateGameModulesPostFrame", Color32::LightBlue);
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);
    for (RuntimeModuleVec::iterator i = params.runtimeModules->begin(), e = params.runtimeModules->end(); i != e; ++i)
    {
        RuntimeModule* runtimeModule = *i;
        if (runtimeModule->isKindOf<GameModule>())
        {
            static_cast<GameModule*>(runtimeModule)->serverPostFrameUpdate(params);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::constructSimUpdateInfo(const UpdateParameters& params)
{
    m_simUpdateInfo.deltaTime = params.simulationDeltaTime.toSecondsAsFloat();
    m_simUpdateInfo.realDeltaTime = params.wallDeltaTime.toSecondsAsFloat();
    m_simUpdateInfo.tick = m_gameTime->ticks();
    m_simUpdateInfo.ignoredTickDiff = 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePassPreSimUpdate(const UpdateParameters& params)
{
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);

    if (m_level)
        m_level->simUpdatePreNetwork(m_simUpdateInfo.deltaTime);

    m_peer->receivePulse();
    g_eventAndPropertyModificationQueue[Realm_Server].flush();

    m_peer->simulationUpdate(m_simUpdateInfo.deltaTime); // This must be updated before logic and physics

    m_playerManager->update(m_simUpdateInfo.deltaTime);

    // FIFA_BEGIN | SENDBACK:FB-132384 | cburns | 2020-01-29 | allow loads to start immediately rather than on frame boundaries
    ServerSubLevelManager& subLevelManager = getServerSubLevelManager();

    subLevelManager.getBundleManager().update();

    subLevelManager.update(m_simUpdateInfo.deltaTime);
    // FIFA_END

    if (m_level)
    {
        // prevent players from being deleted during simUpdate
        m_playerManager->lockDeleteOperations();
        m_level->getGameWorld()->preSimUpdate(m_simUpdateInfo);
    }
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updatePassPostSimUpdate(const UpdateParameters& params)
{
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);

    if (m_level)
    {
        m_level->postSimUpdate(m_simUpdateInfo);
        m_playerManager->unlockAndFlushPendingDeleteOperations();
    }

    #if defined(FB_DEBUGMENU_ENABLE)
    if (g_inGameMenu)
        g_inGameMenu->serverUpdate(params.simulationDeltaTime.toSecondsAsFloat(), true);
    #endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::startLoadLevel(const UpdateParameters& params)
{
    // In between the "load level" message and here, the server could have been
    // shutdown. No sense if loading anything...
    if (m_shouldQuit)
        return;

    FB_FATAL_ASSERT(!m_level);
    FB_FATAL_ASSERT(!m_nextLevelSetup.getName().empty());

    m_isLoading = true;

    // even if we don't have any save data, consider this a load from a save
    ServerSaveGameLoadSession::SaveType saveType;
    if (m_nextLevelSetup.getIsSaveGame())
    {
        FB_ASSERT(!m_nextSaveData.empty());
        saveType = ServerSaveGameLoadSession::SaveType_Full;
    }
    else if (m_nextLevelSetup.getHasPersistentSave())
    {
        FB_ASSERT(!m_nextSaveData.empty());
        saveType = ServerSaveGameLoadSession::SaveType_Persistent;
    }
    else
    {
        FB_ASSERT(m_nextSaveData.empty());
        saveType = ServerSaveGameLoadSession::SaveType_Full; // we'll still treat this as a full save, even though we're not providing any data
    }
        
    FB_ASSERT(m_loadInfo->loadSession == nullptr);
    m_loadInfo->loadSession = new (FB_SERVER_ARENA) ServerSaveGameLoadSession(m_nextSaveData, saveType);

    FB_ASSERT(m_nextSaveData.empty()); // if we had any savegame data, it should have been taken over

    if (EA_UNLIKELY(!m_loadInfo->loadSession->beginLoad()))
    {
        FB_INFO_FORMAT("Failed to load save data. Falling back to beginning of level.");

        // abort load of savegame, but keep proceeding with load of server
        m_loadInfo->loadSession->clearContext();
    }

    m_loadInfo->loadSession->performPreLevelOperations();

    m_activeLevel = m_nextLevelSetup.getName();
    if (!m_keepResources)
    {
        CommonLevelDataParams setupParams;
        setupParams.levelName = m_activeLevel.c_str();
        setupParams.needUI = !m_isDedicated;
        setupParams.runtimeModules = params.runtimeModules;

        setupLevelData(setupParams);
    }
#if FB_USING(FB_DEBUG_GAME_EVENT_HANDLER)
    if (DebugGameEventHandler::getInstance() != nullptr)
    {
        FixedDbObjectBuilder<512> builder;
        builder << "gameTime" << ServerGameContext::context()->gameTime()->time() << "stringResult" << m_nextLevelSetup.getName().c_str();
        DebugGameEventHandler::getInstance()->logGameEvent("Server", "LevelLoadStart", builder.done());
    }
#endif
    m_clientDestroyLevelDone = m_isDedicated;
    m_clientLoadingLevelDone = m_isDedicated;

    m_gameTime->set(0.0, 0);
    m_peer->beginLoadLevel();
    m_peer->beginSpawnLevel();

    ResourceManager_loadScreenControl(1);

    FB_SERVER_LOAD_TITLE("Creating level");

    m_serverCallbacks->serverIsCreatingLevel(m_nextLevelSetup);

    m_loadLevel = createLevel(FB_LEVEL_ARENA, ResourceCompartment_Game, *ServerGameContext::context(), m_isDedicated);
    if (m_clientLoadingScreenReady)
        m_loadLevel->onLoadingScreenReady();
    
    m_loadInfo->loadResources = !m_keepResources;
    m_loadInfo->tickFrequency = m_gameTime->tickFrequency();

    FB_INFO_FORMAT("ServerLevel initialized");
    FB_ENSEMBLE_PUBLISH("ServerLevelInitialized", FB_DBOBJ("SessionId" << Environment::getSessionOid()));

    if (!m_loadLevel->startLoad(*m_loadInfo, m_nextLevelSetup))
        FB_ERROR("");
}

void
Server::finalizeLoadLevel()
{
    m_shouldLoadDoneDeferredRun = m_loadLevel->isLoadDoneDeferred();
    if (!m_shouldLoadDoneDeferredRun)
    {
        m_loadInfo->reset();
    }
    else
    {
        m_loadInfo->loadSession->levelLoaded();
    }

    Malloc::setAllocationMode(MallocAllocator::BestPerformance);
    ResourceManager_loadScreenControl(-1);

    m_level = m_loadLevel;
    m_loadLevel = 0;
    FB_INFO_FORMAT("ServerLevel finalized");
    FB_ENSEMBLE_PUBLISH("LevelLoaded", FB_DBOBJ(
        "Level" << m_level->getName() <<
        "GameMode" << m_level->setup().getInclusionOption("GameMode") <<
        "SessionId" << Environment::getSessionOid()
    ));
    
#if FB_USING(FB_ENTITY_TWEAK_MANAGER)
    LevelTweaker::allowTweaks(this, true);
#endif

    m_serverCallbacks->serverHasLoadedLevel();

    ServerLevelLoadedMessage message;
    message.setLevelSetup(m_nextLevelSetup);
    m_gameContext->messageManager()->executeMessage(message);

    if (isSinglePlayer()) // Enable stats again if it was disable by RestartCheckpoint/Mission in Singleplayer
        m_gameContext->messageManager()->executeMessage(StatEnableMessage());

    if (auto damageArbitrator = m_gameContext->damageArbitrator())
        damageArbitrator->reset();

    // This is a horrible hack but is needed in order to be able to test dedicated servers with icepick
    if (m_isDedicated)
    {
        FB_ENSEMBLE_PUBLISH("Client.EnteredInGame", FB_DBOBJ(
            "Level" << m_level->getName() <<
            "SessionId" << Environment::getSessionOid()
            ));
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

ServerLevel*
Server::createLevel(MemoryArena& arena, ResourceCompartment compartment, ServerGameContext& gameContext, bool isDedicated)
{
    return new (arena) ServerLevel(arena, compartment, isDedicated, gameContext.destructionManager(), gameContext.serverPeer());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::handleServerMessage(const Message& message)
{
    switch (message.type)
    {
    case MessageType_ServerStop:
        FB_REQUEST_QUIT("Server shutdown requested via ServerStopMessage");
        requestStop();
        break;

    case MessageType_ServerLoadLevel:
        {
            const ServerLoadLevelMessage& msg = message.as<ServerLoadLevelMessage>();
            const LevelSetup& nextLevel = msg.levelSetup();
            if (m_destroyLevel || m_waitingToCleanResources)
            {
                // Skip this message.. we're working on it!
                if (fb_typeof<LevelSetup>()->compare(&nextLevel, &m_nextLevelSetup) == 0)
                    return;

                FB_ASSERT_FORMAT(false, "Can't handle new load level messages before last one is finished (asking for %s while previous ask was %s)", nextLevel.getName().c_str(), m_nextLevelSetup.getName().c_str());
                return;
            }

            if (nextLevel.getName().empty())
            {
                FB_INFO_FORMAT("Server shutdown due to ServerLoadLevel with empty level name.");
                m_keepResources = false;
                ServerStopMessage::post();
            }
            else if (!m_level)
            {
                m_keepResources = false;
            }
            else
            {
                m_destroyLevel = true;
                m_keepResources = shouldKeepResources(m_level->setup(), nextLevel);
            }

            m_nextLevelSetup = nextLevel;
            FB_ASSERT_FORMAT(m_nextLevelSetup.getIsSaveGame() == !m_nextSaveData.empty(), (m_nextLevelSetup.getIsSaveGame() ? "Is loading save game but no save data found" : "Is not loading save game but save data exists"));

            // if not loading a savegame, and a persistent save is requested, make one now, before requesting restart
            if (!m_nextLevelSetup.getIsSaveGame() && m_nextLevelSetup.getHasPersistentSave())
            {
                FB_ASSERT(m_nextSaveData.empty());
                g_serverSaveGameManager.savePersistent(m_nextSaveData, m_nextLevelSetup);

                if (m_nextSaveData.empty())
                    m_nextLevelSetup.setHasPersistentSave(false);
            }

            if (!m_keepResources)
            {
                // BEGIN replay support
                g_subLevelManager[Realm_Server]->getBundleManager().unpinAllBundles(true);
                // END replay support

                m_waitingToCleanResources = true;

                bool serverRequiresRestart = nextLevelRequiresRestart(m_level->setup(), m_nextLevelSetup);

                if (serverRequiresRestart)
                {
                    FB_ASSERT_FORMAT(!m_isDedicated, "Trying to load singleplayer level with dedicated server? Map rotation problem? Level: %s", m_nextLevelSetup.getName().c_str());
                    if (!m_isDedicated)
                        m_serverCallbacks->fromServerRestart(m_nextLevelSetup, m_nextSaveData.data(), m_nextSaveData.size());  // Lets restart the entire server instead.
                }
            }
            else if(m_level)
            {
                m_level->pinSubLevelBundlesForRestart(m_nextLevelSetup);
            }
        }
        break;

#if !defined(FB_FINAL)
    case MessageType_ServerRestartTimer:
        {
            const ServerRestartTimerMessage& msg = message.as<ServerRestartTimerMessage>();
            int newTick = msg.getTicks();
            FB_INFO_FORMAT("Changing server ticks from %u to %d", m_gameTime->ticks(), newTick);
            double newTime = newTick * (1.0 / double(m_gameTime->tickFrequency())); // Hacky solution
            m_gameTime->set(newTime, newTick);
            if (g_serverGameWorld)
                g_serverGameWorld->resetUpdaters();
        }
        break;
#endif

    case MessageType_ServerLoadGame:
        {
            const ServerLoadGameMessage& msg = message.as<ServerLoadGameMessage>();
            setNextSaveData(msg.getSaveData(), msg.getSaveDataSize());

            ServerLoadLevelMessage::post(msg.getLevelSetup());
        }
        break;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::handleServerPeerMessage(const Message& message)
{
    switch (message.type)
    {
        case MessageType_ServerPeerLoadLevel:
            requestStartLoadLevel();
            break;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::requestStartLoadLevel()
{
    if (m_shouldQuit)
        return;

    if (m_level)
    {
        FB_WARNING_FORMAT("Load level is called but m_level is not null.");
        return;
    }

    FB_ASSERT(!m_nextLevelSetup.getName().empty());
    m_loadNextLevelRequested = true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool
Server::shouldQuit() const
{
    return m_shouldQuit && !m_peer->isExitingLevel() && m_clientDestroyLevelDone && !m_waitingToCleanResources && !m_isLoading && !m_destroyLevel;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool
Server::canDestroy() const
{
    return !m_loadLevel && !m_waitingToCleanResources;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool
Server::shouldRunAsJob() const
{
    return !m_isDedicated && m_serverSettings->getJobEnable();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::setServerName(const char* name)
{
    FB_INFO_FORMAT("The server is called '%s'", name);

    Settings<ServerSettings>()->setServerName(name);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool
Server::listen(const char* address)
{
    // format examples
    // :port
    // :port1-portN
    // ip:port
    // ip:port1-portN
    // ip

    const char* pos = strchr(address, ':');
    if (pos != nullptr)
    {
        m_serverAddress.set(address, eastl::string::size_type(pos - address));
        const char* ports = pos + 1;
        pos = strchr(ports, '-');
        if (pos != nullptr)
        {
            StringBuilder<> rangeStart;
            rangeStart.append(ports, eastl::string::size_type(pos - ports));
            m_serverPort = atoi(rangeStart.c_str());
            m_serverPortRange = atoi(pos + 1);
            if (m_serverPortRange < m_serverPort)
                m_serverPortRange = 1;
            else
                m_serverPortRange = m_serverPortRange - m_serverPort + 1;
        }
        else
        {
            m_serverPort = atoi(ports);
            m_serverPortRange = 16;
        }
    }
    else
    {
        m_serverAddress = address;
        m_serverPort = Settings<NetworkSettings>()->getServerPort();
        m_serverPortRange = 16;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::updateStatus(float deltaTime, ServerCallbacks::ServerStatusList& statusList)
{
#if defined(FB_HAS_DEDICATED_SERVER)
    FB_CPU_SCOPE_TIMER("Server_updateStatus", Color32::Red);

    static u32 startSystemTime = getSystemTime();
    u32 sec = (getSystemTime() - startSystemTime)/1000;
    u32 min = (sec / 60) % 60;
    u32 hour = (sec / (60*60));

    static u32 lastSec = 0;
    static float currentDeltaTime = 0;
    static float sumDeltaTime = 0;
    static u32 frameCount = 0;

    sumDeltaTime += deltaTime;
    ++frameCount;

    if (lastSec != sec)
    {
        lastSec = sec;
        currentDeltaTime = sumDeltaTime / float(frameCount);
        sumDeltaTime = 0;
        frameCount = 0;
    }

    statusList.push_back(eastl::make_pair("FPS", toString(int(1.0f/currentDeltaTime))));
    statusList.push_back(eastl::make_pair("UpTime", FormattedStringBuffer<>("%02u:%02u:%02u", hour, min, sec%60).c_str()));
    if (m_playerManager)
    {
        uint maxPlayerCount = Settings<NetworkSettings>()->getMaxClientCount();
        if (m_peer)
            maxPlayerCount = fb::min(maxPlayerCount, m_peer->maxClients());

        StringBuilder<24> playerCountVal;
        playerCountVal << m_playerManager->playerCount() << "/" << (maxPlayerCount - m_playerManager->getMaxSpectatorCount()) << " (" << m_playerManager->spectatorCount() << "/" << m_playerManager->getMaxSpectatorCount() << ") [" << Settings<NetworkSettings>()->getMaxClientCount() << "]";
        statusList.push_back(eastl::make_pair("PlayerCount", c_str(playerCountVal)));
    }
    if (m_peer && m_peer->serverGhostManager())
        statusList.push_back(eastl::make_pair("GhostCount", toString(m_peer->serverGhostManager()->ghostCount())));

    VirtualAlloc::MemoryStats virtualAllocStats;
    VirtualAlloc::MemoryStats hwvirtualAllocStats;
    VirtualAlloc::getOSVirtualMemoryStats(virtualAllocStats, hwvirtualAllocStats);
    float cpuMem = virtualAllocStats.usedPhys / (1024.f*1024.f);
    float gpuMem = 0.f;
    statusList.push_back(eastl::make_pair("Memory (CPU/GPU)", FormattedStringBuffer<>("%.1f/%.1f", cpuMem, gpuMem).c_str()));
    #if !defined(FB_FINAL)
    statusList.push_back(eastl::make_pair("State", m_loadInfo->get()));
    #endif
    statusList.push_back(eastl::make_pair("Level", extractFileName(levelName())));

    const char* platformStr = toString(m_gameSettings->getPlatform()) + 13;
    statusList.push_back(eastl::make_pair("Hosted platform", platformStr));

#ifndef FB_RETAIL
    static BuildSettings buildSettings;
    static const eastl::string changelist = toString(buildSettings.getChangelist());
    statusList.push_back(eastl::make_pair("BuildId", changelist));
#endif

#if !defined(FB_HAS_CLIENT) || !defined(FB_RETAIL)
    if (m_peer)
    {
        ServerPeer::Stats peerStats;
        m_peer->getStats(peerStats);
        statusList.push_back(eastl::make_pair("Average kbps in", FormattedStringBuffer<>("%0.01f", peerStats.receivedKbpsPerConnection).c_str()));
        statusList.push_back(eastl::make_pair("Average kbps out", FormattedStringBuffer<>("%0.01f", peerStats.sentKbpsPerConnection).c_str()));
        statusList.push_back(eastl::make_pair("Clients with PL in/out", FormattedStringBuffer<>("%u/%u", peerStats.connectionsWithPacketLossInCount, peerStats.connectionsWithPacketLossOutCount).c_str()));
        statusList.push_back(eastl::make_pair("Average PL % in/out", FormattedStringBuffer<>("%u/%u", uint(peerStats.packetLossInRatio*100), uint(peerStats.packetLossOutRatio*100)).c_str()));
    }
#endif
#endif // FB_HAS_DEDICATED_SERVER
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char*
Server::levelName() const
{
    if (!m_level)
        return "No level";
    return m_level->name();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char*
Server::name() const
{
    return Settings<ServerSettings>()->getServerName().c_str();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::onMessage(const Message& message)
{
    switch (message.category)
    {
        case MessageCategory_Server:
            handleServerMessage(message);
            break;

        case MessageCategory_ServerPeer:
            handleServerPeerMessage(message);
            break;

        default:
            FB_ERROR_FORMAT("Unhandled category");
        break;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool
Server::nextLevelRequiresRestart(const LevelSetup& currentLevelSetup, const LevelSetup& nextLevelSetup)
{
    bool serverRequiresRestart = m_isSinglePlayer;
    
#ifndef FB_RETAIL
    serverRequiresRestart |= Common::isLevelSingleplayer(nextLevelSetup.getName().c_str());
#endif

    // If we have changed local player count, force restart the server
    const uint validLocalPlayersMask = Settings<NetworkSettings>()->getValidLocalPlayersMask();
    const uint desiredLocalPlayersMask = Settings<NetworkSettings>()->getDesiredLocalPlayersMask();
    serverRequiresRestart |= (validLocalPlayersMask != m_validLocalPlayersMask || desiredLocalPlayersMask != m_validLocalPlayersMask);

    return serverRequiresRestart;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::setNextSaveData(const u8* saveData, uint saveDataSize)
{
    m_nextSaveData.resize_rawcopy(saveDataSize, saveData);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::onRestartLevel(const u8* saveData, uint saveDataSize)
{
    // If its singleplayer, we disable stats until the restart is complete.
    // This prevents a new session from being created when just doing "restart from checkpoint"
    if (isSinglePlayer())
        m_gameContext->messageManager()->executeMessage(StatDisableMessage());

    LevelSetup setup(m_level ? m_level->setup() : m_nextLevelSetup);

    // If there is a proper save game this will be set to true when reading the level setup.
    // The purpose of clearing the IsSaveGame bool is to handle a "restart mission" (i.e. no save game)
    // after a "restart checkpoint" (i.e. load save from last checkpoint).
    setup.setIsSaveGame(false);

    if (saveData)
    {
        if (ServerSaveGameLoadSession::readLevelSetup(setup, saveData, saveDataSize))
            setNextSaveData(saveData, saveDataSize);
    }

#ifndef FB_FINAL
    // Avoid fading out levels on save if we're live editing
    // There's no corresponding code-driven FadeIn that happens when a level loads, so if we fade out here we force
    // content to contain a FadeIn entity in order to be properly live-editable, which is bad.
    const bool fadeOut = !g_coreSettings.getLiveEditingEnable();
#else
    const bool fadeOut = true;
#endif
    ServerLoadLevelMessage::post(setup, fadeOut);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::onLocalClientLoadingScreenReady()
{
    FB_INFO_FORMAT("Server_onLocalClientLoadingScreenReady");
    if (ServerLevel* currentlyLoadingLevel = m_loadLevel)
        currentlyLoadingLevel->onLoadingScreenReady();
    m_clientLoadingScreenReady = true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void
Server::onLocalClientLoadingLevelDone()
{
    FB_INFO_FORMAT("Server_onLocalClientLoadingLevelDone");
    m_clientLoadingLevelDone = true;
}

void
Server::onLocalClientDestroyLevelDone()
{
    FB_INFO_FORMAT("Server_onLocalClientDestroyLevelDone");
    if (ServerLevel* currentlyLoadingLevel = m_loadLevel)
        currentlyLoadingLevel->onLoadingScreenReady();
    m_clientLoadingLevelDone = true;
    m_clientDestroyLevelDone = true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Server::requestStop(bool keepResources)
{
    FB_ENTITY_SYSTEM_WRITE_SCOPE(Realm_Server);

    if (m_peer)
        m_peer->disconnectAll(SecureReason_Ok, "Server shutdown");
    FB_INFO_FORMAT("SERVER IS SHUTTING DOWN!! %s", m_level ? "" : "(Will wait for load level to complete)");
    m_shouldQuit = true;
    m_keepResources = keepResources;
    m_loadInfo->loadResources = !keepResources;
    if (m_level)
    {
        m_destroyLevel = true;
        m_waitingToCleanResources = !keepResources;
    }
    else if (!m_loadLevel)
    {
        // quitting during an unload needs to also clean kept resources
        m_waitingToCleanResources = !keepResources;
    }

    return isLevelSetup() && keepResources;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Server::wantsToQuit() const
{
    return m_shouldQuit;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const String& Server::getNextLevelName() const
{
    return m_nextLevelSetup.getName();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#if defined(FB_HAS_DEDICATED_SERVER)

void Server::loadServerSidePatchData(ServerGameContext* serverGameContext)
{
    if (const char* hostedPlatform = ExecutionContext::getOptionValue("platform"))
    {
        eastl::string platformName(hostedPlatform);
        platformName.make_lower();
        VirtualFileSystem* vfs = ExecutionContext::getVirtualFileSystem();
        fb::ExtendableStringBuilder<64> filePath;
        SmartRef<FsBackend> overrideBackend;
        if (const char* serverSidePatchPath = ExecutionContext::getOptionValue("serverSidePatchPath"))
        {
            overrideBackend = ExecutionContext::createNativeFs(serverSidePatchPath);
            vfs->mount(overrideBackend, "/ClientPatch");
            filePath << "/ClientPatch/";
        }
        else
        {
            filePath << "/native_data/ClientPatch/";
        }
        filePath << platformName << ".delta";
        ScopedPtr<Buffer> patchFile(vfs->open(filePath.c_str()));
        if (patchFile.get())
        {
            m_serverSidePatchData = SharedBuffer::create(FB_SERVER_ARENA, u32(patchFile->getAvailableBytes()));
            patchFile->read(m_serverSidePatchData->data(), m_serverSidePatchData->size());
        }
        if (overrideBackend.get())
        {
            vfs->unmount("/ClientPatch");
        }
    }
    serverGameContext->setServerSidePatchData(m_serverSidePatchData);
}

#endif

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}
[Image: FiHzeWA.jpg]
Reply
#6
manu157: -

Code:
typedef int(__thiscall* sub_1409D7230)(fb::server* Server, fb::ServerSpawnInfo* info, fb::ServerSpawnOverrides* spawnOverrides);
sub_1409D7230 sub_1409D7230_Original;
int __fastcall Hooked_sub_1409D7230(fb::server* Server, fb::ServerSpawnInfo* info, fb::ServerSpawnOverrides* spawnOverrides)
{
    fb::ServerSpawnOverrides* v3=spawnOverrides;
    fb::server* v4=Server;
    fb::SocketManager* v5;
    fb::ISocketManager* v6 = spawnOverrides->socketManager;

    info->byte25 = false; // isLocalHost
    info->gap24 = false;  // isSinglePlayer
    info->gap26 = true;   // isDedicated

    v4->m_defaultSocketManager = v6;
    spawnOverrides->socketManager = v6;

        __int64 socketManagerPtr= *(__int64*)(spawnOverrides->socketManager);  // Get pointer
          Log("*(__int64*)(spawnOverrides->socketManager = %p\n", socketManagerPtr);

    __int64 socketManagerPtr3 = *(__int64*)(socketManagerPtr + 16); // Get pointer // 000000014065A0B0 // Called 1st
    Log("*(__int64*)(socketManagerPtr3 = %p\n", socketManagerPtr3); // This is call to listen
    Log("Check memory and press [enter]\n");
    int flag;
    flag = getchar();

    return sub_1409D7230_Original(Server, info, spawnOverrides);
}


manu157: -

Code:
typedef BYTE _BYTE;
typedef unsigned __int64 _QWORD;
typedef unsigned long _DWORD;
namespace fb {
struct server {
    _BYTE gap0[131];
    _BYTE byte83;
    unsigned __int8 unsigned___int884;
    _QWORD qword88;
    _BYTE gap90[30]; // pfb__twinkle90;
    __declspec(align(64))_QWORD qwordC0;
    __declspec(align(16)) _QWORD m_crypto;
    _BYTE gapD8[24];
    _QWORD qwordF0;
    _BYTE gapF8[24];
    _QWORD qword110;
    _DWORD dword118;
    _DWORD dword11C;
    _BYTE gap120[24];
    fb::ISocketManager* m_defaultSocketManager;
};
__unaligned struct __declspec(align(2)) ServerSpawnInfo {
    _BYTE gap0[32];
    unsigned int unsigned_int20;    // tickFrequency
    bool gap24;                        // isSinglePlayer
    bool byte25;                    // isLocalHost
    bool gap26;                        // isDedicated
    bool isEncrypted;                // isEncrypted
    bool gap28;                        // isCoop
    bool byte29;
};
struct ServerSpawnOverrides {
        __int64 gap0; // maybe fb::LevelSetup* levelSetup;
    fb::ISocketManager* socketManager; //__int64 socketManager;
    __int64 qword10;
};
    struct ISocketManagerVtbl {
        void(__thiscall* destroy)(fb::ISocketManager*);
        //fb::ISocket* (__thiscall* connect)(fb::ISocketManager*, const char*, bool, bool, bool);
        //fb::ISocket* (__thiscall* listen)(fb::ISocketManager*, const char*, bool, bool, bool);
        //fb::ISocket* (__thiscall* createSocket)(fb::ISocketManager*, bool, bool);
        void(__thiscall* sleep)(fb::ISocketManager*, float);
    };
    //struct ISocketManager {
    //    ISocketManagerVtbl* vfptr;
    //};
}

manu157: -
"Hooked_sub_1409D7230" = server:Confusedtart, EA evrsion is
Code:
void
Server::start(ServerSpawnInfo& info, ServerSpawnOverrides& spawnOverrides, ISocketManager* socketManager)
{   
    // we don't own the socketManager passed in the ctor. Clear it here   
Code:
    if (!socketManager)
    {
        socketManager = spawnOverrides.socketManager;
    }   
   
    if (!socketManager)
    {
        FB_WARNING_FORMAT("No socket manager was provided by game modules -- defaulting to memory sockets");
        m_defaultSocketManager = new (FB_SERVER_ARENA) MemorySocketManager(FB_SERVER_ARENA);
        socketManager = m_defaultSocketManager;
    }

    bool isLocalOnly = m_isSinglePlayer || info.isLocalHost || isGameRunningInGameView();

    ServerPeerCreationInfo sci = {m_isSinglePlayer, m_isDedicated, false, isLocalOnly, info.isMenu, spawnOverrides.connectionCreator, m_validLocalPlayersMask, m_destructionManager };
    m_peer = m_peerCreator->createServerPeer(sci);

    const String& serverPassword = m_serverSettings->getServerPassword();
    if (!serverPassword.empty())
    {
        m_peer->setPassword(serverPassword.c_str());
    }

    listen(ExecutionContext::getOptionValue("listen", ""));

    bool useSocketDebug = false;
#ifndef FB_RETAIL
    useSocketDebug = !m_isSinglePlayer && m_serverSettings->getUseDebugSocket() && !isGameRunningInGameView();
#endif



Code:
  bool ok = true;
    uint tries = 0;
    uint port = m_serverPort;
    FB_ASSERT_DESC(port != 0, "Must set a specific port for the server to use.");

    if (m_serverAddress.empty() && isLocalOnly)
        m_serverAddress = "127.0.0.1";

    do
    {
        StringBuilder<256> fullAddress;
        fullAddress << m_serverAddress << ":" << port;

        FB_INFO_FORMAT("Attempting listen to %s", fullAddress.c_str());

        uint titleId = fb_lexical_cast<uint>(Settings<NetworkSettings>()->getTitleId());

        m_serverPort = port;
        FB_INFO_FORMAT("Network.ProtocolVersion: %u gameProtocol: %u", Settings<NetworkSettings>()->getProtocolVersion(), getGameProtocolVersion());
        ok = m_peer->init(
            socketManager,
            fullAddress.c_str(),
            titleId,
            getGameProtocolVersion(),
            useSocketDebug);

        if (!ok)
        {
            ++tries;
            if (Settings<NetworkSettings>()->getIncrementServerPortOnFail())
            {
                ++port;
            }
            else
            {
                // if we are forced to use a port we sleep awhile between tries to give the port time to free up
                Thread::sleep(100);
            }
        }


    } while (!ok && tries < m_serverPortRange);

    FB_INFO_FORMAT("Successful.");

    if (!ok)
    {
        FB_ERROR_FORMAT("Failed trying to listen to %s:%u. Cannot recover from this.", m_serverAddress.c_str(), m_serverPort);
        FB_BREAK;
    }

    m_peer->postInit();
    m_gameContext->setServerPeer(m_peer);

    m_playerManager->registerNetObject(m_peer->serverGhostManager());

    m_gameTime = new (FB_SERVER_ARENA) GameTime(info.tickFrequency);
    m_gameContext->setGameTime(m_gameTime);

    m_peer->listen(true);
    m_peer->host(true);

    ServerStartedMessage startedMsg;
    startedMsg.setIsDedicated(m_isDedicated);
    m_gameContext->messageManager()->executeMessage(startedMsg);
}

manu157
and maybeand m

Code:
m_defaultSocketManager = new (FB_SERVER_ARENA) MemorySocketManager(FB_SERVER_ARENA);
v4->m_defaultSocketManager = m_defaultSocketManager;
spawnOverrides->socketManager = m_defaultSocketManager;
[Image: FiHzeWA.jpg]
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)