This commit is contained in:
TheUbMunster 2024-07-20 18:28:22 -06:00
commit 6d3772efef
29 changed files with 351 additions and 218 deletions

4
.gitignore vendored
View File

@ -21,7 +21,9 @@ __pycache__/
build/
debug/
x64/
Launcher/
data/
packages/
externals/
*.log
offset.txt
comport.txt

3
.gitmodules vendored
View File

@ -19,9 +19,6 @@
[submodule "externals/json"]
path = externals/json
url = https://github.com/nlohmann/json
[submodule "externals/HTTPRequest"]
path = externals/HTTPRequest
url = https://github.com/elnormous/HTTPRequest
[submodule "externals/cpp-httplib"]
path = externals/cpp-httplib
url = https://github.com/yhirose/cpp-httplib

View File

@ -142,10 +142,6 @@ struct OnlineCTR
char desiredFPS;
// control when PSX and PC send/recv
char sleepControl;
char gpuSubmitTooLate;
char enableDeferredGPU;
#ifdef PINE_DEBUG
int stateChangeCounter;
#endif

View File

@ -5,23 +5,6 @@ typedef void (*VehicleFuncPtr)(struct Thread* thread, struct Driver* driver);
#ifdef USE_ONLINE
#include "../AltMods/OnlineCTR/global.h"
void RunVehicleThread(VehicleFuncPtr func, struct Thread* thread, struct Driver* driver);
#pragma optimize("", off)
void FrameStall()
{
// dont stall for this
if(octr->CurrState < LOBBY_HOST_TRACK_PICK)
return;
// wait for PC client to reset
while (octr->sleepControl == 1)
{
// required, or the register never updates
printf("");
}
}
#pragma optimize("", on)
#endif
void DECOMP_MainFrame_GameLogic(struct GameTracker* gGT, struct GamepadSystem* gGamepads)
@ -221,7 +204,7 @@ LAB_80035098:
(gGT->threadBuckets[iVar4].thread != 0)
)
{
// online multiplayer
#ifdef USE_ONLINE
@ -234,37 +217,31 @@ LAB_80035098:
if(gGT->trafficLightsTimer > 3600)
continue;
}
if (iVar4 == 0)
{
struct Driver* dOnline = gGT->drivers[0];
if(dOnline != 0)
{
struct Thread* dThread = dOnline->instSelf->thread;
DECOMP_VehPickupItem_ShootOnCirclePress(dOnline);
RunVehicleSet13(dThread, dOnline);
octr->sleepControl = 1;
octr->desiredFPS = FPS_DOUBLE(30);
// stall
if(octr->enableDeferredGPU == 1)
FrameStall();
}
for(int other = 1; other < 8; other++)
{
dOnline = gGT->drivers[other];
if(dOnline == 0) continue;
struct Thread* dThread = dOnline->instSelf->thread;
RunVehicleSet13(dThread, dOnline);
}
}
// offline
#else
if (iVar4 == 0)

View File

