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/
data/
packages/
externals/
*.log
offset.txt
comport.txt

View File

@ -4,27 +4,34 @@
#include <filesystem>
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()

View File

@ -6,14 +6,17 @@
#include <string>
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;

View File

@ -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;
}

View File

@ -5,16 +5,31 @@
#include <httplib.h>
#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::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<std::string> 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;

View File

@ -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);
}

View File

@ -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()); }

View File

@ -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;
};

View File

@ -2,12 +2,15 @@
#include "requests.h"
#include "dataManager.h"
#include "patch.h"
#include "io.h"
#include <fstream>
#include <filesystem>
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<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...";
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;
}
);
}

View File

@ -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<void(void)>& 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;
};