WIP: [frontend, android] Move update_checker to frontend_common and add Android support #2687

Draft
inix wants to merge 4 commits from inix/eden:update-checker-android into master
20 changed files with 209 additions and 28 deletions
Showing only changes of commit eec63a0f85 - Show all commits

View file

@ -68,7 +68,7 @@ else
fi
if [ "$DEVEL" != "true" ]; then
export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DENABLE_QT_UPDATE_CHECKER=ON)
export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DENABLE_UPDATE_CHECKER=ON)
fi
if [ "$USE_WEBENGINE" = "true" ]; then

View file

@ -37,7 +37,7 @@ cmake .. -G Ninja \
-DDYNARMIC_ENABLE_LTO=ON \
-DYUZU_USE_BUNDLED_QT=${BUNDLE_QT:-false} \
-DUSE_CCACHE=${CCACHE:-false} \
-DENABLE_QT_UPDATE_CHECKER=${DEVEL:-true} \
-DENABLE_UPDATE_CHECKER=${DEVEL:-true} \
"${EXTRA_CMAKE_FLAGS[@]}" \
"$@"

View file

@ -133,7 +133,7 @@ jobs:
echo $GIT_TAG_NAME
- name: Build
run: ANDROID_HOME=/home/runner/sdk ./.ci/android/build.sh
run: DEVEL=false ANDROID_HOME=/home/runner/sdk ./.ci/android/build.sh
env:
ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}

View file

@ -160,7 +160,7 @@ endif()
# qt stuff
option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(ENABLE_QT_UPDATE_CHECKER "Enable update checker for the Qt frontend" OFF)
option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF)
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
@ -563,7 +563,7 @@ if (ENABLE_WEB_SERVICE)
find_package(httplib)
endif()
if (ENABLE_WEB_SERVICE OR ENABLE_QT_UPDATE_CHECKER)
if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER)
find_package(cpp-jwt)
endif()

View file