@ -340,7 +340,7 @@ void DECOMP_MainFrame_RenderFrame(struct GameTracker* gGT, struct GamepadSystem*
if((gGT->renderFlags & 0x8000) != 0)
{
WindowBoxLines(gGT);
WindowDivsionLines(gGT);
}
#endif
@ -1483,7 +1483,7 @@ void MultiplayerWumpaHUD(struct GameTracker* gGT)
for(int i = 0; i < gGT->numPlyrCurrGame; i++)
{
struct Driver* d = gGT->drivers[i];
// if race is over for driver
if((d->actionsFlagSet & 0x2000000) != 0)
{
@ -1711,16 +1711,10 @@ void RenderVSYNC(struct GameTracker* gGT)
if(ReadyToFlip(gGT))
{
#ifdef USE_ONLINE
if(boolFirstFrame)
octr->gpuSubmitTooLate = 1;
#endif
// quit, end of stall
return;
}
#ifdef USE_ONLINE
// gpu submission is not too late,
// we got to this while() loop before

View File

@ -22,7 +22,7 @@
//#define USE_VR // Virtual Reality
#ifdef USE_ONLINE
#define USE_60FPS
//#define USE_60FPS
#define USE_BOOSTBAR
#define USE_16BY9
#define USE_RAMEX

View File

@ -1320,18 +1320,7 @@ int main(int argc, char *argv[])
//perhaps instead of reading, keep a local counter, increment that, and then
//write it (without needing a blocking read first).
(*octr.get()).windowsClientSync++;
if (octr.get()->windowsClientSync == 0)
{
// On Niko's computer with MAPPED MEMORY
// 30fps 1x resolution = 4500
// 30fps 9x resolution = 2500
// 60fps = 0
// With the new PINE system, always zero,
// We can not defer the GPU until the PC port is done :(
//printf("Debug: SleepCount=%d\n", sleepCount);
}
octr.startWrite();
// should rename to room selection
if (octr.get()->CurrState >= LAUNCH_PICK_ROOM)
@ -1339,55 +1328,18 @@ int main(int argc, char *argv[])
StartAnimation();
// Wait for PSX to have P1 data,
// which is set at octr->sleepControl
void FrameStall(); FrameStall();
if (octr.get()->CurrState >= 0)
ClientState[octr.get()->CurrState]();
//UPDATE: the former version of this code sort of unconditionally usleep'd for a static amount
//of time (depending on 30 or 60fps). It's been updated to be dynamic, in case of lag/poor pc
//perf, or if PINE overhead is particularly large. If at any point in the future duckstation
//isn't at a locked 30/60fps, this may be the culprit.
// check for frame lag
if (octr.get()->gpuSubmitTooLate == 1)
{
octr.get()->gpuSubmitTooLate = 0;
// if 1-9 frame stalls
if (sleepCount >= 500)
{
// remove from sleep
sleepCount -= 500;
}
// if 10+ frame stalls
else
{
sleepCount = 0;
enableDeferredGPU = 0;
}
}
// PC writes to PSX,
// PSX is read-only
octr.get()->enableDeferredGPU = enableDeferredGPU;
// delay GPU between SEND and RECV
if (enableDeferredGPU == 1)
usleep(sleepCount);
// now check for new RECV message
ProcessNewMessages();
// allow PSX to resume
octr.get()->sleepControl = 0;
octr.startWrite(); //only write the things that have changed.
GCDeadPineData(); //this is probably a decent place to do this.
// Wait for PSX to have P1 data,
// which is set at octr->sleepControl
void FrameStall(); FrameStall();
}
printf("\n");
@ -1409,17 +1361,14 @@ void usleep(__int64 usec)
}
#endif
#pragma optimize("", off)
int gGT_timer = 0;
void FrameStall()
{
// wait for next frame
//TODO: make this a submember of octr
ps1ptr<int> OCTRsleepControl = pBuf.at<int>(octr.get_address() + offsetof(OnlineCTR, sleepControl));
while ((*OCTRsleepControl.get()) == 0)
ps1ptr<int> OCTRsleepControl = pBuf.at<int>(0x80096b20 + 0x1cf8);
while (gGT_timer == (*OCTRsleepControl.get()))
{
usleep(1);
OCTRsleepControl.blockingRead();
}
(*octr.get()).sleepControl = (*OCTRsleepControl.get());
}
#pragma optimize("", on)
}

View File

