Posts: 212
Threads: 180
Joined: Jan 2022
Reputation:
1
01-16-2022, 07:18 PM
(This post was last modified: 01-16-2022, 07:20 PM by the1Domo.)
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;
Posts: 212
Threads: 180
Joined: Jan 2022
Reputation:
1
02-01-2022, 04:28 PM
(This post was last modified: 02-01-2022, 04:28 PM by the1Domo.)
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)
Posts: 212
Threads: 180
Joined: Jan 2022
Reputation:
1
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;
};
/// @}
}
Posts: 212
Threads: 180
Joined: Jan 2022
Reputation:
1
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;
};
}
Posts: 212
Threads: 180
Joined: Jan 2022
Reputation:
1
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
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
Posts: 212
Threads: 180
Joined: Jan 2022
Reputation:
1
02-03-2022, 09:34 PM
(This post was last modified: 02-03-2022, 11:41 PM by the1Domo.)
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:  tart, 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 maybe and m
Code: m_defaultSocketManager = new (FB_SERVER_ARENA) MemorySocketManager(FB_SERVER_ARENA);
v4->m_defaultSocketManager = m_defaultSocketManager;
spawnOverrides->socketManager = m_defaultSocketManager;
|