[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

@ -7,16 +7,9 @@ license_header = <<~EOF
EOF EOF
print 'Getting branch changes...' print 'Getting branch changes...'
puts "\n"
branch_name = `git rev-parse --abbrev-ref HEAD`.chomp 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") 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]}" 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") branch_changed_files = `git diff-tree --no-commit-id --name-only #{branch_commit_range} -r`.split("\n")
puts 'done' puts 'done'

85
.ci/license-header.sh Executable file
View file

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

2
.ci/license/header.txt Normal file
View file

@ -0,0 +1,2 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

View file

@ -16,7 +16,7 @@ jobs:
run: git fetch origin master:master run: git fetch origin master:master
- name: Make script executable - name: Make script executable
run: chmod +x ./.ci/license-header.rb run: chmod +x ./.ci/license-header.sh
- name: Check license headers - name: Check license headers
run: ./.ci/license-header.rb run: ./.ci/license-header.sh

View file

@ -246,6 +246,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") 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-graphics-android:1.7.8")
implementation("androidx.compose.ui:ui-text-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 { fun runGitCommand(command: List<String>): String {

View file

@ -16,6 +16,7 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.Keep import androidx.annotation.Keep
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import net.swiftzer.semver.SemVer
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
@ -407,6 +408,26 @@ object NativeLibrary {
*/ */
external fun isFirmwareAvailable(): Boolean 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 * 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_DEVICE_MODEL("show_device_model"),
SHOW_GPU_MODEL("show_gpu_model"), SHOW_GPU_MODEL("show_gpu_model"),
SHOW_SOC_MODEL("show_soc_model"), SHOW_SOC_MODEL("show_soc_model"),
SHOW_FW_VERSION("show_firmware_version"),
SOC_OVERLAY_BACKGROUND("soc_overlay_background"), SOC_OVERLAY_BACKGROUND("soc_overlay_background"),

View file

@ -460,6 +460,14 @@ abstract class SettingsItem(
descriptionId = R.string.show_soc_model_description 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( put(
SingleChoiceSetting( SingleChoiceSetting(

View file

@ -271,6 +271,8 @@ class SettingsFragmentPresenter(
if (Build.VERSION.SDK_INT >= 31) { if (Build.VERSION.SDK_INT >= 31) {
add(BooleanSetting.SHOW_SOC_MODEL.key) 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-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // 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 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() binding.showSocOverlayText.text = sb.toString()
if (BooleanSetting.SOC_OVERLAY_BACKGROUND.getBoolean(NativeConfig.isPerGameConfigLoaded())) { 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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
@ -137,7 +137,7 @@ class HomeSettingsFragment : Fragment() {
binding.root.findNavController() binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment) .navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment)
}, },
{ NativeLibrary.isFirmwareAvailable() }, { NativeLibrary.isFirmwareAvailable() && NativeLibrary.isFirmwareSupported() },
R.string.applets_error_firmware, R.string.applets_error_firmware,
R.string.applets_error_description 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-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -28,6 +32,8 @@ class ProgressDialogFragment : DialogFragment() {
private val PROGRESS_BAR_RESOLUTION = 1000 private val PROGRESS_BAR_RESOLUTION = 1000
var onDialogComplete: (() -> Unit)? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE) val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE) val cancellable = requireArguments().getBoolean(CANCELLABLE)
@ -121,6 +127,11 @@ class ProgressDialogFragment : DialogFragment() {
} }
} }
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
onDialogComplete?.invoke()
}
companion object { companion object {
const val TAG = "IndeterminateProgressDialogFragment" 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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
@ -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( add(
SetupPage( SetupPage(
R.drawable.ic_controller, R.drawable.ic_controller,
@ -321,6 +347,7 @@ class SetupFragment : Fragment() {
} }
private lateinit var keyCallback: SetupCallback private lateinit var keyCallback: SetupCallback
private lateinit var firmwareCallback: SetupCallback
val getProdKey = val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 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 private lateinit var gamesDirCallback: SetupCallback
val getGamesDirectory = 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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
@ -31,6 +31,9 @@ class HomeViewModel : ViewModel() {
private val _checkKeys = MutableStateFlow(false) private val _checkKeys = MutableStateFlow(false)
val checkKeys = _checkKeys.asStateFlow() val checkKeys = _checkKeys.asStateFlow()
private val _checkFirmware = MutableStateFlow(false)
val checkFirmware = _checkFirmware.asStateFlow()
var navigatedToSetup = false var navigatedToSetup = false
fun setStatusBarShadeVisibility(visible: Boolean) { fun setStatusBarShadeVisibility(visible: Boolean) {
@ -63,4 +66,8 @@ class HomeViewModel : ViewModel() {
fun setCheckKeys(value: Boolean) { fun setCheckKeys(value: Boolean) {
_checkKeys.value = value _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 // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui.main package org.yuzu.yuzu_emu.ui.main
@ -62,6 +62,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private val CHECKED_DECRYPTION = "CheckedDecryption" private val CHECKED_DECRYPTION = "CheckedDecryption"
private var checkedDecryption = false private var checkedDecryption = false
private val CHECKED_FIRMWARE = "CheckedFirmware"
private var checkedFirmware = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen() val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@ -76,17 +79,27 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (savedInstanceState != null) { if (savedInstanceState != null) {
checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION) checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION)
checkedFirmware = savedInstanceState.getBoolean(CHECKED_FIRMWARE)
} }
if (!checkedDecryption) { if (!checkedDecryption) {
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext) val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true) .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
if (!firstTimeSetup) { if (!firstTimeSetup) {
checkKeys() checkKeys()
showPreAlphaWarningDialog()
} }
checkedDecryption = true 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) WindowCompat.setDecorFitsSystemWindows(window, false)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
@ -135,6 +148,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (it) checkKeys() if (it) checkKeys()
} }
homeViewModel.checkFirmware.collect(this, resetState = { homeViewModel.setCheckFirmware(false) }) {
if (it) checkFirmware()
}
setInsets() 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) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption) outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption)
outState.putBoolean(CHECKED_FIRMWARE, checkedFirmware)
} }
fun finishSetup(navController: NavController) { fun finishSetup(navController: NavController) {
@ -306,6 +335,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
homeViewModel.setCheckKeys(true) homeViewModel.setCheckKeys(true)
homeViewModel.setCheckFirmware(true)
gamesViewModel.reloadGames(true) gamesViewModel.reloadGames(true)
return true return true
} else { } else {
@ -327,6 +357,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult return@registerForActivityResult
} }
processFirmware(result)
}
fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) {
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") } val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath = val firmwarePath =
@ -357,6 +391,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
cacheFirmwareDir.copyRecursively(firmwarePath, true) cacheFirmwareDir.copyRecursively(firmwarePath, true)
NativeLibrary.initializeSystem(true) NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true) homeViewModel.setCheckKeys(true)
homeViewModel.setCheckFirmware(true)
getString(R.string.save_file_imported_success) getString(R.string.save_file_imported_success)
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -366,8 +401,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
cacheFirmwareDir.deleteRecursively() cacheFirmwareDir.deleteRecursively()
} }
messageToShow messageToShow
}.apply {
onDialogComplete = onComplete
}.show(supportFragmentManager, ProgressDialogFragment.TAG) }.show(supportFragmentManager, ProgressDialogFragment.TAG)
} }
fun uninstallFirmware() { fun uninstallFirmware() {
val firmwarePath = File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") val firmwarePath = File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
@ -382,6 +420,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Optionally reinitialize the system or perform other necessary steps // Optionally reinitialize the system or perform other necessary steps
NativeLibrary.initializeSystem(true) NativeLibrary.initializeSystem(true)
homeViewModel.setCheckKeys(true) homeViewModel.setCheckKeys(true)
homeViewModel.setCheckFirmware(true)
messageToShow = getString(R.string.firmware_uninstalled_success) messageToShow = getString(R.string.firmware_uninstalled_success)
} else { } else {
messageToShow = getString(R.string.firmware_uninstalled_failure) 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-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -136,6 +139,11 @@ namespace AndroidSettings {
Settings::Specialization::Default, true, true, Settings::Specialization::Default, true, true,
&show_performance_overlay}; &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::Setting<bool> soc_overlay_background{linkage, false, "soc_overlay_background",
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Default, true, true, Settings::Specialization::Default, true, true,

View file

@ -57,6 +57,7 @@
#include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/set/system_settings_server.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "frontend_common/config.h" #include "frontend_common/config.h"
#include "hid_core/frontend/emulated_controller.h" #include "hid_core/frontend/emulated_controller.h"
@ -762,7 +763,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass cl
static_cast<Service::NFP::CabinetMode>(jcabinetMode)); static_cast<Service::NFP::CabinetMode>(jcabinetMode));
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) { bool isFirmwarePresent() {
auto bis_system = auto bis_system =
EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents(); EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
if (!bis_system) { if (!bis_system) {
@ -778,6 +779,31 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env,
return true; 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, jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
jstring jpath, jstring jpath,
jstring jprogramId) { jstring jprogramId) {

View file

@ -58,6 +58,8 @@
<string name="show_gpu_model_description">Display the host GPU model</string> <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">Show SoC Model</string>
<string name="show_soc_model_description">Display the host 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 --> <!-- Eden`s Veil -->
<string name="eden_veil">Edens Veil</string> <string name="eden_veil">Edens Veil</string>
@ -220,6 +222,9 @@
<string name="keys">Keys</string> <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="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="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">Games</string>
<string name="games_description">Select your &lt;b>Games&lt;/b> folder with the button below.</string> <string name="games_description">Select your &lt;b>Games&lt;/b> folder with the button below.</string>
<string name="done">Done</string> <string name="done">Done</string>
@ -232,6 +237,8 @@
<string name="step_complete">Complete!</string> <string name="step_complete">Complete!</string>
<!-- Home strings --> <!-- Home strings -->
<string name="firmware_version">Firmware:</string>
<string name="alphabetical">Alphabetical</string> <string name="alphabetical">Alphabetical</string>
<string name="view_list">List</string> <string name="view_list">List</string>
<string name="view_grid">Grid</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">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_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_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">Notifications</string>
<string name="notifications_description">Grant the notification permission with the button below.</string> <string name="notifications_description">Grant the notification permission with the button below.</string>
<string name="give_permission">Grant permission</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="no_save_data_found">No save data found</string>
<string name="verify_installed_content">Verify installed content</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="verify_installed_content_description">Checks all installed content for corruption</string>
<string name="keys_missing">Encryption keys are missing</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_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="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 --> <!-- Applet launcher strings -->
<string name="qlaunch_applet">Qlaunch</string> <string name="qlaunch_applet">Qlaunch</string>
<string name="qlaunch_description">Launch applications from the system home screen</string> <string name="qlaunch_description">Launch applications from the system home screen</string>
<string name="applets">Applet launcher</string> <string name="applets">Applet launcher</string>
<string name="applets_description">Launch system applets using installed firmware</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_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">Album</string>
<string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</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> <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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -11,8 +14,8 @@ namespace HLE::ApiVersion {
// Horizon OS version constants. // Horizon OS version constants.
constexpr u8 HOS_VERSION_MAJOR = 19; constexpr u8 HOS_VERSION_MAJOR = 20;
constexpr u8 HOS_VERSION_MINOR = 0; constexpr u8 HOS_VERSION_MINOR = 1;
constexpr u8 HOS_VERSION_MICRO = 1; constexpr u8 HOS_VERSION_MICRO = 1;
// NintendoSDK version constants. // NintendoSDK version constants.
@ -21,15 +24,15 @@ constexpr u8 SDK_REVISION_MAJOR = 1;
constexpr u8 SDK_REVISION_MINOR = 0; constexpr u8 SDK_REVISION_MINOR = 0;
constexpr char PLATFORM_STRING[] = "NX"; constexpr char PLATFORM_STRING[] = "NX";
constexpr char VERSION_HASH[] = "835c78223df116284ef7e36e8441760edc81729c"; constexpr char VERSION_HASH[] = "9ffad64d79dd150490201461bdf66c8db963f57d";
constexpr char DISPLAY_VERSION[] = "19.0.1"; constexpr char DISPLAY_VERSION[] = "20.1.1";
constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 19.0.1-1.0"; constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 20.1.1-1.0";
// Atmosphere version constants. // Atmosphere version constants.
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1; constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 0; constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 9;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 0; constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 1;
constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) { constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) {
return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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. // 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. // NOTE: Official kernel versions have SVC major = SDK major + 4, SVC minor = SDK minor.
constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(15); constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(20);
constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(3); constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion(5);
constexpr inline u32 SupportedKernelVersion = constexpr inline u32 SupportedKernelVersion =
EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion); EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion);

View file

@ -462,6 +462,9 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
// Gen keys if necessary // Gen keys if necessary
OnCheckFirmwareDecryption(); OnCheckFirmwareDecryption();
// Check firmware
OnCheckFirmware();
game_list->LoadCompatibilityList(); game_list->LoadCompatibilityList();
// force reload on first load to ensure add-ons get updated // force reload on first load to ensure add-ons get updated
game_list->PopulateAsync(UISettings::values.game_dirs, false); game_list->PopulateAsync(UISettings::values.game_dirs, false);
@ -4326,6 +4329,7 @@ void GMainWindow::OnInstallFirmware() {
progress.close(); progress.close();
OnCheckFirmwareDecryption(); OnCheckFirmwareDecryption();
OnCheckFirmware();
} }
void GMainWindow::OnInstallDecryptionKeys() { void GMainWindow::OnInstallDecryptionKeys() {
@ -4404,6 +4408,7 @@ void GMainWindow::OnInstallDecryptionKeys() {
} }
OnCheckFirmwareDecryption(); OnCheckFirmwareDecryption();
OnCheckFirmware();
} }
void GMainWindow::OnAbout() { void GMainWindow::OnAbout() {
@ -5067,6 +5072,34 @@ void GMainWindow::OnCheckFirmwareDecryption() {
UpdateMenuState(); 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() { bool GMainWindow::CheckFirmwarePresence() {
constexpr u64 MiiEditId = static_cast<u64>(Service::AM::AppletProgramId::MiiEdit); 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-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -414,6 +417,7 @@ private slots:
void OnCreateHomeMenuShortcut(GameListShortcutTarget target); void OnCreateHomeMenuShortcut(GameListShortcutTarget target);
void OnCaptureScreenshot(); void OnCaptureScreenshot();
void OnCheckFirmwareDecryption(); void OnCheckFirmwareDecryption();
void OnCheckFirmware();
void OnLanguageChanged(const QString& locale); void OnLanguageChanged(const QString& locale);
void OnMouseActivity(); void OnMouseActivity();
bool OnShutdownBegin(); bool OnShutdownBegin();