@ -157,6 +157,7 @@
<ClCompile Include="requests.cpp" />
<ClCompile Include="third_party\xdelta3\xdelta3.c" />
<ClCompile Include="ui.cpp" />
<ClCompile Include="updater.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="app.h" />
@ -166,6 +167,7 @@
<ClInclude Include="patch.h" />
<ClInclude Include="requests.h" />
<ClInclude Include="ui.h" />
<ClInclude Include="updater.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@ -174,7 +176,7 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets" Condition="Exists('packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets')" />
<Import Project="packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets" Condition="Exists('packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets')" />
<Import Project="packages\EbolaChan.LibZip.1.10.1\build\native\EbolaChan.LibZip.targets" Condition="Exists('packages\EbolaChan.LibZip.1.10.1\build\native\EbolaChan.LibZip.targets')" />
<Import Project="packages\libzip-c.1.9.2.6\build\native\libzip-c.targets" Condition="Exists('packages\libzip-c.1.9.2.6\build\native\libzip-c.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@ -182,6 +184,6 @@
</PropertyGroup>
<Error Condition="!Exists('packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.nuget.redist.2.30.3\build\native\sdl2.nuget.redist.targets'))" />
<Error Condition="!Exists('packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.nuget.2.30.3\build\native\sdl2.nuget.targets'))" />
<Error Condition="!Exists('packages\EbolaChan.LibZip.1.10.1\build\native\EbolaChan.LibZip.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\EbolaChan.LibZip.1.10.1\build\native\EbolaChan.LibZip.targets'))" />
<Error Condition="!Exists('packages\libzip-c.1.9.2.6\build\native\libzip-c.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\libzip-c.1.9.2.6\build\native\libzip-c.targets'))" />
</Target>
</Project>

View File

@ -75,6 +75,9 @@
<ClCompile Include="third_party\xdelta3\xdelta3.c">
<Filter>xdelta3</Filter>
</ClCompile>
<ClCompile Include="updater.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="app.h">
@ -98,6 +101,9 @@
<ClInclude Include="io.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="updater.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@ -122,11 +122,7 @@ void App::Run()
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
int width, height;
SDL_GetWindowSize(m_window, &width, &height);
ui.Render(width, height);
Main();
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(clearColor.x * clearColor.w, clearColor.y * clearColor.w, clearColor.z * clearColor.w, clearColor.w);
@ -136,6 +132,13 @@ void App::Run()
}
}
void App::Main()
{
int width, height;
SDL_GetWindowSize(m_window, &width, &height);
ui.Render(width, height);
}
void App::Close()
{
g_dataManager.SaveData();

View File

@ -10,6 +10,7 @@ class App
public:
void Init();
void Run();
void Main();
void Close();
#ifdef _DEBUG
void RunImGuiExample();
@ -24,7 +25,7 @@ private:
private:
UI ui;
const std::string m_version = "v0.1";
const std::string m_version = "v0.2";
std::string m_glslVer;
SDL_GLContext m_glContext;
SDL_Window* m_window;

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";
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 + "settings.ini";
}
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

@ -1,6 +1,8 @@
#include "io.h"
#include <filesystem>
#include <fstream>
#include <zip.h>
void IO::ReadBinaryFile(std::vector<char>& v, const std::string& path)
{
@ -19,3 +21,35 @@ void IO::WriteBinaryFile(const std::vector<char>& v, const std::string& path)
output.write(v.data(), v.size());
output.close();
}
bool IO::DecompressFiles(const std::string& path, const std::string& filename)
{
int err;
zip_t* zipArchive = zip_open((path + filename).c_str(), 0, &err);
if (!zipArchive) { return false; }
zip_stat_t zipStat;
for (int i = 0; i < zip_get_num_entries(zipArchive, 0); i++)
{
if (zip_stat_index(zipArchive, i, 0, &zipStat) == 0)
{
std::string archiveName(zipStat.name);
if (archiveName.back() == '/' || archiveName.back() == '\\')
{
std::filesystem::create_directory(path + archiveName);
}
else
{
std::vector<char> decompressedFile;
decompressedFile.resize(zipStat.size);
zip_file* file = zip_fopen_index(zipArchive, i, 0);
if (!file) { return false; }
zip_fread(file, decompressedFile.data(), zipStat.size);
zip_fclose(file);
IO::WriteBinaryFile(decompressedFile, path + archiveName);
}
}
}
zip_close(zipArchive);
return true;
}

