Launcher: automatic duckstation

This commit is contained in:
mateusfavarin 2024-07-20 19:13:21 -03:00
parent a42be5271a
commit 6a76670186
10 changed files with 96 additions and 30 deletions

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ debug/
x64/ x64/
data/ data/
packages/ packages/
externals/
*.log *.log
offset.txt offset.txt
comport.txt comport.txt

View File

@ -4,27 +4,34 @@
#include <filesystem> #include <filesystem>
const std::string g_dataFolder = "data/"; 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_clientString = "client.zip";
const std::string g_clientExecutable = "Client.exe"; const std::string g_clientExecutable = "Client.exe";
const std::string g_patchString = "ctr-u_Online30.xdelta"; 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; 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; std::string s_patch = g_dataFolder + version + "/" + g_patchString;
return s_patch.substr(0, s_patch.find(".")) + ".bin"; 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; return g_dataFolder + version + "/" + g_configString;
} }
const std::string GetIniPath_Duck()
{
return g_duckFolder + g_configString;
}
DataManager g_dataManager; DataManager g_dataManager;
DataManager::DataManager() DataManager::DataManager()

View File

@ -6,14 +6,17 @@
#include <string> #include <string>
extern const std::string g_dataFolder; 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_clientString;
extern const std::string g_clientExecutable; extern const std::string g_clientExecutable;
extern const std::string g_patchString; extern const std::string g_patchString;
extern const std::string g_configString; extern const std::string g_configString;
std::string GetClientPath(const std::string& version); const std::string GetClientPath(const std::string& version);
std::string GetPatchedGamePath(const std::string& version); const std::string GetPatchedGamePath(const std::string& version);
std::string GetConfigPath(const std::string& version); const std::string GetIniPath_Version(const std::string& version);
const std::string GetIniPath_Duck();
using json = nlohmann::json; using json = nlohmann::json;

View File

@ -4,8 +4,12 @@ int main(int argc, char* argv[])
{ {
App app; App app;
app.Init(); app.Init();
#ifdef _DEBUG
app.Run();
#else
try { app.Run(); } try { app.Run(); }
catch (...) {}; catch (...) {};
#endif
app.Close(); app.Close();
return 0; return 0;
} }

View File

