From c5f9a519c5bf6b93d6d6ad113e69501af44e3453 Mon Sep 17 00:00:00 2001
From: TheBrokenRail <connor24nolan@live.com>
Date: Fri, 3 Jan 2025 05:36:28 -0500
Subject: [PATCH] Basic About

---
 LICENSE                                 |  2 +-
 cmake/cpack/appimage.cmake              | 10 ++---
 cmake/cpack/packaging.cmake             | 31 ++++++---------
 cmake/options/appimage.cmake            |  0
 cmake/options/extra-options.cmake       | 46 +++++++++++++++++++----
 cmake/options/paths.cmake               |  2 +-
 dependencies/imgui/src                  |  2 +-
 launcher/CMakeLists.txt                 | 17 +++++----
 launcher/src/bootstrap/debug.cpp        | 17 +++------
 launcher/src/client/configuration.h     |  4 ++
 launcher/src/client/ui.cpp              | 50 +++++++++++++++++++++++++
 launcher/src/ui/frame.cpp               | 11 +++++-
 launcher/src/ui/frame.h                 |  2 +-
 launcher/src/updater/appimage.cpp       |  0
 launcher/src/updater/updater.cpp        | 46 +++++++++++++++++++++++
 launcher/src/updater/updater.h          | 30 +++++++++++++++
 libreborn/include/libreborn/config.h.in | 35 +++++++++++++----
 mods/include/mods/options/info.h        |  5 ---
 mods/src/options/info.cpp               |  7 +---
 mods/src/title-screen/welcome.cpp       |  5 +--
 20 files changed, 246 insertions(+), 76 deletions(-)
 create mode 100644 cmake/options/appimage.cmake
 create mode 100644 launcher/src/updater/appimage.cpp
 create mode 100644 launcher/src/updater/updater.cpp
 create mode 100644 launcher/src/updater/updater.h
 delete mode 100644 mods/include/mods/options/info.h

diff --git a/LICENSE b/LICENSE
index 938a0d81..a22ea141 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2024 TheBrokenRail
+Copyright (c) 2025 TheBrokenRail
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/cmake/cpack/appimage.cmake b/cmake/cpack/appimage.cmake
index fb4eaa11..1627104f 100644
--- a/cmake/cpack/appimage.cmake
+++ b/cmake/cpack/appimage.cmake
@@ -35,17 +35,17 @@ execute_process(
         "${CMAKE_COMMAND}" "-E" "env"
         "ARCH=${APPIMAGE_ARCH}"
         "appimagetool"
-        "--updateinformation" "zsync|https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/releases/download/latest/${CPACK_PACKAGE_FILE_NAME_ZSYNC}.AppImage.zsync"
+        "--updateinformation" "zsync|${CPACK_MCPI_REPO}/releases/download/latest/${CPACK_PACKAGE_FILE_NAME_ZSYNC}${CPACK_MCPI_APPIMAGE_ZSYNC_EXT}"
         "--runtime-file" "${RUNTIME}"
         "--comp" "xz"
         "${CPACK_TEMPORARY_DIRECTORY}"
-        "${CPACK_PACKAGE_FILE_NAME}.AppImage"
+        "${CPACK_PACKAGE_FILE_NAME}${CPACK_MCPI_APPIMAGE_EXT}"
     WORKING_DIRECTORY "${CPACK_PACKAGE_DIRECTORY}"
     COMMAND_ERROR_IS_FATAL ANY
 )
 
 # Rename ZSync File
-file(RENAME "${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}.AppImage.zsync" "${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME_ZSYNC}.AppImage.zsync")
+file(RENAME "${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}${CPACK_MCPI_APPIMAGE_ZSYNC_EXT}" "${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME_ZSYNC}${CPACK_MCPI_APPIMAGE_ZSYNC_EXT}")
 
 # Summary Message
 function(check_file name)
@@ -55,5 +55,5 @@ function(check_file name)
         message(FATAL_ERROR "Missing File: ${name}")
     endif()
 endfunction()