View File

@ -7,4 +7,5 @@ namespace IO
{
void ReadBinaryFile(std::vector<char>& v, const std::string& path);
void WriteBinaryFile(const std::vector<char>& v, const std::string& path);
bool DecompressFiles(const std::string& path, const std::string& filename);
}

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

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EbolaChan.LibZip" version="1.10.1" targetFramework="native" />
<package id="libzip-c" version="1.9.2.6" targetFramework="native" />
<package id="sdl2.nuget" version="2.30.3" targetFramework="native" />
<package id="sdl2.nuget.redist" version="2.30.3" targetFramework="native" />
</packages>

View File

@ -1,2 +0,0 @@
此静态库目前仅支持平台工具集v143有Debug和Release两个版本。要使用Debug版本必须将项目配置设置为以字母D开头要使用Release版本必须将项目配置设置为以字母R开头。
This static library currently only supports platform toolset v143, available in Debug and Release versions. To use the Debug version, you must set the project configuration to start with the letter D; to use the Release version, you must set the project configuration to start with the letter R.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@ -3,28 +3,6 @@
#include "io.h"
#include <xdelta3.h>
#include <zip.h>
static bool DecompressFile(const std::string& path, const std::string& filename, const std::string& ext)
{
int err;
zip_t* zipArchive = zip_open(path.c_str(), 0, &err);
if (!zipArchive) { return false; }
zip_stat_t zipStat;
zip_stat_init(&zipStat);
zip_stat(zipArchive, filename.c_str(), 0, &zipStat);
std::vector<char> decompressedFile;
decompressedFile.resize(zipStat.size);
zip_file* file = zip_fopen(zipArchive, filename.c_str(), 0);
zip_fread(file, decompressedFile.data(), zipStat.size);
zip_fclose(file);
zip_close(zipArchive);
std::string newFilePath = path.substr(0, path.find(".")) + ext;
IO::WriteBinaryFile(decompressedFile, newFilePath);
return true;
}
static bool DecompressXDelta(const std::string& xdeltaPath, const std::string& inputPath, const std::string& ext)
{
@ -44,7 +22,19 @@ static bool DecompressXDelta(const std::string& xdeltaPath, const std::string& i
return true;
}
bool Patch::NewVersion(const std::string& path, const std::string& gamePath)
bool Patch::NewVersion(const std::string& path, const std::string& gamePath, std::string& status)
{
return DecompressFile(path + g_clientString, g_clientExecutable, ".exe") && DecompressXDelta(path + g_patchString, gamePath, ".bin");
status = "Decompressing " + g_clientExecutable + "...";
if (!IO::DecompressFiles(path, g_clientString))
{
status = "Error decompressing " + g_clientExecutable;
return false;
}
status = "Applying xdelta patch...";
if (!DecompressXDelta(path + g_patchString, gamePath, ".bin"))
{
status = "Error applying xdelta patch";
return false;
}
return true;
}

View File

@ -4,5 +4,5 @@
namespace Patch
{
bool NewVersion(const std::string& path, const std::string& gamePath);
bool NewVersion(const std::string& path, const std::string& gamePath, std::string& status);
}

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;
}
@ -29,13 +44,20 @@ bool Requests::CheckUpdates(std::string& version)
return false;
}
bool Requests::DownloadUpdates(const std::string& path)
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)
{
if (!DownloadFile(file, path + file)) { return false; }
status = "Downloading " + file + "...";
if (!DownloadFile(octrDomain, octrPath + file, path + file))
{
status = "Error downloading " + file;
return false;
}
}
return true;
}

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);
bool DownloadUpdates(const std::string& version, std::string& status);
}

View File

