Update checker.

This commit is contained in:
Dario 2025-01-30 21:12:11 -03:00
parent 21c1d36836
commit 4c1aa74101
8 changed files with 218 additions and 1 deletions

9
.gitmodules vendored
View file

@ -61,3 +61,12 @@
[submodule "thirdparty/implot"] [submodule "thirdparty/implot"]
path = thirdparty/implot path = thirdparty/implot
url = https://github.com/epezent/implot.git url = https://github.com/epezent/implot.git
[submodule "thirdparty/json"]
path = thirdparty/json
url = https://github.com/nlohmann/json
[submodule "thirdparty/cpp-httplib"]
path = thirdparty/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "thirdparty/curl"]
path = thirdparty/curl
url = https://github.com/curl/curl

View file

@ -208,6 +208,7 @@ set(UNLEASHED_RECOMP_THIRDPARTY_INCLUDES
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/ddspp" "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/ddspp"
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/imgui" "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/imgui"
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot" "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot"
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/json/include"
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/libmspack/libmspack/mspack" "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/libmspack/libmspack/mspack"
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/magic_enum/include" "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/magic_enum/include"
"${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/stb" "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/stb"
@ -321,6 +322,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux")
endif() endif()
find_package(directx-dxc REQUIRED) find_package(directx-dxc REQUIRED)
find_package(CURL REQUIRED)
if (UNLEASHED_RECOMP_D3D12) if (UNLEASHED_RECOMP_D3D12)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
@ -366,6 +368,7 @@ target_link_libraries(UnleashedRecomp PRIVATE
tomlplusplus::tomlplusplus tomlplusplus::tomlplusplus
UnleashedRecompLib UnleashedRecompLib
xxHash::xxhash xxHash::xxhash
CURL::libcurl
) )
target_include_directories(UnleashedRecomp PRIVATE target_include_directories(UnleashedRecomp PRIVATE

View file

@ -0,0 +1,166 @@
#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/blueskythlikesclouds/MikuMikuLibrary/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::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;
g_updateCheckerResult = InProgress;
std::thread thread(&updateCheckerThread);
thread.detach();
return true;
}
UpdateChecker::Result UpdateChecker::check()
{
if (g_updateCheckerFinished)
{
return g_updateCheckerResult;
}
else if (g_updateCheckerInProgress)
{
return UpdateChecker::InProgress;
}
else
{
return UpdateChecker::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
}

View file

@ -0,0 +1,18 @@
#pragma once
struct UpdateChecker
{
enum Result
{
NotStarted,
InProgress,
AlreadyUpToDate,
UpdateAvailable,
Failed
};
static void initialize();
static bool start();
static Result check();
static void visitWebsite();
};

View file

@ -15,12 +15,17 @@
#include <user/registry.h> #include <user/registry.h>
#include <kernel/xdbf.h> #include <kernel/xdbf.h>
#include <install/installer.h> #include <install/installer.h>
#include <install/update_checker.h>
#include <os/logger.h> #include <os/logger.h>
#include <os/process.h> #include <os/process.h>
#include <os/registry.h> #include <os/registry.h>
#include <ui/installer_wizard.h> #include <ui/installer_wizard.h>
#include <mod/mod_loader.h> #include <mod/mod_loader.h>
#ifdef _WIN32
#include <timeapi.h>
#endif
const size_t XMAIOBegin = 0x7FEA0000; const size_t XMAIOBegin = 0x7FEA0000;
const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF; const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF;
@ -172,6 +177,18 @@ int main(int argc, char *argv[])
Config::Load(); 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) if (Config::ShowConsole)
os::process::ShowConsole(); os::process::ShowConsole();

View file

@ -85,3 +85,5 @@ CONFIG_DEFINE_HIDDEN("Exports", bool, SaveScoreAtCheckpoints, false);
CONFIG_DEFINE_HIDDEN("Exports", bool, SkipIntroLogos, false); CONFIG_DEFINE_HIDDEN("Exports", bool, SkipIntroLogos, false);
CONFIG_DEFINE_HIDDEN("Exports", bool, UseOfficialTitleOnTitleBar, false); CONFIG_DEFINE_HIDDEN("Exports", bool, UseOfficialTitleOnTitleBar, false);
CONFIG_DEFINE_HIDDEN("Exports", bool, HUDToggleHotkey, false); CONFIG_DEFINE_HIDDEN("Exports", bool, HUDToggleHotkey, false);
CONFIG_DEFINE("Update", time_t, LastChecked, 0);

1
thirdparty/json vendored Submodule

@ -0,0 +1 @@
Subproject commit 606b6347edf0758c531abb6c36743e09a4c48a84

View file

@ -10,6 +10,7 @@
"platform": "windows" "platform": "windows"
}, },
"directx-dxc", "directx-dxc",
"freetype" "freetype",
"curl"
] ]
} }