mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2025-04-28 13:27:58 +03:00
Update checker. (#251)
* Update checker. * Fix build and enum class. * Get rid of submodule for httplib. * Get rid of submodule for curl. * Minor style changes and fix video.cpp Linux build error. * CTitleStateIntro_patches: implemented update message * Update update_checker.cpp * CTitleStateIntro_patches: fix fade out accepting input --------- Co-authored-by: Hyper <34012267+hyperbx@users.noreply.github.com>
This commit is contained in:
parent
54d5588d79
commit
cd38776576
11 changed files with 282 additions and 15 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -61,3 +61,6 @@
|
|||
[submodule "thirdparty/implot"]
|
||||
path = thirdparty/implot
|
||||
url = https://github.com/epezent/implot.git
|
||||
[submodule "thirdparty/json"]
|
||||
path = thirdparty/json
|
||||
url = https://github.com/nlohmann/json
|
||||
|
|
|
@ -164,6 +164,7 @@ set(UNLEASHED_RECOMP_INSTALL_CXX_SOURCES
|
|||
"install/installer.cpp"
|
||||
"install/iso_file_system.cpp"
|
||||
"install/memory_mapped_file.cpp"
|
||||
"install/update_checker.cpp"
|
||||
"install/xcontent_file_system.cpp"
|
||||
"install/xex_patcher.cpp"
|
||||
"install/hashes/apotos_shamar.cpp"
|
||||
|
@ -208,6 +209,7 @@ set(UNLEASHED_RECOMP_THIRDPARTY_INCLUDES
|
|||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/ddspp"
|
||||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/imgui"
|
||||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot"
|
||||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/json/include"
|
||||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/libmspack/libmspack/mspack"
|
||||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/magic_enum/include"
|
||||
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/stb"
|
||||
|
@ -321,6 +323,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|||
endif()
|
||||
|
||||
find_package(directx-dxc REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
if (UNLEASHED_RECOMP_D3D12)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
|
||||
|
@ -366,6 +369,7 @@ target_link_libraries(UnleashedRecomp PRIVATE
|
|||
tomlplusplus::tomlplusplus
|
||||
UnleashedRecompLib
|
||||
xxHash::xxhash
|
||||
CURL::libcurl
|
||||
)
|
||||
|
||||
target_include_directories(UnleashedRecomp PRIVATE
|
||||
|
|
|
@ -6094,9 +6094,7 @@ static void CompileParticleMaterialPipeline(const Hedgehog::Sparkle::CParticleMa
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
static std::thread::id g_mainThreadId = std::this_thread::get_id();
|
||||
#endif
|
||||
|
||||
// SWA::CGameModeStage::ExitLoading
|
||||
PPC_FUNC_IMPL(__imp__sub_825369A0);
|
||||
|
|
170
UnleashedRecomp/install/update_checker.cpp
Normal file
170
UnleashedRecomp/install/update_checker.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
#include "update_checker.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
// UpdateChecker
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
static const char *CHECK_URL = "https://api.github.com/repos/hedge-dev/UnleashedRecomp/releases/latest";
|
||||
static const char *VISIT_URL = "https://github.com/hedge-dev/UnleashedRecomp/releases/latest";
|
||||
static const char *USER_AGENT = "UnleashedRecomp-Agent";
|
||||
|
||||
static std::atomic<bool> g_updateCheckerInProgress = false;
|
||||
static std::atomic<bool> g_updateCheckerFinished = false;
|
||||
static UpdateChecker::Result g_updateCheckerResult = UpdateChecker::Result::NotStarted;
|
||||
|
||||
size_t updateCheckerWriteCallback(void *contents, size_t size, size_t nmemb, std::string *output)
|
||||
{
|
||||
size_t totalSize = size * nmemb;
|
||||
output->append((char *)contents, totalSize);
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
static bool parseVersion(const std::string &versionStr, int &major, int &minor, int &revision)
|
||||
{
|
||||
size_t start = 0;
|
||||
if (versionStr[0] == 'v')
|
||||
{
|
||||
start = 1;
|
||||
}
|
||||
|
||||
size_t firstDot = versionStr.find('.', start);
|
||||
size_t secondDot = versionStr.find('.', firstDot + 1);
|
||||
|
||||
if (firstDot == std::string::npos || secondDot == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
major = std::stoi(versionStr.substr(start, firstDot - start));
|
||||
minor = std::stoi(versionStr.substr(firstDot + 1, secondDot - firstDot - 1));
|
||||
revision = std::stoi(versionStr.substr(secondDot + 1));
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fmt::println("Error while parsing version: {}.", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateCheckerThread()
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
CURLcode res;
|
||||
int major, minor, revision;
|
||||
std::string response;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, CHECK_URL);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, updateCheckerWriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
|
||||
|
||||
res = curl_easy_perform(curl);
|
||||
if (res == CURLE_OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
json root = json::parse(response);
|
||||
auto tag_name_element = root.find("tag_name");
|
||||
if (tag_name_element != root.end() && tag_name_element->is_string())
|
||||
{
|
||||
if (parseVersion(*tag_name_element, major, minor, revision))
|
||||
{
|
||||
if ((g_versionMajor < major) || (g_versionMajor == major && g_versionMinor < minor) || (g_versionMajor == major && g_versionMinor == minor && g_versionRevision < revision))
|
||||
{
|
||||
g_updateCheckerResult = UpdateChecker::Result::UpdateAvailable;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_updateCheckerResult = UpdateChecker::Result::AlreadyUpToDate;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::println("Error while parsing response: tag_name does not contain a valid version string.");
|
||||
g_updateCheckerResult = UpdateChecker::Result::Failed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::println("Error while parsing response: tag_name not found or not the right type.");
|
||||
g_updateCheckerResult = UpdateChecker::Result::Failed;
|
||||
}
|
||||
}
|
||||
catch (const json::exception &e)
|
||||
{
|
||||
fmt::println("Error while parsing response: {}", e.what());
|
||||
g_updateCheckerResult = UpdateChecker::Result::Failed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::println("Error while performing request: {}", curl_easy_strerror(res));
|
||||
g_updateCheckerResult = UpdateChecker::Result::Failed;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
g_updateCheckerFinished = true;
|
||||
g_updateCheckerInProgress = false;
|
||||
}
|
||||
|
||||
void UpdateChecker::initialize()
|
||||
{
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
}
|
||||
|
||||
bool UpdateChecker::start()
|
||||
{
|
||||
if (g_updateCheckerInProgress)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
g_updateCheckerInProgress = true;
|
||||
g_updateCheckerFinished = false;
|
||||
std::thread thread(&updateCheckerThread);
|
||||
thread.detach();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UpdateChecker::Result UpdateChecker::check()
|
||||
{
|
||||
if (g_updateCheckerFinished)
|
||||
{
|
||||
return g_updateCheckerResult;
|
||||
}
|
||||
else if (g_updateCheckerInProgress)
|
||||
{
|
||||
return UpdateChecker::Result::InProgress;
|
||||
}
|
||||
else
|
||||
{
|
||||
return UpdateChecker::Result::NotStarted;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateChecker::visitWebsite()
|
||||
{
|
||||
#if defined(WIN32)
|
||||
ShellExecuteA(0, 0, VISIT_URL, 0, 0, SW_SHOW);
|
||||
#elif defined(__linux__)
|
||||
std::string command = "xdg-open " + std::string(VISIT_URL) + " &";
|
||||
std::system(command.c_str());
|
||||
#else
|
||||
static_assert(false, "Visit website not implemented for this platform.");
|
||||
#endif
|
||||
}
|
18
UnleashedRecomp/install/update_checker.h
Normal file
18
UnleashedRecomp/install/update_checker.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
struct UpdateChecker
|
||||
{
|
||||
enum class Result
|
||||
{
|
||||
NotStarted,
|
||||
InProgress,
|
||||
AlreadyUpToDate,
|
||||
UpdateAvailable,
|
||||
Failed
|
||||
};
|
||||
|
||||
static void initialize();
|
||||
static bool start();
|
||||
static Result check();
|
||||
static void visitWebsite();
|
||||
};
|
|
@ -377,10 +377,16 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo
|
|||
{ ELanguage::English, "The achievement data could not be loaded.\nYour achievements will not be saved." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Title_Message_UpdateAvailable",
|
||||
{
|
||||
{ ELanguage::English, "An update is available!\n\nWould you like to visit the\nreleases page to download it?" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Video_BackendError",
|
||||
{
|
||||
{ ELanguage::English, "Unable to create a D3D12 (Windows) or Vulkan backend.\n\nPlease make sure that:\n\n- Your system meets the minimum requirements.\n- Your GPU drivers are up to date.\n- Your operating system is on the latest version available." },
|
||||
{ ELanguage::English, "Unable to create a D3D12 (Windows) or Vulkan backend.\n\nPlease make sure that:\n\n- Your system meets the minimum requirements.\n- Your GPU drivers are up to date.\n- Your operating system is on the latest version available." }
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <user/registry.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <install/installer.h>
|
||||
#include <install/update_checker.h>
|
||||
#include <os/logger.h>
|
||||
#include <os/process.h>
|
||||
#include <os/registry.h>
|
||||
|
@ -22,6 +23,10 @@
|
|||
#include <ui/installer_wizard.h>
|
||||
#include <mod/mod_loader.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <timeapi.h>
|
||||
#endif
|
||||
|
||||
const size_t XMAIOBegin = 0x7FEA0000;
|
||||
const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF;
|
||||
|
||||
|
@ -173,6 +178,18 @@ int main(int argc, char *argv[])
|
|||
|
||||
Config::Load();
|
||||
|
||||
// Check the time since the last time an update was checked. Store the new time if the difference is more than six hours.
|
||||
constexpr double TimeBetweenUpdateChecksInSeconds = 6 * 60 * 60;
|
||||
time_t timeNow = std::time(nullptr);
|
||||
double timeDifferenceSeconds = difftime(timeNow, Config::LastChecked);
|
||||
if (timeDifferenceSeconds > TimeBetweenUpdateChecksInSeconds)
|
||||
{
|
||||
UpdateChecker::initialize();
|
||||
UpdateChecker::start();
|
||||
Config::LastChecked = timeNow;
|
||||
Config::Save();
|
||||
}
|
||||
|
||||
if (Config::ShowConsole)
|
||||
os::process::ShowConsole();
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "CTitleStateIntro_patches.h"
|
||||
#include <api/SWA.h>
|
||||
#include <install/update_checker.h>
|
||||
#include <locale/locale.h>
|
||||
#include <ui/fader.h>
|
||||
#include <ui/message_window.h>
|
||||
|
@ -7,8 +8,9 @@
|
|||
#include <user/paths.h>
|
||||
#include <app.h>
|
||||
|
||||
static bool g_faderBegun = false;
|
||||
|
||||
bool g_quitMessageOpen = false;
|
||||
static bool g_quitMessageFaderBegun = false;
|
||||
static int g_quitMessageResult = -1;
|
||||
|
||||
static std::atomic<bool> g_corruptSaveMessageOpen = false;
|
||||
|
@ -17,6 +19,9 @@ static int g_corruptSaveMessageResult = -1;
|
|||
static bool g_corruptAchievementsMessageOpen = false;
|
||||
static int g_corruptAchievementsMessageResult = -1;
|
||||
|
||||
static bool g_updateAvailableMessageOpen = false;
|
||||
static int g_updateAvailableMessageResult = -1;
|
||||
|
||||
static bool ProcessQuitMessage()
|
||||
{
|
||||
if (g_corruptSaveMessageOpen)
|
||||
|
@ -25,7 +30,7 @@ static bool ProcessQuitMessage()
|
|||
if (!g_quitMessageOpen)
|
||||
return false;
|
||||
|
||||
if (g_quitMessageFaderBegun)
|
||||
if (g_faderBegun)
|
||||
return true;
|
||||
|
||||
std::array<std::string, 2> options = { Localise("Common_Yes"), Localise("Common_No") };
|
||||
|
@ -36,7 +41,7 @@ static bool ProcessQuitMessage()
|
|||
{
|
||||
case 0:
|
||||
Fader::FadeOut(1, []() { App::Exit(); });
|
||||
g_quitMessageFaderBegun = true;
|
||||
g_faderBegun = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
@ -87,13 +92,40 @@ static bool ProcessCorruptAchievementsMessage()
|
|||
return true;
|
||||
}
|
||||
|
||||
void StorageDevicePromptMidAsmHook()
|
||||
static bool ProcessUpdateAvailableMessage()
|
||||
{
|
||||
AchievementManager::Load();
|
||||
if (!g_updateAvailableMessageOpen)
|
||||
return false;
|
||||
|
||||
if (AchievementManager::Status != EAchStatus::Success)
|
||||
g_corruptAchievementsMessageOpen = true;
|
||||
if (g_faderBegun)
|
||||
return true;
|
||||
|
||||
std::array<std::string, 2> options = { Localise("Common_Yes"), Localise("Common_No") };
|
||||
|
||||
if (MessageWindow::Open(Localise("Title_Message_UpdateAvailable"), &g_updateAvailableMessageResult, options) == MSG_CLOSED)
|
||||
{
|
||||
if (!g_updateAvailableMessageResult)
|
||||
{
|
||||
Fader::FadeOut(1,
|
||||
//
|
||||
[]()
|
||||
{
|
||||
UpdateChecker::visitWebsite();
|
||||
App::Exit();
|
||||
}
|
||||
);
|
||||
|
||||
g_faderBegun = true;
|
||||
}
|
||||
|
||||
g_updateAvailableMessageOpen = false;
|
||||
g_updateAvailableMessageResult = -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StorageDevicePromptMidAsmHook() {}
|
||||
|
||||
// Save data validation hook.
|
||||
PPC_FUNC_IMPL(__imp__sub_822C55B0);
|
||||
|
@ -115,12 +147,27 @@ PPC_FUNC(sub_82587E50)
|
|||
{
|
||||
__imp__sub_82587E50(ctx, base);
|
||||
}
|
||||
else if (!ProcessCorruptSaveMessage() && !ProcessCorruptAchievementsMessage())
|
||||
else if (!ProcessUpdateAvailableMessage() && !ProcessCorruptSaveMessage() && !ProcessCorruptAchievementsMessage() && !g_faderBegun)
|
||||
{
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
if (auto pInputState = SWA::CInputState::GetInstance())
|
||||
{
|
||||
auto& rPadState = pInputState->GetPadState();
|
||||
auto isAccepted = rPadState.IsTapped(SWA::eKeyState_A) || rPadState.IsTapped(SWA::eKeyState_Start);
|
||||
auto isDeclined = rPadState.IsTapped(SWA::eKeyState_B);
|
||||
|
||||
if (pInputState && pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
if (isAccepted)
|
||||
{
|
||||
g_updateAvailableMessageOpen = UpdateChecker::check() == UpdateChecker::Result::UpdateAvailable;
|
||||
|
||||
AchievementManager::Load();
|
||||
|
||||
if (AchievementManager::Status != EAchStatus::Success)
|
||||
g_corruptAchievementsMessageOpen = true;
|
||||
}
|
||||
|
||||
if (isDeclined)
|
||||
g_quitMessageOpen = true;
|
||||
}
|
||||
|
||||
if (!ProcessQuitMessage())
|
||||
__imp__sub_82587E50(ctx, base);
|
||||
|
|
|
@ -108,3 +108,5 @@ CONFIG_DEFINE_HIDDEN("Exports", bool, HUDToggleHotkey, false);
|
|||
CONFIG_DEFINE_HIDDEN("Exports", bool, SaveScoreAtCheckpoints, false);
|
||||
CONFIG_DEFINE_HIDDEN("Exports", bool, SkipIntroLogos, false);
|
||||
CONFIG_DEFINE_HIDDEN("Exports", bool, UseOfficialTitleOnTitleBar, false);
|
||||
|
||||
CONFIG_DEFINE("Update", time_t, LastChecked, 0);
|
||||
|
|
1
thirdparty/json
vendored
Submodule
1
thirdparty/json
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 606b6347edf0758c531abb6c36743e09a4c48a84
|
|
@ -10,6 +10,7 @@
|
|||
"platform": "windows"
|
||||
},
|
||||
"directx-dxc",
|
||||
"freetype"
|
||||
"freetype",
|
||||
"curl"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue