Launcher UX Improvements

* Fix bug regarding searching for prerelease builds, prerelease was assumed to be always at the top but this isn't true.
* Make the prerelease toggle button check for updates instantly.
* Automatically launch the game after the update has finished.
This commit is contained in:
Kawe Mazidjatari 2023-10-29 20:08:10 +01:00
parent 633292a820
commit a7277b47fc
7 changed files with 244 additions and 29 deletions

View File

@ -12,6 +12,7 @@
#include "tier1/utlmap.h"
#include "tier2/curlutils.h"
#include "zip/src/ZipFile.h"
#include "utility/vdf_parser.h"
#define WINDOW_SIZE_X 400
#define WINDOW_SIZE_Y 224
@ -29,6 +30,9 @@ CBaseSurface::CBaseSurface()
this->SetMaximizeBox(false);
this->SetBackColor(Drawing::Color(47, 54, 61));
this->Load += &OnLoad;
this->FormClosing += &OnClose;
this->m_BaseGroup = new UIX::UIXGroupBox();
this->m_ManageButton = new UIX::UIXButton();
this->m_RepairButton = new UIX::UIXButton();
@ -102,16 +106,13 @@ CBaseSurface::CBaseSurface()
this->m_ExperimentalBuildsCheckbox->SetTabIndex(0);
this->m_ExperimentalBuildsCheckbox->SetText(XorStr("Check for playtest/event updates"));
this->m_ExperimentalBuildsCheckbox->SetAnchor(Forms::AnchorStyles::Top | Forms::AnchorStyles::Left);
this->m_ExperimentalBuildsCheckbox->Click += OnExperimentalBuildsClick;
m_BaseGroup->AddControl(this->m_ExperimentalBuildsCheckbox);
// TODO: Use a toggle item instead; remove this field.
m_bPartialInstall = true;
m_bUpdateViewToggled = false;
Threading::Thread([this] {
this->Frame();
}).Start();
}
void CBaseSurface::ToggleUpdateView(bool bValue)
@ -119,7 +120,13 @@ 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"));
// Don't switch it to the same view, else we install the callbacks
// twice and thus call them twice.
if (bValue == m_bUpdateViewToggled)
return;
m_bUpdateViewToggled = bValue;
this->m_ManageButton->SetText(bValue ? XorStr("Update && Launch Apex") : XorStr("Launch Apex"));
if (bValue)
{
@ -131,8 +138,23 @@ void CBaseSurface::ToggleUpdateView(bool bValue)
this->m_ManageButton->Click -= &OnUpdateClick;
this->m_ManageButton->Click += &OnLaunchClick;
}
}
m_bUpdateViewToggled = bValue;
bool CBaseSurface::CheckForUpdate()
{
printf("%s: runframe; interval=%f\n", __FUNCTION__, g_flUpdateCheckRate);
const bool updateAvailable = SDKLauncher_CheckForUpdate(m_ExperimentalBuildsCheckbox->Checked());
if (m_bIsInstalled && updateAvailable)
{
printf("%s: found update; interval=%f\n", __FUNCTION__, g_flUpdateCheckRate);
ToggleUpdateView(true);
return true;
}
ToggleUpdateView(false);
return false;
}
@ -142,9 +164,8 @@ void CBaseSurface::Frame()
{
printf("%s: runframe; interval=%f\n", __FUNCTION__, g_flUpdateCheckRate);
if (!m_bUpdateViewToggled && m_bIsInstalled && SDKLauncher_CheckForUpdate(m_ExperimentalBuildsCheckbox->Checked()))
if (CheckForUpdate())
{
ToggleUpdateView(true);
printf("%s: found update; interval=%f\n", __FUNCTION__, g_flUpdateCheckRate);
}
@ -152,6 +173,124 @@ void CBaseSurface::Frame()
}
}
//-----------------------------------------------------------------------------
// Purpose: load callback
// Input : *pSender -
//-----------------------------------------------------------------------------
void CBaseSurface::OnLoad(Forms::Control* pSender)
{
CBaseSurface* pBaseSurface = (CBaseSurface*)pSender->FindForm();
pBaseSurface->LoadSettings();
Threading::Thread([pBaseSurface] {
pBaseSurface->Frame();
}).Start();
}
//-----------------------------------------------------------------------------
// Purpose: close callback
// Input : *pSender -
//-----------------------------------------------------------------------------
void CBaseSurface::OnClose(const std::unique_ptr<FormClosingEventArgs>& /*pEventArgs*/, Forms::Control* pSender)
{
((CBaseSurface*)pSender->FindForm())->SaveSettings();
}
void CBaseSurface::LoadSettings()
{
const fs::path settingsPath(Format("platform/%s/%s", SDK_SYSTEM_CFG_PATH, LAUNCHER_SETTING_FILE));
if (!fs::exists(settingsPath))
{
return;
}
bool success;
std::ifstream fileStream(settingsPath, fstream::in);
vdf::object vRoot = vdf::read(fileStream, &success);
if (!success)
{
printf("%s: Failed to parse VDF file: '%s'\n", __FUNCTION__,
settingsPath.u8string().c_str());
return;
}
try
{
string& attributeView = vRoot.attribs["version"];
int settingsVersion = atoi(attributeView.c_str());
if (settingsVersion != SDK_LAUNCHER_VERSION)
return;
vdf::object* pSubKey = vRoot.childs["vars"].get();
if (!pSubKey)
return;
attributeView = pSubKey->attribs["experimentalBuilds"];
this->m_ExperimentalBuildsCheckbox->SetChecked(attributeView != "0");
}
catch (const std::exception& e)
{
printf("%s: Exception while parsing VDF file: %s\n", __FUNCTION__, e.what());
}
}
//-----------------------------------------------------------------------------
// Purpose: gets the control item value
// Input : *pControl -
//-----------------------------------------------------------------------------
const char* GetControlValue(Forms::Control* pControl)
{
switch (pControl->GetType())
{
case Forms::ControlTypes::CheckBox:
case Forms::ControlTypes::RadioButton:
{
return reinterpret_cast<UIX::UIXCheckBox*>(pControl)->Checked() ? "1" : "0";
}
default:
{
return pControl->Text().ToCString();
}
}
}
void CBaseSurface::SaveSettings()
{
const fs::path settingsPath(Format("platform/%s/%s", SDK_SYSTEM_CFG_PATH, LAUNCHER_SETTING_FILE));
const fs::path parentPath = settingsPath.parent_path();
if (!fs::exists(parentPath) && !fs::create_directories(parentPath))
{
printf("%s: Failed to create directory: '%s'\n", __FUNCTION__,
parentPath.relative_path().u8string().c_str());
return;
}
std::ofstream fileStream(settingsPath, fstream::out);
if (!fileStream)
{
printf("%s: Failed to create VDF file: '%s'\n", __FUNCTION__,
settingsPath.u8string().c_str());
return;
}
vdf::object vRoot;
vRoot.set_name("LauncherSettings");
vRoot.add_attribute("version", std::to_string(SDK_LAUNCHER_VERSION));
vdf::object* vVars = new vdf::object();
vVars->set_name("vars");
// Game.
vVars->add_attribute("experimentalBuilds", GetControlValue(this->m_ExperimentalBuildsCheckbox));
vRoot.add_child(std::unique_ptr<vdf::object>(vVars));
vdf::write(fileStream, vRoot);
}
void CBaseSurface::OnUpdateClick(Forms::Control* Sender)
{
//CBaseSurface* pSurf = (CBaseSurface*)Sender;
@ -283,6 +422,12 @@ void CBaseSurface::OnAdvancedClick(Forms::Control* Sender)
pAdvancedSurface->ShowDialog((Forms::Form*)Sender->FindForm());
}
void CBaseSurface::OnExperimentalBuildsClick(Forms::Control* Sender)
{
CBaseSurface* pBaseSurface = reinterpret_cast<CBaseSurface*>(Sender->FindForm());
pBaseSurface->CheckForUpdate();
}
void CBaseSurface::OnSupportClick(Forms::Control* /*Sender*/)
{
ShellExecute(0, 0, XorStr("https://ko-fi.com/amos0"), 0, 0, SW_SHOW);

View File

@ -9,14 +9,18 @@ public:
{};
void ToggleUpdateView(bool bValue);
bool CheckForUpdate();
protected:
static void OnLoad(Forms::Control* pSender);
static void OnClose(const std::unique_ptr<FormClosingEventArgs>& pEventArgs, Forms::Control* pSender);
static void OnInstallClick(Forms::Control* Sender);
static void OnUpdateClick(Forms::Control* Sender);
static void OnLaunchClick(Forms::Control* Sender);
static void OnAdvancedClick(Forms::Control* Sender);
static void OnExperimentalBuildsClick(Forms::Control* Sender);
static void OnSupportClick(Forms::Control* Sender);
static void OnJoinClick(Forms::Control* Sender);
@ -25,6 +29,9 @@ protected:
private:
void Frame();
void LoadSettings();
void SaveSettings();
enum class eMode
{
NONE = -1,

View File

@ -56,7 +56,12 @@ int CLauncher::HandleCommandLine(int argc, char* argv[])
string arg = argv[i];
eLaunchMode mode = eLaunchMode::LM_NONE;
if ((arg == "-developer") || (arg == "-dev"))
if ((arg == "-launch"))
{
LaunchGameDefault();
return -2;
}
else if ((arg == "-developer") || (arg == "-dev"))
{
mode = eLaunchMode::LM_GAME_DEV;
}
@ -326,6 +331,26 @@ bool CLauncher::LaunchProcess() const
return true;
}
///////////////////////////////////////////////////////////////////////////////
// Purpose: launches the game with default parameters
// Output : true on success, false otherwise
///////////////////////////////////////////////////////////////////////////////
bool CLauncher::LaunchGameDefault() const
{
// Hack: free the console before launching since we only want it to be shown
// for the command line interface.
FreeConsole();
// !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();
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Purpose: Window enumerator callback.
// Input : hwnd -
@ -386,6 +411,17 @@ void LauncherLoggerSink(LogType_t logType, LogLevel_t logLevel, eDLL_T context,
}
}
void RunGUI()
{
#ifdef NDEBUG
FreeConsole();
#endif // NDEBUG
curl_global_init(CURL_GLOBAL_ALL);
g_CoreMsgVCallback = LauncherLoggerSink;
g_pLauncher->RunSurface();
curl_global_cleanup();
}
///////////////////////////////////////////////////////////////////////////////
// EntryPoint.
///////////////////////////////////////////////////////////////////////////////
@ -400,21 +436,23 @@ int main(int argc, char* argv[]/*, char* envp[]*/)
g_pLauncher->InitLogger();
if (argc < 2)
{
#ifdef NDEBUG
FreeConsole();
#endif // NDEBUG
curl_global_init(CURL_GLOBAL_ALL);
g_CoreMsgVCallback = LauncherLoggerSink;
g_pLauncher->RunSurface();
curl_global_cleanup();
RunGUI();
}
else
{
int results = g_pLauncher->HandleCommandLine(argc, argv);
if (results != -1)
if (results == -1)
{
return g_pLauncher->HandleInput();
}
else if (results == -2)
{
RunGUI();
}
else
{
return results;
return g_pLauncher->HandleInput();
}
}
return EXIT_SUCCESS;

View File

@ -50,6 +50,7 @@ public:
bool CreateLaunchContext(eLaunchMode lMode, uint64_t nProcessorAffinity = NULL, const char* szCommandLine = nullptr, const char* szConfig = nullptr);
void SetupLaunchContext(const char* szConfig, const char* szGameDll, const char* szCommandLine);
bool LaunchProcess() const;
bool LaunchGameDefault() const;
CBaseSurface* GetMainSurface() const { return m_pSurface; }

View File

@ -1,10 +1,10 @@
#include "sdklauncher_utils.h"
#include "windows/window.h"
#include "tier0/binstream.h"
#include "tier1/xorstr.h"
#include "tier1/utlmap.h"
#include "tier2/curlutils.h"
#include "zip/src/ZipFile.h"
#include <public/tier0/binstream.h>
bool g_bPartialInstall = false;
//bool g_bExperimentalBuilds = false;
@ -331,12 +331,25 @@ bool SDKLauncher_AcquireReleaseManifest(const char* url, string& responseMessage
{
auto& release = responseJson[i];
if (preRelease && release["prerelease"])
if (preRelease)
{
outManifest = release;
break;
if (i == responseJson.size() - 1 && outManifest.empty())
{
// TODO: we probably do not want to take the first one, as it might not be ordered
// needs to be confirmed!
outManifest = responseJson[0]; // Just take the first one then.
break;
}
const bool isPreRelease = release["prerelease"].get<bool>();
if (isPreRelease)
{
outManifest = release;
break;
}
}
else if (!release["prerelease"])
else
{
outManifest = release;
break;

View File

@ -122,17 +122,28 @@ void MoveFiles(const char* pSourceDir, const char* pDestDir)
FindClose(hFind);
}
//-----------------------------------------------------------------------------
// Purpose: print version numbers, warnings, etc
//-----------------------------------------------------------------------------
void PrintHeader()
{
printf("********************************************************************************\n");
printf("R5 updater [Version %s]\n", UPDATER_VERSION);
printf("!!! DO NOT INTERRUPT THE APPLICATION WHILE THE UPDATE IS IN PROGRESS !!!\n");
printf("********************************************************************************\n");
}
//-----------------------------------------------------------------------------
// Purpose: entry point
//-----------------------------------------------------------------------------
int main(int argc, char** argv)
{
printf("R5 updater [Version %s]\n", UPDATER_VERSION);
PrintHeader();
// Make sure the caller passed in a process id.
if (argc < 1)
{
printf("%s: Updater must be invoked with a ProcessID of the parent process!\n", "Error");
printf("%s: Updater must be invoked with a Process ID of the parent process!\n", "Error");
Sleep(UPDATER_SLEEP_TIME_BEFORE_EXIT);
return EXIT_FAILURE;
@ -199,7 +210,7 @@ int main(int argc, char** argv)
BOOL createResult = CreateProcessA(
"launcher.exe", // lpApplicationName
NULL, // lpCommandLine
(LPSTR)"-launch", // lpCommandLine
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
FALSE, // bInheritHandles

View File

@ -1,7 +1,7 @@
#ifndef SDKUPDATER_H
#define SDKUPDATER_H
#define UPDATER_VERSION "v0.2a"
#define UPDATER_VERSION "v0.3a"
#define UPDATER_SLEEP_TIME_BEFORE_EXIT 15000
#endif // SDKUPDATER_H