329 lines
9.3 KiB
C++
329 lines
9.3 KiB
C++
#include "pch.h"
|
|
#include "dedicated.h"
|
|
#include "hookutils.h"
|
|
#include "gameutils.h"
|
|
#include "serverauthentication.h"
|
|
#include "masterserver.h"
|
|
|
|
bool IsDedicated()
|
|
{
|
|
static bool result = strstr(GetCommandLineA(), "-dedicated");
|
|
return result;
|
|
}
|
|
|
|
// CDedidcatedExports defs
|
|
struct CDedicatedExports; // forward declare
|
|
|
|
typedef void (*DedicatedSys_PrintfType)(CDedicatedExports* dedicated, const char* msg);
|
|
typedef void (*DedicatedRunServerType)(CDedicatedExports* dedicated);
|
|
|
|
// would've liked to just do this as a class but have not been able to get it to work
|
|
struct CDedicatedExports
|
|
{
|
|
void* vtable; // because it's easier, we just set this to &this, since CDedicatedExports has no props we care about other than funcs
|
|
|
|
char unused[56];
|
|
|
|
DedicatedSys_PrintfType Sys_Printf;
|
|
DedicatedRunServerType RunServer;
|
|
};
|
|
|
|
void Sys_Printf(CDedicatedExports* dedicated, const char* msg)
|
|
{
|
|
spdlog::info("[DEDICATED PRINT] {}", msg);
|
|
}
|
|
|
|
typedef void (*CHostState__InitType)(CHostState* self);
|
|
|
|
void RunServer(CDedicatedExports* dedicated)
|
|
{
|
|
spdlog::info("CDedicatedExports::RunServer(): starting");
|
|
spdlog::info(CommandLine()->GetCmdLine());
|
|
|
|
// initialise engine
|
|
g_pEngine->Frame();
|
|
|
|
// add +map if not present
|
|
// don't manually execute this from cbuf as users may have it in their startup args anyway, easier just to run from stuffcmds if present
|
|
if (!CommandLine()->CheckParm("+map"))
|
|
CommandLine()->AppendParm("+map", g_pCVar->FindVar("match_defaultMap")->GetString());
|
|
|
|
// run server autoexec and re-run commandline
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode);
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "stuffcmds", cmd_source_t::kCommandSrcCode);
|
|
Cbuf_Execute();
|
|
|
|
// ensure playlist initialises right, if we've not explicitly called setplaylist
|
|
SetCurrentPlaylist(GetCurrentPlaylistName());
|
|
|
|
// note: we no longer manually set map and hoststate to start server in g_pHostState, we just use +map which seems to initialise stuff
|
|
// better
|
|
|
|
// get tickinterval
|
|
ConVar* Cvar_base_tickinterval_mp = g_pCVar->FindVar("base_tickinterval_mp");
|
|
|
|
// main loop
|
|
double frameTitle = 0;
|
|
while (g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING)
|
|
{
|
|
double frameStart = Plat_FloatTime();
|
|
g_pEngine->Frame();
|
|
|
|
// only update the title after at least 500ms since the last update
|
|
if ((frameStart - frameTitle) > 0.5)
|
|
{
|
|
frameTitle = frameStart;
|
|
|
|
// this way of getting playercount/maxplayers honestly really sucks, but not got any other methods of doing it rn
|
|
const char* maxPlayers = GetCurrentPlaylistVar("max_players", false);
|
|
if (!maxPlayers)
|
|
maxPlayers = "6";
|
|
|
|
SetConsoleTitleA(fmt::format(
|
|
"{} - {} {}/{} players ({})",
|
|
g_MasterServerManager->ns_auth_srvName,
|
|
g_pHostState->m_levelName,
|
|
g_ServerAuthenticationManager->m_additionalPlayerData.size(),
|
|
maxPlayers,
|
|
GetCurrentPlaylistName())
|
|
.c_str());
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::duration<double, std::ratio<1>>(
|
|
Cvar_base_tickinterval_mp->GetFloat() - fmin(Plat_FloatTime() - frameStart, 0.25)));
|
|
}
|
|
}
|
|
|
|
typedef bool (*IsGameActiveWindowType)();
|
|
IsGameActiveWindowType IsGameActiveWindow;
|
|
bool IsGameActiveWindowHook()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
HANDLE consoleInputThreadHandle = NULL;
|
|
|
|
DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter)
|
|
{
|
|
while (!g_pEngine || !g_pHostState || g_pHostState->m_iCurrentState != HostState_t::HS_RUN)
|
|
Sleep(1000);
|
|
|
|
// Bind stdin to receive console input.
|
|
FILE* fp = nullptr;
|
|
freopen_s(&fp, "CONIN$", "r", stdin);
|
|
|
|
spdlog::info("Ready to receive console commands.");
|
|
|
|
{
|
|
// Process console input
|
|
std::string input;
|
|
while (g_pEngine && g_pEngine->m_nQuitting == EngineQuitState::QUIT_NOTQUITTING && std::getline(std::cin, input))
|
|
{
|
|
input += "\n";
|
|
Cbuf_AddText(Cbuf_GetCurrentPlayer(), input.c_str(), cmd_source_t::kCommandSrcCode);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#include "NSMem.h"
|
|
void InitialiseDedicated(HMODULE engineAddress)
|
|
{
|
|
spdlog::info("InitialiseDedicated");
|
|
|
|
uintptr_t ea = (uintptr_t)engineAddress;
|
|
|
|
{
|
|
// Host_Init
|
|
// prevent a particle init that relies on client dll
|
|
NSMem::NOP(ea + 0x156799, 5);
|
|
}
|
|
|
|
{
|
|
// CModAppSystemGroup::Create
|
|
// force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment
|
|
auto ptr = ea + 0x1C4EBD;
|
|
|
|
// cmp => mov
|
|
NSMem::BytePatch(ptr + 1, "C6 87");
|
|
|
|
// 00 => 01
|
|
NSMem::BytePatch(ptr + 7, "01");
|
|
}
|
|
|
|
{
|
|
// Some init that i'm not sure of that crashes
|
|
// nop the call to it
|
|
NSMem::NOP(ea + 0x156A63, 5);
|
|
}
|
|
|
|
{
|
|
// runframeserver
|
|
// nop some access violations
|
|
NSMem::NOP(ea + 0x159819, 17);
|
|
}
|
|
|
|
{
|
|
NSMem::NOP(ea + 0x156B4C, 7);
|
|
|
|
// previously patched these, took me a couple weeks to figure out they were the issue
|
|
// removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address
|
|
// so uhh, don't do that
|
|
// NSMem::NOP(ea + 0x156B4C + 7, 8);
|
|
|
|
NSMem::NOP(ea + 0x156B4C + 15, 9);
|
|
}
|
|
|
|
{
|
|
// HostState_State_NewGame
|
|
// nop an access violation
|
|
NSMem::NOP(ea + 0xB934C, 9);
|
|
}
|
|
|
|
{
|
|
// CEngineAPI::Connect
|
|
// remove call to Shader_Connect
|
|
NSMem::NOP(ea + 0x1C4D7D, 5);
|
|
}
|
|
|
|
// currently does not work, crashes stuff, likely gotta keep this here
|
|
//{
|
|
// // CEngineAPI::Connect
|
|
// // remove calls to register ui rpak asset types
|
|
// NSMem::NOP(ea + 0x1C4E07, 5);
|
|
//}
|
|
|
|
{
|
|
// Host_Init
|
|
// remove call to ui loading stuff
|
|
NSMem::NOP(ea + 0x156595, 5);
|
|
}
|
|
|
|
{
|
|
// some function that gets called from RunFrameServer
|
|
// nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway
|
|
NSMem::NOP(ea + 0x15A0BB, 5);
|
|
}
|
|
|
|
{
|
|
// RunFrameServer
|
|
// nop a function that access violations
|
|
NSMem::NOP(ea + 0x159BF3, 5);
|
|
}
|
|
|
|
{
|
|
// func that checks if origin is inited
|
|
// always return 1
|
|
NSMem::BytePatch(
|
|
ea + 0x183B70,
|
|
{
|
|
0xB0,
|
|
0x01, // mov al,01
|
|
0xC3 // ret
|
|
});
|
|
}
|
|
|
|
{
|
|
// HostState_State_ChangeLevel
|
|
// nop clientinterface call
|
|
NSMem::NOP(ea + 0x1552ED, 16);
|
|
}
|
|
|
|
{
|
|
// HostState_State_ChangeLevel
|
|
// nop clientinterface call
|
|
NSMem::NOP(ea + 0x155363, 16);
|
|
}
|
|
|
|
// note: previously had DisableDedicatedWindowCreation patches here, but removing those rn since they're all shit and unstable and bad
|
|
// and such check commit history if any are needed for reimplementation
|
|
{
|
|
// IVideoMode::CreateGameWindow
|
|
// nop call to ShowWindow
|
|
NSMem::NOP(ea + 0x1CD146, 5);
|
|
}
|
|
|
|
CDedicatedExports* dedicatedExports = new CDedicatedExports;
|
|
dedicatedExports->vtable = dedicatedExports;
|
|
dedicatedExports->Sys_Printf = Sys_Printf;
|
|
dedicatedExports->RunServer = RunServer;
|
|
|
|
CDedicatedExports** exports = (CDedicatedExports**)((char*)engineAddress + 0x13F0B668);
|
|
*exports = dedicatedExports;
|
|
|
|
HookEnabler hook;
|
|
ENABLER_CREATEHOOK(hook, (char*)engineAddress + 0x1CDC80, &IsGameActiveWindowHook, reinterpret_cast<LPVOID*>(&IsGameActiveWindow));
|
|
|
|
// extra potential patches:
|
|
// nop engine.dll+1c67d1 and +1c67d8 to skip videomode creategamewindow
|
|
// also look into launcher.dll+d381, seems to cause renderthread to get made
|
|
// this crashes HARD if no window which makes sense tbh
|
|
// also look into materialsystem + 5B344 since it seems to be the base of all the renderthread stuff
|
|
|
|
// big note: datatable gets registered in window creation
|
|
// make sure it still gets registered
|
|
|
|
// add cmdline args that are good for dedi
|
|
CommandLine()->AppendParm("-nomenuvid", 0);
|
|
CommandLine()->AppendParm("-nosound", 0);
|
|
CommandLine()->AppendParm("-windowed", 0);
|
|
CommandLine()->AppendParm("-nomessagebox", 0);
|
|
CommandLine()->AppendParm("+host_preload_shaders", "0");
|
|
CommandLine()->AppendParm("+net_usesocketsforloopback", "1");
|
|
|
|
// Disable Quick Edit mode to reduce chance of user unintentionally hanging their server by selecting something.
|
|
if (!CommandLine()->CheckParm("-bringbackquickedit"))
|
|
{
|
|
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
DWORD mode = 0;
|
|
|
|
if (GetConsoleMode(stdIn, &mode))
|
|
{
|
|
if (mode & ENABLE_QUICK_EDIT_MODE)
|
|
{
|
|
mode &= ~ENABLE_QUICK_EDIT_MODE;
|
|
mode &= ~ENABLE_MOUSE_INPUT;
|
|
|
|
mode |= ENABLE_PROCESSED_INPUT;
|
|
|
|
SetConsoleMode(stdIn, mode);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
spdlog::info("Quick Edit enabled by user request");
|
|
|
|
// create console input thread
|
|
if (!CommandLine()->CheckParm("-noconsoleinput"))
|
|
consoleInputThreadHandle = CreateThread(0, 0, ConsoleInputThread, 0, 0, NULL);
|
|
else
|
|
spdlog::info("Console input disabled by user request");
|
|
}
|
|
|
|
void InitialiseDedicatedOrigin(HMODULE baseAddress)
|
|
{
|
|
// disable origin on dedicated
|
|
// for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without
|
|
// an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server
|
|
|
|
NSMem::BytePatch(
|
|
(uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"),
|
|
{
|
|
0xC3 // ret
|
|
});
|
|
}
|
|
|
|
typedef void (*PrintFatalSquirrelErrorType)(void* sqvm);
|
|
PrintFatalSquirrelErrorType PrintFatalSquirrelError;
|
|
void PrintFatalSquirrelErrorHook(void* sqvm)
|
|
{
|
|
PrintFatalSquirrelError(sqvm);
|
|
g_pEngine->m_nQuitting = EngineQuitState::QUIT_TODESKTOP;
|
|
}
|
|
|
|
void InitialiseDedicatedServerGameDLL(HMODULE baseAddress)
|
|
{
|
|
HookEnabler hook;
|
|
ENABLER_CREATEHOOK(hook, baseAddress + 0x794D0, &PrintFatalSquirrelErrorHook, reinterpret_cast<LPVOID*>(&PrintFatalSquirrelError));
|
|
} |