-check_file("${CPACK_PACKAGE_FILE_NAME}.AppImage")
-check_file("${CPACK_PACKAGE_FILE_NAME_ZSYNC}.AppImage.zsync")
+check_file("${CPACK_PACKAGE_FILE_NAME}${CPACK_MCPI_APPIMAGE_EXT}")
+check_file("${CPACK_PACKAGE_FILE_NAME_ZSYNC}${CPACK_MCPI_APPIMAGE_ZSYNC_EXT}")
diff --git a/cmake/cpack/packaging.cmake b/cmake/cpack/packaging.cmake
index e1de6aa7..8fbb5bab 100644
--- a/cmake/cpack/packaging.cmake
+++ b/cmake/cpack/packaging.cmake
@@ -1,25 +1,10 @@
-# Determine Architecture
-set(CPACK_MCPI_ARCH "unknown")
-include(CheckSymbolExists)
-function(check_arch symbol name)
-    set(CMAKE_REQUIRED_QUIET TRUE)
-    check_symbol_exists("${symbol}" "" "IS_ARCH_${name}")
-    unset(CMAKE_REQUIRED_QUIET)
-    if("${IS_ARCH_${name}}")
-        set(CPACK_MCPI_ARCH "${name}" PARENT_SCOPE)
-    endif()
-endfunction()
-check_arch("__arm__" "armhf")
-check_arch("__aarch64__" "arm64")
-check_arch("__x86_64__" "amd64")
-
 # CPack
-set(CPACK_PACKAGE_NAME "${MCPI_VARIANT_NAME}")
-set(CPACK_PACKAGE_VENDOR "TheBrokenRail & Mojang AB")
+set(CPACK_PACKAGE_NAME "${MCPI_APP_NAME}")
+set(CPACK_PACKAGE_VENDOR "${MCPI_AUTHOR} & Mojang AB")
 set(CPACK_VERBATIM_VARIABLES TRUE)
 set(CPACK_MONOLITHIC_INSTALL TRUE)
-set(CPACK_PACKAGE_FILE_NAME "${MCPI_VARIANT_NAME}-${MCPI_VERSION}-${CPACK_MCPI_ARCH}")
-set(CPACK_PACKAGE_FILE_NAME_ZSYNC "${MCPI_VARIANT_NAME}-latest-${CPACK_MCPI_ARCH}")
+get_package_file_name(CPACK_PACKAGE_FILE_NAME "${MCPI_VERSION}")
+get_package_file_name(CPACK_PACKAGE_FILE_NAME_ZSYNC "latest")
 
 # Version
 string(REPLACE "." ";" VERSION_LIST "${MCPI_VERSION}")
@@ -32,6 +17,14 @@ if(MCPI_IS_APPIMAGE_BUILD)
     set(CPACK_GENERATOR "External")
     set(CPACK_EXTERNAL_ENABLE_STAGING TRUE)
     set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/appimage.cmake")
+    # Pass Variable To CPack
+    macro(pass_to_cpack var)
+        set("CPACK_MCPI_${var}" "${MCPI_${var}}")
+    endmacro()
+    pass_to_cpack(ARCH)
+    pass_to_cpack(REPO)
+    pass_to_cpack(APPIMAGE_EXT)
+    pass_to_cpack(APPIMAGE_ZSYNC_EXT)
 endif()
 
 # Package
diff --git a/cmake/options/appimage.cmake b/cmake/options/appimage.cmake
new file mode 100644
index 00000000..e69de29b
diff --git a/cmake/options/extra-options.cmake b/cmake/options/extra-options.cmake
index 74d0b46e..6cfd6770 100644
--- a/cmake/options/extra-options.cmake
+++ b/cmake/options/extra-options.cmake
@@ -29,13 +29,9 @@ else()
     set(BUILD_MEDIA_LAYER_CORE "${BUILD_ARM_COMPONENTS}")
 endif()
 
-# Specify Variant Name
-set(MCPI_VARIANT_NAME "minecraft-pi-reborn")
-
-# App ID
+# App Information
+mcpi_option(APP_NAME "App Name" STRING "minecraft-pi-reborn")
 mcpi_option(APP_ID "App ID" STRING "com.thebrokenrail.MCPIReborn")
-
-# App Title
 mcpi_option(APP_TITLE "App Title" STRING "Minecraft: Pi Edition: Reborn")
 
 # Skin Server
