feature: auto updater

This commit is contained in:
Diavolo 2023-12-30 12:58:51 +01:00
parent 43c620c47e
commit 8a27c06d62
No known key found for this signature in database
GPG Key ID: FA77F074E98D98A5
9 changed files with 150 additions and 13 deletions

View File

@ -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());

View File

@ -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

View File

@ -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)
{ {

View File

@ -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;
}; };
} }

View File

@ -0,0 +1,106 @@
#include <STDInclude.hpp>
#include "Updater.hpp"
#include "Scheduler.hpp"
#include <Utils/WebIO.hpp>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.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* revisionFile = ".iw4xrevision";
void CheckForUpdate()
{
std::string revision;
if (!Utils::IO::ReadFile(revisionFile, &revision) || revision.empty())
{
Logger::Print(".iw4xrevision does not exist. Notifying the user an update is available\n");
Game::Dvar_SetBool(cl_updateAvailable, true);
}
const auto result = Utils::WebIO("IW4x", "https://api.github.com/repos/iw4x/iw4x-client/releases/latest").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\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\n");
return;
}
const auto* tag = doc["tag_name"].GetString();
if (revision != tag)
{
// A new version came out!
Game::Dvar_SetBool(cl_updateAvailable, true);
}
}
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("https://forum.alterware.dev/t/how-to-install-the-alterware-launcher/56");
});
}
}

View File

@ -0,0 +1,10 @@
#pragma once
namespace Components
{
class Updater : public Component
{
public:
Updater();
};
}

View File

@ -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);

View File

@ -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;

View File

@ -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);
} }
} }