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

3
.gitmodules vendored
View File

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

View File

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

View File

@ -5,23 +5,6 @@ typedef void (*VehicleFuncPtr)(struct Thread* thread, struct Driver* driver);
#ifdef USE_ONLINE #ifdef USE_ONLINE
#include "../AltMods/OnlineCTR/global.h" #include "../AltMods/OnlineCTR/global.h"
void RunVehicleThread(VehicleFuncPtr func, struct Thread* thread, struct Driver* driver); 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 #endif
void DECOMP_MainFrame_GameLogic(struct GameTracker* gGT, struct GamepadSystem* gGamepads) void DECOMP_MainFrame_GameLogic(struct GameTracker* gGT, struct GamepadSystem* gGamepads)
@ -221,7 +204,7 @@ LAB_80035098:
(gGT->threadBuckets[iVar4].thread != 0) (gGT->threadBuckets[iVar4].thread != 0)
) )
{ {
// online multiplayer // online multiplayer
#ifdef USE_ONLINE #ifdef USE_ONLINE
@ -234,37 +217,31 @@ LAB_80035098:
if(gGT->trafficLightsTimer > 3600) if(gGT->trafficLightsTimer > 3600)
continue; continue;
} }
if (iVar4 == 0) if (iVar4 == 0)
{ {
struct Driver* dOnline = gGT->drivers[0]; struct Driver* dOnline = gGT->drivers[0];
if(dOnline != 0) if(dOnline != 0)
{ {
struct Thread* dThread = dOnline->instSelf->thread; struct Thread* dThread = dOnline->instSelf->thread;
DECOMP_VehPickupItem_ShootOnCirclePress(dOnline); DECOMP_VehPickupItem_ShootOnCirclePress(dOnline);
RunVehicleSet13(dThread, dOnline); RunVehicleSet13(dThread, dOnline);
octr->sleepControl = 1;
octr->desiredFPS = FPS_DOUBLE(30); octr->desiredFPS = FPS_DOUBLE(30);
// stall
if(octr->enableDeferredGPU == 1)
FrameStall();
} }
for(int other = 1; other < 8; other++) for(int other = 1; other < 8; other++)
{ {
dOnline = gGT->drivers[other]; dOnline = gGT->drivers[other];
if(dOnline == 0) continue; if(dOnline == 0) continue;
struct Thread* dThread = dOnline->instSelf->thread; struct Thread* dThread = dOnline->instSelf->thread;
RunVehicleSet13(dThread, dOnline); RunVehicleSet13(dThread, dOnline);
} }
} }
// offline // offline
#else #else
if (iVar4 == 0) if (iVar4 == 0)

View File

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

View File

@ -22,7 +22,7 @@
//#define USE_VR // Virtual Reality //#define USE_VR // Virtual Reality
#ifdef USE_ONLINE #ifdef USE_ONLINE
#define USE_60FPS //#define USE_60FPS
#define USE_BOOSTBAR #define USE_BOOSTBAR
#define USE_16BY9 #define USE_16BY9
#define USE_RAMEX #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 //perhaps instead of reading, keep a local counter, increment that, and then
//write it (without needing a blocking read first). //write it (without needing a blocking read first).
(*octr.get()).windowsClientSync++; (*octr.get()).windowsClientSync++;
octr.startWrite();
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);
}
// should rename to room selection // should rename to room selection
if (octr.get()->CurrState >= LAUNCH_PICK_ROOM) if (octr.get()->CurrState >= LAUNCH_PICK_ROOM)
@ -1339,55 +1328,18 @@ int main(int argc, char *argv[])
StartAnimation(); StartAnimation();
// Wait for PSX to have P1 data,
// which is set at octr->sleepControl
void FrameStall(); FrameStall();
if (octr.get()->CurrState >= 0) if (octr.get()->CurrState >= 0)
ClientState[octr.get()->CurrState](); 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 // now check for new RECV message
ProcessNewMessages(); ProcessNewMessages();
// allow PSX to resume
octr.get()->sleepControl = 0;
octr.startWrite(); //only write the things that have changed. octr.startWrite(); //only write the things that have changed.
GCDeadPineData(); //this is probably a decent place to do this. 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"); printf("\n");
@ -1409,17 +1361,14 @@ void usleep(__int64 usec)
} }
#endif #endif
#pragma optimize("", off) int gGT_timer = 0;
void FrameStall() void FrameStall()
{ {
// wait for next frame // wait for next frame
//TODO: make this a submember of octr //TODO: make this a submember of octr
ps1ptr<int> OCTRsleepControl = pBuf.at<int>(octr.get_address() + offsetof(OnlineCTR, sleepControl)); ps1ptr<int> OCTRsleepControl = pBuf.at<int>(0x80096b20 + 0x1cf8);
while ((*OCTRsleepControl.get()) == 0) while (gGT_timer == (*OCTRsleepControl.get()))
{ {
usleep(1);
OCTRsleepControl.blockingRead(); OCTRsleepControl.blockingRead();
} }
(*octr.get()).sleepControl = (*OCTRsleepControl.get()); }
}
#pragma optimize("", on)

View File

@ -157,6 +157,7 @@
<ClCompile Include="requests.cpp" /> <ClCompile Include="requests.cpp" />
<ClCompile Include="third_party\xdelta3\xdelta3.c" /> <ClCompile Include="third_party\xdelta3\xdelta3.c" />
<ClCompile Include="ui.cpp" /> <ClCompile Include="ui.cpp" />
<ClCompile Include="updater.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="app.h" /> <ClInclude Include="app.h" />
@ -166,6 +167,7 @@
<ClInclude Include="patch.h" /> <ClInclude Include="patch.h" />
<ClInclude Include="requests.h" /> <ClInclude Include="requests.h" />
<ClInclude Include="ui.h" /> <ClInclude Include="ui.h" />
<ClInclude Include="updater.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
@ -174,7 +176,7 @@
<ImportGroup Label="ExtensionTargets"> <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.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\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> </ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
@ -182,6 +184,6 @@
</PropertyGroup> </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.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\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> </Target>
</Project> </Project>

View File

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

View File

@ -122,11 +122,7 @@ void App::Run()
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(); ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
Main();
int width, height;
SDL_GetWindowSize(m_window, &width, &height);
ui.Render(width, height);
ImGui::Render(); ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); 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); 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() void App::Close()
{ {
g_dataManager.SaveData(); g_dataManager.SaveData();

View File

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

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

@ -1,6 +1,8 @@
#include "io.h" #include "io.h"
#include <filesystem>
#include <fstream> #include <fstream>
#include <zip.h>
void IO::ReadBinaryFile(std::vector<char>& v, const std::string& path) 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.write(v.data(), v.size());
output.close(); 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 ReadBinaryFile(std::vector<char>& v, const std::string& path);
void WriteBinaryFile(const 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 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

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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" version="2.30.3" targetFramework="native" />
<package id="sdl2.nuget.redist" version="2.30.3" targetFramework="native" /> <package id="sdl2.nuget.redist" version="2.30.3" targetFramework="native" />
</packages> </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 "io.h"
#include <xdelta3.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) 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; 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 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 <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;
} }
@ -29,13 +44,20 @@ bool Requests::CheckUpdates(std::string& version)
return false; 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 }; 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)
{ {
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; return true;
} }

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

View File

@ -1,10 +1,7 @@
#include "ui.h" #include "ui.h"
#include "dataManager.h" #include "dataManager.h"
#include "IconsFontAwesome6.h" #include "IconsFontAwesome6.h"
#include "requests.h"
#include "patch.h"
#include <imgui.h>
#include <misc/cpp/imgui_stdlib.h> #include <misc/cpp/imgui_stdlib.h>
#include <portable-file-dialogs.h> #include <portable-file-dialogs.h>
#include <filesystem> #include <filesystem>
@ -12,54 +9,66 @@
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");
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) void UI::Render(int width, int height)
{ {
static bool update = false;
if (update) { Update(); update = false; }
ImGui::SetNextWindowPos(ImVec2(.0f, .0f), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(.0f, .0f), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(static_cast<float>(width), static_cast<float>(height)), 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); 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; std::string icon = m_username.empty() ? ICON_FA_CIRCLE_XMARK : ICON_FA_CIRCLE_CHECK;
ImGui::InputText(("Username " + icon).c_str(), &m_username); ImGui::InputText(("Username " + icon).c_str(), &m_username, ImGuiInputTextFlags_CallbackCharFilter, FilterUsernameChar);
ImGui::SetItemTooltip("Special characters:\n* = Cross Button\n< = Left Arrow\n@ = Circle\n[ = Square\n^ = Triangle\n& = Space");
if (m_username.size() > 9) { m_username = m_username.substr(0, 9); } if (m_username.size() > 9) { m_username = m_username.substr(0, 9); }
static bool readBios = true;
bool updateReady = 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_biosPath, "Bios Path ", {".bin"}, {"PSX Bios File", "*.bin"}, "Path to a PS1 NTSC-U bios.");
updateReady &= SelectFile(m_duckPath, "Duck Path ", ".exe", {"Executable Files", "*.exe"}, "Path to the duckstation executable"); if (updateReady)
updateReady &= SelectFolder(m_iniPath, "Ini Path ", "Duckstation gamesettings folder.\nUsually in Documents/DuckStation/gamesettings"); {
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::Text(("Version: " + m_version).c_str());
ImGui::BeginDisabled(!m_updated); 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(!updateReady); ImGui::BeginDisabled(m_updater.IsBusy() || !updateReady);
if (ImGui::Button("Update")) { update = true; m_status = "Updating..."; } 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()); }
@ -67,41 +76,27 @@ void UI::Render(int width, int height)
ImGui::End(); 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) std::string lowercaseStr;
{ for (char c : str)
bool validPath = std::filesystem::exists(str) && str.ends_with(ext); {
std::string icon = validPath ? ICON_FA_CIRCLE_CHECK : ICON_FA_CIRCLE_XMARK; 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); ImGui::InputText((label + " " + icon).c_str(), &str);
if (!tip.empty()) { ImGui::SetItemTooltip(tip.c_str()); } if (!tip.empty()) { ImGui::SetItemTooltip(tip.c_str()); }
ImGui::SameLine(); ImGui::SameLine();
@ -111,7 +106,7 @@ bool UI::SelectFile(std::string& str, const std::string& label, const std::strin
if (selection.empty()) { return false; } if (selection.empty()) { return false; }
str = selection.front(); str = selection.front();
} }
return validPath; return checkValidPath();
} }
bool UI::SelectFolder(std::string& str, const std::string& label, const std::string& tip) bool UI::SelectFolder(std::string& str, const std::string& label, const std::string& tip)

View File

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

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