@@ -53,5 +49,41 @@ set_property(
 file(STRINGS "${CMAKE_CURRENT_LIST_DIR}/../../VERSION" MCPI_VERSION)
 file(TIMESTAMP "${CMAKE_CURRENT_LIST_DIR}/../../VERSION" MCPI_VERSION_DATE "%Y-%m-%d" UTC)
 
+# Author
+mcpi_option(AUTHOR "Author" STRING "TheBrokenRail")
+
+# Homepage
+mcpi_option(REPO_HOST "Repository Host" STRING "https://gitea.thebrokenrail.com")
+mcpi_option(REPO_PATH "Repository Path" STRING "minecraft-pi-reborn/minecraft-pi-reborn")
+mcpi_option(REPO "Repository URL" STRING "${MCPI_REPO_HOST}/${MCPI_REPO_PATH}")
+
 # Documentation URL
-mcpi_option(DOCUMENTATION "Documentation URL" STRING "https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/tag/${MCPI_VERSION}/docs/")
\ No newline at end of file
+mcpi_option(DOCS "Documentation URL" STRING "${MCPI_REPO}/src/tag/${MCPI_VERSION}/docs/")
+
+# Packaging
+set(MCPI_ARCH "unknown")
+include(CheckSymbolExists)
+function(check_arch symbol name)
+    set(CMAKE_REQUIRED_QUIET TRUE)
+    check_symbol_exists("${symbol}" "" "IS_ARCH_${name}")
+    unset(CMAKE_REQUIRED_QUIET)
+    if("${IS_ARCH_${name}}")
+        set(MCPI_ARCH "${name}" PARENT_SCOPE)
+    endif()
+endfunction()
+check_arch("__arm__" "armhf")
+check_arch("__aarch64__" "arm64")
+check_arch("__x86_64__" "amd64")
+macro(get_package_file_name out version)
+    set("${out}" "${MCPI_APP_NAME}-${version}-${MCPI_ARCH}")
+endmacro()
+
+# AppImage
+if(MCPI_IS_APPIMAGE_BUILD)
+    mcpi_option(APPIMAGE_EXT "AppImage Extension" STRING ".AppImage")
+    mcpi_option(APPIMAGE_ZSYNC_EXT "AppImage Update Extension" STRING "${MCPI_APPIMAGE_EXT}.zsync")
+    mcpi_option(APPIMAGE_JSON_URL "AppImage Update Checker URL" STRING "${MCPI_REPO_HOST}/api/v1/repos/${MCPI_REPO_PATH}/releases/latest")
+    mcpi_option(APPIMAGE_VERSION_PLACEHOLDER "Version Placeholder In AppImage Download URL" STRING "%VERSION%")
+    get_package_file_name(appimage_package_file_name "${MCPI_APPIMAGE_VERSION_PLACEHOLDER}")
+    mcpi_option(APPIMAGE_DOWNLOAD_URL "AppImage Download URL" STRING "${MCPI_REPO}/releases/download/${MCPI_APPIMAGE_VERSION_PLACEHOLDER}/${appimage_package_file_name}${MCPI_APPIMAGE_EXT}")
+endif()
\ No newline at end of file
diff --git a/cmake/options/paths.cmake b/cmake/options/paths.cmake
index eaf05cf1..7e796f8a 100644
--- a/cmake/options/paths.cmake
+++ b/cmake/options/paths.cmake
@@ -1,5 +1,5 @@
 # Specify Installation Paths
-set(MCPI_INSTALL_DIR "lib/${MCPI_VARIANT_NAME}")
+set(MCPI_INSTALL_DIR "lib/${MCPI_APP_NAME}")
 set(MCPI_BIN_DIR "${MCPI_INSTALL_DIR}/bin")
 set(MCPI_LEGAL_DIR "${MCPI_INSTALL_DIR}/legal") # For Software Licenses
 set(MCPI_SDK_DIR "${MCPI_INSTALL_DIR}/sdk")
diff --git a/dependencies/imgui/src b/dependencies/imgui/src
index f5f11e94..6982ce43 160000
--- a/dependencies/imgui/src
+++ b/dependencies/imgui/src
@@ -1 +1 @@
-Subproject commit f5f11e94be35078c3bbb5196f55269f88634b9bd
+Subproject commit 6982ce43f5b143c5dce5fab0ce07dd4867b705ae
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 181f8d53..fffca1aa 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -19,6 +19,7 @@ add_executable(launcher
     src/client/configuration.cpp
     src/client/cache.cpp
     src/client/ui.cpp
+    src/updater/updater.cpp
 )
 target_link_libraries(launcher
     reborn-util
@@ -34,7 +35,7 @@ target_compile_definitions(launcher PRIVATE _FILE_OFFSET_BITS=64)
 
 # Install
 install(TARGETS launcher DESTINATION "${MCPI_INSTALL_DIR}")
-install_symlink("../${MCPI_INSTALL_DIR}/launcher" "bin/${MCPI_VARIANT_NAME}")
+install_symlink("../${MCPI_INSTALL_DIR}/launcher" "bin/${MCPI_APP_NAME}")
 
 # Install Desktop Entry
 file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
@@ -42,7 +43,7 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
     "Name=${MCPI_APP_TITLE}\n"
     "Comment=Fun with Blocks\n"
     "Icon=${MCPI_APP_ID}\n"
-    "Exec=${MCPI_VARIANT_NAME}\n"
+    "Exec=${MCPI_APP_NAME}\n"
     "Type=Application\n"
     "Categories=Game;\n"
 )
@@ -69,13 +70,13 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml"
     "        <p>Minecraft: Pi Edition Modding Project.</p>\n"
     "        <p>NOTE: This is not verified by, affiliated with, or supported by Mojang or Microsoft.</p>\n"
     "    </description>\n"
-    "    <url type=\"homepage\">https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn</url>\n"
+    "    <url type=\"homepage\">${MCPI_REPO}</url>\n"
     "    <launchable type=\"desktop-id\">${MCPI_APP_ID}.desktop</launchable>\n"
     "    <provides>\n"
-    "        <id>com.thebrokenrail.MCPIRebornClient.desktop</id>\n"
+    "        <id>${MCPI_APP_ID}.desktop</id>\n"
     "    </provides>\n"
     "    <project_license>LicenseRef-proprietary</project_license>\n"
-    "    <developer_name>TheBrokenRail &amp; Mojang AB</developer_name>\n"
+    "    <developer_name>${MCPI_AUTHOR} &amp; Mojang AB</developer_name>\n"
     "    <content_rating type=\"oars-1.0\">\n"
     "        <content_attribute id=\"violence-cartoon\">moderate</content_attribute>\n"
     "        <content_attribute id=\"violence-fantasy\">none</content_attribute>\n"
@@ -103,7 +104,7 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml"
     "    </releases>\n"
     "    <screenshots>\n"
     "        <screenshot type=\"default\">\n"
-    "            <image>https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn/raw/branch/master/images/start.png</image>\n"
+    "            <image>${MCPI_REPO}/raw/branch/master/images/start.png</image>\n"
     "        </screenshot>\n"
     "    </screenshots>\n"
     "</component>\n"
@@ -116,6 +117,8 @@ install(
 
 # AppImage
 if(MCPI_IS_APPIMAGE_BUILD)
-    install_symlink("bin/${MCPI_VARIANT_NAME}" "AppRun")
+    install_symlink("bin/${MCPI_APP_NAME}" "AppRun")
     install_symlink("${MCPI_SHARE_DIR}/applications/${MCPI_APP_ID}.desktop" "${MCPI_APP_ID}.desktop")
+    # Updater
+    target_sources(launcher PRIVATE src/updater/appimage.cpp)
 endif()
diff --git a/launcher/src/bootstrap/debug.cpp b/launcher/src/bootstrap/debug.cpp
index 06cdfe1e..f81d8a57 100644
--- a/launcher/src/bootstrap/debug.cpp
+++ b/launcher/src/bootstrap/debug.cpp
@@ -30,16 +30,9 @@ void print_debug_information() {
     DEBUG("Reborn Version: v%s", MCPI_VERSION);
 
     // Architecture
-    const char *arch =
-#ifdef __x86_64__
-        "AMD64"
-#elif defined(__aarch64__)
-        "ARM64"
-#elif defined(__arm__)
-        "ARM32"
-#else
-        "Unknown"
-#endif
-        ;
-    DEBUG("Reborn Target Architecture: %s", arch);
+    std::string arch = MCPI_ARCH;
+    for (char &c : arch) {
+        c = char(std::toupper(c));
+    }
+    DEBUG("Reborn Target Architecture: %s", arch.c_str());
 }
\ No newline at end of file
diff --git a/launcher/src/client/configuration.h b/launcher/src/client/configuration.h
index 727cd68f..9adeb243 100644
--- a/launcher/src/client/configuration.h
+++ b/launcher/src/client/configuration.h
@@ -43,6 +43,10 @@ private:
     // Server List
     void draw_servers() const;
     void draw_server_list() const;
+    // About
+    static void draw_centered_text(const std::string &str);
+    static void draw_links(const std::vector<std::pair<std::string, std::string>> &links);
+    static void draw_about();
     // State
     const State original_state;
     State &state;
diff --git a/launcher/src/client/ui.cpp b/launcher/src/client/ui.cpp
index d575fa5b..8b980663 100644
--- a/launcher/src/client/ui.cpp
+++ b/launcher/src/client/ui.cpp
@@ -1,9 +1,13 @@
 #include <vector>
 #include <limits>
+#include <ranges>
 
 #include <libreborn/util/util.h>
+#include <libreborn/config.h>
+#include <libreborn/util/exec.h>
 
 #include "configuration.h"
+#include "../updater/updater.h"
 
 #include <imgui_stdlib.h>
 
@@ -43,6 +47,11 @@ int ConfigurationUI::render() {
                 draw_servers();
                 ImGui::EndTabItem();
             }
+            // About Tab
+            if (ImGui::BeginTabItem("About")) {
+                draw_about();
+                ImGui::EndTabItem();
+            }
             ImGui::EndTabBar();
         }
     }
@@ -248,3 +257,44 @@ void ConfigurationUI::draw_server_list() const {
         }
     }
 }
+
+// About
+void ConfigurationUI::draw_centered_text(const std::string &str) {
+    const float width = ImGui::GetWindowSize().x;
+    const float text_width = ImGui::CalcTextSize(str.c_str()).x;
+    ImGui::SetCursorPosX((width - text_width) / 2.0f);
+    ImGui::Text("%s", str.c_str());
+}
+void ConfigurationUI::draw_links(const std::vector<std::pair<std::string, std::string>> &links) {
+    std::vector<const char *> buttons;
+    for (const std::string &text : links | std::views::keys) {
+        buttons.push_back(text.c_str());
+    }
+    draw_right_aligned_buttons(buttons, [&links](const int id, const bool was_clicked) {
+        if (was_clicked) {
+            open_url(links[id].second);
+        }
+    }, true);
+}
+void ConfigurationUI::draw_about() {
+    // Text
+    draw_centered_text("By " MCPI_AUTHOR);
+    draw_centered_text("Version " MCPI_VERSION);
+    // Links
+    ImGui::Separator();
+    draw_links({
+        {"Home", MCPI_REPO},
+        {"Changelog", MCPI_DOCS_CHANGELOG},
+        {"Credits", MCPI_DOCS "CREDITS.md"}
+    });
+    // Updater
+    Updater *updater = Updater::instance;
+    if (updater) {
+        ImGui::Separator();
+        draw_right_aligned_buttons({updater->get_status().c_str()}, [&updater](__attribute__((unused)) int id, const bool was_clicked) {
+            if (was_clicked) {
+                updater->start();
+            }
+        }, true);
+    }
+}
diff --git a/launcher/src/ui/frame.cpp b/launcher/src/ui/frame.cpp
index d95a2946..a9d68bc5 100644
--- a/launcher/src/ui/frame.cpp
+++ b/launcher/src/ui/frame.cpp
@@ -107,7 +107,7 @@ float Frame::get_frame_width(const char *str) {
     const ImGuiStyle &style = ImGui::GetStyle();
     return ImGui::CalcTextSize(str).x + style.FramePadding.x * 2.0f;
 }
-void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback) {
+void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback, const bool should_actually_center) {
     // Calculate Position
     const ImGuiStyle &style = ImGui::GetStyle();
     float width_needed = 0;
@@ -117,7 +117,14 @@ void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons,
         }
         width_needed += get_frame_width(text);
     }
-    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed);
+    float cursor_pos;
+    if (should_actually_center) {
+        cursor_pos = ImGui::GetWindowSize().x - width_needed;
+        cursor_pos /= 2.0f;
+    } else {
+        cursor_pos = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed;
+    }
+    ImGui::SetCursorPosX(cursor_pos);
     // Draw
     for (std::vector<const char *>::size_type id = 0; id < buttons.size(); id++) {
         if (id > 0) {
diff --git a/launcher/src/ui/frame.h b/launcher/src/ui/frame.h
index d66c4e01..0ff9a061 100644
--- a/launcher/src/ui/frame.h
+++ b/launcher/src/ui/frame.h
@@ -20,7 +20,7 @@ protected:
     // API For Sub-Classes
     ImFont *monospace = nullptr;
     static float get_frame_width(const char *str);
-    static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback);
+    static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback, bool should_actually_center = false);
     static constexpr const char *quit_text = "Quit";
 private:
     // Properties
diff --git a/launcher/src/updater/appimage.cpp b/launcher/src/updater/appimage.cpp
new file mode 100644
index 00000000..e69de29b
diff --git a/launcher/src/updater/updater.cpp b/launcher/src/updater/updater.cpp
new file mode 100644
index 00000000..e4e7b362
--- /dev/null
+++ b/launcher/src/updater/updater.cpp
@@ -0,0 +1,46 @@
+#include <pthread.h>
+#include <libreborn/log.h>
+
+#include "updater.h"
+
+// Instance
+Updater *Updater::instance = nullptr;
+Updater::Updater() {
+    instance = this;
+}
+
+// Check Status
+bool Updater::can_start() const {
+    return status == NOT_STARTED || status == RESTART_NEEDED;
+}
+std::string Updater::get_status() const {
+    switch (status) {
+        case NOT_STARTED: return "Update";
+        case RESTART_NEEDED: return "Restart!";
+        case CHECKING: return "Checking...";
+        case UP_TO_DATE: return "Up-To-Date";
+        case DOWNLOADING: return "Downloading...";
+        default: return "";
+    }
+}
+
+// Run
+static void *update_thread(void *data) {
+    Updater *updater = (Updater *) data;
+    updater->update();
+    return nullptr;
+}
+void Updater::start() {
+    switch (status) {
+        case NOT_STARTED: {
+            pthread_t thread;
+            pthread_create(&thread, nullptr, update_thread, this);
+            break;
+        }
+        case RESTART_NEEDED: {
+            restart();
+            break;
+        }
+        default: IMPOSSIBLE();
+    }
+}
\ No newline at end of file
diff --git a/launcher/src/updater/updater.h b/launcher/src/updater/updater.h
new file mode 100644
index 00000000..ebbd5713
--- /dev/null
+++ b/launcher/src/updater/updater.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <string>
+
+// Update Status
+enum UpdateStatus {
+    NOT_STARTED,
+    CHECKING,
+    UP_TO_DATE,
+    DOWNLOADING,
+    RESTART_NEEDED
+};
+
+// Updater
+struct Updater {
+    // Instance
+    static Updater *instance;
+    // Constructor
+    Updater();
+    virtual ~Updater() = default;
+    // Implementation
+    virtual void update() = 0;
+    virtual void restart() = 0;
+    // Methods
+    [[nodiscard]] std::string get_status() const;
+    [[nodiscard]] bool can_start() const;
+    void start();
+    // Properties
+    UpdateStatus status = NOT_STARTED;
+};
\ No newline at end of file
diff --git a/libreborn/include/libreborn/config.h.in b/libreborn/include/libreborn/config.h.in
index 6013be86..83674102 100644
--- a/libreborn/include/libreborn/config.h.in
+++ b/libreborn/include/libreborn/config.h.in
@@ -1,16 +1,37 @@
 #pragma once
 
