From b5626cff2325091bf8e84de242360553a0d086a2 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sun, 30 Jul 2023 16:51:44 +0200 Subject: [PATCH] Major cleanup Decouple all logic from installer to shared utils, group all constants into 'sdklauncher_const.h'. --- r5dev/sdklauncher/CMakeLists.txt | 2 + r5dev/sdklauncher/base_surface.cpp | 392 ++------------------- r5dev/sdklauncher/download_surface.cpp | 22 +- r5dev/sdklauncher/download_surface.h | 6 +- r5dev/sdklauncher/sdklauncher_const.h | 13 + r5dev/sdklauncher/sdklauncher_pch.h | 2 + r5dev/sdklauncher/sdklauncher_utils.cpp | 436 ++++++++++++++++++++++++ r5dev/sdklauncher/sdklauncher_utils.h | 18 + 8 files changed, 513 insertions(+), 378 deletions(-) create mode 100644 r5dev/sdklauncher/sdklauncher_utils.cpp create mode 100644 r5dev/sdklauncher/sdklauncher_utils.h diff --git a/r5dev/sdklauncher/CMakeLists.txt b/r5dev/sdklauncher/CMakeLists.txt index d41ea0f1..51c104c1 100644 --- a/r5dev/sdklauncher/CMakeLists.txt +++ b/r5dev/sdklauncher/CMakeLists.txt @@ -7,6 +7,8 @@ add_sources( SOURCE_GROUP "Core" "sdklauncher.cpp" "sdklauncher.h" "sdklauncher_const.h" + "sdklauncher_utils.cpp" + "sdklauncher_utils.h" ) add_sources( SOURCE_GROUP "GUI" diff --git a/r5dev/sdklauncher/base_surface.cpp b/r5dev/sdklauncher/base_surface.cpp index 72d9c159..69fc18cc 100644 --- a/r5dev/sdklauncher/base_surface.cpp +++ b/r5dev/sdklauncher/base_surface.cpp @@ -6,22 +6,12 @@ #include "base_surface.h" #include "advanced_surface.h" #include "download_surface.h" +#include "sdklauncher_utils.h" #include "tier1/xorstr.h" #include "tier1/utlmap.h" #include "tier2/curlutils.h" #include "zip/src/ZipFile.h" -// Gigabytes. -// TODO: obtain these from the repo... -#define MIN_REQUIRED_DISK_SPACE 20 -#define MIN_REQUIRED_DISK_SPACE_OPT 55 // Optional textures -#define DEFAULT_DEPOT_DOWNLOAD_DIR "platform\\depot\\" - -// Files that need to be installed during launcher restart, -// have to go here!! -#define RESTART_DEPOT_DOWNLOAD_DIR DEFAULT_DEPOT_DOWNLOAD_DIR "temp\\" - - #define WINDOW_SIZE_X 400 #define WINDOW_SIZE_Y 194 @@ -73,7 +63,9 @@ CBaseSurface::CBaseSurface() this->m_RepairButton->SetEnabled(isInstalled); this->m_RepairButton->SetText(XorStr("Repair Apex")); this->m_RepairButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Left); - this->m_RepairButton->Click += &OnAdvancedClick; + // TODO: should hash every file against a downloaded manifest instead and + // start repairing what mismatches. + this->m_RepairButton->Click += &OnInstallClick; m_BaseGroup->AddControl(this->m_RepairButton); this->m_DonateButton = new UIX::UIXButton(); @@ -108,378 +100,50 @@ CBaseSurface::CBaseSurface() m_bPartialInstall = false; } -bool QueryServer(const char* url, string& outResponse, string& outMessage, CURLINFO& outStatus) -{ - curl_slist* sList = nullptr; - - CURLParams params; - - params.writeFunction = CURLWriteStringCallback; - params.timeout = 15; - params.verifyPeer = true; - params.verbose = false; - - CURL* curl = CURLInitRequest(url, nullptr, outResponse, sList, params); - - if (!curl) - { - return false; - } - - CURLcode res = CURLSubmitRequest(curl, sList); - if (!CURLHandleError(curl, res, outMessage, true)) - { - return false; - } - - outStatus = CURLRetrieveInfo(curl); - return true; -} - -void CreateDownloadDirectories() -{ - // Make sure the download path exists. - CreateDirectories(DEFAULT_DEPOT_DOWNLOAD_DIR); - CreateDirectories(RESTART_DEPOT_DOWNLOAD_DIR); -} - - -int DownloadStatusCallback(CURLProgress* progessData, double dltotal, double dlnow, double ultotal, double ulnow) -{ - CDownloadProgress* pDownloadSurface = (CDownloadProgress*)progessData->cust; - - if (pDownloadSurface->IsCanceled()) - return -1; - - double downloaded; - curl_easy_getinfo(progessData->curl, CURLINFO_SIZE_DOWNLOAD, &downloaded); - - size_t percentage = ((size_t)downloaded * 100) / progessData->size; - pDownloadSurface->UpdateProgress((uint32_t)percentage, false); - - return 0; -} - -void DownloadAsset(CDownloadProgress* pProgress, const char* url, const char* path, - const char* fileName, const size_t fileSize, const char* options) -{ - CURLParams params; - - params.writeFunction = CURLWriteFileCallback; - params.statusFunction = DownloadStatusCallback; - - CURLDownloadFile(url, path, fileName, options, fileSize, pProgress, params); -} - -void DownloadAssetList(CDownloadProgress* pProgress, CUtlVector& fileList, nlohmann::json& assetList, std::set& blackList, const char* pPath) -{ - int i = 1; - for (auto& asset : assetList) - { - if (pProgress->IsCanceled()) - { - pProgress->Close(); - break; - } - - if (!asset.contains("browser_download_url") || - !asset.contains("name") || - !asset.contains("size")) - { - Assert(0); - return; - } - const string fileName = asset["name"]; - - // 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->SetExportLabel(Format("%s (%i of %i)", fileName.c_str(), i, assetList.size()).c_str()); - DownloadAsset(pProgress, downloadLink.c_str(), pPath, fileName.c_str(), fileSize, "wb+"); - - CUtlString filePath = pPath; - filePath.Append(fileName.c_str()); - - fileList.AddToTail(filePath); - i++; - } -} - -bool DownloadLatestGitHubReleaseManifest(const char* url, string& responseMessage, nlohmann::json& outManifest, const bool preRelease) -{ - string responseBody; - CURLINFO status; - - if (!QueryServer(url, responseBody, responseMessage, status)) - { - // TODO: Error dialog... - printf("Query error!\n"); - return false; - } - - if (status != 200) - { - // TODO: Error dialog... - printf("Query error; status not 200!\n%s\n", responseBody.c_str()); - return false; - } - - try - { - nlohmann::json responseJson = nlohmann::json::parse(responseBody); - - for (size_t i = 0; i < responseJson.size(); i++) - { - auto& release = responseJson[i]; - - if (preRelease && release["prerelease"]) - { - outManifest = release["assets"]; - break; - } - else if (!preRelease && !release["prerelease"]) - { - outManifest = release["assets"]; - break; - } - - if (i == responseJson.size()-1 && outManifest.empty()) - release[0]["assets"]; // Just take the first one then. - } - } - catch (const std::exception& ex) - { - printf("%s - Exception while parsing response:\n%s\n", __FUNCTION__, ex.what()); - - //Warning(eDLL_T::ENGINE, "%s - Exception while parsing response:\n%s\n", __FUNCTION__, ex.what()); - // TODO: Error dialog; report to amos or something like that. - - return false; - } - - return !outManifest.empty(); -} - -bool ExtractZipFile(CDownloadProgress* pProgress, const char* pZipFile, const char* pDestPath) -{ - ZipArchive::Ptr archive = ZipFile::Open(pZipFile); - size_t entries = archive->GetEntriesCount(); - - CUtlMap fileList(UtlStringLessFunc); - - for (size_t i = 0; i < entries; ++i) - { - auto entry = archive->GetEntry(int(i)); - - if (entry->IsDirectory()) - continue; - - string fullName = entry->GetFullName(); - - // TODO: ideally there will be a list in a json - // that the launcher downloads to determine what - // has to be installed during the restart. - bool installDuringRestart = false; - if (fullName.compare("launcher.exe") == NULL) - { - installDuringRestart = true; - } - - fileList.Insert(fullName.c_str(), installDuringRestart); - printf("Added: %s\n", fullName.c_str()); - } - - printf("Num files: %d\n", fileList.Count()); - - FOR_EACH_MAP(fileList, i) - { - CUtlString& fileName = fileList.Key(i); - CUtlString absDirName = fileName.AbsPath(); - CUtlString dirName = absDirName.DirName(); - - CreateDirectories(absDirName.Get()); - pProgress->SetExportLabel(Format("%s (%i of %i)", fileName.Get(), i+1, fileList.Count()).c_str()); - - int percentage = (i * 100) / fileList.Count(); - pProgress->UpdateProgress(percentage, false); - - printf("Extracting: %s to %s\n", fileName.Get(), dirName.Get()); - - if (fileList[i]) - { - printf("File %s has to be installed after a restart!\n", fileName.Get()); - - CUtlString tempDir = RESTART_DEPOT_DOWNLOAD_DIR; - tempDir.Append(fileName); - - ZipFile::ExtractFile(pZipFile, fileName.Get(), tempDir.Get()); - } - else - { - ZipFile::ExtractFile(pZipFile, fileName.Get(), fileName.Get()); - } - } - - return true; -} - - -void BeginInstall(CDownloadProgress* pProgress, const bool bPreRelease, const bool bOptionalAssets) -{ - CreateDownloadDirectories(); - - string responseMesage; - nlohmann::json manifest; - - // These files will NOT be downloaded from the release depots. - std::set blackList; - blackList.insert("symbols.zip"); - - // All the downloaded files. - CUtlVector fileList; - - CUtlString test = DEFAULT_DEPOT_DOWNLOAD_DIR; - test.Append("depot.zip"); - - fileList.AddToTail(test); - - // Download core game files. - //if (!DownloadLatestGitHubReleaseManifest(XorStr("https://api.github.com/repos/SlaveBuild/N1094_CL456479/releases"), responseMesage, manifest, bPreRelease)) - //{ - // // TODO: Error dialog. - // return; - //} - //DownloadAssetList(pProgress, fileList, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR); - - //if (pProgress->IsCanceled()) - // return; - - //// Download SDK files. - //if (!DownloadLatestGitHubReleaseManifest(XorStr("https://api.github.com/repos/Mauler125/r5sdk/releases"), responseMesage, manifest, bPreRelease)) - //{ - // // TODO: Error dialog. - // return; - //} - //DownloadAssetList(pProgress, fileList, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR); - - //if (pProgress->IsCanceled()) - // return; - - // Install process cannot be canceled. - pProgress->SetCanCancel(false); - - FOR_EACH_VEC(fileList, i) - { - pProgress->SetText(Format("Installing package %i of %i...", i+1, fileList.Count()).c_str()); - - CUtlString& fileName = fileList[i]; - ExtractZipFile(pProgress, fileName.Get(), ""); - } - - pProgress->Close(); -} - - -void RestartLauncher() -{ - char currentPath[MAX_PATH]; - BOOL getResult = GetCurrentDirectoryA(sizeof(currentPath), currentPath); - - if (!getResult) - { - // TODO: dialog box and instruct user to manually open the launcher again. - printf("%s: Failed to update SDK: error code = %08x\n", "GetCurrentDirectory", GetLastError()); - ExitProcess(0xBADC0DE); - } - - /////////////////////////////////////////////////////////////////////////// - STARTUPINFOA StartupInfo = { 0 }; - PROCESS_INFORMATION ProcInfo = { 0 }; - - // Initialize startup info struct. - StartupInfo.cb = sizeof(STARTUPINFOA); - - DWORD processId = GetProcessId(GetCurrentProcess()); - - char commandLine[MAX_PATH]; - sprintf(commandLine, "%lu %s %s", processId, RESTART_DEPOT_DOWNLOAD_DIR, currentPath); - - BOOL createResult = CreateProcessA( - "bin\\updater.exe", // lpApplicationName - commandLine, // lpCommandLine - NULL, // lpProcessAttributes - NULL, // lpThreadAttributes - FALSE, // bInheritHandles - CREATE_SUSPENDED, // dwCreationFlags - NULL, // lpEnvironment - currentPath, // lpCurrentDirectory - &StartupInfo, // lpStartupInfo - &ProcInfo // lpProcessInformation - ); - - if (!createResult) - { - // TODO: dialog box and instruct user to update again. - printf("%s: Failed to update SDK: error code = %08x\n", "CreateProcess", GetLastError()); - ExitProcess(0xBADC0DE); - } - - /////////////////////////////////////////////////////////////////////////// - // Resume the process. - ResumeThread(ProcInfo.hThread); - - /////////////////////////////////////////////////////////////////////////// - // Close the process and thread handles. - CloseHandle(ProcInfo.hProcess); - CloseHandle(ProcInfo.hThread); - - ExitProcess(EXIT_SUCCESS); -} - void CBaseSurface::OnInstallClick(Forms::Control* Sender) { CBaseSurface* pSurf = (CBaseSurface*)Sender; const bool bPartial = pSurf->m_bPartialInstall; - //---------------------------------------------------------------------------- - // Disk space check - //---------------------------------------------------------------------------- - char currentDir[MAX_PATH]; // Get current dir. - GetCurrentDirectoryA(sizeof(currentDir), currentDir); + //const int minRequiredSpace = bPartial ? MIN_REQUIRED_DISK_SPACE : MIN_REQUIRED_DISK_SPACE_OPT; + //int currentDiskSpace; - // Does this disk have enough space? - ULARGE_INTEGER avaliableSize; - GetDiskFreeSpaceEx(currentDir, &avaliableSize, nullptr, nullptr); - - const int minRequiredSpace = bPartial ? MIN_REQUIRED_DISK_SPACE : MIN_REQUIRED_DISK_SPACE_OPT; - const int currentAvailSpace = (int)(avaliableSize.QuadPart / uint64_t(1024 * 1024 * 1024)); - - //if (currentAvailSpace < minRequiredSpace) + //if (!SDKLauncher_CheckDiskSpace(minRequiredSpace, ¤tDiskSpace)) //{ - // // TODO: Make dialog box. - // printf("There is not enough space available on the disk to install R5Reloaded; you need at least %iGB, you currently have %iGB\n", minRequiredSpace, currentAvailSpace); + // 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; //} - auto downloadSurface = std::make_unique(); - CDownloadProgress* pProgress = downloadSurface.get(); + auto downloadSurface = std::make_unique(); + 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); - BeginInstall(pProgress, false, false); + return; + } - }).Start(); + CUtlVector fileList; + SDKLauncher_BeginDownload(true, false, fileList, pProgress); + SDKLauncher_InstallAssetList(false, fileList, pProgress); + + // Close on finish. + pProgress->Close(); + }).Start(); pProgress->ShowDialog(); // Restart the launcher process from here through updater.exe! - RestartLauncher(); + SDKLauncher_Restart(); } void CBaseSurface::OnAdvancedClick(Forms::Control* Sender) diff --git a/r5dev/sdklauncher/download_surface.cpp b/r5dev/sdklauncher/download_surface.cpp index 97f7577d..4190e509 100644 --- a/r5dev/sdklauncher/download_surface.cpp +++ b/r5dev/sdklauncher/download_surface.cpp @@ -1,13 +1,13 @@ #include "pch.h" #include "download_surface.h" -CDownloadProgress::CDownloadProgress() +CProgressPanel::CProgressPanel() : Forms::Form(), m_bCanClose(false), m_bCanceled(false), m_bAutoClose(false) { this->InitializeComponent(); } -void CDownloadProgress::UpdateProgress(uint32_t Progress, bool Finished) +void CProgressPanel::UpdateProgress(uint32_t Progress, bool Finished) { this->m_ProgressBar->SetValue(Progress); @@ -23,27 +23,27 @@ void CDownloadProgress::UpdateProgress(uint32_t Progress, bool Finished) } } -bool CDownloadProgress::IsCanceled() +bool CProgressPanel::IsCanceled() { return this->m_bCanceled; } -void CDownloadProgress::SetCanCancel(bool bEnable) +void CProgressPanel::SetCanCancel(bool bEnable) { m_CancelButton->SetEnabled(bEnable); } -void CDownloadProgress::SetAutoClose(bool Value) +void CProgressPanel::SetAutoClose(bool Value) { this->m_bAutoClose = Value; } -void CDownloadProgress::SetExportLabel(const char* pNewLabel) +void CProgressPanel::SetExportLabel(const char* pNewLabel) { m_DownloadLabel->SetText(pNewLabel); } -void CDownloadProgress::InitializeComponent() +void CProgressPanel::InitializeComponent() { this->SuspendLayout(); this->SetAutoScaleDimensions({ 6, 13 }); @@ -98,17 +98,17 @@ void CDownloadProgress::InitializeComponent() // END DESIGNER CODE } -void CDownloadProgress::OnFinishClick(Forms::Control* Sender) +void CProgressPanel::OnFinishClick(Forms::Control* Sender) { ((Forms::Form*)Sender->FindForm())->Close(); } -void CDownloadProgress::OnCancelClick(Forms::Control* Sender) +void CProgressPanel::OnCancelClick(Forms::Control* Sender) { - ((CDownloadProgress*)Sender->FindForm())->CancelProgress(); + ((CProgressPanel*)Sender->FindForm())->CancelProgress(); } -void CDownloadProgress::CancelProgress() +void CProgressPanel::CancelProgress() { this->m_CancelButton->SetEnabled(false); this->m_CancelButton->SetText("Canceling..."); diff --git a/r5dev/sdklauncher/download_surface.h b/r5dev/sdklauncher/download_surface.h index b00e3492..3aa5ea53 100644 --- a/r5dev/sdklauncher/download_surface.h +++ b/r5dev/sdklauncher/download_surface.h @@ -1,10 +1,10 @@ #pragma once -class CDownloadProgress : public Forms::Form +class CProgressPanel : public Forms::Form { public: - CDownloadProgress(); - virtual ~CDownloadProgress() = default; + CProgressPanel(); + virtual ~CProgressPanel() = default; // Updates the progress void UpdateProgress(uint32_t Progress, bool Finished); diff --git a/r5dev/sdklauncher/sdklauncher_const.h b/r5dev/sdklauncher/sdklauncher_const.h index 6be7d829..d3d61a7b 100644 --- a/r5dev/sdklauncher/sdklauncher_const.h +++ b/r5dev/sdklauncher/sdklauncher_const.h @@ -10,6 +10,19 @@ #define DEFAULT_WINDOW_CLASS_NAME "Respawn001" #define LAUNCHER_SETTING_FILE "launcher.vdf" +// Gigabytes. +// TODO: obtain these from the repo... +#define MIN_REQUIRED_DISK_SPACE 20 +#define MIN_REQUIRED_DISK_SPACE_OPT 55 // Optional textures + +#define QUERY_TIMEOUT 15 +#define BASE_PLATFORM_DIR "platform\\" +#define DEFAULT_DEPOT_DOWNLOAD_DIR BASE_PLATFORM_DIR "depot\\" + +// Files that need to be installed during launcher restart, +// have to go here!! +#define RESTART_DEPOT_DOWNLOAD_DIR DEFAULT_DEPOT_DOWNLOAD_DIR "temp\\" + //----------------------------------------------------------------------------- // Launch and inject specified dll based on launch mode //----------------------------------------------------------------------------- diff --git a/r5dev/sdklauncher/sdklauncher_pch.h b/r5dev/sdklauncher/sdklauncher_pch.h index e59ea186..cb82bf3e 100644 --- a/r5dev/sdklauncher/sdklauncher_pch.h +++ b/r5dev/sdklauncher/sdklauncher_pch.h @@ -16,5 +16,7 @@ #include "thirdparty/cppnet/cppkore/UIXProgressBar.h" #include "thirdparty/cppnet/cppkore/KoreTheme.h" #include "thirdparty/cppnet/cppkore/Thread.h" +#include "thirdparty/cppnet/cppkore/MessageBox.h" +#include "thirdparty/cppnet/cppkore/MessageBoxButtons.h" #include "sdklauncher/sdklauncher_const.h" diff --git a/r5dev/sdklauncher/sdklauncher_utils.cpp b/r5dev/sdklauncher/sdklauncher_utils.cpp new file mode 100644 index 00000000..27f57516 --- /dev/null +++ b/r5dev/sdklauncher/sdklauncher_utils.cpp @@ -0,0 +1,436 @@ +#include "sdklauncher_utils.h" +#include "tier1/xorstr.h" +#include "tier1/utlmap.h" +#include "tier2/curlutils.h" +#include "zip/src/ZipFile.h" + +// !TODO: perhaps this should be a core utility shared across +// the entire SDK to allow processes to restart them selfs. +void SDKLauncher_Restart() +{ + char currentPath[MAX_PATH]; + BOOL getResult = GetCurrentDirectoryA(sizeof(currentPath), currentPath); + + if (!getResult) + { + // TODO: dialog box and instruct user to manually open the launcher again. + printf("%s: Failed to obtain current directory: error code = %08x\n", __FUNCTION__, GetLastError()); + ExitProcess(0xBADC0DE); + } + + /////////////////////////////////////////////////////////////////////////// + STARTUPINFOA StartupInfo = { 0 }; + PROCESS_INFORMATION ProcInfo = { 0 }; + + // Initialize startup info struct. + StartupInfo.cb = sizeof(STARTUPINFOA); + + DWORD processId = GetProcessId(GetCurrentProcess()); + + char commandLine[MAX_PATH]; + sprintf(commandLine, "%lu %s %s", processId, RESTART_DEPOT_DOWNLOAD_DIR, currentPath); + + BOOL createResult = CreateProcessA( + "bin\\updater.exe", // lpApplicationName + commandLine, // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + FALSE, // bInheritHandles + CREATE_SUSPENDED, // dwCreationFlags + NULL, // lpEnvironment + currentPath, // lpCurrentDirectory + &StartupInfo, // lpStartupInfo + &ProcInfo // lpProcessInformation + ); + + if (!createResult) + { + // TODO: dialog box and instruct user to update again. + printf("%s: Failed to create update process: error code = %08x\n", __FUNCTION__, GetLastError()); + ExitProcess(0xBADC0DE); + } + + /////////////////////////////////////////////////////////////////////////// + // Resume the process. + ResumeThread(ProcInfo.hThread); + + /////////////////////////////////////////////////////////////////////////// + // Close the process and thread handles. + CloseHandle(ProcInfo.hProcess); + CloseHandle(ProcInfo.hThread); + + ExitProcess(EXIT_SUCCESS); +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool SDKLauncher_CreateDepotDirectories() +{ + if ((!CreateDirectoryA(BASE_PLATFORM_DIR, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) || + (!CreateDirectoryA(DEFAULT_DEPOT_DOWNLOAD_DIR, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) || + (!CreateDirectoryA(RESTART_DEPOT_DOWNLOAD_DIR, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)) + { + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool SDKLauncher_ClearDepotDirectories() +{ + // Clear all depot files. + if (!RemoveDirectoryA(RESTART_DEPOT_DOWNLOAD_DIR) || + !RemoveDirectoryA(DEFAULT_DEPOT_DOWNLOAD_DIR)) + { + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool SDKLauncher_ExtractZipFile(const char* pZipFile, const char* pDestPath, CProgressPanel* pProgress) +{ + ZipArchive::Ptr archive = ZipFile::Open(pZipFile); + size_t entries = archive->GetEntriesCount(); + + CUtlMap fileList(UtlStringLessFunc); + + for (size_t i = 0; i < entries; ++i) + { + auto entry = archive->GetEntry(int(i)); + + if (entry->IsDirectory()) + continue; + + string fullName = entry->GetFullName(); + + // TODO: ideally there will be a list in a json + // that the launcher downloads to determine what + // has to be installed during the restart. + bool installDuringRestart = false; + if (fullName.compare("launcher.exe") == NULL) + { + installDuringRestart = true; + } + + fileList.Insert(fullName.c_str(), installDuringRestart); + printf("Added: %s\n", fullName.c_str()); + } + + printf("Num files: %d\n", fileList.Count()); + + FOR_EACH_MAP(fileList, i) + { + CUtlString& fileName = fileList.Key(i); + CUtlString absDirName = fileName.AbsPath(); + CUtlString dirName = absDirName.DirName(); + + CreateDirectories(absDirName.Get()); + + if (pProgress) + { + pProgress->SetExportLabel(Format("%s (%i of %i)", fileName.Get(), i + 1, fileList.Count()).c_str()); + + int percentage = (i * 100) / fileList.Count(); + pProgress->UpdateProgress(percentage, false); + } + + printf("Extracting: %s to %s\n", fileName.Get(), dirName.Get()); + + if (fileList[i]) + { + printf("File %s has to be installed after a restart!\n", fileName.Get()); + + CUtlString tempDir = RESTART_DEPOT_DOWNLOAD_DIR; + tempDir.Append(fileName); + + ZipFile::ExtractFile(pZipFile, fileName.Get(), tempDir.Get()); + } + else + { + ZipFile::ExtractFile(pZipFile, fileName.Get(), fileName.Get()); + } + } + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool SDKLauncher_QueryServer(const char* url, string& outResponse, string& outMessage, CURLINFO& outStatus) +{ + curl_slist* sList = nullptr; + CURLParams params; + + params.writeFunction = CURLWriteStringCallback; + params.timeout = QUERY_TIMEOUT; + params.verifyPeer = true; + params.verbose = IsDebug(); + + CURL* curl = CURLInitRequest(url, nullptr, outResponse, sList, params); + + if (!curl) + { + return false; + } + + CURLcode res = CURLSubmitRequest(curl, sList); + + if (!CURLHandleError(curl, res, outMessage, true)) + { + return false; + } + + outStatus = CURLRetrieveInfo(curl); + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool SDKLauncher_GetLatestReleaseManifest(const char* url, string& responseMessage, + nlohmann::json& outManifest, const bool preRelease) +{ + string responseBody; + CURLINFO status; + + if (!SDKLauncher_QueryServer(url, responseBody, responseMessage, status)) + { + return false; + } + + if (status != 200) + { + return false; + } + + try + { + nlohmann::json responseJson = nlohmann::json::parse(responseBody); + + for (size_t i = 0; i < responseJson.size(); i++) + { + auto& release = responseJson[i]; + + if (preRelease && release["prerelease"]) + { + outManifest = release["assets"]; + break; + } + else if (!release["prerelease"]) + { + outManifest = release["assets"]; + break; + } + + if (i == responseJson.size() - 1 && outManifest.empty()) + release[0]["assets"]; // Just take the first one then. + } + } + catch (const std::exception& ex) + { + printf("%s - Exception while parsing response:\n%s\n", __FUNCTION__, ex.what()); + return false; + } + + Assert(!outManifest.empty()); + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +int SDKLauncher_ProgressCallback(CURLProgress* progessData, double dltotal, + double dlnow, double ultotal, double ulnow) +{ + CProgressPanel* pDownloadSurface = (CProgressPanel*)progessData->cust; + + if (pDownloadSurface->IsCanceled()) + return -1; + + double downloaded; + curl_easy_getinfo(progessData->curl, CURLINFO_SIZE_DOWNLOAD, &downloaded); + + pDownloadSurface->SetExportLabel(Format("%s (%llu of %llu)", progessData->name, (size_t)downloaded, progessData->size).c_str()); + + size_t percentage = ((size_t)downloaded * 100) / progessData->size; + pDownloadSurface->UpdateProgress((uint32_t)percentage, false); + + return 0; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void SDKLauncher_DownloadAsset(const char* url, const char* path, const char* fileName, + const size_t fileSize, const char* options, CProgressPanel* pProgress) +{ + CURLParams params; + + params.writeFunction = CURLWriteFileCallback; + params.statusFunction = SDKLauncher_ProgressCallback; + + CURLDownloadFile(url, path, fileName, options, fileSize, pProgress, params); +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void SDKLauncher_BeginDownload(const bool bPreRelease, const bool bOptionalAssets, + CUtlVector& fileList, CProgressPanel* pProgress) +{ + string responseMesage; + nlohmann::json manifest; + + // These files will NOT be downloaded from the release depots. + std::set blackList; + blackList.insert("symbols.zip"); + + //fileList.AddToTail("audio_0.zip"); + //fileList.AddToTail("audio_1.zip"); + //fileList.AddToTail("binaries.zip"); + //fileList.AddToTail("materials.zip"); + //fileList.AddToTail("media.zip"); + //fileList.AddToTail("paks.zip"); + //fileList.AddToTail("starpak_0.zip"); + //fileList.AddToTail("starpak_1.zip"); + //fileList.AddToTail("stbsp.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)) + //{ + // // 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)) + { + // TODO: Error dialog. + return; + } + SDKLauncher_DownloadAssetList(fileList, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR, pProgress); + + if (pProgress->IsCanceled()) + return; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void SDKLauncher_DownloadAssetList(CUtlVector& fileList, nlohmann::json& assetList, + std::set& blackList, const char* pPath, CProgressPanel* pProgress) +{ + int i = 1; + for (auto& asset : assetList) + { + if (pProgress->IsCanceled()) + { + break; + } + + if (!asset.contains("browser_download_url") || + !asset.contains("name") || + !asset.contains("size")) + { + Assert(0); + return; + } + + const string fileName = asset["name"]; + const char* const pFileName = fileName.c_str(); + + // 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()); + SDKLauncher_DownloadAsset(downloadLink.c_str(), pPath, pFileName, fileSize, "wb+", pProgress); + + // Check if its a zip file, as these are + // the only files that will be installed. + if (V_strcmp(V_GetFileExtension(pFileName), "zip") == NULL) + { + CUtlString filePath = pPath; + + filePath.AppendSlash(); + filePath.Append(fileName.c_str()); + + fileList.AddToTail(filePath); + } + + i++; + } +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void SDKLauncher_InstallAssetList(const bool bOptionalAssets, + CUtlVector& fileList, CProgressPanel* pProgress) +{ + // Install process cannot be canceled. + pProgress->SetCanCancel(false); + + FOR_EACH_VEC(fileList, i) + { + pProgress->SetText(Format("Installing package %i of %i...", i + 1, fileList.Count()).c_str()); + + CUtlString& fileName = fileList[i]; + SDKLauncher_ExtractZipFile(fileName.Get(), "", pProgress); + } +} + +//---------------------------------------------------------------------------- +// Purpose: checks if client has enough disk space +// Input : minRequiredSpace - +// : *availableSize - +// Output : true if space is sufficient, false otherwise +//---------------------------------------------------------------------------- +bool SDKLauncher_CheckDiskSpace(const int minRequiredSpace, int* const availableSize) +{ + char currentDir[MAX_PATH]; // Get current dir. + GetCurrentDirectoryA(sizeof(currentDir), currentDir); + + // Does this disk have enough space? + ULARGE_INTEGER avaliableSize; + GetDiskFreeSpaceEx(currentDir, &avaliableSize, nullptr, nullptr); + + const int currentAvailSpace = (int)(avaliableSize.QuadPart / uint64_t(1024 * 1024 * 1024)); + + if (availableSize) + { + *availableSize = currentAvailSpace; + } + + if (currentAvailSpace < minRequiredSpace) + { + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool SDKLauncher_CheckForUpdate() +{ + return true; +} diff --git a/r5dev/sdklauncher/sdklauncher_utils.h b/r5dev/sdklauncher/sdklauncher_utils.h new file mode 100644 index 00000000..fcddfb59 --- /dev/null +++ b/r5dev/sdklauncher/sdklauncher_utils.h @@ -0,0 +1,18 @@ +#pragma once +#include "download_surface.h" + +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& fileList, CProgressPanel* pProgress = nullptr); + +void SDKLauncher_DownloadAssetList(CUtlVector& fileList, nlohmann::json& assetList, + std::set& blackList, const char* pPath, CProgressPanel* pProgress); + +void SDKLauncher_InstallAssetList(const bool bOptionalAssets, + CUtlVector& fileList, CProgressPanel* pProgress); + +bool SDKLauncher_CheckDiskSpace(const int minRequiredSpace, int* const availableSize = nullptr);