@ -62,7 +62,7 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
* [libusb](https://github.com/libusb/libusb)
* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
* [sirit](https://github.com/eden-emulator/sirit)
* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_QT_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on
* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on
- This package is known to be broken on the AUR.
* [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on
* [unordered-dense](https://github.com/martinus/unordered_dense)

View file

@ -38,6 +38,7 @@ Notes:
- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC) Download bundled OpenSSL build
* Always on for Android
* Unavailable on OpenBSD
- `ENABLE_UPDATE_CHECKER` (OFF) Enable update checker for the Qt an Android frontends
The following options are desktop only:
- `ENABLE_SDL2` (ON) Enable the SDL2 desktop, audio, and input frontend (HIGHLY RECOMMENDED!)
@ -51,7 +52,6 @@ The following options are desktop only:
* Unavailable on Windows/ARM64 and Android
- `ENABLE_QT` (ON) Enable the Qt frontend (recommended)
- `ENABLE_QT_TRANSLATION` (OFF) Enable translations for the Qt frontend
- `ENABLE_QT_UPDATE_CHECKER` (OFF) Enable update checker for the Qt frontend
- `YUZU_USE_BUNDLED_QT` (ON for MSVC) Download bundled Qt binaries
* Note that using **system Qt** requires you to include the Qt CMake directory in `CMAKE_PREFIX_PATH`, e.g:
* `-DCMAKE_PREFIX_PATH=C:/Qt/6.9.0/msvc2022_64/lib/cmake/Qt6`

View file

@ -207,7 +207,7 @@ if (VulkanMemoryAllocator_ADDED)
endif()
# httplib
if (ENABLE_WEB_SERVICE OR ENABLE_QT_UPDATE_CHECKER)
if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER)
AddJsonPackage(httplib)
endif()

View file

@ -166,6 +166,8 @@ android {
defaultConfig {
externalNativeBuild {
cmake {
val enableUpdater = if (System.getenv("DEVEL") != "true") "OM" else "OFF"
arguments(
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
@ -178,7 +180,8 @@ android {
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
"-DBUILD_TESTING=OFF",
"-DYUZU_TESTS=OFF",
"-DDYNARMIC_TESTS=OFF"
"-DDYNARMIC_TESTS=OFF",
"-DENABLE_UPDATE_CHECKER=$enableUpdater"
)
abiFilters("arm64-v8a")

View file

@ -200,6 +200,21 @@ object NativeLibrary {
external fun logSettings()
/**
* Sets the path to CA certificates for SSL/TLS verification.
*/
external fun setCACertificatePath(path: String)
/**
* Checks for available updates.
*/
external fun checkForUpdate(): String?
/**
* Return the URL to the release page
*/
external fun getUpdateUrl(version: String): String
enum class CoreError {
ErrorSystemFiles,
ErrorSavestate,

View file

@ -12,6 +12,10 @@ import android.app.NotificationManager
import android.content.Context
import org.yuzu.yuzu_emu.features.input.NativeInput
import java.io.File
import java.io.FileOutputStream
import java.security.KeyStore
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
@ -59,9 +63,53 @@ class YuzuApplication : Application() {
PowerStateUpdater.start()
Log.logDeviceInfo()
// Initialize CA certificates for HTTPS
initializeCACertificates()
createNotificationChannels()
}
// required for httplib and update checker
private fun initializeCACertificates() {
try {
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
trustManagerFactory.init(null as KeyStore?)
val trustManagers = trustManagerFactory.trustManagers
if (trustManagers.isEmpty()) {
Log.error("[SSL] No trust managers found")
return
}
val x509TrustManager = trustManagers[0] as X509TrustManager
val acceptedIssuers = x509TrustManager.acceptedIssuers
if (acceptedIssuers.isEmpty()) {
Log.error("[SSL] No CA certificates found")
return
}
val certFile = File(filesDir, "cacert.pem")
FileOutputStream(certFile).use { outputStream ->
for (cert in acceptedIssuers) {
outputStream.write("-----BEGIN CERTIFICATE-----\n".toByteArray())
val encoded = android.util.Base64.encodeToString(
cert.encoded,
android.util.Base64.DEFAULT
)
outputStream.write(encoded.toByteArray())
outputStream.write("-----END CERTIFICATE-----\n".toByteArray())
}
}
NativeLibrary.setCACertificatePath(certFile.absolutePath)
Log.info("[SSL] Initialized ${acceptedIssuers.size} CA certificates at: ${certFile.absolutePath}")
} catch (e: Exception) {
Log.error("[SSL] Failed to initialize CA certificates: ${e.message}")
}
}
companion object {
var documentsTree: DocumentsTree? = null
lateinit var application: YuzuApplication

View file

@ -23,6 +23,7 @@ import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.File
import java.io.FilenameFilter
import org.yuzu.yuzu_emu.NativeLibrary
@ -50,6 +51,7 @@ import java.util.zip.ZipInputStream
import androidx.core.content.edit
import org.yuzu.yuzu_emu.activities.EmulationActivity
import kotlin.text.compareTo
import androidx.core.net.toUri
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
@ -192,9 +194,38 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Dismiss previous notifications (should not happen unless a crash occurred)
EmulationActivity.stopForegroundService(this)
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
if (!firstTimeSetup) {
checkForUpdates()
}
setInsets()
}
private fun checkForUpdates() {
Thread {
val latestVersion = NativeLibrary.checkForUpdate()
if (latestVersion != null) {
runOnUiThread {
showUpdateDialog(latestVersion)
}
}
}.start()
}
private fun showUpdateDialog(version: String) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_available)
.setMessage(getString(R.string.update_available_description, version))
.setPositiveButton(android.R.string.ok) { _, _ ->
val url = NativeLibrary.getUpdateUrl(version)
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
startActivity(intent)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
fun showPreAlphaWarningDialog() {
val shouldDisplayAlphaWarning =
PreferenceManager.getDefaultSharedPreferences(applicationContext)

View file

@ -30,4 +30,8 @@ if (ENABLE_OPENSSL OR ENABLE_WEB_SERVICE)
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
endif()
if (ENABLE_UPDATE_CHECKER)
target_compile_definitions(yuzu-android PRIVATE ENABLE_UPDATE_CHECKER)
endif()
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} yuzu-android)

View file

@ -60,6 +60,9 @@
#include "core/loader/loader.h"
#include "frontend_common/config.h"
#include "frontend_common/firmware_manager.h"
#ifdef ENABLE_UPDATE_CHECKER
#include "frontend_common/update_checker.h"
#endif
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
@ -85,6 +88,9 @@ std::atomic<int> g_battery_percentage = {100};
std::atomic<bool> g_is_charging = {false};
std::atomic<bool> g_has_battery = {true};
// SSL Certificate path for HTTPS requests
std::string g_ca_cert_path;
EmulationSession::EmulationSession() {
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
}
@ -1057,4 +1063,45 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_updatePowerState(
g_is_charging.store(isCharging, std::memory_order_relaxed);
g_has_battery.store(hasBattery, std::memory_order_relaxed);
}
#ifdef ENABLE_UPDATE_CHECKER
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setCACertificatePath(
JNIEnv* env,
jobject obj,
jstring path) {
const char* path_str = env->GetStringUTFChars(path, nullptr);
g_ca_cert_path = std::string(path_str);
env->ReleaseStringUTFChars(path, path_str);
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEnv* env,
jobject obj) {
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != nullptr) ||
(strstr(Common::g_build_version, "alpha") != nullptr) ||
(strstr(Common::g_build_version, "beta") != nullptr) ||
(strstr(Common::g_build_version, "rc") != nullptr));
const std::optional<std::string> latest_release_tag =
UpdateChecker::GetLatestRelease(is_prerelease);
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
return env->NewStringUTF(latest_release_tag.value().c_str());
}
return nullptr;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
JNIEnv* env,
jobject obj,
jstring version) {
const char* version_str = env->GetStringUTFChars(version, nullptr);
const std::string url = fmt::format("{}/{}/releases/tag/{}",
std::string{Common::g_build_auto_update_website},
std::string{Common::g_build_auto_update_repo},
version_str);
env->ReleaseStringUTFChars(version, version_str);
return env->NewStringUTF(url.c_str());
}
#endif
} // extern "C"

View file

@ -297,6 +297,8 @@
<string name="pre_alpha_warning_description">WARNING: This software is in the pre-alpha stage and may have bugs and incomplete feature implementations.</string>
<string name="dont_show_again">Don\'t Show Again</string>
<string name="pre_alpha_warning">PRE-ALPHA SOFTWARE</string>
<string name="update_available">Update Available</string>
<string name="update_available_description">A new version is available: %1$s\n\nWould you like to download it?</string>
<string name="add_directory_success">New game directory added successfully </string>
<string name="home_games">Games</string>
<string name="home_search">Search</string>

View file

@ -9,5 +9,20 @@ add_library(frontend_common STATIC
firmware_manager.cpp
)
if (ENABLE_UPDATE_CHECKER)
target_link_libraries(frontend_common PRIVATE httplib::httplib)
target_link_libraries(frontend_common PRIVATE nlohmann_json::nlohmann_json)
target_sources(frontend_common PRIVATE
update_checker.cpp
update_checker.h
)
if (ENABLE_OPENSSL)
target_compile_definitions(frontend_common PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(frontend_common PRIVATE OpenSSL::SSL)
endif()
endif()
create_target_directory_groups(frontend_common)
target_link_libraries(frontend_common PUBLIC core SimpleIni::SimpleIni PRIVATE common Boost::headers)

View file

@ -5,9 +5,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// SPDX-FileCopyrightText: eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "update_checker.h"
#include "common/logging/log.h"
#include <fmt/format.h>
@ -17,11 +14,28 @@
#include <string>
#include "common/scm_rev.h"
std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::string path)
{
#ifdef ANDROID
extern std::string g_ca_cert_path;
#endif
namespace UpdateChecker {
std::optional<std::string> GetResponse(std::string url, std::string path) {
constexpr std::size_t timeout_seconds = 15;
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(url);
#ifdef ANDROID
// this is required in order for SSL to work on Android
if (!g_ca_cert_path.empty()) {
client->set_ca_cert_path(g_ca_cert_path.c_str());
client->enable_server_certificate_verification(true);
LOG_INFO(Frontend, "Using Android system CA certificates for SSL verification");
} else {
LOG_ERROR(Frontend, "No CA certificate path set for Android, skipping SSL verification");
}
#endif
client->set_connection_timeout(timeout_seconds);
client->set_read_timeout(timeout_seconds);
client->set_write_timeout(timeout_seconds);
@ -56,8 +70,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
return response.body;
}
std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prereleases)
{
std::optional<std::string> GetLatestRelease(bool include_prereleases) {
const auto update_check_url = std::string{Common::g_build_auto_update_api};
std::string update_check_path = fmt::format("/repos/{}", std::string{Common::g_build_auto_update_repo});
try {
@ -111,3 +124,5 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
return {};
}
}
} // namespace UpdateChecker

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,5 +12,5 @@
namespace UpdateChecker {
std::optional<std::string> GetResponse(std::string url, std::string path);
std::optional<std::string> GetLatestRelease(bool);
std::optional<std::string> GetLatestRelease(bool include_prereleases);
} // namespace UpdateChecker

View file

@ -260,10 +260,8 @@ file(GLOB COMPAT_LIST
file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
if (ENABLE_QT_UPDATE_CHECKER)
target_link_libraries(yuzu PRIVATE httplib::httplib)
target_sources(yuzu PRIVATE update_checker.cpp)
target_compile_definitions(yuzu PUBLIC ENABLE_QT_UPDATE_CHECKER)
if (ENABLE_UPDATE_CHECKER)
target_compile_definitions(yuzu PUBLIC ENABLE_UPDATE_CHECKER)
endif()
if (ENABLE_QT_TRANSLATION)

View file

@ -52,8 +52,8 @@
#include "yuzu/multiplayer/state.h"
#include "yuzu/util/controller_navigation.h"
#ifdef ENABLE_QT_UPDATE_CHECKER
#include "yuzu/update_checker.h"
#ifdef ENABLE_UPDATE_CHECKER
#include "frontend_common/update_checker.h"
#endif
#ifdef YUZU_ROOM
@ -514,7 +514,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
show();
#ifdef ENABLE_QT_UPDATE_CHECKER
#ifdef ENABLE_UPDATE_CHECKER
if (UISettings::values.check_for_updates) {
update_future = QtConcurrent::run([]() -> QString {
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != NULL) ||
@ -4190,7 +4190,7 @@ void GMainWindow::MigrateConfigFiles() {
}
}
#ifdef ENABLE_QT_UPDATE_CHECKER
#ifdef ENABLE_UPDATE_CHECKER
void GMainWindow::OnEmulatorUpdateAvailable() {
QString version_string = update_future.result();
if (version_string.isEmpty())

View file

@ -32,7 +32,7 @@
#include <QtDBus/QtDBus>
#endif
#ifdef ENABLE_QT_UPDATE_CHECKER
#ifdef ENABLE_UPDATE_CHECKER
#include <QFuture>
#include <QFutureWatcher>
#endif
@ -418,7 +418,7 @@ private slots:
void OnEmulationStopped();
void OnEmulationStopTimeExpired();
#ifdef ENABLE_QT_UPDATE_CHECKER
#ifdef ENABLE_UPDATE_CHECKER
void OnEmulatorUpdateAvailable();
#endif
@ -474,7 +474,7 @@ private:
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
#ifdef ENABLE_QT_UPDATE_CHECKER
#ifdef ENABLE_UPDATE_CHECKER
QFuture<QString> update_future;
QFutureWatcher<QString> update_watcher;
#endif