From 00fee9c4109a6d8c714864974ed80dfb7af46cc8 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Fri, 3 Jan 2025 15:03:30 -0500 Subject: [PATCH] Basic AppImage Updater --- CMakeLists.txt | 6 +- launcher/src/client/ui.cpp | 2 + launcher/src/main.cpp | 23 ++++++ launcher/src/options/option-list.h | 3 +- launcher/src/updater/appimage.cpp | 122 +++++++++++++++++++++++++++++ launcher/src/updater/updater.cpp | 2 + launcher/src/updater/updater.h | 3 +- libreborn/src/util/exec.cpp | 2 +- scripts/lib/util.mjs | 10 ++- 9 files changed, 166 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bc983c74..c23d11753 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,9 @@ if(BUILD_ARM_COMPONENTS) endif() cmake_language(EVAL CODE "${COMPILE_FLAGS_SETUP}") +# Build Dependencies +add_subdirectory(dependencies) + # Fast Math add_compile_options(-ffast-math) @@ -91,9 +94,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU") endif() endif() -# Build Dependencies -add_subdirectory(dependencies) - # Build libreborn add_subdirectory(libreborn) diff --git a/launcher/src/client/ui.cpp b/launcher/src/client/ui.cpp index 8b9806639..d32da9249 100644 --- a/launcher/src/client/ui.cpp +++ b/launcher/src/client/ui.cpp @@ -291,10 +291,12 @@ void ConfigurationUI::draw_about() { Updater *updater = Updater::instance; if (updater) { ImGui::Separator(); + ImGui::BeginDisabled(!updater->can_start()); draw_right_aligned_buttons({updater->get_status().c_str()}, [&updater](__attribute__((unused)) int id, const bool was_clicked) { if (was_clicked) { updater->start(); } }, true); + ImGui::EndDisabled(); } } diff --git a/launcher/src/main.cpp b/launcher/src/main.cpp index 467ffa7eb..a94af371b 100644 --- a/launcher/src/main.cpp +++ b/launcher/src/main.cpp @@ -9,6 +9,7 @@ #include "logger/logger.h" #include "util/util.h" #include "client/configuration.h" +#include "updater/updater.h" // Bind Options To Environmental Variable static void bind_to_env(const char *env, const bool value) { @@ -39,12 +40,34 @@ static void setup_environment(const options_t &options) { // Non-Launch Commands static void handle_non_launch_commands(const options_t &options) { + // SDK if (options.copy_sdk) { const std::string binary_directory = get_binary_directory(); copy_sdk(binary_directory, true); fflush(stdout); exit(EXIT_SUCCESS); } + // Updater + if (options.run_update) { + Updater *updater = Updater::instance; + if (updater) { + updater->update(); + if (updater->status == ERROR) { + ERR("Unable To Update"); + } else if (updater->status == UP_TO_DATE) { + INFO("Already Up-To-Date"); + } else { + if (updater->status != RESTART_NEEDED) { + IMPOSSIBLE(); + } + INFO("Update Completed"); + } + } else { + ERR("Built-In Updater Unavailable, Use System Package Manager"); + } + fflush(stdout); + exit(EXIT_SUCCESS); + } } // Start The Game diff --git a/launcher/src/options/option-list.h b/launcher/src/options/option-list.h index df7636f50..7e05a5832 100644 --- a/launcher/src/options/option-list.h +++ b/launcher/src/options/option-list.h @@ -10,4 +10,5 @@ OPTION(only_generate, "only-generate", -8, "Generate World And Exit (Server-Mode OPTION(force_headless, "force-headless", -9, "Force Disable Game Rendering") OPTION(force_non_headless, "force-non-headless", -10, "Force Enable Game Rendering") OPTION(server_mode, "server", -11, "Run In Server-Mode") -OPTION(skip_pagesize_check, "skip-pagesize-check", -12, "Skip Page-Size Check (Not Recommended)") \ No newline at end of file +OPTION(skip_pagesize_check, "skip-pagesize-check", -12, "Skip Page-Size Check (Not Recommended)") +OPTION(run_update, "update", -13, "Run Updater (If Available)") \ No newline at end of file diff --git a/launcher/src/updater/appimage.cpp b/launcher/src/updater/appimage.cpp index e69de29bb..1f86f3fb8 100644 --- a/launcher/src/updater/appimage.cpp +++ b/launcher/src/updater/appimage.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "updater.h" + +// Implement +struct AppImageUpdater final : Updater { + void update() override; + void restart() override; + static AppImageUpdater instance; +}; +AppImageUpdater AppImageUpdater::instance; + +// Update +template +static std::optional run_wget(Args... args) { + int status = 0; + const char *const command[] = {"wget", "-O", std::forward(args)..., nullptr}; + const std::vector *output = run_command(command, &status); + std::string output_str = (const char *) output->data(); + delete output; + if (!is_exit_status_success(status)) { + return std::nullopt; + } else { + return output_str; + } +} +static std::string extract_from_json(const std::string &json_str, const std::string &key) { + std::string::size_type pos = json_str.find(key); + std::array indices = {}; + unsigned int i = 0; + while (true) { + if (pos == std::string::npos) { + return ""; + } + if (i >= indices.size()) { + break; + } + pos = json_str.find('"', pos) + 1; + indices[i++] = pos; + } + const std::string::size_type start = indices[1]; + const std::string::size_type end = indices[2]; + return json_str.substr(start, end - start - 1); +} +static const char *get_appimage_path() { + const char *path = getenv("APPIMAGE"); + if (path == nullptr) { + IMPOSSIBLE(); + } + return path; +} +void AppImageUpdater::update() { + // Check + if (status != CHECKING) { + IMPOSSIBLE(); + } + const std::optional json = run_wget("-", MCPI_APPIMAGE_JSON_URL); + if (!json.has_value()) { + status = ERROR; + return; + } + const std::string tag_name = extract_from_json(json.value(), "tag_name"); + + // Check Version + if (tag_name == MCPI_VERSION) { + status = UP_TO_DATE; + return; + } + + // Get URL + std::string url = MCPI_APPIMAGE_DOWNLOAD_URL; + while (true) { + const std::string placeholder = MCPI_APPIMAGE_VERSION_PLACEHOLDER; + const std::string::size_type pos = url.find(placeholder); + if (pos == std::string::npos) { + break; + } + url.replace(pos, placeholder.size(), tag_name); + } + + // Get Path + const char *appimage_path = get_appimage_path(); + const std::string new_appimage_path_base = std::string(appimage_path) + ".new"; + std::string new_appimage_path = new_appimage_path_base; + int num = 1; + while (access(new_appimage_path.c_str(), F_OK) != -1) { + new_appimage_path = new_appimage_path_base + '.' + std::to_string(num++); + } + + // Download + status = DOWNLOADING; + const std::optional out = run_wget(new_appimage_path.c_str(), url.c_str()); + bool ret = out.has_value(); + if (ret) { + ret = chmod(new_appimage_path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0; + } + if (ret) { + ret = rename(new_appimage_path.c_str(), appimage_path) == 0; + } + if (!ret) { + unlink(new_appimage_path.c_str()); + status = ERROR; + return; + } + + // Done + status = RESTART_NEEDED; +} + +// Restart +void AppImageUpdater::restart() { + const char *const command[] = {get_appimage_path(), nullptr}; + safe_execvpe(command, environ); +} \ No newline at end of file diff --git a/launcher/src/updater/updater.cpp b/launcher/src/updater/updater.cpp index e4e7b362e..1095771bf 100644 --- a/launcher/src/updater/updater.cpp +++ b/launcher/src/updater/updater.cpp @@ -20,6 +20,7 @@ std::string Updater::get_status() const { case CHECKING: return "Checking..."; case UP_TO_DATE: return "Up-To-Date"; case DOWNLOADING: return "Downloading..."; + case ERROR: return "Error"; default: return ""; } } @@ -33,6 +34,7 @@ static void *update_thread(void *data) { void Updater::start() { switch (status) { case NOT_STARTED: { + status = CHECKING; pthread_t thread; pthread_create(&thread, nullptr, update_thread, this); break; diff --git a/launcher/src/updater/updater.h b/launcher/src/updater/updater.h index ebbd57137..bd8c966cd 100644 --- a/launcher/src/updater/updater.h +++ b/launcher/src/updater/updater.h @@ -8,7 +8,8 @@ enum UpdateStatus { CHECKING, UP_TO_DATE, DOWNLOADING, - RESTART_NEEDED + RESTART_NEEDED, + ERROR }; // Updater diff --git a/libreborn/src/util/exec.cpp b/libreborn/src/util/exec.cpp index 0afebb682..d0758a43c 100644 --- a/libreborn/src/util/exec.cpp +++ b/libreborn/src/util/exec.cpp @@ -142,7 +142,7 @@ std::vector *run_command(const char *const command[], int *exit_s // Child Process reborn_debug_tag = CHILD_PROCESS_TAG; // Run - safe_execvpe(command, (const char *const *) environ); + safe_execvpe(command, environ); } else { // Read stdout std::vector *output = new std::vector; diff --git a/scripts/lib/util.mjs b/scripts/lib/util.mjs index 2fb743831..75c9d2e48 100644 --- a/scripts/lib/util.mjs +++ b/scripts/lib/util.mjs @@ -49,7 +49,15 @@ export function getDebianVersion() { // Make File Executable export function makeExecutable(path) { - fs.chmodSync(path, 0o755); + fs.chmodSync(path, + fs.constants.S_IRUSR | + fs.constants.S_IWUSR | + fs.constants.S_IXUSR | + fs.constants.S_IRGRP | + fs.constants.S_IXGRP | + fs.constants.S_IROTH | + fs.constants.S_IXOTH + ); } // Get Scripts Directory