feature: auto updater
This commit is contained in:
parent
43c620c47e
commit
8a27c06d62
@ -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());
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
106
src/Components/Modules/Updater.cpp
Normal file
106
src/Components/Modules/Updater.cpp
Normal 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
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