@ -1,10 +1,7 @@
#include "ui.h"
#include "dataManager.h"
#include "IconsFontAwesome6.h"
#include "requests.h"
#include "patch.h"
#include <imgui.h>
#include <misc/cpp/imgui_stdlib.h>
#include <portable-file-dialogs.h>
#include <filesystem>
@ -12,54 +9,66 @@
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");
g_dataManager.BindData(&m_updated, DataType::BOOL, "Updated");
m_updater.CheckForUpdates(m_status, m_version);
}
static int FilterUsernameChar(ImGuiInputTextCallbackData* data)
{
if (data->EventChar >= 'a' && data->EventChar <= 'z') { return 0; }
if (data->EventChar >= 'A' && data->EventChar <= 'Z') { return 0; }
if (data->EventChar >= '0' && data->EventChar <= '9') { return 0; }
return 1;
}
void UI::Render(int width, int height)
{
static bool update = false;
if (update) { Update(); update = false; }
ImGui::SetNextWindowPos(ImVec2(.0f, .0f), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(static_cast<float>(width), static_cast<float>(height)), ImGuiCond_Always);
ImGui::Begin("Main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
std::string icon = m_username.empty() ? ICON_FA_CIRCLE_XMARK : ICON_FA_CIRCLE_CHECK;
ImGui::InputText(("Username " + icon).c_str(), &m_username);
ImGui::SetItemTooltip("Special characters:\n* = Cross Button\n< = Left Arrow\n@ = Circle\n[ = Square\n^ = Triangle\n& = Space");
ImGui::InputText(("Username " + icon).c_str(), &m_username, ImGuiInputTextFlags_CallbackCharFilter, FilterUsernameChar);
if (m_username.size() > 9) { m_username = m_username.substr(0, 9); }
static bool readBios = true;
bool updateReady = true;
updateReady &= SelectFile(m_gamePath, "Game Path", ".bin", {"Game Files", "*.bin"}, "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");
updateReady &= SelectFile(m_biosPath, "Bios Path ", {".bin"}, {"PSX Bios File", "*.bin"}, "Path to a PS1 NTSC-U bios.");
if (updateReady)
{
if (readBios)
{
if (m_updater.IsValidBios(m_biosPath)) { readBios = false; }
else { updateReady = false; }
}
}
else { readBios = true; }
updateReady &= SelectFile(m_gamePath, "Game Path", {".bin", ".img", ".iso"}, {"Game Files", "*.bin *.img *.iso"}, "Path to the clean NTSC-U version of CTR");
ImGui::Text(("Version: " + m_version).c_str());
ImGui::BeginDisabled(!m_updated);
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(!updateReady);
if (ImGui::Button("Update")) { update = true; m_status = "Updating..."; }
ImGui::BeginDisabled(m_updater.IsBusy() || !updateReady);
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()); }
@ -67,41 +76,27 @@ void UI::Render(int width, int height)
ImGui::End();
}
void UI::Update()
bool UI::SelectFile(std::string& str, const std::string& label, const std::vector<std::string>& ext, const std::vector<std::string>& filters, const std::string& tip)
{
std::string version;
if (Requests::CheckUpdates(version))
{
if (version != m_version)
{
m_updated = false;
std::string path = g_dataFolder + version + "/";
if (Requests::DownloadUpdates(path))
{
if (Patch::NewVersion(path, m_gamePath))
{
std::string s_ini;
if (m_iniPath.back() == '/' || m_iniPath.back() == '\\') { s_ini = m_iniPath + g_configString; }
else { s_ini = m_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_version = version;
m_status = "Update completed.";
}
else { m_status = "Error: could not decompress files"; }
}
else { m_status = "Error: could not establish connection"; }
}
else { m_status = "Already on the latest patch"; }
}
else { m_status = "Error: could not establish connection"; }
}
bool UI::SelectFile(std::string& str, const std::string& label, const std::string& ext, const std::vector<std::string>& filters, const std::string& tip)
{
bool validPath = std::filesystem::exists(str) && str.ends_with(ext);
std::string icon = validPath ? ICON_FA_CIRCLE_CHECK : ICON_FA_CIRCLE_XMARK;
std::string lowercaseStr;
for (char c : str)
{
if (c <= 'Z' && c >= 'A') { c = c - ('Z' - 'z'); };
lowercaseStr += c;
}
auto checkValidPath = [&]
{
if (std::filesystem::exists(str))
{
for (const std::string& s : ext)
{
if (lowercaseStr.ends_with(s)) { return true; }
}
}
};
std::string icon = checkValidPath() ? ICON_FA_CIRCLE_CHECK : ICON_FA_CIRCLE_XMARK;
ImGui::InputText((label + " " + icon).c_str(), &str);
if (!tip.empty()) { ImGui::SetItemTooltip(tip.c_str()); }
ImGui::SameLine();
@ -111,7 +106,7 @@ bool UI::SelectFile(std::string& str, const std::string& label, const std::strin
if (selection.empty()) { return false; }
str = selection.front();
}
return validPath;
return checkValidPath();
}
bool UI::SelectFolder(std::string& str, const std::string& label, const std::string& tip)

View File

@ -1,5 +1,8 @@
#pragma once
#include "updater.h"
#include <imgui.h>
#include <string>
#include <vector>
@ -10,16 +13,14 @@ public:
void Render(int width, int height);
private:
void Update();
bool SelectFile(std::string& str, const std::string& label, const std::string& ext, const std::vector<std::string>& filters, const std::string& tip);
bool SelectFile(std::string& str, const std::string& label, const std::vector<std::string>& ext, const std::vector<std::string>& filters, const std::string& tip);
bool SelectFolder(std::string& str, const std::string& label, const std::string& tip);
private:
bool m_updated = false;
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;
};