-#cmakedefine MCPI_IS_APPIMAGE_BUILD
-#cmakedefine MCPI_IS_FLATPAK_BUILD
-#cmakedefine MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
+// General
+#cmakedefine MCPI_VERSION "@MCPI_VERSION@"
+#cmakedefine MCPI_AUTHOR "@MCPI_AUTHOR@"
+#cmakedefine MCPI_ARCH "@MCPI_ARCH@"
+
+// App Information
 #cmakedefine MCPI_APP_TITLE "@MCPI_APP_TITLE@"
 #cmakedefine MCPI_APP_ID "@MCPI_APP_ID@"
-#cmakedefine MCPI_VERSION "@MCPI_VERSION@"
-#cmakedefine MCPI_VARIANT_NAME "@MCPI_VARIANT_NAME@"
-#cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@"
+#cmakedefine MCPI_APP_NAME "@MCPI_APP_NAME@"
+
+// Extra Options
 #cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@"
 #cmakedefine MCPI_DISCORD_INVITE "@MCPI_DISCORD_INVITE@"
-#cmakedefine MCPI_DOCUMENTATION "@MCPI_DOCUMENTATION@"
+#cmakedefine MCPI_REPO "@MCPI_REPO@"
+
+// Documentation
+#cmakedefine MCPI_DOCS "@MCPI_DOCUMENTATION@"
+#define MCPI_DOCS_CHANGELOG MCPI_DOCS "CHANGELOG.md"
+#define MCPI_DOCS_GETTING_STARTED MCPI_DOCS "GETTING_STARTED.md"
+
+// Internal
+#cmakedefine MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
+#cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@"
+
+// AppImage
+#cmakedefine MCPI_IS_APPIMAGE_BUILD
+#cmakedefine MCPI_APPIMAGE_JSON_URL "@MCPI_APPIMAGE_JSON_URL@"
+#cmakedefine MCPI_APPIMAGE_VERSION_PLACEHOLDER "@MCPI_APPIMAGE_VERSION_PLACEHOLDER@"
+#cmakedefine MCPI_APPIMAGE_DOWNLOAD_URL "@MCPI_APPIMAGE_DOWNLOAD_URL@"
+
+// Flatpak
+#cmakedefine MCPI_IS_FLATPAK_BUILD
 
 // Access Configuration At Runtime
 const char *reborn_get_version();
