From 1a9ca774aec77f852507fe478543b427956be2b9 Mon Sep 17 00:00:00 2001 From: wolf109909 <84360921+wolf109909@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:13:25 +0800 Subject: [PATCH] add NorthstarCN_Updater intergration, fix logging on pdata writes Co-authored-by: ston --- primedev/dllmain.cpp | 31 +++++---- primedev/masterserver/masterserver.cpp | 88 ++++++++++++++------------ primedev/primelauncher/main.cpp | 74 +++++++++++++++++++++- primedev/wsockproxy/loader.cpp | 33 +++++++++- 4 files changed, 170 insertions(+), 56 deletions(-) diff --git a/primedev/dllmain.cpp b/primedev/dllmain.cpp index 3082bcce3..da6c0c8c7 100644 --- a/primedev/dllmain.cpp +++ b/primedev/dllmain.cpp @@ -71,6 +71,23 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv return TRUE; } +void CheckWindowsVersion() +{ + if(strstr(GetCommandLineA(), "-no_windows_version_check") != NULL) + return; + + if (WindowsVersionSupportsUtf8() && !IsUtf8BetaOptionEnabled()) { + if(strstr(GetCommandLineA(), "-dedicated") == NULL) + { + MessageBoxW(nullptr, + L"由于运行库与旧版Windows存在兼容性问题,北极星CN 1.17只能保证在Windows 10 1903以上的版本在系统默认设置的情况下正常运行,若是Windows10 1903以下的系统版本,需要在控制面板-时钟和区域-区域-管理-更改系统区域设置-勾选使用Unicode UTF-8。如果在旧系统版本中不启用UTF-8,北极星CN将无法正确处理Windows中文字符。\n这可能会导致以下问题:\n(1) 带有任何中文路径的Mod无法正常工作或导致游戏崩溃\n(2) 游戏安装路径中存在中文字符时崩溃。\n(3) 系统用户名或用户目录名称存在中文字符时游戏崩溃\n若您的系统版本无Unicode UTF-8 选项,或是启用此选项后其他应用程序或目录出现乱码问题,可以通过以下方法解决:\n(1) 升级操作系统版本至Windows 10 1903或更高版本。\n(2) 手动重命名系统用户名和“文档”目录、游戏安装目录、Mods目录中所有含中文名称的文件夹(如模型和音频替换Mod等)以避免北极星CN出现故障。", + L"警告: 您的操作系统可能不受支持", + MB_OK | MB_ICONWARNING); + + } + std::wcout << L"警告:由于运行库与旧版Windows存在兼容性问题,北极星CN 1.17只能保证在Windows 10 1903以上的版本在系统默认设置的情况下正常运行,若是Windows10 1903以下的系统版本,需要在控制面板-时钟和区域-区域-管理-更改系统区域设置-勾选使用Unicode UTF-8。如果在旧系统版本中不启用UTF-8,北极星CN将无法正确处理Windows中文字符。\n这可能会导致以下问题:\n(1) 带有任何中文路径的Mod无法正常工作或导致游戏崩溃\n(2) 游戏安装路径中存在中文字符时崩溃。\n(3) 系统用户名或用户目录名称存在中文字符时游戏崩溃\n若您的系统版本无Unicode UTF-8 选项,或是启用此选项后其他应用程序或目录出现乱码问题,可以通过以下方法解决:\n(1) 升级操作系统版本至Windows 10 1903或更高版本。\n(2) 手动重命名系统用户名和“文档”目录、游戏安装目录、Mods目录中所有含中文名称的文件夹(如模型和音频替换Mod等)以避免北极星CN出现故障。" << std::endl; +} +} extern "C" bool InitialiseNorthstar() { @@ -81,7 +98,7 @@ extern "C" bool InitialiseNorthstar() bInitialised = true; - + CheckWindowsVersion(); InitialiseNorthstarPrefix(); @@ -89,18 +106,8 @@ extern "C" bool InitialiseNorthstar() InitialiseConsole(); // initialise logging before most other things so that they can use spdlog and it have the proper formatting InitialiseLogging(); + - if (WindowsVersionSupportsUtf8() && !IsUtf8BetaOptionEnabled()) { - if(strstr(GetCommandLineA(), "-dedicated") == NULL) - { - MessageBoxW(nullptr, - L"由于运行库与旧版Windows存在兼容性问题,北极星CN 1.17只能保证在Windows 10 1903以上的版本在系统默认设置的情况下正常运行,若是Windows10 1903以下的系统版本,需要在控制面板-时钟和区域-区域-管理-更改系统区域设置-勾选使用Unicode UTF-8。如果在旧系统版本中不启用UTF-8,北极星CN将无法正确处理Windows中文字符。\n这可能会导致以下问题:\n(1) 带有任何中文路径的Mod无法正常工作或导致游戏崩溃\n(2) 游戏安装路径中存在中文字符时崩溃。\n(3) 系统用户名或用户目录名称存在中文字符时游戏崩溃\n若您的系统版本无Unicode UTF-8 选项,或是启用此选项后其他应用程序或目录出现乱码问题,可以通过以下方法解决:\n(1) 升级操作系统版本至Windows 10 1903或更高版本。\n(2) 手动重命名系统用户名和“文档”目录、游戏安装目录、Mods目录中所有含中文名称的文件夹(如模型和音频替换Mod等)以避免北极星CN出现故障。", - L"警告: 您的操作系统可能不受支持", - MB_OK | MB_ICONWARNING); - - } - std::wcout << L"警告:由于运行库与旧版Windows存在兼容性问题,北极星CN 1.17只能保证在Windows 10 1903以上的版本在系统默认设置的情况下正常运行,若是Windows10 1903以下的系统版本,需要在控制面板-时钟和区域-区域-管理-更改系统区域设置-勾选使用Unicode UTF-8。如果在旧系统版本中不启用UTF-8,北极星CN将无法正确处理Windows中文字符。\n这可能会导致以下问题:\n(1) 带有任何中文路径的Mod无法正常工作或导致游戏崩溃\n(2) 游戏安装路径中存在中文字符时崩溃。\n(3) 系统用户名或用户目录名称存在中文字符时游戏崩溃\n若您的系统版本无Unicode UTF-8 选项,或是启用此选项后其他应用程序或目录出现乱码问题,可以通过以下方法解决:\n(1) 升级操作系统版本至Windows 10 1903或更高版本。\n(2) 手动重命名系统用户名和“文档”目录、游戏安装目录、Mods目录中所有含中文名称的文件夹(如模型和音频替换Mod等)以避免北极星CN出现故障。" << std::endl; - } InitialiseVersion(); CreateLogFiles(); diff --git a/primedev/masterserver/masterserver.cpp b/primedev/masterserver/masterserver.cpp index b97652f25..a66c8cb4f 100644 --- a/primedev/masterserver/masterserver.cpp +++ b/primedev/masterserver/masterserver.cpp @@ -30,7 +30,6 @@ #include #include - using namespace std::chrono_literals; MasterServerManager* g_pMasterServerManager; ClientAnticheatSystem g_ClientAnticheatSystem; @@ -80,8 +79,8 @@ void SetCommonHttpClientOptions(CURL* curl) curl_easy_setopt(curl, CURLOPT_RESOLVE, host); } else - { - //spdlog::warn("[DOH] service is not available. falling back to DNS"); + { + // spdlog::warn("[DOH] service is not available. falling back to DNS"); } } else @@ -94,7 +93,6 @@ void SetCommonHttpClientOptions(CURL* curl) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); } - httplib::Client SetupHttpClient() { std::string ms_addr = Cvar_ns_masterserver_hostname->GetString(); @@ -105,10 +103,10 @@ httplib::Client SetupHttpClient() cli.set_decompress(true); cli.set_read_timeout(10, 0); cli.set_write_timeout(10, 0); - //cli.enable_server_certificate_verification(false); - + // cli.enable_server_certificate_verification(false); + cli.load_ca_cert_store(cabundle, sizeof(cabundle)); - //cli.set_ca_cert_path("ca-bundle.crt"); + // cli.set_ca_cert_path("ca-bundle.crt"); if (!strstr(GetCommandLineA(), "-disabledoh")) { std::string doh_result = g_DohWorker->GetDOHResolve(ms_addr); @@ -118,7 +116,7 @@ httplib::Client SetupHttpClient() } else { - //spdlog::warn("[DOH] service is not available. falling back to DNS"); + // spdlog::warn("[DOH] service is not available. falling back to DNS"); } } return cli; @@ -142,7 +140,7 @@ httplib::Client SetupMatchmakerHttpClient() } else { - //spdlog::warn("[DOH] service is not available. falling back to DNS"); + // spdlog::warn("[DOH] service is not available. falling back to DNS"); } } return cli; @@ -180,16 +178,16 @@ bool MasterServerManager::StartMatchmaking(MatchmakeInfo* status) } std::string query = fmt::format(fmt::runtime(query_fmt_str), Cvar_ns_matchmaker_hostname->GetString(), local_uid_escaped, token_escaped, "true") - .c_str(); // TODO: add working AA selection - //spdlog::warn("{}", query); - //return false; + .c_str(); // TODO: add working AA selection + // spdlog::warn("{}", query); + // return false; curl_easy_setopt(curl, CURLOPT_URL, query.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer); - + const CURLcode result = curl_easy_perform(curl); - //spdlog::info("[Matchmaker] JOIN: Result:{},buffer:{}", result, read_buffer.c_str()); + // spdlog::info("[Matchmaker] JOIN: Result:{},buffer:{}", result, read_buffer.c_str()); if (result == CURLcode::CURLE_OK) { try @@ -238,18 +236,16 @@ bool MasterServerManager::CancelMatchmaking() curl_easy_setopt( curl, CURLOPT_URL, - fmt::format("{}/quit?id={}&token={}", Cvar_ns_matchmaker_hostname->GetString(), local_uid_escaped, token_escaped) - .c_str()); + fmt::format("{}/quit?id={}&token={}", Cvar_ns_matchmaker_hostname->GetString(), local_uid_escaped, token_escaped).c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer); const CURLcode result = curl_easy_perform(curl); - if (result == CURLcode::CURLE_OK) { - //spdlog::info("[Matchmaker] Result:{},buffer:{}", result, read_buffer.c_str()); + // spdlog::info("[Matchmaker] Result:{},buffer:{}", result, read_buffer.c_str()); try { nlohmann::json resjson = nlohmann::json::parse(read_buffer); @@ -300,8 +296,7 @@ bool MasterServerManager::UpdateMatchmakingStatus(MatchmakeInfo* status) curl_easy_setopt( curl, CURLOPT_URL, - fmt::format("{}/state?id={}&token={}", Cvar_ns_matchmaker_hostname->GetString(), local_uid_escaped, token_escaped) - .c_str()); + fmt::format("{}/state?id={}&token={}", Cvar_ns_matchmaker_hostname->GetString(), local_uid_escaped, token_escaped).c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer); @@ -310,25 +305,25 @@ bool MasterServerManager::UpdateMatchmakingStatus(MatchmakeInfo* status) if (result == CURLcode::CURLE_OK) { - //spdlog::info("[Matchmaker] STATE: Result:{},buffer:{}", result, read_buffer.c_str()); + // spdlog::info("[Matchmaker] STATE: Result:{},buffer:{}", result, read_buffer.c_str()); try { nlohmann::json server_response = nlohmann::json::parse(read_buffer); if (server_response.at("success") == true) { std::string state_type = server_response.at("state"); - + if (!strcmp(state_type.c_str(), "#MATCHMAKING_QUEUED")) { status->etaSeconds = ""; status->status = state_type; - //spdlog::info("[Matchmaker] MATCHMAKING_QUEUED"); + // spdlog::info("[Matchmaker] MATCHMAKING_QUEUED"); curl_easy_cleanup(curl); return true; } if (!strcmp(state_type.c_str(), "#MATCHMAKING_ALLOCATING_SERVER")) { - //spdlog::info("[Matchmaker] MATCHMAKING_ALLOCATING_SERVER"); + // spdlog::info("[Matchmaker] MATCHMAKING_ALLOCATING_SERVER"); status->status = state_type; curl_easy_cleanup(curl); return true; @@ -485,7 +480,7 @@ void MasterServerManager::RequestServerList() m_bScriptRequestingServerList = true; httplib::Client cli = SetupHttpClient(); - + if (auto res = cli.Get("/client/servers")) { m_bSuccessfullyConnected = true; @@ -693,7 +688,7 @@ void MasterServerManager::AuthenticateWithServer( const char* uid, const std::string& playerToken, const std::string& serverId, const char* password) { // dont wait, just stop if we're trying to do 2 auth requests at once - if (m_bAuthenticatingWithGameServer|| g_pVanillaCompatibility->GetVanillaCompatibility()) + if (m_bAuthenticatingWithGameServer || g_pVanillaCompatibility->GetVanillaCompatibility()) return; m_sAuthFailureReason = "No error message provided"; m_sAuthFailureMessage = "No error message provided"; @@ -733,7 +728,7 @@ void MasterServerManager::AuthenticateWithServer( nlohmann::json connection_info_json = nlohmann::json::parse(res->body); if (connection_info_json.at("success") == true) { - //spdlog::info("[auth_with_server] body: {}", res->body); + // spdlog::info("[auth_with_server] body: {}", res->body); m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(std::string(connection_info_json.at("ip")).c_str()); m_pendingConnectionInfo.port = static_cast(connection_info_json.at("port")); m_pendingConnectionInfo.authToken = connection_info_json.at("authToken"); @@ -783,13 +778,13 @@ void MasterServerManager::WritePlayerPersistentData(const char* player_id, const // still call this if we don't have a server id, since lobbies that aren't port forwarded need to be able to call it if (m_sPlayerPersistenceStates.contains(strPlayerId)) { - spdlog::warn("player {} attempted to write pdata while previous request still exists!"); + spdlog::warn("player {} attempted to write pdata while previous request still exists!", strPlayerId); // player is already requesting for leave, ignore the request. return; } if (!pdata_size) { - spdlog::warn("attempted to write pdata of size 0!"); + spdlog::warn("player {} attempted to write pdata of size 0!", strPlayerId); return; } @@ -809,10 +804,20 @@ void MasterServerManager::WritePlayerPersistentData(const char* player_id, const "/accounts/write_persistence?id={}&serverId={}", encode_query_param(strPlayerId), encode_query_param(m_sOwnServerId)); const std::string encoded = base64_encode(str_pdata.data(), pdata_size); auto res = cli.Post(querystring, encoded, "text/plain"); - if (res && res->status == 200) + if (res != nullptr) { - spdlog::info("[Pdata] Successfully wrote pdata for user: {}", strPlayerId); - m_bSuccessfullyConnected = true; + if (res->status == 200) + { + spdlog::info("[Pdata] Successfully wrote pdata for user: {}", strPlayerId); + m_bSuccessfullyConnected = true; + } + else + { + auto err = res->body; + spdlog::error("[Pdata] Write persistence failed for user: {}, error: {}", strPlayerId, err); + + m_bSuccessfullyConnected = true; + } } else { @@ -835,7 +840,12 @@ void ConCommand_ns_fetchservers(const CCommand& args) g_pMasterServerManager->RequestServerList(); } -MasterServerManager::MasterServerManager() : m_sOwnServerId {""}, m_sOwnClientAuthToken {""}, m_pendingConnectionInfo {} {} +MasterServerManager::MasterServerManager() + : m_sOwnServerId {""} + , m_sOwnClientAuthToken {""} + , m_pendingConnectionInfo {} +{ +} ON_DLL_LOAD_RELIESON("engine.dll", MasterServer, (ConCommand, ServerPresence), (CModule module)) { @@ -905,14 +915,12 @@ void MasterServerPresenceReporter::DestroyPresence(const ServerPresence* pServer // Not bothering with better thread safety in this case since DestroyPresence() is called when the game is shutting down. *g_pMasterServerManager->m_sOwnServerId = 0; - httplib::Client cli = SetupHttpClient(); const std::string querystring = fmt::format( "/server/remove_server?id={}?serverAuthToken={}", encode_query_param(g_pMasterServerManager->m_sOwnServerId), encode_query_param(g_pMasterServerManager->m_sOwnServerAuthToken)); cli.Delete(querystring); - } void MasterServerPresenceReporter::RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) @@ -1048,9 +1056,9 @@ void MasterServerPresenceReporter::InternalAddServer(const ServerPresence* pServ data.serverAuthToken = server_auth_token; return data; }; - //spdlog::info("{}", mod_info); + // spdlog::info("{}", mod_info); auto res = cli.Post(querystring, mod_info, "application/json"); - + if (res && res->status == 200) { try @@ -1085,10 +1093,8 @@ void MasterServerPresenceReporter::InternalAddServer(const ServerPresence* pServ } else { - spdlog::error( - "Failed adding self to server list: error {}", - std::to_string(static_cast(res.error()))); - if(!res->body.empty()) + spdlog::error("Failed adding self to server list: error {}", std::to_string(static_cast(res.error()))); + if (!res->body.empty()) spdlog::error("res:{}", res->body); return return_cleanup(MasterServerReportPresenceResult::FailedNoConnect); } diff --git a/primedev/primelauncher/main.cpp b/primedev/primelauncher/main.cpp index 51d15b35b..89cccc425 100644 --- a/primedev/primelauncher/main.cpp +++ b/primedev/primelauncher/main.cpp @@ -28,6 +28,76 @@ HMODULE hTier0Module; wchar_t exePath[4096]; wchar_t buffer[8192]; +static std::string ConvertWideToANSI(const std::wstring& wstr) +{ + int count = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.length(), NULL, 0, NULL, NULL); + std::string str(count, 0); + WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &str[0], count, NULL, NULL); + return str; +} + +static std::wstring ConvertAnsiToWide(const std::string& str) +{ + int count = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), NULL, 0); + std::wstring wstr(count, 0); + MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), &wstr[0], count); + return wstr; +} + +static std::string ConvertWideToUtf8(const std::wstring& wstr) +{ + int count = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), NULL, 0, NULL, NULL); + std::string str(count, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], count, NULL, NULL); + return str; +} + +static std::wstring ConvertUtf8ToWide(const std::string& str) +{ + int count = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0); + std::wstring wstr(count, 0); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &wstr[0], count); + return wstr; +} + +static std::string SanitizeEncodings(const char* buf) +{ + std::wstring ws = ConvertAnsiToWide(buf); + return ConvertWideToUtf8(ws); +} + + +void RunUpdater() +{ + fs::path updater_path = std::filesystem::current_path() / L"NSCN_Updater.exe"; + // run updater when we don't have -updated present and updater exists + if(std::filesystem::exists(updater_path) && !strstr(GetCommandLineA(), "-updated")) + { + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + STARTUPINFO si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_MINIMIZE; + CreateProcessW( + updater_path.c_str(), + L"", + NULL, + NULL, + false, + CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + (LPSTARTUPINFOW)&si, + &pi); + exit(0); + } + + + +} + DWORD GetProcessByName(std::wstring processName) { HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); @@ -76,7 +146,7 @@ FARPROC GetLauncherMain() void LibraryLoadError(DWORD dwMessageId, const wchar_t* libName, const wchar_t* location) { wchar_t text[8192]; - std::string message = std::system_category().message(dwMessageId); + std::wstring message = ConvertUtf8ToWide(std::system_category().message(dwMessageId)); swprintf_s( text, @@ -352,7 +422,7 @@ int main(int argc, char* argv[]) Sleep(100); } } - + RunUpdater(); if (!GetExePathWide(exePath, sizeof(exePath))) { MessageBoxA( diff --git a/primedev/wsockproxy/loader.cpp b/primedev/wsockproxy/loader.cpp index 2032364f8..a8c49179e 100644 --- a/primedev/wsockproxy/loader.cpp +++ b/primedev/wsockproxy/loader.cpp @@ -40,6 +40,37 @@ void LibraryLoadError(DWORD dwMessageId, const wchar_t* libName, const wchar_t* MessageBoxA(GetForegroundWindow(), text, "Northstar Wsock32 Proxy Error", 0); } +void RunUpdater() +{ + fs::path updater_path = std::filesystem::current_path() / L"NSCN_Updater.exe"; + // run updater when we don't have -updated present and updater exists + if(std::filesystem::exists(updater_path) && !strstr(GetCommandLineA(), "-updated")) + { + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + STARTUPINFO si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_MINIMIZE; + CreateProcessW( + updater_path.c_str(), + L"", + NULL, + NULL, + false, + CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + (LPSTARTUPINFOW)&si, + &pi); + exit(0); + } + + + +} + bool ShouldLoadNorthstar() { bool loadNorthstar = strstr(GetCommandLineA(), "-northstar"); @@ -137,7 +168,7 @@ bool ProvisionNorthstar() { if (!ShouldLoadNorthstar()) return true; - + RunUpdater(); if (MH_Initialize() != MH_OK) { MessageBoxA(