From 41ebc431b2f3ec4bd7916fa81cf18ec5f01b2a95 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:50:46 +0200 Subject: [PATCH] Finish downloading of game and sdk files Moved entire logic to dedicated wrappers. --- r5dev/sdklauncher/CMakeLists.txt | 10 + r5dev/sdklauncher/base_surface.cpp | 235 +++++++++++++++++- r5dev/sdklauncher/base_surface.h | 6 + r5dev/sdklauncher/download_surface.cpp | 115 +++++++++ r5dev/sdklauncher/download_surface.h | 37 +++ r5dev/sdklauncher/sdklauncher.cpp | 37 +++ r5dev/sdklauncher/sdklauncher_pch.h | 2 + r5dev/thirdparty/cppnet/cppkore/KoreTheme.cpp | 2 +- 8 files changed, 436 insertions(+), 8 deletions(-) create mode 100644 r5dev/sdklauncher/download_surface.cpp create mode 100644 r5dev/sdklauncher/download_surface.h diff --git a/r5dev/sdklauncher/CMakeLists.txt b/r5dev/sdklauncher/CMakeLists.txt index 1591d26c..c9569c9c 100644 --- a/r5dev/sdklauncher/CMakeLists.txt +++ b/r5dev/sdklauncher/CMakeLists.txt @@ -10,10 +10,14 @@ add_sources( SOURCE_GROUP "Core" ) add_sources( SOURCE_GROUP "GUI" + "basepanel.cpp" + "basepanel.h" "advanced_surface.cpp" "advanced_surface.h" "base_surface.cpp" "base_surface.h" + "download_surface.cpp" + "download_surface.h" ) add_sources( SOURCE_GROUP "Resource" @@ -38,8 +42,14 @@ target_precompile_headers( ${PROJECT_NAME} PRIVATE ) target_link_libraries( ${PROJECT_NAME} PRIVATE "tier0" + "tier1" + "tier2" "libdetours" "libcppkore" "libspdlog" + "libcurl" + "crypt32.lib" + "ws2_32.lib" + "wldap32.lib" "Rpcrt4.lib" ) diff --git a/r5dev/sdklauncher/base_surface.cpp b/r5dev/sdklauncher/base_surface.cpp index 80e02372..a0d4a489 100644 --- a/r5dev/sdklauncher/base_surface.cpp +++ b/r5dev/sdklauncher/base_surface.cpp @@ -5,8 +5,17 @@ //=============================================================================// #include "base_surface.h" #include "advanced_surface.h" +#include "download_surface.h" #include "tier1/xorstr.h" +#include "tier2/curlutils.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\\" + CBaseSurface::CBaseSurface() { const INT WindowX = 400; @@ -41,7 +50,7 @@ CBaseSurface::CBaseSurface() this->m_ManageButton->SetTabIndex(9); this->m_ManageButton->SetText(isInstalled ? XorStr("Launch Apex") : XorStr("Install Apex")); this->m_ManageButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Left); - this->m_ManageButton->Click += isInstalled ? &OnAdvancedClick : &OnAdvancedClick; + this->m_ManageButton->Click += isInstalled ? &OnAdvancedClick : &OnInstallClick; m_BaseGroup->AddControl(this->m_ManageButton); this->m_RepairButton = new UIX::UIXButton(); @@ -81,6 +90,224 @@ CBaseSurface::CBaseSurface() this->m_AdvancedButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Left); this->m_AdvancedButton->Click += &OnAdvancedClick; m_BaseGroup->AddControl(this->m_AdvancedButton); + + // TODO: Use a toggle item instead; remove this field. + 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; +} + +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) +{ + CreateDirectoryA(path, NULL); + + CURLParams params; + + params.writeFunction = CURLWriteFileCallback; + params.statusFunction = DownloadStatusCallback; + + CURLDownloadFile(url, path, fileName, options, fileSize, pProgress, params); +} + +void DownloadAssetList(CDownloadProgress* pProgress, 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("File %s (%i of %i)", fileName.c_str(), i, assetList.size()).c_str()); + DownloadAsset(pProgress, downloadLink.c_str(), pPath, fileName.c_str(), fileSize, "wb+"); + + 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(); +} + +void BeginInstall(CDownloadProgress* pProgress, const bool bPrerelease, const bool bOptionalAssets) +{ + string responseMesage; + nlohmann::json manifest; + + // These files will NOT be downloaded from the release depots. + std::set blackList; + blackList.insert("symbols.zip"); + + // 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, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR); + + // Download SDK files. + if (!DownloadLatestGitHubReleaseManifest(XorStr("https://api.github.com/repos/Mauler125/r5sdk/releases"), responseMesage, manifest, bPrerelease)) + { + // TODO: Error dialog. + return; + } + DownloadAssetList(pProgress, manifest, blackList, DEFAULT_DEPOT_DOWNLOAD_DIR); + + + // Install process cannot be canceled. + pProgress->SetCanCancel(false); +} + +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); + + // 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) + //{ + // // 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); + // return; + //} + + auto downloadSurface = std::make_unique(); + CDownloadProgress* pProgress = downloadSurface.get(); + + pProgress->SetAutoClose(true); + + Threading::Thread([pProgress] { + + BeginInstall(pProgress); + + }).Start(); + + pProgress->ShowDialog(); } void CBaseSurface::OnAdvancedClick(Forms::Control* Sender) @@ -91,16 +318,10 @@ void CBaseSurface::OnAdvancedClick(Forms::Control* Sender) void CBaseSurface::OnSupportClick(Forms::Control* /*Sender*/) { - //auto pAdvancedSurface = std::make_unique(); - //pAdvancedSurface->ShowDialog((Forms::Form*)Sender->FindForm()); - ShellExecute(0, 0, XorStr("https://www.paypal.com/donate/?hosted_button_id=S28DHC2TF6UV4"), 0, 0, SW_SHOW); } void CBaseSurface::OnJoinClick(Forms::Control* /*Sender*/) { - //auto pAdvancedSurface = std::make_unique(); - //pAdvancedSurface->ShowDialog((Forms::Form*)Sender->FindForm()); - ShellExecute(0, 0, XorStr("https://discord.com/invite/jqMkUdXrBr"), 0, 0, SW_SHOW); } diff --git a/r5dev/sdklauncher/base_surface.h b/r5dev/sdklauncher/base_surface.h index 1a9effb2..bc21454c 100644 --- a/r5dev/sdklauncher/base_surface.h +++ b/r5dev/sdklauncher/base_surface.h @@ -10,6 +10,8 @@ public: }; protected: + static void OnInstallClick(Forms::Control* Sender); + static void OnAdvancedClick(Forms::Control* Sender); @@ -40,4 +42,8 @@ private: UIX::UIXButton* m_DonateButton; UIX::UIXButton* m_JoinButton; UIX::UIXButton* m_AdvancedButton; + + // When this is false, the installer will + // download the HD textures as well (STARPAK's). + bool m_bPartialInstall; }; diff --git a/r5dev/sdklauncher/download_surface.cpp b/r5dev/sdklauncher/download_surface.cpp new file mode 100644 index 00000000..858bc7d8 --- /dev/null +++ b/r5dev/sdklauncher/download_surface.cpp @@ -0,0 +1,115 @@ +#include "pch.h" +#include "download_surface.h" + +CDownloadProgress::CDownloadProgress() + : Forms::Form(), m_bCanClose(false), m_bCanceled(false), m_bAutoClose(false) +{ + this->InitializeComponent(); +} + +void CDownloadProgress::UpdateProgress(uint32_t Progress, bool Finished) +{ + this->m_ProgressBar->SetValue(Progress); + + if (Finished) + { + this->m_FinishButton->SetEnabled(true); + this->m_CancelButton->SetEnabled(false); + this->m_bCanClose = true; + this->SetText("Download Complete"); + + if (this->m_bCanceled || this->m_bAutoClose) + this->Close(); + } +} + +bool CDownloadProgress::IsCanceled() +{ + return this->m_bCanceled; +} + +void CDownloadProgress::SetCanCancel(bool bEnable) +{ + m_CancelButton->SetEnabled(bEnable); +} + +void CDownloadProgress::SetAutoClose(bool Value) +{ + this->m_bAutoClose = Value; +} + +void CDownloadProgress::SetExportLabel(const char* pNewLabel) +{ + m_DownloadLabel->SetText(pNewLabel); +} + +void CDownloadProgress::InitializeComponent() +{ + this->SuspendLayout(); + this->SetAutoScaleDimensions({ 6, 13 }); + this->SetAutoScaleMode(Forms::AutoScaleMode::Font); + this->SetText("Downloading Files..."); + this->SetClientSize({ 409, 119 }); + this->SetFormBorderStyle(Forms::FormBorderStyle::FixedSingle); + this->SetStartPosition(Forms::FormStartPosition::CenterParent); + this->SetMinimizeBox(false); + this->SetMaximizeBox(false); + this->SetShowInTaskbar(false); + this->SetBackColor(Drawing::Color(47, 54, 61)); + + this->m_DownloadLabel = new UIX::UIXLabel(); + this->m_DownloadLabel->SetSize({ 385, 17 }); + this->m_DownloadLabel->SetLocation({ 12, 18 }); + this->m_DownloadLabel->SetTabIndex(3); + this->m_DownloadLabel->SetText("Progress:"); + this->m_DownloadLabel->SetAnchor(Forms::AnchorStyles::Top | Forms::AnchorStyles::Left | Forms::AnchorStyles::Right); + this->m_DownloadLabel->SetTextAlign(Drawing::ContentAlignment::TopLeft); + this->AddControl(this->m_DownloadLabel); + + this->m_CancelButton = new UIX::UIXButton(); + this->m_CancelButton->SetSize({ 87, 31 }); + this->m_CancelButton->SetLocation({ 310, 76 }); + this->m_CancelButton->SetTabIndex(2); + this->m_CancelButton->SetText("Cancel"); + this->m_CancelButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Right); + this->m_CancelButton->Click += &OnCancelClick; + this->AddControl(this->m_CancelButton); + + this->m_FinishButton = new UIX::UIXButton(); + this->m_FinishButton->SetSize({ 87, 31 }); + this->m_FinishButton->SetLocation({ 217, 76 }); + this->m_FinishButton->SetTabIndex(1); + this->m_FinishButton->SetText("Ok"); + this->m_FinishButton->SetEnabled(false); + this->m_FinishButton->SetAnchor(Forms::AnchorStyles::Bottom | Forms::AnchorStyles::Right); + this->m_FinishButton->Click += &OnFinishClick; + this->AddControl(this->m_FinishButton); + + this->m_ProgressBar = new UIX::UIXProgressBar(); + this->m_ProgressBar->SetSize({ 385, 29 }); + this->m_ProgressBar->SetLocation({ 12, 38 }); + this->m_ProgressBar->SetTabIndex(0); + this->m_ProgressBar->SetAnchor(Forms::AnchorStyles::Top | Forms::AnchorStyles::Left | Forms::AnchorStyles::Right); + this->AddControl(this->m_ProgressBar); + + this->ResumeLayout(false); + this->PerformLayout(); + // END DESIGNER CODE +} + +void CDownloadProgress::OnFinishClick(Forms::Control* Sender) +{ + ((Forms::Form*)Sender->FindForm())->Close(); +} + +void CDownloadProgress::OnCancelClick(Forms::Control* Sender) +{ + ((CDownloadProgress*)Sender->FindForm())->CancelProgress(); +} + +void CDownloadProgress::CancelProgress() +{ + this->m_CancelButton->SetEnabled(false); + this->m_CancelButton->SetText("Canceling..."); + this->m_bCanceled = true; +} diff --git a/r5dev/sdklauncher/download_surface.h b/r5dev/sdklauncher/download_surface.h new file mode 100644 index 00000000..b00e3492 --- /dev/null +++ b/r5dev/sdklauncher/download_surface.h @@ -0,0 +1,37 @@ +#pragma once + +class CDownloadProgress : public Forms::Form +{ +public: + CDownloadProgress(); + virtual ~CDownloadProgress() = default; + + // Updates the progress + void UpdateProgress(uint32_t Progress, bool Finished); + bool IsCanceled(); + + void SetCanCancel(bool bEnable); + + void SetAutoClose(bool Value); + void SetExportLabel(const char* pNewLabel); + +private: + // Internal routine to setup the component + void InitializeComponent(); + + // Internal event on finish click + static void OnFinishClick(Forms::Control* Sender); + static void OnCancelClick(Forms::Control* Sender); + + void CancelProgress(); // Cancels the progress + + // Internal controls reference + UIX::UIXLabel* m_DownloadLabel; + UIX::UIXButton* m_CancelButton; + UIX::UIXButton* m_FinishButton; + UIX::UIXProgressBar* m_ProgressBar; + + bool m_bCanClose; // Whether or not we can close. + bool m_bCanceled; // Whether or not we canceled it. + bool m_bAutoClose; // Whether or not to automatically close. +}; diff --git a/r5dev/sdklauncher/sdklauncher.cpp b/r5dev/sdklauncher/sdklauncher.cpp index e9f3bf0d..d84fd815 100644 --- a/r5dev/sdklauncher/sdklauncher.cpp +++ b/r5dev/sdklauncher/sdklauncher.cpp @@ -352,6 +352,39 @@ BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) return TRUE; } +void LauncherLoggerSink(LogType_t logType, LogLevel_t logLevel, eDLL_T context, + const char* pszLogger, const char* pszFormat, va_list args, + const UINT exitCode /*= NO_ERROR*/, const char* pszUptimeOverride /*= nullptr*/) +{ + const char* pszUpTime = pszUptimeOverride ? pszUptimeOverride : Plat_GetProcessUpTime(); + string message(pszUpTime); + + const char* pszContext = ""; //"GetContextNameByIndex(context, bUseColor); + message.append(pszContext); + + NOTE_UNUSED(pszLogger); + + //------------------------------------------------------------------------- + // Format actual input + //------------------------------------------------------------------------- + va_list argsCopy; + va_copy(argsCopy, args); + const string formatted = FormatV(pszFormat, argsCopy); + va_end(argsCopy); + + message.append(formatted); + printf("%s", message.c_str()); + + if (exitCode) // Terminate the process if an exit code was passed. + { + if (MessageBoxA(NULL, Format("%s- %s", pszUpTime, formatted.c_str()).c_str(), + "SDK Error", MB_ICONERROR | MB_OK)) + { + TerminateProcess(GetCurrentProcess(), exitCode); + } + } +} + /////////////////////////////////////////////////////////////////////////////// // EntryPoint. /////////////////////////////////////////////////////////////////////////////// @@ -363,7 +396,10 @@ int main(int argc, char* argv[]/*, char* envp[]*/) #ifdef NDEBUG FreeConsole(); #endif // NDEBUG + curl_global_init(CURL_GLOBAL_ALL); + g_CoreMsgVCallback = LauncherLoggerSink; g_pLauncher->RunSurface(); + curl_global_cleanup(); } else { @@ -373,6 +409,7 @@ int main(int argc, char* argv[]/*, char* envp[]*/) return g_pLauncher->HandleInput(); } + return EXIT_SUCCESS; } diff --git a/r5dev/sdklauncher/sdklauncher_pch.h b/r5dev/sdklauncher/sdklauncher_pch.h index b395092a..e59ea186 100644 --- a/r5dev/sdklauncher/sdklauncher_pch.h +++ b/r5dev/sdklauncher/sdklauncher_pch.h @@ -13,6 +13,8 @@ #include "thirdparty/cppnet/cppkore/UIXGroupBox.h" #include "thirdparty/cppnet/cppkore/UIXButton.h" #include "thirdparty/cppnet/cppkore/UIXRadioButton.h" +#include "thirdparty/cppnet/cppkore/UIXProgressBar.h" #include "thirdparty/cppnet/cppkore/KoreTheme.h" +#include "thirdparty/cppnet/cppkore/Thread.h" #include "sdklauncher/sdklauncher_const.h" diff --git a/r5dev/thirdparty/cppnet/cppkore/KoreTheme.cpp b/r5dev/thirdparty/cppnet/cppkore/KoreTheme.cpp index f38e9c14..596aee4b 100644 --- a/r5dev/thirdparty/cppnet/cppkore/KoreTheme.cpp +++ b/r5dev/thirdparty/cppnet/cppkore/KoreTheme.cpp @@ -158,8 +158,8 @@ namespace Themes Gdiplus::RectF FillBounds(0, 0, (Rect.Width - 1.f) * (Progress / 100.0f), Rect.Height - 1.f); Drawing::FillRoundRectangle(EventArgs->Graphics.get(), &FillBrush, FillBounds, 2); } - EventArgs->Graphics->SetSmoothingMode(SmMode); + EventArgs->Graphics->SetSmoothingMode(SmMode); //EventArgs->Graphics->FillRectangle(&FillBrush, Gdiplus::RectF(2, 2, (Rect.Width - 4.f) * (Progress / 100.0f), Rect.Height - 4.f)); }