diff --git a/mods/include/mods/options/info.h b/mods/include/mods/options/info.h
deleted file mode 100644
index 4ec72ab4..00000000
--- a/mods/include/mods/options/info.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-#include <string>
-
-#define CHANGELOG_FILE "CHANGELOG.md"
\ No newline at end of file
diff --git a/mods/src/options/info.cpp b/mods/src/options/info.cpp
index 0791cc61..5a6b9461 100644
--- a/mods/src/options/info.cpp
+++ b/mods/src/options/info.cpp
@@ -1,14 +1,11 @@
-#include <libreborn/log.h>
 #include <libreborn/util/exec.h>
 #include <libreborn/config.h>
 #include <libreborn/util/util.h>
 
 #include <symbols/minecraft.h>
-#include <GLES/gl.h>
 
 #include <mods/touch/touch.h>
 #include <mods/misc/misc.h>
-#include <mods/options/info.h>
 #include <mods/extend/extend.h>
 
 #include "options-internal.h"
@@ -53,7 +50,7 @@ static info_line info[] = {
         .get_text = []() {
             return std::string("Version: v") + reborn_get_version() + extra_version_info_full;
         },
-        .button_url = MCPI_DOCUMENTATION CHANGELOG_FILE,
+        .button_url = MCPI_DOCS_CHANGELOG,
         .button_text = "Changelog"
     },
     {
@@ -67,7 +64,7 @@ static info_line info[] = {
         .get_text = []() {
             return std::string("Sound Data: ") + info_sound_data_state;
         },
-        .button_url = MCPI_DOCUMENTATION "GETTING_STARTED.md#sound",
+        .button_url = MCPI_DOCS_GETTING_STARTED "#sound",
         .button_text = "More Info"
     },
 };
diff --git a/mods/src/title-screen/welcome.cpp b/mods/src/title-screen/welcome.cpp
index a5432d24..6de870eb 100644
--- a/mods/src/title-screen/welcome.cpp
+++ b/mods/src/title-screen/welcome.cpp
@@ -8,7 +8,6 @@
 #include <symbols/minecraft.h>
 
 #include <mods/touch/touch.h>
-#include <mods/options/info.h>
 #include <mods/misc/misc.h>
 #include <mods/extend/extend.h>
 
@@ -115,9 +114,9 @@ struct WelcomeScreen final : CustomScreen {
     // Handle Button Click
     void buttonClicked(Button *button) override {
         if (button == getting_started) {
-            open_url(MCPI_DOCUMENTATION "GETTING_STARTED.md");
+            open_url(MCPI_DOCS_GETTING_STARTED);
         } else if (button == changelog) {
-            open_url(MCPI_DOCUMENTATION CHANGELOG_FILE);
+            open_url(MCPI_DOCS_CHANGELOG);
         } else if (button == proceed) {
             mark_welcome_as_shown();
             self->minecraft->screen_chooser.setScreen(1);