diff --git a/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt
index bef01f156..164e85b49 100644
--- a/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt
@@ -4,6 +4,7 @@
package org.citron.citron_emu.activities
import android.annotation.SuppressLint
+import android.app.AlertDialog
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
@@ -80,6 +81,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onCreate(savedInstanceState)
+ // Check if firmware is available
+ if (!NativeLibrary.isFirmwareAvailable() || !NativeLibrary.checkFirmwarePresence()) {
+ AlertDialog.Builder(this)
+ .setTitle(R.string.firmware_missing_title)
+ .setMessage(R.string.firmware_missing_message)
+ .setPositiveButton(R.string.ok) { _, _ ->
+ finish()
+ }
+ .setCancelable(false)
+ .show()
+ return
+ }
+
// Add license verification at the start
LicenseVerifier.verifyLicense(this)
diff --git a/src/android/app/src/main/jni/native_library.cpp b/src/android/app/src/main/jni/native_library.cpp
new file mode 100644
index 000000000..41152ef41
--- /dev/null
+++ b/src/android/app/src/main/jni/native_library.cpp
@@ -0,0 +1,31 @@
+#include "core/crypto/key_manager.h"
+#include "core/hle/service/am/am.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/content_archive.h"
+#include "core/system.h"
+
+extern "C" {
+
+JNIEXPORT jboolean JNICALL Java_org_citron_citron_1emu_NativeLibrary_isFirmwareAvailable(
+ JNIEnv* env, jobject obj) {
+ return Core::Crypto::KeyManager::Instance().IsFirmwareAvailable();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_citron_citron_1emu_NativeLibrary_checkFirmwarePresence(
+ JNIEnv* env, jobject obj) {
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ constexpr u64 QLaunchId = 0x0100000000001000; // Home Menu applet ID
+
+ auto& system = Core::System::GetInstance();
+ auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+
+ return (mii_applet_nca != nullptr && qlaunch_nca != nullptr);
+}
+
+} // extern "C"
\ No newline at end of file
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index ce2b21bf1..355384ab4 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -23,6 +23,9 @@
Keys
Select your <b>prod.keys</b> file with the button below.
Select Keys
+ Missing Firmware
+ Firmware is required to launch games.\n\nPlease install firmware by placing your Switch firmware files in the appropriate location.
+ OK
Games
Select your <b>Games</b> folder with the button below.
Done
diff --git a/src/citron/main.cpp b/src/citron/main.cpp
index cb6cedd19..d4ff764a8 100644
--- a/src/citron/main.cpp
+++ b/src/citron/main.cpp
@@ -4787,19 +4787,24 @@ void GMainWindow::OnCheckFirmwareDecryption() {
}
bool GMainWindow::CheckFirmwarePresence() {
- constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit);
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ constexpr u64 QLaunchId = 0x0100000000001000; // Home Menu applet ID
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
return false;
}
+ // Check for essential system applets
auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
- if (!mii_applet_nca) {
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+
+ if (!mii_applet_nca || !qlaunch_nca) {
return false;
}
- return true;
+ // Also check for essential keys
+ return Core::Crypto::KeyManager::Instance().IsFirmwareAvailable();
}
void GMainWindow::SetFirmwareVersion() {
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index e61a59fc9..eb5dd8cb1 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -1290,4 +1290,31 @@ bool KeyManager::AddTicket(const Ticket& ticket) {
SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]);
return true;
}
+
+bool KeyManager::IsFirmwareAvailable() const {
+ // Check for essential keys that would only be present with firmware
+ if (!HasKey(S128KeyType::Master, 0)) {
+ return false;
+ }
+
+ // Check for at least one titlekek
+ bool has_titlekek = false;
+ for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
+ if (HasKey(S128KeyType::Titlekek, i)) {
+ has_titlekek = true;
+ break;
+ }
+ }
+
+ if (!has_titlekek) {
+ return false;
+ }
+
+ // Check for header key
+ if (!HasKey(S256KeyType::Header)) {
+ return false;
+ }
+
+ return true;
+}
} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 0adf3701f..2a5f0c093 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -296,6 +296,9 @@ public:
void ReloadKeys();
bool AreKeysLoaded() const;
+ // Check if firmware is installed by verifying essential keys
+ bool IsFirmwareAvailable() const;
+
private:
KeyManager();
diff --git a/src/core/file_sys/content_manager.cpp b/src/core/file_sys/content_manager.cpp
new file mode 100644
index 000000000..fd53978fc
--- /dev/null
+++ b/src/core/file_sys/content_manager.cpp
@@ -0,0 +1,25 @@
+#include "core/system.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/content_archive.h"
+#include "core/crypto/key_manager.h"
+
+bool ContentManager::IsFirmwareAvailable() {
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ constexpr u64 QLaunchId = 0x0100000000001000; // Home Menu applet ID
+
+ auto& system = Core::System::GetInstance();
+ auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+
+ if (!mii_applet_nca || !qlaunch_nca) {
+ return false;
+ }
+
+ // Also check for essential keys
+ return Core::Crypto::KeyManager::Instance().IsFirmwareAvailable();
+}
\ No newline at end of file
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index b6e355622..0135d6f81 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -19,6 +19,10 @@
#include "core/loader/nso.h"
#include "core/loader/nsp.h"
#include "core/loader/xci.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/content_archive.h"
namespace Loader {
@@ -250,6 +254,20 @@ std::unique_ptr GetLoader(Core::System& system, FileSys::VirtualFile
return nullptr;
}
+ // Check if firmware is available
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
+ if (bis_system) {
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ if (!mii_applet_nca) {
+ LOG_ERROR(Loader, "Firmware is required to launch games but is not available");
+ return nullptr;
+ }
+ } else {
+ LOG_ERROR(Loader, "System NAND contents not available");
+ return nullptr;
+ }
+
FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName());