From 6a76670186c515e73bc96ff766689a4714d121e9 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Sat, 20 Jul 2024 19:13:21 -0300 Subject: [PATCH] Launcher: automatic duckstation --- .gitignore | 1 + tools/Launcher/dataManager.cpp | 15 ++++++++--- tools/Launcher/dataManager.h | 9 ++++--- tools/Launcher/main.cpp | 4 +++ tools/Launcher/requests.cpp | 27 +++++++++++++++---- tools/Launcher/requests.h | 1 + tools/Launcher/ui.cpp | 16 +++++------- tools/Launcher/ui.h | 3 +-- tools/Launcher/updater.cpp | 47 +++++++++++++++++++++++++++++----- tools/Launcher/updater.h | 3 ++- 10 files changed, 96 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 73e12275..af2d4974 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ debug/ x64/ data/ packages/ +externals/ *.log offset.txt comport.txt diff --git a/tools/Launcher/dataManager.cpp b/tools/Launcher/dataManager.cpp index e0dc2f65..a0842933 100644 --- a/tools/Launcher/dataManager.cpp +++ b/tools/Launcher/dataManager.cpp @@ -4,27 +4,34 @@ #include const std::string g_dataFolder = "data/"; +const std::string g_duckFolder = g_dataFolder + "duckstation/"; +const std::string g_duckExecutable = g_duckFolder + "duckstation-qt-x64-ReleaseLTCG.exe"; const std::string g_clientString = "client.zip"; const std::string g_clientExecutable = "Client.exe"; const std::string g_patchString = "ctr-u_Online30.xdelta"; -const std::string g_configString = "SCUS-94426.ini"; +const std::string g_configString = "settings.ini"; -std::string GetClientPath(const std::string& version) +const std::string GetClientPath(const std::string& version) { return g_dataFolder + version + "/" + g_clientExecutable; } -std::string GetPatchedGamePath(const std::string& version) +const std::string GetPatchedGamePath(const std::string& version) { std::string s_patch = g_dataFolder + version + "/" + g_patchString; return s_patch.substr(0, s_patch.find(".")) + ".bin"; } -std::string GetConfigPath(const std::string& version) +const std::string GetIniPath_Version(const std::string& version) { return g_dataFolder + version + "/" + g_configString; } +const std::string GetIniPath_Duck() +{ + return g_duckFolder + g_configString; +} + DataManager g_dataManager; DataManager::DataManager() diff --git a/tools/Launcher/dataManager.h b/tools/Launcher/dataManager.h index f48c7f8e..01cb3ee0 100644 --- a/tools/Launcher/dataManager.h +++ b/tools/Launcher/dataManager.h @@ -6,14 +6,17 @@ #include extern const std::string g_dataFolder; +extern const std::string g_duckFolder; +extern const std::string g_duckExecutable; extern const std::string g_clientString; extern const std::string g_clientExecutable; extern const std::string g_patchString; extern const std::string g_configString; -std::string GetClientPath(const std::string& version); -std::string GetPatchedGamePath(const std::string& version); -std::string GetConfigPath(const std::string& version); +const std::string GetClientPath(const std::string& version); +const std::string GetPatchedGamePath(const std::string& version); +const std::string GetIniPath_Version(const std::string& version); +const std::string GetIniPath_Duck(); using json = nlohmann::json; diff --git a/tools/Launcher/main.cpp b/tools/Launcher/main.cpp index 05d4a8b8..3bbaeb55 100644 --- a/tools/Launcher/main.cpp +++ b/tools/Launcher/main.cpp @@ -4,8 +4,12 @@ int main(int argc, char* argv[]) { App app; app.Init(); +#ifdef _DEBUG + app.Run(); +#else try { app.Run(); } catch (...) {}; +#endif app.Close(); return 0; } diff --git a/tools/Launcher/requests.cpp b/tools/Launcher/requests.cpp index f9800e3d..f2b6ea23 100644 --- a/tools/Launcher/requests.cpp +++ b/tools/Launcher/requests.cpp @@ -5,16 +5,31 @@ #include #include -static bool DownloadFile(const std::string& sitePath, const std::string& filePath) +bool Requests::DownloadFile(const std::string& domain, const std::string& sitePath, const std::string& filePath) { - httplib::SSLClient request("www.online-ctr.com"); - httplib::Result response = request.Get("/wp-content/uploads/onlinectr_patches/" + sitePath); - if (response && response->status == 200) { + httplib::SSLClient request(domain); + httplib::Result response = request.Get(sitePath); + if (!response) { return false; } + if (response->status == 200) + { std::ofstream file(filePath.c_str(), std::ios::binary); file.write(response->body.c_str(), response->body.size()); file.close(); return true; } + if (response->status == 302) + { + const std::string hostStart = "://"; + const std::string pathStart = "/"; + std::string location = response->get_header_value("Location"); + if (location == domain + sitePath) { return false; } + + size_t hostStartIndex = location.find(hostStart) + hostStart.length(); + size_t pathStartIndex = location.find(pathStart, hostStartIndex); + std::string host = location.substr(hostStartIndex, pathStartIndex - hostStartIndex); + std::string path = location.substr(pathStartIndex); + return DownloadFile(host, path, filePath); + } return false; } @@ -31,12 +46,14 @@ bool Requests::CheckUpdates(std::string& version) bool Requests::DownloadUpdates(const std::string& path, std::string& status) { + const std::string octrDomain = "www.online-ctr.com"; + const std::string octrPath = "/wp-content/uploads/onlinectr_patches/"; const std::vector files = { g_clientString, g_patchString, g_configString }; if (!std::filesystem::is_directory(path)) { std::filesystem::create_directory(path); } for (const std::string& file : files) { status = "Downloading " + file + "..."; - if (!DownloadFile(file, path + file)) + if (!DownloadFile(octrDomain, octrPath + file, path + file)) { status = "Error downloading " + file; return false; diff --git a/tools/Launcher/requests.h b/tools/Launcher/requests.h index 30f5e93c..39b822a6 100644 --- a/tools/Launcher/requests.h +++ b/tools/Launcher/requests.h @@ -4,6 +4,7 @@ namespace Requests { + bool DownloadFile(const std::string& domain, const std::string& sitePath, const std::string& filePath); bool CheckUpdates(std::string& version); bool DownloadUpdates(const std::string& version, std::string& status); } \ No newline at end of file diff --git a/tools/Launcher/ui.cpp b/tools/Launcher/ui.cpp index b1e4736d..a4e97d49 100644 --- a/tools/Launcher/ui.cpp +++ b/tools/Launcher/ui.cpp @@ -10,10 +10,9 @@ UI::UI() { + g_dataManager.BindData(&m_biosPath, DataType::STRING, "BiosPath"); g_dataManager.BindData(&m_gamePath, DataType::STRING, "GamePath"); - g_dataManager.BindData(&m_duckPath, DataType::STRING, "DuckPath"); g_dataManager.BindData(&m_version, DataType::STRING, "GameVersion"); - g_dataManager.BindData(&m_iniPath, DataType::STRING, "IniPath"); g_dataManager.BindData(&m_username, DataType::STRING, "Username"); m_updater.CheckForUpdates(m_status, m_version); } @@ -30,31 +29,30 @@ void UI::Render(int width, int height) if (m_username.size() > 9) { m_username = m_username.substr(0, 9); } bool updateReady = true; + updateReady &= SelectFile(m_biosPath, "Bios Path ", {".bin"}, {"PSX Bios File", "*.bin"}, "Path to a PS1 NTSC-U bios."); updateReady &= SelectFile(m_gamePath, "Game Path", {".bin", ".img", ".iso"}, {"Game Files", "*.bin *.img *.iso"}, "Path to the clean NTSC-U version of CTR"); - updateReady &= SelectFile(m_duckPath, "Duck Path ", {".exe"}, {"Executable Files", "*.exe"}, "Path to the duckstation executable"); - updateReady &= SelectFolder(m_iniPath, "Ini Path ", "Duckstation gamesettings folder.\nUsually in Documents/DuckStation/gamesettings"); ImGui::Text(("Version: " + m_version).c_str()); ImGui::BeginDisabled(m_updater.IsBusy() || !m_updater.IsUpdated()); if (ImGui::Button("Launch Game")) { - std::string s_clientPath = GetClientPath(m_version); - std::string s_patchedPath = GetPatchedGamePath(m_version); + const std::string s_clientPath = GetClientPath(m_version); + const std::string s_patchedPath = GetPatchedGamePath(m_version); if (!std::filesystem::exists(s_clientPath)) { m_status = "Error: could not find " + s_clientPath; } else if (!std::filesystem::exists(s_patchedPath)) { m_status = "Error: could not find " + s_patchedPath; } else { g_dataManager.SaveData(); - std::string clientCommand = "start /b \"\" \"" + std::filesystem::current_path().string() + "/" + GetClientPath(m_version) + "\" " + m_username + " &"; + const std::string clientCommand = "start /b \"\" \"" + std::filesystem::current_path().string() + "/" + GetClientPath(m_version) + "\" " + m_username + " &"; std::system(clientCommand.c_str()); - const std::string duckCommand = "start /b \"\" \"" + m_duckPath + "\" \"" + s_patchedPath + "\" &"; + const std::string duckCommand = "start /b \"\" \"" + g_duckExecutable + "\" \"" + s_patchedPath + "\" &"; std::system(duckCommand.c_str()); } } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(m_updater.IsBusy() || !updateReady); - if (ImGui::Button("Update")) { m_updater.Update(m_status, m_version, m_gamePath, m_iniPath); } + if (ImGui::Button("Update")) { m_updater.Update(m_status, m_version, m_gamePath, m_biosPath); } ImGui::EndDisabled(); if (!m_status.empty()) { ImGui::Text(m_status.c_str()); } diff --git a/tools/Launcher/ui.h b/tools/Launcher/ui.h index 21324762..b6987521 100644 --- a/tools/Launcher/ui.h +++ b/tools/Launcher/ui.h @@ -17,9 +17,8 @@ private: private: Updater m_updater; std::string m_version = "None"; + std::string m_biosPath; std::string m_gamePath; - std::string m_duckPath; - std::string m_iniPath; std::string m_username; std::string m_status; }; \ No newline at end of file diff --git a/tools/Launcher/updater.cpp b/tools/Launcher/updater.cpp index 5fec28d9..08c1a155 100644 --- a/tools/Launcher/updater.cpp +++ b/tools/Launcher/updater.cpp @@ -2,12 +2,15 @@ #include "requests.h" #include "dataManager.h" #include "patch.h" +#include "io.h" +#include #include Updater::Updater() { g_dataManager.BindData(&m_updated, DataType::BOOL, "Updated"); + g_dataManager.BindData(&m_hasDuckstation, DataType::BOOL, "Duck"); } bool Updater::IsUpdated() @@ -36,11 +39,45 @@ bool Updater::CheckForUpdates(std::string& status, const std::string& currVersio ); } -bool Updater::Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& iniPath) +bool Updater::Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& biosPath) { return StartRoutine([&] { std::string version; + if (!m_hasDuckstation) + { + status = "Downloading Duckstation..."; + std::filesystem::create_directory(g_duckFolder); + const std::string duckArchive = "duckstation.zip"; + if (!Requests::DownloadFile("github.com", "/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip", g_duckFolder + duckArchive)) + { + status = "Error: could not download Duckstation."; + return false; + } + status = "Decompressing Duckstation..."; + if (!IO::DecompressFiles(g_duckFolder, duckArchive)) + { + status = "Error: could not decompress Duckstation."; + return false; + } + status = "Installing custom settings..."; + const std::string g_biosFolder = g_duckFolder + "bios/"; + std::filesystem::create_directory(g_biosFolder); + std::string biosName; + for (int i = static_cast(biosPath.size()) - 1; i >= 0; i--) + { + if (biosPath[i] == '/' || biosPath[i] == '\\') + { + biosName = biosPath.substr(i + 1); + break; + } + } + std::filesystem::copy_file(biosPath, g_biosFolder + biosName); + const std::string duckPortable = g_duckFolder + "portable.txt"; + std::ofstream portableFile(duckPortable.c_str()); + portableFile.close(); + m_hasDuckstation = true; + } status = "Checking for new updates..."; if (m_updateAvailable || Requests::CheckUpdates(version)) { @@ -50,20 +87,18 @@ bool Updater::Update(std::string& status, std::string& currVersion, const std::s std::string path = g_dataFolder + m_versionAvailable + "/"; if (Requests::DownloadUpdates(path, status) && Patch::NewVersion(path, gamePath, status)) { - std::string s_ini; - if (iniPath.back() == '/' || iniPath.back() == '\\') { s_ini = iniPath + g_configString; } - else { s_ini = iniPath + "/" + g_configString; } - if (std::filesystem::exists(s_ini)) { std::filesystem::remove(s_ini); } - std::filesystem::copy(path + g_configString, s_ini); + std::filesystem::copy_file(GetIniPath_Version(currVersion), GetIniPath_Duck()); m_updated = true; m_updateAvailable = false; currVersion = m_versionAvailable; status = "Update completed."; + return true; } } else { status = "Already on the latest patch"; } } else { status = "Error: could not establish connection"; } + return false; } ); } diff --git a/tools/Launcher/updater.h b/tools/Launcher/updater.h index f4ba6324..8626d085 100644 --- a/tools/Launcher/updater.h +++ b/tools/Launcher/updater.h @@ -10,7 +10,7 @@ public: bool IsUpdated(); bool IsBusy(); bool CheckForUpdates(std::string& status, const std::string& currVersion); - bool Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& iniPath); + bool Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& biosPath); private: bool StartRoutine(const std::function& func); @@ -21,5 +21,6 @@ private: std::string m_versionAvailable; bool m_routineRunning = false; bool m_updateAvailable = false; + bool m_hasDuckstation = false; bool m_updated; }; \ No newline at end of file