New features

- check if game is running, and if so, warn and discard update/install request.
- check if launcher instance is running, and if so, not create another one but instead move focus to existing one
- periodic update checks and update installations
This commit is contained in:
Kawe Mazidjatari 2023-07-31 02:51:27 +02:00
parent b5626cff23
commit 9642ea18f3
6 changed files with 298 additions and 44 deletions

View File

@ -4,6 +4,7 @@
//
//=============================================================================//
#include "base_surface.h"
#include "sdklauncher.h"
#include "advanced_surface.h"
#include "download_surface.h"
#include "sdklauncher_utils.h"
@ -13,7 +14,7 @@
#include "zip/src/ZipFile.h"
#define WINDOW_SIZE_X 400
#define WINDOW_SIZE_Y 194
#define WINDOW_SIZE_Y 224
CBaseSurface::CBaseSurface()
{
@ -45,22 +46,22 @@ CBaseSurface::CBaseSurface()
this->m_BaseGroup->SetAnchor(Forms::AnchorStyles::Top | Forms::AnchorStyles::Left);
this->AddControl(this->m_BaseGroup);
const bool isInstalled = fs::exists("r5apex.exe");
m_bIsInstalled = fs::exists("r5apex.exe");
this->m_ManageButton = new UIX::UIXButton();
this->m_ManageButton->SetSize({ 168, 70 });
this->m_ManageButton->SetLocation({ 10, 10 });
this->m_ManageButton->SetTabIndex(9);
this->m_ManageButton->SetText(isInstalled ? XorStr("Launch Apex") : XorStr("Install Apex"));
this->m_ManageButton->SetText(m_bIsInstalled ? XorStr("Launch Apex") : XorStr("Install Apex"));
this->m_ManageButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Left);
this->m_ManageButton->Click += isInstalled ? &OnAdvancedClick : &OnInstallClick;
this->m_ManageButton->Click += m_bIsInstalled ? &OnLaunchClick : &OnInstallClick;
m_BaseGroup->AddControl(this->m_ManageButton);
this->m_RepairButton = new UIX::UIXButton();
this->m_RepairButton->SetSize({ 168, 70 });
this->m_RepairButton->SetLocation({ 10, 90 });
this->m_RepairButton->SetTabIndex(9);
this->m_RepairButton->SetEnabled(isInstalled);
this->m_RepairButton->SetEnabled(/*m_bIsInstalled*/ false);
this->m_RepairButton->SetText(XorStr("Repair Apex"));
this->m_RepairButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Left);
// TODO: should hash every file against a downloaded manifest instead and
@ -90,32 +91,147 @@ CBaseSurface::CBaseSurface()
this->m_AdvancedButton->SetSize({ 178, 43 });
this->m_AdvancedButton->SetLocation({ 188, 116 });
this->m_AdvancedButton->SetTabIndex(9);
this->m_AdvancedButton->SetEnabled(isInstalled);
this->m_AdvancedButton->SetEnabled(m_bIsInstalled);
this->m_AdvancedButton->SetText(XorStr("Advanced Options"));
this->m_AdvancedButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Left);
this->m_AdvancedButton->Click += &OnAdvancedClick;
m_BaseGroup->AddControl(this->m_AdvancedButton);
m_ExperimentalBuildsCheckbox = new UIX::UIXCheckBox();
this->m_ExperimentalBuildsCheckbox->SetSize({ 250, 18 });
this->m_ExperimentalBuildsCheckbox->SetLocation({ 10, 170 });
this->m_ExperimentalBuildsCheckbox->SetTabIndex(0);
this->m_ExperimentalBuildsCheckbox->SetText(XorStr("Also check for playtest versions updates"));
this->m_ExperimentalBuildsCheckbox->SetAnchor(Forms::AnchorStyles::Top | Forms::AnchorStyles::Left);
// TODO: remove this when its made public!!!
m_ExperimentalBuildsCheckbox->SetChecked(true);
m_BaseGroup->AddControl(this->m_ExperimentalBuildsCheckbox);
// TODO: Use a toggle item instead; remove this field.
m_bPartialInstall = false;
m_bUpdateViewToggled = false;
Threading::Thread([this] {
this->Frame();
}).Start();
}
void CBaseSurface::ToggleUpdateView(bool bValue)
{
// Game must be installed before this can be called!!
Assert(m_bIsInstalled);
this->m_ManageButton->SetText(bValue ? XorStr("Update Apex") : XorStr("Install Apex"));
if (bValue)
{
this->m_ManageButton->Click -= &OnLaunchClick;
this->m_ManageButton->Click += &OnUpdateClick;
}
else
{
this->m_ManageButton->Click -= &OnUpdateClick;
this->m_ManageButton->Click += &OnLaunchClick;
}
m_bUpdateViewToggled = bValue;
}
void CBaseSurface::Frame()
{
for (;;)
{
printf("%s: runframe; interval=%f\n", __FUNCTION__, g_flUpdateCheckRate);
if (!m_bUpdateViewToggled && m_bIsInstalled && SDKLauncher_CheckForUpdate(m_ExperimentalBuildsCheckbox->Checked()))
{
ToggleUpdateView(true);
printf("%s: found update; interval=%f\n", __FUNCTION__, g_flUpdateCheckRate);
}
std::this_thread::sleep_for(IntervalToDuration(g_flUpdateCheckRate));
}
}
void CBaseSurface::OnUpdateClick(Forms::Control* Sender)
{
//CBaseSurface* pSurf = (CBaseSurface*)Sender;
vector<HWND> vecHandles;
EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&vecHandles));
if (!vecHandles.empty())
{
Forms::MessageBox::Show("Close all game instances before updating the game!\n",
"Warning", Forms::MessageBoxButtons::OK, Forms::MessageBoxIcon::Warning);
return;
}
auto downloadSurface = std::make_unique<CProgressPanel>();
CProgressPanel* pProgress = downloadSurface.get();
pProgress->SetAutoClose(true);
Threading::Thread([pProgress] {
if (!SDKLauncher_CreateDepotDirectories())
{
Forms::MessageBox::Show(Format("Failed to create depot directories: Error code = %08x\n", GetLastError()).c_str(),
"Error", Forms::MessageBoxButtons::OK, Forms::MessageBoxIcon::Error);
return;
}
CUtlVector<CUtlString> fileList;
SDKLauncher_BeginDownload(true, false, true, fileList, pProgress);
pProgress->SetCanCancel(false);
SDKLauncher_InstallAssetList(false, fileList, pProgress);
// Close on finish.
pProgress->Close();
}).Start();
pProgress->ShowDialog();
// Restart the launcher process from here through updater.exe!
SDKLauncher_Restart();
}
void CBaseSurface::OnInstallClick(Forms::Control* Sender)
{
vector<HWND> vecHandles;
EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&vecHandles));
if (!vecHandles.empty())
{
Forms::MessageBox::Show("Close all game instances before installing the game!\n",
"Warning", Forms::MessageBoxButtons::OK, Forms::MessageBoxIcon::Warning);
return;
}
CBaseSurface* pSurf = (CBaseSurface*)Sender;
const bool bPartial = pSurf->m_bPartialInstall;
//const int minRequiredSpace = bPartial ? MIN_REQUIRED_DISK_SPACE : MIN_REQUIRED_DISK_SPACE_OPT;
//int currentDiskSpace;
const int minRequiredSpace = bPartial ? MIN_REQUIRED_DISK_SPACE : MIN_REQUIRED_DISK_SPACE_OPT;
int currentDiskSpace;
//if (!SDKLauncher_CheckDiskSpace(minRequiredSpace, &currentDiskSpace))
//{
// Forms::MessageBox::Show(Format("There is not enough space available on the disk to install R5Reloaded;"
// " you need at least %iGB, you currently have %iGB\n", minRequiredSpace, currentDiskSpace).c_str(),
// "Error", Forms::MessageBoxButtons::OK, Forms::MessageBoxIcon::Error);
if (!SDKLauncher_CheckDiskSpace(minRequiredSpace, &currentDiskSpace))
{
Forms::MessageBox::Show(Format("There is not enough space available on the disk to install R5Reloaded;"
" you need at least %iGB, you currently have %iGB\n", minRequiredSpace, currentDiskSpace).c_str(),
"Error", Forms::MessageBoxButtons::OK, Forms::MessageBoxIcon::Error);
// return;
//}
return;
}
auto downloadSurface = std::make_unique<CProgressPanel>();
CProgressPanel* pProgress = downloadSurface.get();
@ -133,7 +249,7 @@ void CBaseSurface::OnInstallClick(Forms::Control* Sender)
}
CUtlVector<CUtlString> fileList;
SDKLauncher_BeginDownload(true, false, fileList, pProgress);
SDKLauncher_BeginDownload(true, false, false, fileList, pProgress);
SDKLauncher_InstallAssetList(false, fileList, pProgress);
// Close on finish.
@ -146,6 +262,14 @@ void CBaseSurface::OnInstallClick(Forms::Control* Sender)
SDKLauncher_Restart();
}
void CBaseSurface::OnLaunchClick(Forms::Control* Sender)
{
// !TODO: parameter building and settings loading should be its own class!!
if (g_pLauncher->CreateLaunchContext(eLaunchMode::LM_CLIENT, 0, "", "startup_launcher.cfg"))
g_pLauncher->LaunchProcess();
}
void CBaseSurface::OnAdvancedClick(Forms::Control* Sender)
{
auto pAdvancedSurface = std::make_unique<CAdvancedSurface>();

View File

@ -8,8 +8,12 @@ public:
virtual ~CBaseSurface()
{};
void ToggleUpdateView(bool bValue);
protected:
static void OnInstallClick(Forms::Control* Sender);
static void OnUpdateClick(Forms::Control* Sender);
static void OnLaunchClick(Forms::Control* Sender);
static void OnAdvancedClick(Forms::Control* Sender);
@ -18,6 +22,7 @@ protected:
static void OnJoinClick(Forms::Control* Sender);
private:
void Frame();
enum class eMode
{
@ -42,7 +47,12 @@ private:
UIX::UIXButton* m_JoinButton;
UIX::UIXButton* m_AdvancedButton;
UIX::UIXCheckBox* m_ExperimentalBuildsCheckbox;
// When this is false, the installer will
// download the HD textures as well (STARPAK's).
bool m_bPartialInstall;
bool m_bUpdateViewToggled;
bool m_bIsInstalled;
};

View File

@ -390,6 +390,24 @@ void LauncherLoggerSink(LogType_t logType, LogLevel_t logLevel, eDLL_T context,
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]/*, char* envp[]*/)
{
HANDLE singleInstanceMutex = CreateMutexW(NULL, TRUE, L"SDKLauncher_Mutex");
if (singleInstanceMutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS)
{
HWND existingApp = FindWindowW(0, L"SDKLauncher001");
if (existingApp) SetForegroundWindow(existingApp);
return EXIT_SUCCESS;
}
WNDCLASSEXW wcex = { sizeof(wcex) };
wcex.lpszClassName = L"SDKLauncher001";
if (!RegisterClassExW(&wcex))
{
DWORD dwError = GetLastError();
return dwError;
}
g_pLauncher->InitLogger();
if (argc < 2)
{

View File

@ -23,6 +23,12 @@
// have to go here!!
#define RESTART_DEPOT_DOWNLOAD_DIR DEFAULT_DEPOT_DOWNLOAD_DIR "temp\\"
#define DEPOT_MANIFEST_FILE "manifest_patch.json"
// TODO: these should be obtained dynamically!!!
#define GAME_DEPOT_VENDOR "https://api.github.com/repos/SlaveBuild/N1094_CL456479/releases"
#define SDK_DEPOT_VENDOR "https://api.github.com/repos/Mauler125/r5sdk/releases"
//-----------------------------------------------------------------------------
// Launch and inject specified dll based on launch mode
//-----------------------------------------------------------------------------

View File

@ -4,6 +4,10 @@
#include "tier2/curlutils.h"
#include "zip/src/ZipFile.h"
bool g_bPartialInstall = false;
//bool g_bExperimentalBuilds = false;
float g_flUpdateCheckRate = 64;
// !TODO: perhaps this should be a core utility shared across
// the entire SDK to allow processes to restart them selfs.
void SDKLauncher_Restart()
@ -173,7 +177,7 @@ bool SDKLauncher_QueryServer(const char* url, string& outResponse, string& outMe
params.writeFunction = CURLWriteStringCallback;
params.timeout = QUERY_TIMEOUT;
params.verifyPeer = true;
params.verbose = IsDebug();
params.verbose = 0;// IsDebug();
CURL* curl = CURLInitRequest(url, nullptr, outResponse, sList, params);
@ -204,11 +208,13 @@ bool SDKLauncher_GetLatestReleaseManifest(const char* url, string& responseMessa
if (!SDKLauncher_QueryServer(url, responseBody, responseMessage, status))
{
responseMessage = responseBody;
return false;
}
if (status != 200)
{
responseMessage = responseBody;
return false;
}
@ -222,17 +228,17 @@ bool SDKLauncher_GetLatestReleaseManifest(const char* url, string& responseMessa
if (preRelease && release["prerelease"])
{
outManifest = release["assets"];
outManifest = release;
break;
}
else if (!release["prerelease"])
{
outManifest = release["assets"];
outManifest = release;
break;
}
if (i == responseJson.size() - 1 && outManifest.empty())
release[0]["assets"]; // Just take the first one then.
release[0]; // Just take the first one then.
}
}
catch (const std::exception& ex)
@ -254,7 +260,10 @@ int SDKLauncher_ProgressCallback(CURLProgress* progessData, double dltotal,
CProgressPanel* pDownloadSurface = (CProgressPanel*)progessData->cust;
if (pDownloadSurface->IsCanceled())
{
pDownloadSurface->Close();
return -1;
}
double downloaded;
curl_easy_getinfo(progessData->curl, CURLINFO_SIZE_DOWNLOAD, &downloaded);
@ -270,7 +279,7 @@ int SDKLauncher_ProgressCallback(CURLProgress* progessData, double dltotal,
//----------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------
void SDKLauncher_DownloadAsset(const char* url, const char* path, const char* fileName,
bool SDKLauncher_DownloadAsset(const char* url, const char* path, const char* fileName,
const size_t fileSize, const char* options, CProgressPanel* pProgress)
{
CURLParams params;
@ -278,22 +287,24 @@ void SDKLauncher_DownloadAsset(const char* url, const char* path, const char* fi
params.writeFunction = CURLWriteFileCallback;
params.statusFunction = SDKLauncher_ProgressCallback;
CURLDownloadFile(url, path, fileName, options, fileSize, pProgress, params);
return CURLDownloadFile(url, path, fileName, options, fileSize, pProgress, params);
}
//----------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------
void SDKLauncher_BeginDownload(const bool bPreRelease, const bool bOptionalAssets,
CUtlVector<CUtlString>& fileList, CProgressPanel* pProgress)
const bool bSdkOnly/*!!! REFACTOR ME MAYBE !!!*/, CUtlVector<CUtlString>& fileList, CProgressPanel* pProgress)
{
string responseMesage;
string responseMessage;
nlohmann::json manifest;
// These files will NOT be downloaded from the release depots.
std::set<string> blackList;
blackList.insert("symbols.zip");
// DEBUG CODE!!!
//fileList.AddToTail("audio_0.zip");
//fileList.AddToTail("audio_1.zip");
//fileList.AddToTail("binaries.zip");
@ -303,21 +314,30 @@ void SDKLauncher_BeginDownload(const bool bPreRelease, const bool bOptionalAsset
//fileList.AddToTail("starpak_0.zip");
//fileList.AddToTail("starpak_1.zip");
//fileList.AddToTail("stbsp.zip");
//fileList.AddToTail("depot.zip");
//fileList.AddToTail("/depot.zip");
//// Download core game files.
//if (!SDKLauncher_GetLatestReleaseManifest(XorStr("https://api.github.com/repos/SlaveBuild/N1094_CL456479/releases"), responseMesage, manifest, bPreRelease))
//FOR_EACH_VEC(fileList, i)
//{
// // TODO: Error dialog.
// return;
// CUtlString& filePath = fileList[i];
// filePath = filePath.Replace("/", DEFAULT_DEPOT_DOWNLOAD_DIR);
//}
//SDKLauncher_DownloadAssetList(fileList, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR, pProgress);
//if (pProgress->IsCanceled())
// return;
if (!bSdkOnly)
{
// Download core game files.
if (!SDKLauncher_GetLatestReleaseManifest(XorStr(GAME_DEPOT_VENDOR), responseMessage, manifest, bPreRelease))
{
// TODO: Error dialog.
return;
}
SDKLauncher_DownloadAssetList(fileList, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR, pProgress);
if (pProgress->IsCanceled())
return;
}
// Download SDK files.
if (!SDKLauncher_GetLatestReleaseManifest(XorStr("https://api.github.com/repos/Mauler125/r5sdk/releases"), responseMesage, manifest, bPreRelease))
if (!SDKLauncher_GetLatestReleaseManifest(XorStr(SDK_DEPOT_VENDOR), responseMessage, manifest, bPreRelease))
{
// TODO: Error dialog.
return;
@ -325,17 +345,27 @@ void SDKLauncher_BeginDownload(const bool bPreRelease, const bool bOptionalAsset
SDKLauncher_DownloadAssetList(fileList, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR, pProgress);
if (pProgress->IsCanceled())
{
return;
}
}
//----------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------
void SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::json& assetList,
bool SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::json& assetList,
std::set<string>& blackList, const char* pPath, CProgressPanel* pProgress)
{
if (!assetList.contains("assets"))
{
Assert(0);
return false;
}
int i = 1;
for (auto& asset : assetList)
const auto assetListArray = assetList["assets"];
for (auto& asset : assetListArray)
{
if (pProgress->IsCanceled())
{
@ -347,7 +377,7 @@ void SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::j
!asset.contains("size"))
{
Assert(0);
return;
return false;
}
const string fileName = asset["name"];
@ -355,12 +385,14 @@ void SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::j
// Asset is filtered, don't download.
if (blackList.find(fileName) != blackList.end())
{
continue;
}
const string downloadLink = asset["browser_download_url"];
const size_t fileSize = asset["size"];
pProgress->SetText(Format("Downloading package %i of %i...", i, assetList.size()).c_str());
pProgress->SetText(Format("Downloading package %i of %i...", i, assetListArray.size()).c_str());
SDKLauncher_DownloadAsset(downloadLink.c_str(), pPath, pFileName, fileSize, "wb+", pProgress);
// Check if its a zip file, as these are
@ -377,12 +409,14 @@ void SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::j
i++;
}
return true;
}
//----------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------
void SDKLauncher_InstallAssetList(const bool bOptionalAssets,
bool SDKLauncher_InstallAssetList(const bool bOptionalAssets,
CUtlVector<CUtlString>& fileList, CProgressPanel* pProgress)
{
// Install process cannot be canceled.
@ -393,8 +427,11 @@ void SDKLauncher_InstallAssetList(const bool bOptionalAssets,
pProgress->SetText(Format("Installing package %i of %i...", i + 1, fileList.Count()).c_str());
CUtlString& fileName = fileList[i];
SDKLauncher_ExtractZipFile(fileName.Get(), "", pProgress);
if (!SDKLauncher_ExtractZipFile(fileName.Get(), "", pProgress))
return false;
}
return true;
}
//----------------------------------------------------------------------------
@ -427,10 +464,66 @@ bool SDKLauncher_CheckDiskSpace(const int minRequiredSpace, int* const available
return true;
}
bool SDKLauncher_GetLocalManifest(nlohmann::json& localManifest)
{
const char* pManifestFileName = BASE_PLATFORM_DIR DEPOT_MANIFEST_FILE;
if (!fs::exists(pManifestFileName))
return false;
ifstream localFile(pManifestFileName);
if (!localFile.good())
return false;
try
{
localManifest = nlohmann::json::parse(localFile);
}
catch (const std::exception& ex)
{
printf("%s - Exception while parsing manifest:\n%s\n", __FUNCTION__, ex.what());
return false;
}
return !localManifest.empty();
}
//bool SDKLauncher_WriteLocalManifest()
//{
//
//}
//----------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------
bool SDKLauncher_CheckForUpdate()
bool SDKLauncher_CheckForUpdate(const bool bPreRelease)
{
return true;
nlohmann::json remoteManifest;
string responseMessage;
if (!SDKLauncher_GetLatestReleaseManifest(XorStr(SDK_DEPOT_VENDOR), responseMessage, remoteManifest, bPreRelease))
{
printf("%s: Failed to obtain remote manifest: %s\n", __FUNCTION__, responseMessage.c_str());
return true; // Can't determine if there is an update or not; skip...
}
nlohmann::json localManifest;
if (!SDKLauncher_GetLocalManifest(localManifest))
{
// Failed to load a local one; assume an update is required.
printf("%s: Failed to obtain local manifest\n", __FUNCTION__);
return true;
}
if (!localManifest.contains("version"))
{
// No version information; assume an update is required.
printf("%s: local manifest does not contain field '%s'!\n", __FUNCTION__, "version");
return true;
}
// This evaluates to '0' if the version tags are equal.
return !(localManifest["version"] == remoteManifest["tag_name"]);
}

View File

@ -1,18 +1,21 @@
#pragma once
#include "download_surface.h"
extern float g_flUpdateCheckRate;
void SDKLauncher_Restart();
bool SDKLauncher_CreateDepotDirectories();
bool SDKLauncher_ClearDepotDirectories();
bool SDKLauncher_ExtractZipFile(const char* pZipFile, const char* pDestPath, CProgressPanel* pProgress = nullptr);
void SDKLauncher_BeginDownload(const bool bPreRelease, const bool bOptionalAssets, CUtlVector<CUtlString>& fileList, CProgressPanel* pProgress = nullptr);
void SDKLauncher_BeginDownload(const bool bPreRelease, const bool bOptionalAssets, const bool bSdkOnly, CUtlVector<CUtlString>& fileList, CProgressPanel* pProgress = nullptr);
void SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::json& assetList,
bool SDKLauncher_DownloadAssetList(CUtlVector<CUtlString>& fileList, nlohmann::json& assetList,
std::set<string>& blackList, const char* pPath, CProgressPanel* pProgress);
void SDKLauncher_InstallAssetList(const bool bOptionalAssets,
bool SDKLauncher_InstallAssetList(const bool bOptionalAssets,
CUtlVector<CUtlString>& fileList, CProgressPanel* pProgress);
bool SDKLauncher_CheckDiskSpace(const int minRequiredSpace, int* const availableSize = nullptr);
bool SDKLauncher_CheckForUpdate(const bool bPreRelease);