[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 <swurl@swurl.xyz>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/222
Co-authored-by: crueter <swurl@swurl.xyz>
Co-committed-by: crueter <swurl@swurl.xyz>
This commit is contained in:
crueter 2025-06-27 23:23:25 +00:00 committed by crueter
parent 20bf141faa
commit f121df0aa3
22 changed files with 379 additions and 74 deletions

View file

@ -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>): String {

View file

@ -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
*

View file

@ -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"),

View file

@ -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(

View file

@ -271,6 +271,8 @@ class SettingsFragmentPresenter(
if (Build.VERSION.SDK_INT >= 31) {
add(BooleanSetting.SHOW_SOC_MODEL.key)
}
add(BooleanSetting.SHOW_FW_VERSION.key)
}
}

View file

@ -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())) {

View file

@ -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
)

View file

@ -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"

View file

@ -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 =

View file

@ -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
}
}

View file

@ -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)

View file

@ -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<bool> show_fw_version{linkage, true, "show_firmware_version",
Settings::Category::Overlay,
Settings::Specialization::Default, true, true,
&show_performance_overlay};
Settings::Setting<bool> soc_overlay_background{linkage, false, "soc_overlay_background",
Settings::Category::Overlay,
Settings::Specialization::Default, true, true,

View file

@ -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<Service::NFP::CabinetMode>(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) {

View file

@ -58,6 +58,8 @@
<string name="show_gpu_model_description">Display the host GPU model</string>
<string name="show_soc_model">Show SoC Model</string>
<string name="show_soc_model_description">Display the host SoC model</string>
<string name="show_fw_version">Show Firmware Version</string>
<string name="show_fw_version_description">Display the installed firmware version</string>
<!-- Eden`s Veil -->
<string name="eden_veil">Edens Veil</string>
@ -220,6 +222,9 @@
<string name="keys">Keys</string>
<string name="keys_description">Select your &lt;b>prod.keys&lt;/b> file with the button below.</string>
<string name="select_keys">Select Keys</string>
<string name="firmware">Firmware</string>
<string name="firmware_description">Select your &lt;b>firmware.zip&lt;/b> file with the button below.\nEden currently requires version &lt;b>19.0.1&lt;/b> or below.</string>
<string name="select_firmware">Select Firmware</string>
<string name="games">Games</string>
<string name="games_description">Select your &lt;b>Games&lt;/b> folder with the button below.</string>
<string name="done">Done</string>
@ -232,6 +237,8 @@
<string name="step_complete">Complete!</string>
<!-- Home strings -->
<string name="firmware_version">Firmware:</string>
<string name="alphabetical">Alphabetical</string>
<string name="view_list">List</string>
<string name="view_grid">Grid</string>
@ -262,6 +269,9 @@
<string name="install_prod_keys_warning">Skip adding keys?</string>
<string name="install_prod_keys_warning_description">Valid keys are required to emulate retail games. Only homebrew apps will function if you continue.</string>
<string name="install_prod_keys_warning_help">https://yuzu-mirror.github.io/help/quickstart/#guide-introduction</string>
<string name="install_firmware_warning">Skip adding firmware?</string>
<string name="install_firmware_warning_description">Many games require access to firmware to run properly.</string>
<string name="install_firmware_warning_help">https://yuzu-mirror.github.io/help/quickstart/#guide-introduction</string>
<string name="notifications">Notifications</string>
<string name="notifications_description">Grant the notification permission with the button below.</string>
<string name="give_permission">Grant permission</string>
@ -357,18 +367,24 @@
<string name="no_save_data_found">No save data found</string>
<string name="verify_installed_content">Verify installed content</string>
<string name="verify_installed_content_description">Checks all installed content for corruption</string>
<string name="keys_missing">Encryption keys are missing</string>
<string name="keys_missing_description">Firmware and retail games cannot be decrypted</string>
<string name="keys_missing_help">https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys</string>
<string name="firmware_missing">Firmware is missing or too new</string>
<string name="firmware_missing_description">Some games may not function properly. Eden requires firmware 19.0.1 or below.</string>
<string name="firmware_missing_help">https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware</string>
<!-- Applet launcher strings -->
<string name="qlaunch_applet">Qlaunch</string>
<string name="qlaunch_description">Launch applications from the system home screen</string>
<string name="applets">Applet launcher</string>
<string name="applets_description">Launch system applets using installed firmware</string>
<string name="applets_error_firmware">Firmware not installed</string>
<string name="applets_error_firmware">Firmware not installed or invalid version</string>
<string name="applets_error_applet">Applet not available</string>
<string name="applets_error_description"><![CDATA[Please ensure your <a href="https://yuzu-mirror.github.io/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file and <a href="https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware">firmware</a> are installed and try again.]]></string>
<string name="applets_error_description"><![CDATA[Please ensure your <a href="https://yuzu-mirror.github.io/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file and
<a href="https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware">firmware</a> are installed and try again.<br>Additionally, ensure your firmware is of version 19.0.1 or older.]]></string>
<string name="album_applet">Album</string>
<string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</string>
<string name="mii_edit_applet">Mii edit</string>

View file

@ -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};

View file

@ -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);

View file

@ -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<u64>(Service::AM::AppletProgramId::MiiEdit);

View file

@ -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();