From f121df0aa3a09012c64fb1d1eeb96d0d2a95c5b7 Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 27 Jun 2025 23:23:25 +0000 Subject: [PATCH] [frontend] Firmware setup & requirement (#222) Currently Android only, will need to be added to desktop. Android incorrectly records firmware as 19.0.1 if on a higher version... TODO: - [x] desktop - [x] fix android Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/222 Co-authored-by: crueter Co-committed-by: crueter --- .ci/license-header.rb | 7 - .ci/license-header.sh | 85 ++++++++++++ .ci/license/header.txt | 2 + .github/workflows/license-header.yml | 4 +- src/android/app/build.gradle.kts | 1 + .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 21 +++ .../features/settings/model/BooleanSetting.kt | 1 + .../settings/model/view/SettingsItem.kt | 8 ++ .../settings/ui/SettingsFragmentPresenter.kt | 2 + .../yuzu_emu/fragments/EmulationFragment.kt | 12 +- .../fragments/HomeSettingsFragment.kt | 4 +- .../fragments/ProgressDialogFragment.kt | 11 ++ .../yuzu/yuzu_emu/fragments/SetupFragment.kt | 42 +++++- .../org/yuzu/yuzu_emu/model/HomeViewModel.kt | 9 +- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 123 ++++++++++++------ .../app/src/main/jni/android_settings.h | 8 ++ src/android/app/src/main/jni/native.cpp | 32 ++++- .../app/src/main/res/values/strings.xml | 20 ++- src/core/hle/api_version.h | 17 ++- src/core/hle/kernel/svc_version.h | 7 +- src/yuzu/main.cpp | 33 +++++ src/yuzu/main.h | 4 + 22 files changed, 379 insertions(+), 74 deletions(-) create mode 100755 .ci/license-header.sh create mode 100644 .ci/license/header.txt diff --git a/.ci/license-header.rb b/.ci/license-header.rb index 8e76366064..dda5522026 100644 --- a/.ci/license-header.rb +++ b/.ci/license-header.rb @@ -7,16 +7,9 @@ license_header = <<~EOF EOF print 'Getting branch changes...' -puts "\n" branch_name = `git rev-parse --abbrev-ref HEAD`.chomp -print branch_name -puts "\n" branch_commits = `git log #{branch_name} --not master --pretty=format:"%h"`.split("\n") -print branch_commits -puts "\n" branch_commit_range = "#{branch_commits[-1]}^..#{branch_commits[0]}" -print branch_commit_range -puts "\n" branch_changed_files = `git diff-tree --no-commit-id --name-only #{branch_commit_range} -r`.split("\n") puts 'done' diff --git a/.ci/license-header.sh b/.ci/license-header.sh new file mode 100755 index 0000000000..64db98132f --- /dev/null +++ b/.ci/license-header.sh @@ -0,0 +1,85 @@ +#!/bin/sh -e + +HEADER="$(cat "$PWD/.ci/license/header.txt")" + +echo "Getting branch changes" + +# I created this cursed POSIX abomination only to discover a better solution +#BRANCH=`git rev-parse --abbrev-ref HEAD` +#COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"` +#RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}" +#FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r` + +FILES=$(git diff --name-only master) + +echo "Done" + +for file in $FILES; do + EXTENSION="${file##*.}" + case "$EXTENSION" in + kts|kt|cpp|h) + CONTENT="`cat $file`" + case "$CONTENT" in + "$HEADER"*) ;; + *) BAD_FILES="$BAD_FILES $file" ;; + esac + ;; + esac +done + +if [ "$BAD_FILES" = "" ]; then + echo + echo "All good." + + exit +fi + +echo "The following files have incorrect license headers:" +echo + +for file in $BAD_FILES; do echo $file; done + +cat << EOF + +The following license header should be added to the start of all offending files: + +=== BEGIN === +$HEADER +=== END === + +If some of the code in this PR is not being contributed by the original author, +the files which have been exclusively changed by that code can be ignored. +If this happens, this PR requirement can be bypassed once all other files are addressed. +EOF + +if [ "$FIX" = "true" ]; then + echo + echo "FIX set to true. Fixing headers." + echo + + for file in $BAD_FILES; do + cat $file > $file.bak + + cat .ci/license/header.txt > $file + echo >> $file + cat $file.bak >> $file + + rm $file.bak + + git add $file + done + + echo "License headers fixed." +fi + +if [ "$COMMIT" = "true" ]; then + echo + echo "COMMIT set to true. Committing changes." + echo + + git commit -m "Fix license headers" + + echo "Changes committed. You may now push." +fi + +exit diff --git a/.ci/license/header.txt b/.ci/license/header.txt new file mode 100644 index 0000000000..53a4f4396e --- /dev/null +++ b/.ci/license/header.txt @@ -0,0 +1,2 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later diff --git a/.github/workflows/license-header.yml b/.github/workflows/license-header.yml index 13ed6f34c6..d6935dcac9 100644 --- a/.github/workflows/license-header.yml +++ b/.github/workflows/license-header.yml @@ -16,7 +16,7 @@ jobs: run: git fetch origin master:master - name: Make script executable - run: chmod +x ./.ci/license-header.rb + run: chmod +x ./.ci/license-header.sh - name: Check license headers - run: ./.ci/license-header.rb + run: ./.ci/license-header.sh diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index cc38141456..fb15783426 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -246,6 +246,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("androidx.compose.ui:ui-graphics-android:1.7.8") implementation("androidx.compose.ui:ui-text-android:1.7.8") + implementation("net.swiftzer.semver:semver:2.0.0") } fun runGitCommand(command: List): String { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 3435d08132..c7d6d6c33a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -16,6 +16,7 @@ import android.view.View import android.widget.TextView import androidx.annotation.Keep import com.google.android.material.dialog.MaterialAlertDialogBuilder +import net.swiftzer.semver.SemVer import java.lang.ref.WeakReference import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment @@ -407,6 +408,26 @@ object NativeLibrary { */ external fun isFirmwareAvailable(): Boolean + /** + * Gets the firmware version. + * + * @return Reported firmware version + */ + external fun firmwareVersion(): String + + fun isFirmwareSupported(): Boolean { + var version: SemVer + + try { + version = SemVer.parse(firmwareVersion()) + } catch (_: Exception) { + return false + } + val max = SemVer(19, 0, 1) + + return version <= max + } + /** * Checks the PatchManager for any addons that are available * diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index f43e5f9dd6..4c527163c8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -41,6 +41,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SHOW_DEVICE_MODEL("show_device_model"), SHOW_GPU_MODEL("show_gpu_model"), SHOW_SOC_MODEL("show_soc_model"), + SHOW_FW_VERSION("show_firmware_version"), SOC_OVERLAY_BACKGROUND("soc_overlay_background"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 84c9f419a4..b52355e255 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -460,6 +460,14 @@ abstract class SettingsItem( descriptionId = R.string.show_soc_model_description ) ) + put( + SwitchSetting( + BooleanSetting.SHOW_FW_VERSION, + titleId = R.string.show_fw_version, + descriptionId = R.string.show_fw_version_description + ) + ) + put( SingleChoiceSetting( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 24214c3895..2337bc5821 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -271,6 +271,8 @@ class SettingsFragmentPresenter( if (Build.VERSION.SDK_INT >= 31) { add(BooleanSetting.SHOW_SOC_MODEL.key) } + + add(BooleanSetting.SHOW_FW_VERSION.key) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 4b7da6ef23..641522de4d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -1,9 +1,8 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later package org.yuzu.yuzu_emu.fragments @@ -754,6 +753,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + if (BooleanSetting.SHOW_FW_VERSION.getBoolean(NativeConfig.isPerGameConfigLoaded())) { + if (sb.isNotEmpty()) sb.append(" | ") + sb.append(NativeLibrary.firmwareVersion()) + } + binding.showSocOverlayText.text = sb.toString() if (BooleanSetting.SOC_OVERLAY_BACKGROUND.getBoolean(NativeConfig.isPerGameConfigLoaded())) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index f9ecd6f49f..f39d9514b3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -137,7 +137,7 @@ class HomeSettingsFragment : Fragment() { binding.root.findNavController() .navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment) }, - { NativeLibrary.isFirmwareAvailable() }, + { NativeLibrary.isFirmwareAvailable() && NativeLibrary.isFirmwareSupported() }, R.string.applets_error_firmware, R.string.applets_error_description ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt index ee3bb0386a..26fcf7c0db 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later package org.yuzu.yuzu_emu.fragments import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -28,6 +32,8 @@ class ProgressDialogFragment : DialogFragment() { private val PROGRESS_BAR_RESOLUTION = 1000 + var onDialogComplete: (() -> Unit)? = null + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val titleId = requireArguments().getInt(TITLE) val cancellable = requireArguments().getBoolean(CANCELLABLE) @@ -121,6 +127,11 @@ class ProgressDialogFragment : DialogFragment() { } } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onDialogComplete?.invoke() + } + companion object { const val TAG = "IndeterminateProgressDialogFragment" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 6828b32e84..6443067885 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -129,7 +129,7 @@ class SetupFragment : Fragment() { 0, { if (NotificationManagerCompat.from(requireContext()) - .areNotificationsEnabled() + .areNotificationsEnabled() ) { StepState.COMPLETE } else { @@ -166,6 +166,32 @@ class SetupFragment : Fragment() { } ) ) + add( + SetupPage( + R.drawable.ic_firmware, + R.string.firmware, + R.string.firmware_description, + R.drawable.ic_add, + true, + R.string.select_firmware, + { + firmwareCallback = it + getFirmware.launch(arrayOf("application/zip")) + }, + true, + R.string.install_firmware_warning, + R.string.install_firmware_warning_description, + R.string.install_firmware_warning_help, + { + if (NativeLibrary.isFirmwareAvailable()) { + StepState.COMPLETE + } else { + StepState.INCOMPLETE + } + } + ) + ) + add( SetupPage( R.drawable.ic_controller, @@ -321,6 +347,7 @@ class SetupFragment : Fragment() { } private lateinit var keyCallback: SetupCallback + private lateinit var firmwareCallback: SetupCallback val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> @@ -332,6 +359,17 @@ class SetupFragment : Fragment() { } } + val getFirmware = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result != null) { + mainActivity.processFirmware(result) { + if (NativeLibrary.isFirmwareAvailable()) { + firmwareCallback.onStepCompleted() + } + } + } + } + private lateinit var gamesDirCallback: SetupCallback val getGamesDirectory = diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 08eee8c815..97a60ee184 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.model @@ -31,6 +31,9 @@ class HomeViewModel : ViewModel() { private val _checkKeys = MutableStateFlow(false) val checkKeys = _checkKeys.asStateFlow() + private val _checkFirmware = MutableStateFlow(false) + val checkFirmware = _checkFirmware.asStateFlow() + var navigatedToSetup = false fun setStatusBarShadeVisibility(visible: Boolean) { @@ -63,4 +66,8 @@ class HomeViewModel : ViewModel() { fun setCheckKeys(value: Boolean) { _checkKeys.value = value } + + fun setCheckFirmware(value: Boolean) { + _checkFirmware.value = value + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 3e3759d7eb..3a9c5486a8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.ui.main @@ -62,6 +62,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { private val CHECKED_DECRYPTION = "CheckedDecryption" private var checkedDecryption = false + private val CHECKED_FIRMWARE = "CheckedFirmware" + private var checkedFirmware = false + override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } @@ -76,17 +79,27 @@ class MainActivity : AppCompatActivity(), ThemeProvider { if (savedInstanceState != null) { checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION) + checkedFirmware = savedInstanceState.getBoolean(CHECKED_FIRMWARE) } if (!checkedDecryption) { val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) if (!firstTimeSetup) { checkKeys() - showPreAlphaWarningDialog() } checkedDecryption = true } + if (!checkedFirmware) { + val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) + .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) + if (!firstTimeSetup) { + checkFirmware() + showPreAlphaWarningDialog() + } + checkedFirmware = true + } + WindowCompat.setDecorFitsSystemWindows(window, false) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) @@ -135,6 +148,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider { if (it) checkKeys() } + homeViewModel.checkFirmware.collect(this, resetState = { homeViewModel.setCheckFirmware(false) }) { + if (it) checkFirmware() + } + setInsets() } @@ -175,9 +192,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } + private fun checkFirmware() { + if (!NativeLibrary.isFirmwareAvailable() || !NativeLibrary.isFirmwareSupported()) { + MessageDialogFragment.newInstance( + titleId = R.string.firmware_missing, + descriptionId = R.string.firmware_missing_description, + helpLinkId = R.string.firmware_missing_help + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } + } + + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption) + outState.putBoolean(CHECKED_FIRMWARE, checkedFirmware) } fun finishSetup(navController: NavController) { @@ -306,6 +335,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { Toast.LENGTH_SHORT ).show() homeViewModel.setCheckKeys(true) + homeViewModel.setCheckFirmware(true) gamesViewModel.reloadGames(true) return true } else { @@ -327,47 +357,55 @@ class MainActivity : AppCompatActivity(), ThemeProvider { return@registerForActivityResult } - val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } - - val firmwarePath = - File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") - val cacheFirmwareDir = File("${cacheDir.path}/registered/") - - ProgressDialogFragment.newInstance( - this, - R.string.firmware_installing - ) { progressCallback, _ -> - var messageToShow: Any - try { - FileUtil.unzipToInternalStorage( - result.toString(), - cacheFirmwareDir, - progressCallback - ) - val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 - val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 - messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { - MessageDialogFragment.newInstance( - this, - titleId = R.string.firmware_installed_failure, - descriptionId = R.string.firmware_installed_failure_description - ) - } else { - firmwarePath.deleteRecursively() - cacheFirmwareDir.copyRecursively(firmwarePath, true) - NativeLibrary.initializeSystem(true) - homeViewModel.setCheckKeys(true) - getString(R.string.save_file_imported_success) - } - } catch (e: Exception) { - Log.error("[MainActivity] Firmware install failed - ${e.message}") - messageToShow = getString(R.string.fatal_error) - } finally { - cacheFirmwareDir.deleteRecursively() - } - messageToShow - }.show(supportFragmentManager, ProgressDialogFragment.TAG) + processFirmware(result) } + + fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) { + val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } + + val firmwarePath = + File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") + val cacheFirmwareDir = File("${cacheDir.path}/registered/") + + ProgressDialogFragment.newInstance( + this, + R.string.firmware_installing + ) { progressCallback, _ -> + var messageToShow: Any + try { + FileUtil.unzipToInternalStorage( + result.toString(), + cacheFirmwareDir, + progressCallback + ) + val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 + val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 + messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { + MessageDialogFragment.newInstance( + this, + titleId = R.string.firmware_installed_failure, + descriptionId = R.string.firmware_installed_failure_description + ) + } else { + firmwarePath.deleteRecursively() + cacheFirmwareDir.copyRecursively(firmwarePath, true) + NativeLibrary.initializeSystem(true) + homeViewModel.setCheckKeys(true) + homeViewModel.setCheckFirmware(true) + getString(R.string.save_file_imported_success) + } + } catch (e: Exception) { + Log.error("[MainActivity] Firmware install failed - ${e.message}") + messageToShow = getString(R.string.fatal_error) + } finally { + cacheFirmwareDir.deleteRecursively() + } + messageToShow + }.apply { + onDialogComplete = onComplete + }.show(supportFragmentManager, ProgressDialogFragment.TAG) + } + fun uninstallFirmware() { val firmwarePath = File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") ProgressDialogFragment.newInstance( @@ -382,6 +420,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { // Optionally reinitialize the system or perform other necessary steps NativeLibrary.initializeSystem(true) homeViewModel.setCheckKeys(true) + homeViewModel.setCheckFirmware(true) messageToShow = getString(R.string.firmware_uninstalled_success) } else { messageToShow = getString(R.string.firmware_uninstalled_failure) diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 14d16a2182..67d70a6adc 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -136,6 +139,11 @@ namespace AndroidSettings { Settings::Specialization::Default, true, true, &show_performance_overlay}; + Settings::Setting show_fw_version{linkage, true, "show_firmware_version", + Settings::Category::Overlay, + Settings::Specialization::Default, true, true, + &show_performance_overlay}; + Settings::Setting soc_overlay_background{linkage, false, "soc_overlay_background", Settings::Category::Overlay, Settings::Specialization::Default, true, true, diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index cc00cfe024..4dd6f833b0 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -57,6 +57,7 @@ #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/set/system_settings_server.h" #include "core/loader/loader.h" #include "frontend_common/config.h" #include "hid_core/frontend/emulated_controller.h" @@ -762,22 +763,47 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass cl static_cast(jcabinetMode)); } -jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) { +bool isFirmwarePresent() { auto bis_system = - EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); + EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { return false; } // Query an applet to see if it's available auto applet_nca = - bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program); + bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program); if (!applet_nca) { return false; } return true; } +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) { + return isFirmwarePresent(); +} + +// TODO(crueter): This check is nonfunctional... +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclass clazz) { + Service::Set::FirmwareVersionFormat firmware_data{}; + const auto result = Service::Set::GetFirmwareVersionImpl( + firmware_data, EmulationSession::GetInstance().System(), + Service::Set::GetFirmwareVersionType::Version2); + + if (result.IsError() || !isFirmwarePresent()) { + LOG_INFO(Frontend, "Installed firmware: No firmware available"); + + return Common::Android::ToJString(env, "N/A"); + } + + const std::string display_version(firmware_data.display_version.data()); + const std::string display_title(firmware_data.display_title.data()); + + LOG_INFO(Frontend, "Installed firmware: {}", display_title); + + return Common::Android::ToJString(env, display_version); +} + jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj, jstring jpath, jstring jprogramId) { diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 10d02b4b82..d09da35546 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -58,6 +58,8 @@ Display the host GPU model Show SoC Model Display the host SoC model + Show Firmware Version + Display the installed firmware version Eden’s Veil @@ -220,6 +222,9 @@ Keys Select your <b>prod.keys</b> file with the button below. Select Keys + Firmware + Select your <b>firmware.zip</b> file with the button below.\nEden currently requires version <b>19.0.1</b> or below. + Select Firmware Games Select your <b>Games</b> folder with the button below. Done @@ -232,6 +237,8 @@ Complete! + Firmware: + Alphabetical List Grid @@ -262,6 +269,9 @@ Skip adding keys? Valid keys are required to emulate retail games. Only homebrew apps will function if you continue. https://yuzu-mirror.github.io/help/quickstart/#guide-introduction + Skip adding firmware? + Many games require access to firmware to run properly. + https://yuzu-mirror.github.io/help/quickstart/#guide-introduction Notifications Grant the notification permission with the button below. Grant permission @@ -357,18 +367,24 @@ No save data found Verify installed content Checks all installed content for corruption + Encryption keys are missing Firmware and retail games cannot be decrypted https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys + Firmware is missing or too new + Some games may not function properly. Eden requires firmware 19.0.1 or below. + https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware + Qlaunch Launch applications from the system home screen Applet launcher Launch system applets using installed firmware - Firmware not installed + Firmware not installed or invalid version Applet not available - prod.keys file and firmware are installed and try again.]]> + prod.keys file and + firmware are installed and try again.
Additionally, ensure your firmware is of version 19.0.1 or older.]]>
Album See images stored in the user screenshots folder with the system photo viewer Mii edit diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h index 20e5c7e459..e18397cc04 100644 --- a/src/core/hle/api_version.h +++ b/src/core/hle/api_version.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,8 +14,8 @@ namespace HLE::ApiVersion { // Horizon OS version constants. -constexpr u8 HOS_VERSION_MAJOR = 19; -constexpr u8 HOS_VERSION_MINOR = 0; +constexpr u8 HOS_VERSION_MAJOR = 20; +constexpr u8 HOS_VERSION_MINOR = 1; constexpr u8 HOS_VERSION_MICRO = 1; // NintendoSDK version constants. @@ -21,15 +24,15 @@ constexpr u8 SDK_REVISION_MAJOR = 1; constexpr u8 SDK_REVISION_MINOR = 0; constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "835c78223df116284ef7e36e8441760edc81729c"; -constexpr char DISPLAY_VERSION[] = "19.0.1"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 19.0.1-1.0"; +constexpr char VERSION_HASH[] = "9ffad64d79dd150490201461bdf66c8db963f57d"; +constexpr char DISPLAY_VERSION[] = "20.1.1"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 20.1.1-1.0"; // Atmosphere version constants. constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 0; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 0; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 9; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 1; constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) { return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{rev}; diff --git a/src/core/hle/kernel/svc_version.h b/src/core/hle/kernel/svc_version.h index 3eb95aa7b2..b47874dcde 100644 --- a/src/core/hle/kernel/svc_version.h +++ b/src/core/hle/kernel/svc_version.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -50,8 +53,8 @@ constexpr inline u32 RequiredKernelVersion = // This is the highest SVC version supported, to be updated on new kernel releases. // NOTE: Official kernel versions have SVC major = SDK major + 4, SVC minor = SDK minor. -constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(15); -constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(3); +constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(20); +constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(5); constexpr inline u32 SupportedKernelVersion = EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index cd3239205e..9002fdd0b9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -462,6 +462,9 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) // Gen keys if necessary OnCheckFirmwareDecryption(); + // Check firmware + OnCheckFirmware(); + game_list->LoadCompatibilityList(); // force reload on first load to ensure add-ons get updated game_list->PopulateAsync(UISettings::values.game_dirs, false); @@ -4326,6 +4329,7 @@ void GMainWindow::OnInstallFirmware() { progress.close(); OnCheckFirmwareDecryption(); + OnCheckFirmware(); } void GMainWindow::OnInstallDecryptionKeys() { @@ -4404,6 +4408,7 @@ void GMainWindow::OnInstallDecryptionKeys() { } OnCheckFirmwareDecryption(); + OnCheckFirmware(); } void GMainWindow::OnAbout() { @@ -5067,6 +5072,34 @@ void GMainWindow::OnCheckFirmwareDecryption() { UpdateMenuState(); } +void GMainWindow::OnCheckFirmware() +{ + if (!CheckFirmwarePresence()) { + QMessageBox::warning( + this, tr("Firmware Missing"), + tr("Firmware missing. Firmware is required to run certain games and use the Home Menu.\n" + "Eden only works with firmware 19.0.1 and earlier.")); + } else { + Service::Set::FirmwareVersionFormat firmware_data{}; + const auto result = Service::Set::GetFirmwareVersionImpl( + firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2); + + if (result.IsError()) { + LOG_INFO(Frontend, "Unable to read firmware"); + QMessageBox::warning( + this, tr("Firmware Corrupted"), + tr("Firmware reported as present, but was unable to be read. Check for decryption keys and redump firmware if necessary.")); + return; + } + + if (firmware_data.major > 19) { + QMessageBox::warning( + this, tr("Firmware Too New"), + tr("Firmware is too new. Eden only works with firmware 19.0.1 and earlier.")); + } + } +} + bool GMainWindow::CheckFirmwarePresence() { constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 6e1a47f5ac..84588f641a 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -414,6 +417,7 @@ private slots: void OnCreateHomeMenuShortcut(GameListShortcutTarget target); void OnCaptureScreenshot(); void OnCheckFirmwareDecryption(); + void OnCheckFirmware(); void OnLanguageChanged(const QString& locale); void OnMouseActivity(); bool OnShutdownBegin();