feature: auto updater
This commit is contained in:
parent
43c620c47e
commit
8a27c06d62
@ -59,6 +59,7 @@
|
||||
#include "Modules/Threading.hpp"
|
||||
#include "Modules/Toast.hpp"
|
||||
#include "Modules/UIFeeder.hpp"
|
||||
#include "Modules/Updater.hpp"
|
||||
#include "Modules/VisionFile.hpp"
|
||||
#include "Modules/Voice.hpp"
|
||||
#include "Modules/Vote.hpp"
|
||||
@ -172,6 +173,7 @@ namespace Components
|
||||
Register(new Threading());
|
||||
Register(new Toast());
|
||||
Register(new UIFeeder());
|
||||
Register(new Updater());
|
||||
Register(new VisionFile());
|
||||
Register(new Voice());
|
||||
Register(new Vote());
|
||||
|
@ -246,9 +246,10 @@ namespace Components
|
||||
|
||||
auto workingDir = std::filesystem::current_path().string();
|
||||
const std::string binary = *Game::sys_exitCmdLine;
|
||||
const std::string command = binary == "iw4x-sp.exe" ? "iw4x-sp" : "iw4x";
|
||||
|
||||
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()
|
||||
@ -320,7 +321,7 @@ namespace Components
|
||||
|
||||
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();
|
||||
|
||||
// Fix crash as nullptr goes unchecked
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
HANDLE Singleton::Mutex;
|
||||
|
||||
bool Singleton::FirstInstance = true;
|
||||
|
||||
bool Singleton::IsFirstInstance()
|
||||
@ -13,6 +15,14 @@ namespace Components
|
||||
return FirstInstance;
|
||||
}
|
||||
|
||||
void Singleton::preDestroy()
|
||||
{
|
||||
if (INVALID_HANDLE_VALUE != Mutex)
|
||||
{
|
||||
CloseHandle(Mutex);
|
||||
}
|
||||
}
|
||||
|
||||
Singleton::Singleton()
|
||||
{
|
||||
if (Flags::HasFlag("version"))
|
||||
@ -31,7 +41,8 @@ namespace Components
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -7,9 +7,12 @@ namespace Components
|
||||
public:
|
||||
Singleton();
|
||||
|
||||
void preDestroy() override;
|
||||
|
||||
static bool IsFirstInstance();
|
||||
|
||||
private:
|
||||
static HANDLE Mutex;
|
||||
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_CreateFile_t Sys_CreateFile = Sys_CreateFile_t(0x4B2EF0);
|
||||
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);
|
||||
|
||||
|
@ -71,6 +71,9 @@ namespace Game
|
||||
typedef void(*Sys_OutOfMemErrorInternal_t)(const char* filename, int line);
|
||||
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 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)
|
||||
{
|
||||
STARTUPINFOA startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
STARTUPINFOA startupInfo;
|
||||
PROCESS_INFORMATION processInfo;
|
||||
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
ZeroMemory(&process_info, sizeof(process_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
ZeroMemory(&startupInfo, sizeof(startupInfo));
|
||||
ZeroMemory(&processInfo, sizeof(processInfo));
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
|
||||
CreateProcessA(process.data(), const_cast<char*>(commandLine.data()), nullptr,
|
||||
nullptr, false, NULL, nullptr, currentDir.data(),
|
||||
&startup_info, &process_info);
|
||||
&startupInfo, &processInfo);
|
||||
|
||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(process_info.hThread);
|
||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(process_info.hProcess);
|
||||
if (processInfo.hThread && processInfo.hThread != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(processInfo.hThread);
|
||||
if (processInfo.hProcess && processInfo.hProcess != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(processInfo.hProcess);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user