123
tools/Launcher/updater.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "updater.h"
#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()
{
return m_updated;
}
bool Updater::IsBusy()
{
return m_routineRunning;
}
bool Updater::IsValidBios(const std::string& path)
{
std::vector<char> v;
IO::ReadBinaryFile(v, path);
return v.size() == static_cast<size_t>(0x100000);
}
bool Updater::CheckForUpdates(std::string& status, const std::string& currVersion)
{
return StartRoutine([&]
{
std::string version;
Requests::CheckUpdates(version);
if (currVersion != version)
{
m_updateAvailable = true;
m_versionAvailable = version;
status = "Update available! v" + m_versionAvailable;
}
}
);
}
bool Updater::Update(std::string& status, std::string& currVersion, const std::string& gamePath, const std::string& biosPath)
{
return StartRoutine([&]
{
std::string version;
bool copyIni = false;
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;
copyIni = true;
}
status = "Checking for new updates...";
if (m_updateAvailable || Requests::CheckUpdates(version))
{
if (m_updateAvailable || version != currVersion)
{
m_versionAvailable = m_updateAvailable ? m_versionAvailable : version;
std::string path = g_dataFolder + m_versionAvailable + "/";
if (Requests::DownloadUpdates(path, status) && Patch::NewVersion(path, gamePath, status))
{
if (copyIni) { std::filesystem::copy_file(GetIniPath_Version(m_versionAvailable), 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;
}
);
}
bool Updater::StartRoutine(const std::function<void(void)>& func)
{
if (m_routineRunning) { return false; }
m_routine = func;
m_routineRunning = true;
m_updateRoutine = std::async(std::launch::async, [&] { m_routine(); m_routineRunning = false; });
return true;
}

27
tools/Launcher/updater.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <future>
class Updater
{
public:
Updater();
bool IsUpdated();
bool IsBusy();
bool IsValidBios(const std::string& path);
bool CheckForUpdates(std::string& status, const std::string& currVersion);
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);
private:
std::future<void> m_updateRoutine;
std::function<void(void)> m_routine;
std::string m_versionAvailable;
bool m_routineRunning = false;
bool m_updateAvailable = false;
bool m_hasDuckstation = false;
bool m_updated;
};