@ -5,16 +5,31 @@
#include <httplib.h> #include <httplib.h>
#include <fstream> #include <fstream>
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::SSLClient request(domain);
httplib::Result response = request.Get("/wp-content/uploads/onlinectr_patches/" + sitePath); httplib::Result response = request.Get(sitePath);
if (response && response->status == 200) { if (!response) { return false; }
if (response->status == 200)
{
std::ofstream file(filePath.c_str(), std::ios::binary); std::ofstream file(filePath.c_str(), std::ios::binary);
file.write(response->body.c_str(), response->body.size()); file.write(response->body.c_str(), response->body.size());
file.close(); file.close();
return true; 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; return false;
} }
@ -31,12 +46,14 @@ bool Requests::CheckUpdates(std::string& version)
bool Requests::DownloadUpdates(const std::string& path, std::string& status) 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<std::string> files = { g_clientString, g_patchString, g_configString }; const std::vector<std::string> files = { g_clientString, g_patchString, g_configString };
if (!std::filesystem::is_directory(path)) { std::filesystem::create_directory(path); } if (!std::filesystem::is_directory(path)) { std::filesystem::create_directory(path); }
for (const std::string& file : files) for (const std::string& file : files)
{ {
status = "Downloading " + file + "..."; status = "Downloading " + file + "...";
if (!DownloadFile(file, path + file)) if (!DownloadFile(octrDomain, octrPath + file, path + file))
{ {
status = "Error downloading " + file; status = "Error downloading " + file;
return false; return false;

View File

@ -4,6 +4,7 @@
namespace Requests namespace Requests
{ {
bool DownloadFile(const std::string& domain, const std::string& sitePath, const std::string& filePath);
bool CheckUpdates(std::string& version); bool CheckUpdates(std::string& version);
bool DownloadUpdates(const std::string& version, std::string& status); bool DownloadUpdates(const std::string& version, std::string& status);
} }

View File

@ -10,10 +10,9 @@
UI::UI() UI::UI()
{ {
g_dataManager.BindData(&m_biosPath, DataType::STRING, "BiosPath");
g_dataManager.BindData(&m_gamePath, DataType::STRING, "GamePath"); 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_version, DataType::STRING, "GameVersion");
g_dataManager.BindData(&m_iniPath, DataType::STRING, "IniPath");
g_dataManager.BindData(&m_username, DataType::STRING, "Username"); g_dataManager.BindData(&m_username, DataType::STRING, "Username");
m_updater.CheckForUpdates(m_status, m_version); 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); } if (m_username.size() > 9) { m_username = m_username.substr(0, 9); }
bool updateReady = true; 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_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::Text(("Version: " + m_version).c_str());
ImGui::BeginDisabled(m_updater.IsBusy() || !m_updater.IsUpdated()); ImGui::BeginDisabled(m_updater.IsBusy() || !m_updater.IsUpdated());
if (ImGui::Button("Launch Game")) if (ImGui::Button("Launch Game"))
{ {
std::string s_clientPath = GetClientPath(m_version); const std::string s_clientPath = GetClientPath(m_version);
std::string s_patchedPath = GetPatchedGamePath(m_version); const std::string s_patchedPath = GetPatchedGamePath(m_version);
if (!std::filesystem::exists(s_clientPath)) { m_status = "Error: could not find " + s_clientPath; } 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 if (!std::filesystem::exists(s_patchedPath)) { m_status = "Error: could not find " + s_patchedPath; }
else else
{ {
g_dataManager.SaveData(); 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()); 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()); std::system(duckCommand.c_str());
} }
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(m_updater.IsBusy() || !updateReady); 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(); ImGui::EndDisabled();
if (!m_status.empty()) { ImGui::Text(m_status.c_str()); } if (!m_status.empty()) { ImGui::Text(m_status.c_str()); }

View File

@ -17,9 +17,8 @@ private:
private: private:
Updater m_updater; Updater m_updater;
std::string m_version = "None"; std::string m_version = "None";
std::string m_biosPath;
std::string m_gamePath; std::string m_gamePath;
std::string m_duckPath;
std::string m_iniPath;
std::string m_username; std::string m_username;
std::string m_status; std::string m_status;
}; };

View File

@ -2,12 +2,15 @@
#include "requests.h" #include "requests.h"
#include "dataManager.h" #include "dataManager.h"
#include "patch.h" #include "patch.h"
#include "io.h"
#include <fstream>
#include <filesystem> #include <filesystem>
Updater::Updater() Updater::Updater()
{ {
g_dataManager.BindData(&m_updated, DataType::BOOL, "Updated"); g_dataManager.BindData(&m_updated, DataType::BOOL, "Updated");
g_dataManager.BindData(&m_hasDuckstation, DataType::BOOL, "Duck");
} }
bool Updater::IsUpdated() 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([&] return StartRoutine([&]
{ {
std::string version; 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<int>(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..."; status = "Checking for new updates...";
if (m_updateAvailable || Requests::CheckUpdates(version)) 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 + "/"; std::string path = g_dataFolder + m_versionAvailable + "/";
if (Requests::DownloadUpdates(path, status) && Patch::NewVersion(path, gamePath, status)) if (Requests::DownloadUpdates(path, status) && Patch::NewVersion(path, gamePath, status))
{ {
std::string s_ini; std::filesystem::copy_file(GetIniPath_Version(currVersion), GetIniPath_Duck());
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);
m_updated = true; m_updated = true;
m_updateAvailable = false; m_updateAvailable = false;
currVersion = m_versionAvailable; currVersion = m_versionAvailable;
status = "Update completed."; status = "Update completed.";
return true;
} }
} }
else { status = "Already on the latest patch"; } else { status = "Already on the latest patch"; }
} }
else { status = "Error: could not establish connection"; } else { status = "Error: could not establish connection"; }
return false;
} }
); );
} }

View File

@ -10,7 +10,7 @@ public:
bool IsUpdated(); bool IsUpdated();
bool IsBusy(); bool IsBusy();
bool CheckForUpdates(std::string& status, const std::string& currVersion); 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: private:
bool StartRoutine(const std::function<void(void)>& func); bool StartRoutine(const std::function<void(void)>& func);
@ -21,5 +21,6 @@ private:
std::string m_versionAvailable; std::string m_versionAvailable;
bool m_routineRunning = false; bool m_routineRunning = false;
bool m_updateAvailable = false; bool m_updateAvailable = false;
bool m_hasDuckstation = false;
bool m_updated; bool m_updated;
}; };