Merge branch 'develop' into containers-are-lovely
This commit is contained in:
commit
a5bb81f32b
@ -59,6 +59,7 @@
|
|||||||
#include "Modules/Threading.hpp"
|
#include "Modules/Threading.hpp"
|
||||||
#include "Modules/Toast.hpp"
|
#include "Modules/Toast.hpp"
|
||||||
#include "Modules/UIFeeder.hpp"
|
#include "Modules/UIFeeder.hpp"
|
||||||
|
#include "Modules/Updater.hpp"
|
||||||
#include "Modules/VisionFile.hpp"
|
#include "Modules/VisionFile.hpp"
|
||||||
#include "Modules/Voice.hpp"
|
#include "Modules/Voice.hpp"
|
||||||
#include "Modules/Vote.hpp"
|
#include "Modules/Vote.hpp"
|
||||||
@ -172,6 +173,7 @@ namespace Components
|
|||||||
Register(new Threading());
|
Register(new Threading());
|
||||||
Register(new Toast());
|
Register(new Toast());
|
||||||
Register(new UIFeeder());
|
Register(new UIFeeder());
|
||||||
|
Register(new Updater());
|
||||||
Register(new VisionFile());
|
Register(new VisionFile());
|
||||||
Register(new Voice());
|
Register(new Voice());
|
||||||
Register(new Vote());
|
Register(new Vote());
|
||||||
|
@ -435,7 +435,7 @@ namespace Components
|
|||||||
MongooseLogBuffer.push_back(c);
|
MongooseLogBuffer.push_back(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::ReplyError(mg_connection* connection, int code)
|
void Download::ReplyError(mg_connection* connection, int code, std::string messageOverride)
|
||||||
{
|
{
|
||||||
std::string msg{};
|
std::string msg{};
|
||||||
switch(code)
|
switch(code)
|
||||||
@ -453,6 +453,11 @@ namespace Components
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!messageOverride.empty())
|
||||||
|
{
|
||||||
|
msg = messageOverride;
|
||||||
|
}
|
||||||
|
|
||||||
mg_http_reply(connection, code, "Content-Type: text/plain\r\n", "%s", msg.c_str());
|
mg_http_reply(connection, code, "Content-Type: text/plain\r\n", "%s", msg.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,6 +467,31 @@ namespace Components
|
|||||||
mg_http_reply(connection, 200, formatted.c_str(), "%s", data.c_str());
|
mg_http_reply(connection, 200, formatted.c_str(), "%s", data.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VerifyPassword([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
|
||||||
|
{
|
||||||
|
const std::string g_password = *Game::g_password ? (*Game::g_password)->current.string : "";
|
||||||
|
if (g_password.empty()) return true;
|
||||||
|
|
||||||
|
// SHA256 hashes are 64 characters long but we're gonna be safe here
|
||||||
|
char buffer[128]{};
|
||||||
|
const auto len = mg_http_get_var(&hm->query, "password", buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
if (len <= 0)
|
||||||
|
{
|
||||||
|
Download::ReplyError(c, 403, "Password Required");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto password = std::string(buffer, len);
|
||||||
|
if (password != Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(g_password), ""))
|
||||||
|
{
|
||||||
|
Download::ReplyError(c, 403, "Invalid Password");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<std::string> Download::InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
|
std::optional<std::string> Download::InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
|
||||||
{
|
{
|
||||||
if (!(*Game::com_sv_running)->current.enabled)
|
if (!(*Game::com_sv_running)->current.enabled)
|
||||||
@ -524,6 +554,12 @@ namespace Components
|
|||||||
static nlohmann::json jsonList;
|
static nlohmann::json jsonList;
|
||||||
static std::filesystem::path fsGamePre;
|
static std::filesystem::path fsGamePre;
|
||||||
|
|
||||||
|
if (!VerifyPassword(c, hm))
|
||||||
|
{
|
||||||
|
// Custom reply done in VerifyPassword
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string;
|
const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string;
|
||||||
|
|
||||||
if (!fsGame.empty() && (fsGamePre != fsGame))
|
if (!fsGame.empty() && (fsGamePre != fsGame))
|
||||||
@ -572,6 +608,12 @@ namespace Components
|
|||||||
static std::string mapNamePre;
|
static std::string mapNamePre;
|
||||||
static nlohmann::json jsonList;
|
static nlohmann::json jsonList;
|
||||||
|
|
||||||
|
if (!VerifyPassword(c, hm))
|
||||||
|
{
|
||||||
|
// Custom reply done in VerifyPassword
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const std::string mapName = Party::IsInUserMapLobby() ? (*Game::ui_mapname)->current.string : Maps::GetUserMap()->getName();
|
const std::string mapName = Party::IsInUserMapLobby() ? (*Game::ui_mapname)->current.string : Maps::GetUserMap()->getName();
|
||||||
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
|
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,8 @@ namespace Components
|
|||||||
static void InitiateClientDownload(const std::string& mod, bool needPassword, bool map = false);
|
static void InitiateClientDownload(const std::string& mod, bool needPassword, bool map = false);
|
||||||
static void InitiateMapDownload(const std::string& map, bool needPassword);
|
static void InitiateMapDownload(const std::string& map, bool needPassword);
|
||||||
|
|
||||||
|
static void ReplyError(mg_connection* connection, int code, std::string messageOverride = {});
|
||||||
|
|
||||||
static Dvar::Var SV_wwwDownload;
|
static Dvar::Var SV_wwwDownload;
|
||||||
static Dvar::Var SV_wwwBaseUrl;
|
static Dvar::Var SV_wwwBaseUrl;
|
||||||
|
|
||||||
@ -105,7 +107,6 @@ namespace Components
|
|||||||
static bool DownloadFile(ClientDownload* download, unsigned int index);
|
static bool DownloadFile(ClientDownload* download, unsigned int index);
|
||||||
|
|
||||||
static void LogFn(char c, void* param);
|
static void LogFn(char c, void* param);
|
||||||
static void ReplyError(mg_connection* connection, int code);
|
|
||||||
static void Reply(mg_connection* connection, const std::string& contentType, const std::string& data);
|
static void Reply(mg_connection* connection, const std::string& contentType, const std::string& data);
|
||||||
|
|
||||||
static std::optional<std::string> FileHandler(mg_connection* c, const mg_http_message* hm);
|
static std::optional<std::string> FileHandler(mg_connection* c, const mg_http_message* hm);
|
||||||
|
@ -246,9 +246,10 @@ namespace Components
|
|||||||
|
|
||||||
auto workingDir = std::filesystem::current_path().string();
|
auto workingDir = std::filesystem::current_path().string();
|
||||||
const std::string binary = *Game::sys_exitCmdLine;
|
const std::string binary = *Game::sys_exitCmdLine;
|
||||||
|
const std::string command = binary == "iw4x-sp.exe" ? "iw4x-sp" : "iw4x";
|
||||||
|
|
||||||
SetEnvironmentVariableA("MW2_INSTALL", workingDir.data());
|
SetEnvironmentVariableA("MW2_INSTALL", workingDir.data());
|
||||||
Utils::Library::LaunchProcess(binary, "-singleplayer", workingDir);
|
Utils::Library::LaunchProcess(binary, std::format("{} --pass \"{}\"", command, GetCommandLineA()), workingDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
__declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub()
|
__declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub()
|
||||||
@ -320,7 +321,7 @@ namespace Components
|
|||||||
|
|
||||||
Utils::Hook::Set<void(*)(Game::XAssetHeader, void*)>(0x51FCDD, QuickPatch::R_AddImageToList_Hk);
|
Utils::Hook::Set<void(*)(Game::XAssetHeader, void*)>(0x51FCDD, QuickPatch::R_AddImageToList_Hk);
|
||||||
|
|
||||||
Utils::Hook::Set<const char*>(0x41DB8C, "iw4-sp.exe");
|
Utils::Hook::Set<const char*>(0x41DB8C, "iw4x-sp.exe");
|
||||||
Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick();
|
Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// Fix crash as nullptr goes unchecked
|
// Fix crash as nullptr goes unchecked
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
|
HANDLE Singleton::Mutex;
|
||||||
|
|
||||||
bool Singleton::FirstInstance = true;
|
bool Singleton::FirstInstance = true;
|
||||||
|
|
||||||
bool Singleton::IsFirstInstance()
|
bool Singleton::IsFirstInstance()
|
||||||
@ -13,6 +15,14 @@ namespace Components
|
|||||||
return FirstInstance;
|
return FirstInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Singleton::preDestroy()
|
||||||
|
{
|
||||||
|
if (INVALID_HANDLE_VALUE != Mutex)
|
||||||
|
{
|
||||||
|
CloseHandle(Mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Singleton::Singleton()
|
Singleton::Singleton()
|
||||||
{
|
{
|
||||||
if (Flags::HasFlag("version"))
|
if (Flags::HasFlag("version"))
|
||||||
@ -31,7 +41,8 @@ namespace Components
|
|||||||
|
|
||||||
if (Loader::IsPerformingUnitTests() || Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
if (Loader::IsPerformingUnitTests() || Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
||||||
|
|
||||||
FirstInstance = (CreateMutexA(nullptr, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS);
|
Mutex = CreateMutexA(nullptr, FALSE, "iw4x_mutex");
|
||||||
|
FirstInstance = ((INVALID_HANDLE_VALUE != Mutex) && GetLastError() != ERROR_ALREADY_EXISTS);
|
||||||
|
|
||||||
if (!FirstInstance && !ConnectProtocol::Used() && MessageBoxA(nullptr, "Do you want to start another instance?\nNot all features will be available!", "Game already running", MB_ICONEXCLAMATION | MB_YESNO) == IDNO)
|
if (!FirstInstance && !ConnectProtocol::Used() && MessageBoxA(nullptr, "Do you want to start another instance?\nNot all features will be available!", "Game already running", MB_ICONEXCLAMATION | MB_YESNO) == IDNO)
|
||||||
{
|
{
|
||||||
|
@ -7,9 +7,12 @@ namespace Components
|
|||||||
public:
|
public:
|
||||||
Singleton();
|
Singleton();
|
||||||
|
|
||||||
|
void preDestroy() override;
|
||||||
|
|
||||||
static bool IsFirstInstance();
|
static bool IsFirstInstance();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static HANDLE Mutex;
|
||||||
static bool FirstInstance;
|
static bool FirstInstance;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
107
src/Components/Modules/Updater.cpp
Normal file
107
src/Components/Modules/Updater.cpp
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#include <STDInclude.hpp>
|
||||||
|
|
||||||
|
#include "Updater.hpp"
|
||||||
|
#include "Scheduler.hpp"
|
||||||
|
|
||||||
|
#include <Utils/WebIO.hpp>
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
|
||||||
|
namespace Components
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const Game::dvar_t* cl_updateAvailable;
|
||||||
|
|
||||||
|
// If they use the alterware-launcher once to install they will have this file
|
||||||
|
// If they don't, what are they waiting for?
|
||||||
|
constexpr auto* REVISION_FILE = ".iw4xrevision";
|
||||||
|
constexpr auto* GITHUB_REMOTE_URL = "https://api.github.com/repos/iw4x/iw4x-client/releases/latest";
|
||||||
|
constexpr auto* INSTALL_GUIDE_REMOTE_URL = "https://forum.alterware.dev/t/how-to-install-the-alterware-launcher/56";
|
||||||
|
|
||||||
|
void CheckForUpdate()
|
||||||
|
{
|
||||||
|
std::string revision;
|
||||||
|
if (!Utils::IO::ReadFile(REVISION_FILE, &revision) || revision.empty())
|
||||||
|
{
|
||||||
|
Logger::Print("{} does not exist. Notifying the user an update is available\n", REVISION_FILE);
|
||||||
|
Game::Dvar_SetBool(cl_updateAvailable, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = Utils::WebIO("IW4x", GITHUB_REMOTE_URL).setTimeout(5000)->get();
|
||||||
|
if (result.empty())
|
||||||
|
{
|
||||||
|
// Nothing to do in this situation. We won't know if we need to update or not
|
||||||
|
Logger::Print("Could not fetch latest tag from GitHub\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document doc{};
|
||||||
|
const rapidjson::ParseResult parseResult = doc.Parse(result);
|
||||||
|
if (!parseResult || !doc.IsObject())
|
||||||
|
{
|
||||||
|
// Nothing to do in this situation. We won't know if we need to update or not
|
||||||
|
Logger::Print("GitHub sent an invalid reply (malformed JSON)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.HasMember("tag_name") || !doc["tag_name"].IsString())
|
||||||
|
{
|
||||||
|
// Nothing to do in this situation. We won't know if we need to update or not
|
||||||
|
Logger::Print("GitHub sent an invalid reply (missing 'tag_name' JSON member)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* tag = doc["tag_name"].GetString();
|
||||||
|
if (revision != tag)
|
||||||
|
{
|
||||||
|
// A new version came out!
|
||||||
|
Game::Dvar_SetBool(cl_updateAvailable, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depending on Linux/Windows 32/64 there are a few things we must check
|
||||||
|
std::optional<std::string> GetLauncher()
|
||||||
|
{
|
||||||
|
if (Utils::IO::FileExists("alterware-launcher.exe"))
|
||||||
|
{
|
||||||
|
return "alterware-launcher.exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils::IO::FileExists("alterware-launcher-x86.exe"))
|
||||||
|
{
|
||||||
|
return "alterware-launcher-x86.exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils::IsWineEnvironment() && Utils::IO::FileExists("alterware-launcher"))
|
||||||
|
{
|
||||||
|
return "alterware-launcher";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater::Updater()
|
||||||
|
{
|
||||||
|
cl_updateAvailable = Game::Dvar_RegisterBool("cl_updateAvailable", false, Game::DVAR_NONE, "Whether an update is available or not");
|
||||||
|
Scheduler::Once(CheckForUpdate, Scheduler::Pipeline::ASYNC);
|
||||||
|
|
||||||
|
UIScript::Add("checkForUpdate", [](const UIScript::Token& /*token*/, const Game::uiInfo_s* /*info*/)
|
||||||
|
{
|
||||||
|
CheckForUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
UIScript::Add("getAutoUpdate", [](const UIScript::Token& /*token*/, const Game::uiInfo_s* /*info*/)
|
||||||
|
{
|
||||||
|
if (const auto exe = GetLauncher(); exe.has_value())
|
||||||
|
{
|
||||||
|
Game::Sys_QuitAndStartProcess(exe.value().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No launcher was found on the system, time to tell them to download it from GitHub
|
||||||
|
Utils::OpenUrl(INSTALL_GUIDE_REMOTE_URL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
10
src/Components/Modules/Updater.hpp
Normal file
10
src/Components/Modules/Updater.hpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Components
|
||||||
|
{
|
||||||
|
class Updater : public Component
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Updater();
|
||||||
|
};
|
||||||
|
}
|
@ -25,6 +25,7 @@ namespace Game
|
|||||||
Sys_SetValue_t Sys_SetValue = Sys_SetValue_t(0x4B2F50);
|
Sys_SetValue_t Sys_SetValue = Sys_SetValue_t(0x4B2F50);
|
||||||
Sys_CreateFile_t Sys_CreateFile = Sys_CreateFile_t(0x4B2EF0);
|
Sys_CreateFile_t Sys_CreateFile = Sys_CreateFile_t(0x4B2EF0);
|
||||||
Sys_OutOfMemErrorInternal_t Sys_OutOfMemErrorInternal = Sys_OutOfMemErrorInternal_t(0x4B2E60);
|
Sys_OutOfMemErrorInternal_t Sys_OutOfMemErrorInternal = Sys_OutOfMemErrorInternal_t(0x4B2E60);
|
||||||
|
Sys_QuitAndStartProcess_t Sys_QuitAndStartProcess = Sys_QuitAndStartProcess_t(0x45FCF0);
|
||||||
|
|
||||||
char(*sys_exitCmdLine)[1024] = reinterpret_cast<char(*)[1024]>(0x649FB68);
|
char(*sys_exitCmdLine)[1024] = reinterpret_cast<char(*)[1024]>(0x649FB68);
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@ namespace Game
|
|||||||
typedef void(*Sys_OutOfMemErrorInternal_t)(const char* filename, int line);
|
typedef void(*Sys_OutOfMemErrorInternal_t)(const char* filename, int line);
|
||||||
extern Sys_OutOfMemErrorInternal_t Sys_OutOfMemErrorInternal;
|
extern Sys_OutOfMemErrorInternal_t Sys_OutOfMemErrorInternal;
|
||||||
|
|
||||||
|
typedef void(*Sys_QuitAndStartProcess_t)(const char*);
|
||||||
|
extern Sys_QuitAndStartProcess_t Sys_QuitAndStartProcess;
|
||||||
|
|
||||||
extern char(*sys_exitCmdLine)[1024];
|
extern char(*sys_exitCmdLine)[1024];
|
||||||
|
|
||||||
extern RTL_CRITICAL_SECTION* s_criticalSection;
|
extern RTL_CRITICAL_SECTION* s_criticalSection;
|
||||||
|
@ -104,20 +104,20 @@ namespace Utils
|
|||||||
|
|
||||||
void Library::LaunchProcess(const std::string& process, const std::string& commandLine, const std::string& currentDir)
|
void Library::LaunchProcess(const std::string& process, const std::string& commandLine, const std::string& currentDir)
|
||||||
{
|
{
|
||||||
STARTUPINFOA startup_info;
|
STARTUPINFOA startupInfo;
|
||||||
PROCESS_INFORMATION process_info;
|
PROCESS_INFORMATION processInfo;
|
||||||
|
|
||||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
ZeroMemory(&startupInfo, sizeof(startupInfo));
|
||||||
ZeroMemory(&process_info, sizeof(process_info));
|
ZeroMemory(&processInfo, sizeof(processInfo));
|
||||||
startup_info.cb = sizeof(startup_info);
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
|
||||||
CreateProcessA(process.data(), const_cast<char*>(commandLine.data()), nullptr,
|
CreateProcessA(process.data(), const_cast<char*>(commandLine.data()), nullptr,
|
||||||
nullptr, false, NULL, nullptr, currentDir.data(),
|
nullptr, false, NULL, nullptr, currentDir.data(),
|
||||||
&startup_info, &process_info);
|
&startupInfo, &processInfo);
|
||||||
|
|
||||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)
|
if (processInfo.hThread && processInfo.hThread != INVALID_HANDLE_VALUE)
|
||||||
CloseHandle(process_info.hThread);
|
CloseHandle(processInfo.hThread);
|
||||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE)
|
if (processInfo.hProcess && processInfo.hProcess != INVALID_HANDLE_VALUE)
|
||||||
CloseHandle(process_info.hProcess);
|
CloseHandle(processInfo.hProcess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user