diff --git a/.ci/license-header.sh b/.ci/license-header.sh
index fecffaa7d3..3d4929d1c1 100755
--- a/.ci/license-header.sh
+++ b/.ci/license-header.sh
@@ -5,10 +5,13 @@ HEADER_HASH="$(cat "$PWD/.ci/license/header-hash.txt")"
echo "Getting branch changes"
-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`
+# 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`
+
+BASE=`git merge-base master HEAD`
+FILES=`git diff --name-only $BASE`
#FILES=$(git diff --name-only master)
diff --git a/docs/CPM.md b/docs/CPM.md
index 2afcdaf164..bce224da40 100644
--- a/docs/CPM.md
+++ b/docs/CPM.md
@@ -245,6 +245,6 @@ include(CPMUtil)
Currently, `cpm-fetch.sh` defines the following directories for cpmfiles (max depth of 2, so subdirs are caught as well):
-`externals src/yuzu src/dynarmic .`
+`externals src/qt_common src/dynarmic .`
Whenever you add a new cpmfile, update the script accordingly
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eb66e55964..184b049d06 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -234,6 +234,8 @@ if (YUZU_ROOM_STANDALONE)
endif()
if (ENABLE_QT)
+ add_definitions(-DYUZU_QT_WIDGETS)
+ add_subdirectory(qt_common)
add_subdirectory(yuzu)
endif()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index 0a2cb41745..9ea2a9ee17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -272,8 +272,6 @@ class GameAdapter(private val activity: AppCompatActivity) :
binding.root.findNavController().navigate(action)
}
- val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.loader_requires_firmware)
@@ -288,23 +286,6 @@ class GameAdapter(private val activity: AppCompatActivity) :
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
- } else if (BooleanSetting.DISABLE_NCA_VERIFICATION.getBoolean(false) && !preferences.getBoolean(
- Settings.PREF_HIDE_NCA_POPUP, false)) {
- MaterialAlertDialogBuilder(activity)
- .setTitle(R.string.nca_verification_disabled)
- .setMessage(activity.getString(R.string.nca_verification_disabled_description))
- .setPositiveButton(android.R.string.ok) { _, _ ->
- launch()
- }
- .setNeutralButton(R.string.dont_show_again) { _, _ ->
- preferences.edit {
- putBoolean(Settings.PREF_HIDE_NCA_POPUP, true)
- }
-
- launch()
- }
- .setNegativeButton(android.R.string.cancel) { _, _ -> }
- .show()
} else {
launch()
}
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 6d4bfd97ac..3c5b9003de 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
@@ -35,7 +35,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_SAMPLE_SHADING("sample_shading"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"),
- DISABLE_NCA_VERIFICATION("disable_nca_verification"),
BLACK_BACKGROUNDS("black_backgrounds"),
JOYSTICK_REL_CENTER("joystick_rel_center"),
DPAD_SLIDE("dpad_slide"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 2564849ef4..a52f582031 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -37,7 +37,6 @@ object Settings {
const val PREF_SHOULD_SHOW_PRE_ALPHA_WARNING = "ShouldShowPreAlphaWarning"
const val PREF_SHOULD_SHOW_EDENS_VEIL_DIALOG = "ShouldShowEdensVeilDialog"
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
- const val PREF_HIDE_NCA_POPUP = "HideNCAVerificationPopup"
const val SECTION_STATS_OVERLAY = "Stats Overlay"
// Deprecated input overlay preference keys
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 883d8efaef..1f2ba81a73 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
@@ -297,13 +297,6 @@ abstract class SettingsItem(
descriptionId = R.string.use_custom_rtc_description
)
)
- put(
- SwitchSetting(
- BooleanSetting.DISABLE_NCA_VERIFICATION,
- titleId = R.string.disable_nca_verification,
- descriptionId = R.string.disable_nca_verification_description
- )
- )
put(
StringInputSetting(
StringSetting.WEB_TOKEN,
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 630bcb0d74..14d62ceec3 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
@@ -210,7 +210,6 @@ class SettingsFragmentPresenter(
add(IntSetting.LANGUAGE_INDEX.key)
add(BooleanSetting.USE_CUSTOM_RTC.key)
add(LongSetting.CUSTOM_RTC.key)
- add(BooleanSetting.DISABLE_NCA_VERIFICATION.key)
add(HeaderSetting(R.string.network))
add(StringSetting.WEB_TOKEN.key)
diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml
index c705ae08f1..ed3fc76f3b 100644
--- a/src/android/app/src/main/res/values-ar/strings.xml
+++ b/src/android/app/src/main/res/values-ar/strings.xml
@@ -498,8 +498,6 @@
ساعة مخصصة في الوقت الحقيقي
يسمح لك بتعيين ساعة مخصصة في الوقت الفعلي منفصلة عن وقت النظام الحالي لديك
تعيين ساعة مخصصة في الوقت الحقيقي
- تعطيل التحقق من NCA
- يعطل التحقق من سلامة أرشيفات محتوى NCA. قد يحسن هذا من سرعة التحميل لكنه يخاطر بتلف البيانات أو تمرير ملفات غير صالحة دون اكتشاف. ضروري لجعل الألعاب والتحديثات التي تتطلب نظامًا أساسيًا 20+ تعمل.
توليد
diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml
index af0eeeaa45..34b1ae6252 100644
--- a/src/android/app/src/main/res/values-ckb/strings.xml
+++ b/src/android/app/src/main/res/values-ckb/strings.xml
@@ -482,8 +482,6 @@
RTCی تایبەتمەند
ڕێگەت پێدەدات کاتژمێرێکی کاتی ڕاستەقینەی تایبەتمەند دابنێیت کە جیاوازە لە کاتی ئێستای سیستەمەکەت.
دانانی RTCی تایبەتمەند
- ناچالاککردنی پشکنینی NCA
- پشکنینی پێکهاتەی ئارشیڤەکانی ناوەڕۆکی NCA ناچالاک دەکات. ئەمە لەوانەیە خێرایی بارکردن بهرهوپێش ببات، بەڵام مەترسی لەناوچوونی داتا یان ئەوەی فایلە نادروستەکان بەبێ ئەوەی دۆزرایەوە تێپەڕبن زیاتر دەکات. بۆ ئەوەی یاری و نوێکردنەوەکان کار بکەن کە پێویستی بە فریموێری 20+ هەیە زۆر پێویستە.
بەرهەم هێنان
diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml
index 8d42b8303f..293524271e 100644
--- a/src/android/app/src/main/res/values-cs/strings.xml
+++ b/src/android/app/src/main/res/values-cs/strings.xml
@@ -458,8 +458,6 @@
Vlastní RTC
Vlastní nastavení času
Nastavit vlastní RTC
- Zakázat ověřování NCA
- Zakáže ověřování integrity archivů obsahu NCA. To může zlepšit rychlost načítání, ale hrozí poškození dat nebo neodhalení neplatných souborů. Je nutné, aby fungovaly hry a aktualizace vyžadující firmware 20+.
Generovat
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index 12b7183cae..8712f455de 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -506,8 +506,6 @@
RTC personalizado
Te permite tener un reloj personalizado en tiempo real diferente del tiempo del propio sistema.
Configurar RTC personalizado
- Desactivar verificación NCA
- Desactiva la verificación de integridad de los archivos de contenido NCA. Esto puede mejorar la velocidad de carga, pero arriesga corrupción de datos o que archivos inválidos pasen desapercibidos. Es necesario para que funcionen juegos y actualizaciones que requieren firmware 20+.
Generar
diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml
index d7ac1b770a..07ff8ff4e0 100644
--- a/src/android/app/src/main/res/values-fa/strings.xml
+++ b/src/android/app/src/main/res/values-fa/strings.xml
@@ -504,8 +504,6 @@
زمان سفارشی
به شما امکان میدهد یک ساعت سفارشی جدا از زمان فعلی سیستم خود تنظیم کنید.
تنظیم زمان سفارشی
- غیرفعال کردن تأیید اعتبار NCA
- بررسی صحت آرشیوهای محتوای NCA را غیرفعال میکند. این ممکن است سرعت بارگذاری را بهبود بخشد اما خطر خرابی داده یا تشخیص داده نشدن فایلهای نامعتبر را به همراه دارد. برای کار کردن بازیها و بهروزرسانیهایی که به فرمور ۲۰+ نیاز دارند، ضروری است.
تولید
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index 62e67a6fee..2e06ac98e1 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -506,8 +506,6 @@
RTC personnalisé
Vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système.
Définir l\'horloge RTC personnalisée
- Désactiver la vérification NCA
- Désactive la vérification d\'intégrité des archives de contenu NCA. Cela peut améliorer la vitesse de chargement mais risque une corruption des données ou que des fichiers invalides ne soient pas détectés. Est nécessaire pour faire fonctionner les jeux et mises à jour nécessitant un firmware 20+.
Générer
diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml
index ec5b526ce0..c0c835d633 100644
--- a/src/android/app/src/main/res/values-he/strings.xml
+++ b/src/android/app/src/main/res/values-he/strings.xml
@@ -505,8 +505,6 @@
RTC מותאם אישית
מאפשר לך לקבוע שעון זמן אמת נפרד משעון המערכת שלך.
קבע RTC מותאם אישית
- השבת אימות NCA
- משבית את אימות השלמות של ארכיוני התוכן של NCA. זה עשוי לשפר את מהירות הטעינה אך מסתכן בשחיקת נתונים או שמא קבצים לא חוקיים יעברו ללא זיהוי. זה הכרחי כדי לגרום למשחקים ועדכונים הדורשים firmware 20+ לעבוד.
יצירה
diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml
index b45692f147..46a5ac7cce 100644
--- a/src/android/app/src/main/res/values-hu/strings.xml
+++ b/src/android/app/src/main/res/values-hu/strings.xml
@@ -501,8 +501,6 @@
Egyéni RTC
Megadhatsz egy valós idejű órát, amely eltér a rendszer által használt órától.
Egyéni RTC beállítása
- NCA ellenőrzés letiltása
- Letiltja az NCA tartalomarchívumok integritás-ellenőrzését. Ez javíthatja a betöltési sebességet, de az adatsérülés vagy az érvénytelen fájlok észrevétlen maradásának kockázatával jár. Elengedhetetlen a 20+ firmware-et igénylő játékok és frissítések működtetéséhez.
Generálás
diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml
index 1817fc654a..cffb526ad5 100644
--- a/src/android/app/src/main/res/values-id/strings.xml
+++ b/src/android/app/src/main/res/values-id/strings.xml
@@ -502,8 +502,6 @@
RTC Kustom
Memungkinkan Anda untuk mengatur jam waktu nyata kustom yang terpisah dari waktu sistem saat ini Anda.
Setel RTC Kustom
- Nonaktifkan Verifikasi NCA
- Menonaktifkan verifikasi integritas arsip konten NCA. Ini dapat meningkatkan kecepatan pemuatan tetapi berisiko kerusakan data atau file yang tidak valid tidak terdeteksi. Diperlukan untuk membuat game dan pembaruan yang membutuhkan firmware 20+ bekerja.
Hasilkan
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index ff3706dd40..cb234cf61e 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -505,8 +505,6 @@
RTC Personalizzato
Ti permette di impostare un orologio in tempo reale personalizzato, completamente separato da quello di sistema.
Imposta un orologio in tempo reale personalizzato
- Disabilita verifica NCA
- Disabilita la verifica dell\'integrità degli archivi di contenuto NCA. Può migliorare la velocità di caricamento ma rischia il danneggiamento dei dati o che file non validi passino inosservati. Necessario per far funzionare giochi e aggiornamenti che richiedono il firmware 20+.
Genera
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index a6c1ebf8ab..abedb1e0bc 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -491,8 +491,6 @@
カスタム RTC
現在のシステム時間とは別に、任意のリアルタイムクロックを設定できます。
カスタムRTCを設定
- NCA検証を無効化
- NCAコンテンツアーカイブの整合性検証を無効にします。読み込み速度が向上する可能性がありますが、データ破損や不正なファイルが検出されないリスクがあります。ファームウェア20以上が必要なゲームや更新を動作させるために必要です。
生成
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index 01e2c5f4c0..c6d9457744 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -501,8 +501,6 @@
사용자 지정 RTC
현재 시스템 시간과 별도로 사용자 지정 RTC를 설정할 수 있습니다.
사용자 지정 RTC 설정
- NCA 검증 비활성화
- NCA 콘텐츠 아카이브의 무결성 검증을 비활성화합니다. 로딩 속도를 향상시킬 수 있지만 데이터 손상이나 유효하지 않은 파일이 미검증될 위험이 있습니다. 펌웨어 20+가 필요한 게임 및 업데이트를 실행하려면 필요합니다.
생성
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index 90c0dbf05e..3cc4c6d12c 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -482,8 +482,6 @@
Tilpasset Sannhetstidsklokke
Gjør det mulig å stille inn en egendefinert sanntidsklokke separat fra den gjeldende systemtiden.
Angi tilpasset RTC
- Deaktiver NCA-verifisering
- Deaktiverer integritetsverifisering av NCA-innholdsarkiv. Dette kan forbedre lastehastigheten, men medfører risiko for datakorrupsjon eller at ugyldige filer ikke oppdages. Er nødvendig for å få spill og oppdateringer som trenger firmware 20+ til å fungere.
Generer
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index 080064edaf..b9858838e8 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -482,8 +482,6 @@
Niestandardowy RTC
Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android.
Ustaw niestandardowy czas RTC
- Wyłącz weryfikację NCA
- Wyłącza weryfikację integralności archiwów zawartości NCA. Może to poprawić szybkość ładowania, ale niesie ryzyko uszkodzenia danych lub niezauważenia nieprawidłowych plików. Konieczne, aby działały gry i aktualizacje wymagające firmware\'u 20+.
Generuj
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 3dc1f83e6e..1296fad889 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -506,8 +506,6 @@
Data e hora personalizadas
Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo.
Definir um relógio em tempo real personalizado
- Desativar verificação NCA
- Desativa a verificação de integridade de arquivos de conteúdo NCA. Pode melhorar a velocidade de carregamento, mas arrisica corromper dados ou que arquivos inválidos passem despercebidos. É necessário para fazer jogos e atualizações que exigem firmware 20+ funcionarem.
Gerar
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index feb4950fcb..a166907877 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -506,8 +506,6 @@
RTC personalizado
Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo.
Defina um relógio em tempo real personalizado
- Desativar verificação NCA
- Desativa a verificação de integridade dos arquivos de conteúdo NCA. Pode melhorar a velocidade de carregamento, mas arrisca a corrupção de dados ou que ficheiros inválidos passem despercebidos. É necessário para que jogos e atualizações que necessitam de firmware 20+ funcionem.
Gerar
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index d56c94f10b..dc68c7b817 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -508,8 +508,6 @@
Пользовательский RTC
Позволяет установить пользовательские часы реального времени отдельно от текущего системного времени.
Установить пользовательский RTC
- Отключить проверку NCA
- Отключает проверку целостности архивов содержимого NCA. Может улучшить скорость загрузки, но есть риск повреждения данных или того, что недействительные файлы останутся незамеченными. Необходимо для работы игр и обновлений, требующих прошивку 20+.
Создать
diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml
index 5b444f6187..c547b3f761 100644
--- a/src/android/app/src/main/res/values-sr/strings.xml
+++ b/src/android/app/src/main/res/values-sr/strings.xml
@@ -457,8 +457,6 @@
Цустом РТЦ
Омогућава вам да поставите прилагођени сат у реалном времену одвојено од тренутног времена система.
Подесите прилагођени РТЦ
- Искључи верификацију НЦА
- Искључује верификацију интегритета НЦА архива садржаја. Ово може побољшати брзину учитавања, али ризикује оштећење података или да неважећи фајлови прођу незапажено. Неопходно је да би игре и ажурирања која захтевају firmware 20+ радили.
Генериши
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 4ae61340dc..b48a8a4a58 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -495,8 +495,6 @@
Свій RTC
Дозволяє встановити власний час (Real-time clock, або RTC), відмінний від системного.
Встановити RTC
- Вимкнути перевірку NCA
- Вимкає перевірку цілісності архівів вмісту NCA. Може покращити швидкість завантаження, але ризикує пошкодженням даних або тим, що недійсні файли залишаться непоміченими. Необхідно для роботи ігор та оновлень, які вимагають прошивки 20+.
Створити
diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml
index dd64fbca55..b19d437ceb 100644
--- a/src/android/app/src/main/res/values-vi/strings.xml
+++ b/src/android/app/src/main/res/values-vi/strings.xml
@@ -482,8 +482,6 @@
RTC tuỳ chỉnh
Cho phép bạn thiết lập một đồng hồ thời gian thực tùy chỉnh riêng biệt so với thời gian hệ thống hiện tại.
Thiết lập RTC tùy chỉnh
- Tắt xác minh NCA
- Tắt xác minh tính toàn vẹn của kho lưu trữ nội dung NCA. Có thể cải thiện tốc độ tải nhưng có nguy cơ hỏng dữ liệu hoặc các tệp không hợp lệ không bị phát hiện. Cần thiết để các trò chơi và bản cập nhật yêu cầu firmware 20+ hoạt động.
Tạo
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index a12c00063f..95ab14abd0 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -500,8 +500,6 @@
自定义系统时间
此选项允许您设置与目前系统时间相独立的自定义系统时钟。
设置自定义系统时间
-禁用NCA验证
-禁用NCA内容存档的完整性验证。可能会提高加载速度,但存在数据损坏或无效文件未被检测到的风险。对于需要固件20+的游戏和更新是必需的。
生成
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index a125553102..8640875f2c 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -505,8 +505,6 @@
自訂 RTC
允許您設定與您的目前系統時間相互獨立的自訂即時時鐘。
設定自訂 RTC
- 停用NCA驗證
- 停用NCA內容存檔的完整性驗證。可能會提高載入速度,但存在資料損毀或無效檔案未被偵測到的風險。對於需要韌體20+的遊戲和更新是必需的。
生成
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 20c6e85b6c..d99776b440 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -475,8 +475,6 @@
Custom RTC
Allows you to set a custom real-time clock separate from your current system time.
Set custom RTC
- Disable NCA Verification
- Disables integrity verification of NCA content archives. This may improve loading speed but risks data corruption or invalid files going undetected. Some games that require firmware versions 20+ may need this as well.
Generate
@@ -785,9 +783,6 @@
Game Requires Firmware
dump and install firmware, or press "OK" to launch anyways.]]>
- NCA Verification Disabled
- This is required to run new games and updates, but may cause instability or crashes if NCA files are corrupt, modified, or tampered with. If unsure, re-enable verification in Advanced Settings -> System, and use firmware versions of 19.0.1 or below.
-
Searching for game...
Game not found for Title ID: %1$s
diff --git a/src/common/settings.h b/src/common/settings.h
index 9d448a2b38..fafd765804 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -626,10 +626,6 @@ struct Values {
true, true, &rng_seed_enabled};
Setting device_name{
linkage, "Eden", "device_name", Category::System, Specialization::Default, true, true};
- SwitchableSetting disable_nca_verification{linkage, true, "disable_nca_verification",
- Category::System, Specialization::Default};
- Setting hide_nca_verification_popup{
- linkage, false, "hide_nca_verification_popup", Category::System, Specialization::Default};
Setting current_user{linkage, 0, "current_user", Category::System};
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
index 615a624f4f..71ba458cef 100644
--- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
@@ -238,9 +238,7 @@ void BucketTree::Initialize(size_t node_size, s64 end_offset) {
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
ASSERT(Common::IsPowerOfTwo(node_size));
- if (!Settings::values.disable_nca_verification.GetValue()) {
- ASSERT(end_offset > 0);
- }
+ ASSERT(end_offset > 0);
ASSERT(!this->IsInitialized());
m_node_size = node_size;
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
index 4cfa5c58f8..37fb71e9e3 100644
--- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -1296,91 +1296,65 @@ Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(
ASSERT(base_storage != nullptr);
ASSERT(layer_info_offset >= 0);
- if (!Settings::values.disable_nca_verification.GetValue()) {
- // Define storage types.
- using VerificationStorage = HierarchicalIntegrityVerificationStorage;
- using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
+ // Define storage types.
+ using VerificationStorage = HierarchicalIntegrityVerificationStorage;
+ using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
- // Validate the meta info.
- HierarchicalIntegrityVerificationInformation level_hash_info;
- std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info),
- sizeof(level_hash_info));
+ // Validate the meta info.
+ HierarchicalIntegrityVerificationInformation level_hash_info;
+ std::memcpy(std::addressof(level_hash_info),
+ std::addressof(meta_info.level_hash_info),
+ sizeof(level_hash_info));
- R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
- R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+ R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+ R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
- // Get the base storage size.
- s64 base_storage_size = base_storage->GetSize();
+ // Get the base storage size.
+ s64 base_storage_size = base_storage->GetSize();
- // Create storage info.
- StorageInfo storage_info;
- for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) {
- const auto& layer_info = level_hash_info.info[i];
- R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
- ResultNcaBaseStorageOutOfRangeD);
-
- storage_info[i + 1] = std::make_shared(
- base_storage, layer_info.size, layer_info_offset + layer_info.offset);
- }
-
- // Set the last layer info.
- const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
- const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
- R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
+ // Create storage info.
+ StorageInfo storage_info;
+ for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) {
+ const auto& layer_info = level_hash_info.info[i];
+ R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
ResultNcaBaseStorageOutOfRangeD);
- if (layer_info_offset > 0) {
- R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
- ResultRomNcaInvalidIntegrityLayerInfoOffset);
- }
- storage_info[level_hash_info.max_layers - 1] = std::make_shared(
- std::move(base_storage), layer_info.size, last_layer_info_offset);
- // Make the integrity romfs storage.
- auto integrity_storage = std::make_shared();
- R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
-
- // Initialize the integrity storage.
- R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info,
- max_data_cache_entries, max_hash_cache_entries,
- buffer_level));
-
- // Set the output.
- *out = std::move(integrity_storage);
- R_SUCCEED();
- } else {
- // Read IVFC layout
- HierarchicalIntegrityVerificationInformation lhi{};
- std::memcpy(std::addressof(lhi), std::addressof(meta_info.level_hash_info), sizeof(lhi));
-
- R_UNLESS(IntegrityMinLayerCount <= lhi.max_layers,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
- R_UNLESS(lhi.max_layers <= IntegrityMaxLayerCount,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
-
- const auto& data_li = lhi.info[lhi.max_layers - 2];
-
- const s64 base_size = base_storage->GetSize();
-
- // Compute the data layer window
- const s64 data_off = (layer_info_offset > 0) ? 0LL : data_li.offset.Get();
- R_UNLESS(data_off + data_li.size <= base_size, ResultNcaBaseStorageOutOfRangeD);
- if (layer_info_offset > 0) {
- R_UNLESS(data_off + data_li.size <= layer_info_offset,
- ResultRomNcaInvalidIntegrityLayerInfoOffset);
- }
-
- // TODO: Passthrough (temporary compatibility: integrity disabled)
- auto data_view = std::make_shared(base_storage, data_li.size, data_off);
- R_UNLESS(data_view != nullptr, ResultAllocationMemoryFailedAllocateShared);
-
- auto passthrough = std::make_shared(std::move(data_view));
- R_UNLESS(passthrough != nullptr, ResultAllocationMemoryFailedAllocateShared);
-
- *out = std::move(passthrough);
- R_SUCCEED();
+ storage_info[i + 1] = std::make_shared(base_storage,
+ layer_info.size,
+ layer_info_offset + layer_info.offset);
}
+
+ // Set the last layer info.
+ const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
+ const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
+ R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
+ ResultNcaBaseStorageOutOfRangeD);
+ if (layer_info_offset > 0) {
+ R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
+ ResultRomNcaInvalidIntegrityLayerInfoOffset);
+ }
+ storage_info[level_hash_info.max_layers - 1]
+ = std::make_shared(std::move(base_storage),
+ layer_info.size,
+ last_layer_info_offset);
+
+ // Make the integrity romfs storage.
+ auto integrity_storage = std::make_shared();
+ R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the integrity storage.
+ R_TRY(integrity_storage->Initialize(level_hash_info,
+ meta_info.master_hash,
+ storage_info,
+ max_data_cache_entries,
+ max_hash_cache_entries,
+ buffer_level));
+
+ // Set the output.
+ *out = std::move(integrity_storage);
+ R_SUCCEED();
}
Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out,
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 106922e04f..edf51e74de 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -126,6 +129,10 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
std::string out = GetSaveDataSpaceIdPath(space);
+ LOG_INFO(Common_Filesystem, "Save ID: {:016X}", save_id);
+ LOG_INFO(Common_Filesystem, "User ID[1]: {:016X}", user_id[1]);
+ LOG_INFO(Common_Filesystem, "User ID[0]: {:016X}", user_id[0]);
+
switch (type) {
case SaveDataType::System:
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
diff --git a/src/frontend_common/firmware_manager.h b/src/frontend_common/firmware_manager.h
index 20f3b41478..23fce59eb3 100644
--- a/src/frontend_common/firmware_manager.h
+++ b/src/frontend_common/firmware_manager.h
@@ -7,6 +7,7 @@
#include "common/common_types.h"
#include "core/core.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/filesystem/filesystem.h"
#include
@@ -143,6 +144,8 @@ inline std::pair GetFirmwareVersion
return {firmware_data, result};
}
+
+// TODO(crueter): GET AS STRING
}
-#endif // FIRMWARE_MANAGER_H
+#endif
diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt
new file mode 100644
index 0000000000..9d292da401
--- /dev/null
+++ b/src/qt_common/CMakeLists.txt
@@ -0,0 +1,47 @@
+# 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
+
+find_package(Qt6 REQUIRED COMPONENTS Core)
+find_package(Qt6 REQUIRED COMPONENTS Core)
+
+add_library(qt_common STATIC
+ qt_common.h
+ qt_common.cpp
+
+ uisettings.cpp
+ uisettings.h
+
+ qt_config.cpp
+ qt_config.h
+
+ shared_translation.cpp
+ shared_translation.h
+ qt_path_util.h qt_path_util.cpp
+ qt_game_util.h qt_game_util.cpp
+ qt_frontend_util.h qt_frontend_util.cpp
+ qt_meta.h qt_meta.cpp
+ qt_content_util.h qt_content_util.cpp
+ qt_rom_util.h qt_rom_util.cpp
+ qt_applet_util.h qt_applet_util.cpp
+ qt_progress_dialog.h qt_progress_dialog.cpp
+
+)
+
+create_target_directory_groups(qt_common)
+
+# TODO(crueter)
+if (ENABLE_QT)
+ target_link_libraries(qt_common PRIVATE Qt6::Widgets)
+endif()
+
+add_subdirectory(externals)
+
+target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip frozen::frozen)
+target_link_libraries(qt_common PRIVATE Qt6::Core)
+
+if (NOT WIN32)
+ target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
+endif()
diff --git a/src/yuzu/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt
similarity index 86%
rename from src/yuzu/externals/CMakeLists.txt
rename to src/qt_common/externals/CMakeLists.txt
index 50594a741f..189a52c0a6 100644
--- a/src/yuzu/externals/CMakeLists.txt
+++ b/src/qt_common/externals/CMakeLists.txt
@@ -14,3 +14,7 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
# QuaZip
AddJsonPackage(quazip)
+
+# frozen
+# TODO(crueter): Qt String Lookup
+AddJsonPackage(frozen)
diff --git a/src/yuzu/externals/cpmfile.json b/src/qt_common/externals/cpmfile.json
similarity index 55%
rename from src/yuzu/externals/cpmfile.json
rename to src/qt_common/externals/cpmfile.json
index e3590d0f7f..0b464b95b2 100644
--- a/src/yuzu/externals/cpmfile.json
+++ b/src/qt_common/externals/cpmfile.json
@@ -8,5 +8,12 @@
"options": [
"QUAZIP_INSTALL OFF"
]
+ },
+ "frozen": {
+ "package": "frozen",
+ "repo": "serge-sans-paille/frozen",
+ "sha": "61dce5ae18",
+ "hash": "1ae3d073e659c1f24b2cdd76379c90d6af9e06bc707d285a4fafce05f7a4c9e592ff208c94a9ae0f0d07620b3c6cec191f126b03d70ad4dfa496a86ed5658a6d",
+ "bundled": true
}
}
diff --git a/src/qt_common/qt_applet_util.cpp b/src/qt_common/qt_applet_util.cpp
new file mode 100644
index 0000000000..1b0189392e
--- /dev/null
+++ b/src/qt_common/qt_applet_util.cpp
@@ -0,0 +1,4 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_applet_util.h"
diff --git a/src/qt_common/qt_applet_util.h b/src/qt_common/qt_applet_util.h
new file mode 100644
index 0000000000..2b48d16698
--- /dev/null
+++ b/src/qt_common/qt_applet_util.h
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_APPLET_UTIL_H
+#define QT_APPLET_UTIL_H
+
+// TODO
+namespace QtCommon::Applets {
+
+}
+#endif // QT_APPLET_UTIL_H
diff --git a/src/yuzu/qt_common.cpp b/src/qt_common/qt_common.cpp
similarity index 55%
rename from src/yuzu/qt_common.cpp
rename to src/qt_common/qt_common.cpp
index 413402165c..6be241c740 100644
--- a/src/yuzu/qt_common.cpp
+++ b/src/qt_common/qt_common.cpp
@@ -1,12 +1,19 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_common.h"
+#include "common/fs/fs.h"
#include
#include
-#include
#include "common/logging/log.h"
#include "core/frontend/emu_window.h"
-#include "yuzu/qt_common.h"
+
+#include
+
+#include
+
+#include
#if !defined(WIN32) && !defined(__APPLE__)
#include
@@ -15,7 +22,19 @@
#endif
namespace QtCommon {
-Core::Frontend::WindowSystemType GetWindowSystemType() {
+
+#ifdef YUZU_QT_WIDGETS
+QWidget* rootObject = nullptr;
+#else
+QObject* rootObject = nullptr;
+#endif
+
+std::unique_ptr system = nullptr;
+std::shared_ptr vfs = nullptr;
+std::unique_ptr provider = nullptr;
+
+Core::Frontend::WindowSystemType GetWindowSystemType()
+{
// Determine WSI type based on Qt platform.
QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("windows"))
@@ -35,7 +54,8 @@ Core::Frontend::WindowSystemType GetWindowSystemType() {
return Core::Frontend::WindowSystemType::Windows;
} // namespace Core::Frontend::WindowSystemType
-Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
+Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
+{
Core::Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
@@ -43,8 +63,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
// Our Win32 Qt external doesn't have the private API.
wsi.render_surface = reinterpret_cast(window->winId());
#elif defined(__APPLE__)
- wsi.render_surface = reinterpret_cast(objc_msgSend)(
- reinterpret_cast(window->winId()), sel_registerName("layer"));
+ wsi.render_surface = reinterpret_cast(
+ objc_msgSend)(reinterpret_cast(window->winId()), sel_registerName("layer"));
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
@@ -57,4 +77,46 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
return wsi;
}
+
+const QString tr(const char* str)
+{
+ return QGuiApplication::tr(str);
+}
+
+const QString tr(const std::string& str)
+{
+ return QGuiApplication::tr(str.c_str());
+}
+
+#ifdef YUZU_QT_WIDGETS
+void Init(QWidget* root)
+#else
+void Init(QObject* root)
+#endif
+{
+ system = std::make_unique();
+ rootObject = root;
+ vfs = std::make_unique();
+ provider = std::make_unique();
+}
+
+std::filesystem::path GetEdenCommand() {
+ std::filesystem::path command;
+
+ QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE"));
+ if (!appimage.isEmpty()) {
+ command = std::filesystem::path{appimage.toStdString()};
+ } else {
+ const QStringList args = QGuiApplication::arguments();
+ command = args[0].toStdString();
+ }
+
+ // If relative path, make it an absolute path
+ if (command.c_str()[0] == '.') {
+ command = Common::FS::GetCurrentDir() / command;
+ }
+
+ return command;
+}
+
} // namespace QtCommon
diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h
new file mode 100644
index 0000000000..a2700427ab
--- /dev/null
+++ b/src/qt_common/qt_common.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_COMMON_H
+#define QT_COMMON_H
+
+#include
+#include "core/core.h"
+#include "core/file_sys/registered_cache.h"
+#include
+#include
+
+#include
+
+namespace QtCommon {
+
+#ifdef YUZU_QT_WIDGETS
+extern QWidget *rootObject;
+#else
+extern QObject *rootObject;
+#endif
+
+extern std::unique_ptr system;
+extern std::shared_ptr vfs;
+extern std::unique_ptr provider;
+
+typedef std::function QtProgressCallback;
+
+Core::Frontend::WindowSystemType GetWindowSystemType();
+
+Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window);
+
+#ifdef YUZU_QT_WIDGETS
+void Init(QWidget *root);
+#else
+void Init(QObject *root);
+#endif
+
+const QString tr(const char *str);
+const QString tr(const std::string &str);
+
+std::filesystem::path GetEdenCommand();
+} // namespace QtCommon
+#endif
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/qt_common/qt_config.cpp
similarity index 99%
rename from src/yuzu/configuration/qt_config.cpp
rename to src/qt_common/qt_config.cpp
index ae5b330e23..f787873cf6 100644
--- a/src/yuzu/configuration/qt_config.cpp
+++ b/src/qt_common/qt_config.cpp
@@ -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
diff --git a/src/yuzu/configuration/qt_config.h b/src/qt_common/qt_config.h
similarity index 94%
rename from src/yuzu/configuration/qt_config.h
rename to src/qt_common/qt_config.h
index dc2dceb4d7..a8c80dd273 100644
--- a/src/yuzu/configuration/qt_config.h
+++ b/src/qt_common/qt_config.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
diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp
new file mode 100644
index 0000000000..e4625aa423
--- /dev/null
+++ b/src/qt_common/qt_content_util.cpp
@@ -0,0 +1,313 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_content_util.h"
+#include "common/fs/fs.h"
+#include "frontend_common/content_manager.h"
+#include "frontend_common/firmware_manager.h"
+#include "qt_common/qt_common.h"
+#include "qt_common/qt_progress_dialog.h"
+#include "qt_frontend_util.h"
+
+#include
+
+namespace QtCommon::Content {
+
+bool CheckGameFirmware(u64 program_id, QObject* parent)
+{
+ if (FirmwareManager::GameRequiresFirmware(program_id)
+ && !FirmwareManager::CheckFirmwarePresence(*system)) {
+ auto result = QtCommon::Frontend::ShowMessage(
+ QMessageBox::Warning,
+ "Game Requires Firmware",
+ "The game you are trying to launch requires firmware to boot or to get past the "
+ "opening menu. Please "
+ "dump and install firmware, or press \"OK\" to launch anyways.",
+ QMessageBox::Ok | QMessageBox::Cancel,
+ parent);
+
+ return result == QMessageBox::Ok;
+ }
+
+ return true;
+}
+
+void InstallFirmware(const QString& location, bool recursive)
+{
+ QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."),
+ tr("Cancel"),
+ 0,
+ 100,
+ rootObject);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+ progress.show();
+
+ // Declare progress callback.
+ auto callback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ static constexpr const char* failedTitle = "Firmware Install Failed";
+ static constexpr const char* successTitle = "Firmware Install Succeeded";
+ QMessageBox::Icon icon;
+ FirmwareInstallResult result;
+
+ const auto ShowMessage = [&]() {
+ QtCommon::Frontend::ShowMessage(icon,
+ failedTitle,
+ GetFirmwareInstallResultString(result));
+ };
+
+ LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
+
+ // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in
+ // there.)
+ std::filesystem::path firmware_source_path = location.toStdString();
+ if (!Common::FS::IsDir(firmware_source_path)) {
+ return;
+ }
+
+ std::vector out;
+ const Common::FS::DirEntryCallable dir_callback =
+ [&out](const std::filesystem::directory_entry& entry) {
+ if (entry.path().has_extension() && entry.path().extension() == ".nca") {
+ out.emplace_back(entry.path());
+ }
+
+ return true;
+ };
+
+ callback(100, 10);
+
+ if (recursive) {
+ Common::FS::IterateDirEntriesRecursively(firmware_source_path,
+ dir_callback,
+ Common::FS::DirEntryFilter::File);
+ } else {
+ Common::FS::IterateDirEntries(firmware_source_path,
+ dir_callback,
+ Common::FS::DirEntryFilter::File);
+ }
+
+ if (out.size() <= 0) {
+ result = FirmwareInstallResult::NoNCAs;
+ icon = QMessageBox::Warning;
+ ShowMessage();
+ return;
+ }
+
+ // Locate and erase the content of nand/system/Content/registered/*.nca, if any.
+ auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
+ if (sysnand_content_vdir->IsWritable()
+ && !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
+ result = FirmwareInstallResult::FailedDelete;
+ icon = QMessageBox::Critical;
+ ShowMessage();
+ return;
+ }
+
+ LOG_INFO(Frontend,
+ "Cleaned nand/system/Content/registered folder in preparation for new firmware.");
+
+ callback(100, 20);
+
+ auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
+
+ bool success = true;
+ int i = 0;
+ for (const auto& firmware_src_path : out) {
+ i++;
+ auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(),
+ FileSys::OpenMode::Read);
+ auto firmware_dst_vfile = firmware_vdir
+ ->CreateFileRelative(firmware_src_path.filename().string());
+
+ if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
+ LOG_ERROR(Frontend,
+ "Failed to copy firmware file {} to {} in registered folder!",
+ firmware_src_path.generic_string(),
+ firmware_src_path.filename().string());
+ success = false;
+ }
+
+ if (callback(100, 20 + static_cast(((i) / static_cast(out.size())) * 70.0))) {
+ result = FirmwareInstallResult::FailedCorrupted;
+ icon = QMessageBox::Warning;
+ ShowMessage();
+ return;
+ }
+ }
+
+ if (!success) {
+ result = FirmwareInstallResult::FailedCopy;
+ icon = QMessageBox::Critical;
+ ShowMessage();
+ return;
+ }
+
+ // Re-scan VFS for the newly placed firmware files.
+ system->GetFileSystemController().CreateFactories(*vfs);
+
+ auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(90 + static_cast((processed_size * 10) / total_size));
+ return progress.wasCanceled();
+ };
+
+ auto results = ContentManager::VerifyInstalledContents(*QtCommon::system,
+ *QtCommon::provider,
+ VerifyFirmwareCallback,
+ true);
+
+ if (results.size() > 0) {
+ const auto failed_names = QString::fromStdString(
+ fmt::format("{}", fmt::join(results, "\n")));
+ progress.close();
+ QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"),
+ tr("Verification failed for the following files:\n\n%1")
+ .arg(failed_names));
+ return;
+ }
+
+ progress.close();
+
+ const auto pair = FirmwareManager::GetFirmwareVersion(*system);
+ const auto firmware_data = pair.first;
+ const std::string display_version(firmware_data.display_version.data());
+
+ result = FirmwareInstallResult::Success;
+ QtCommon::Frontend::Information(rootObject,
+ tr(successTitle),
+ tr(GetFirmwareInstallResultString(result))
+ .arg(QString::fromStdString(display_version)));
+}
+
+QString UnzipFirmwareToTmp(const QString& location)
+{
+ namespace fs = std::filesystem;
+ fs::path tmp{fs::temp_directory_path()};
+
+ if (!fs::create_directories(tmp / "eden" / "firmware")) {
+ return "";
+ }
+
+ tmp /= "eden";
+ tmp /= "firmware";
+
+ QString qCacheDir = QString::fromStdString(tmp.string());
+
+ QFile zip(location);
+
+ QStringList result = JlCompress::extractDir(&zip, qCacheDir);
+ if (result.isEmpty()) {
+ return "";
+ }
+
+ return qCacheDir;
+}
+
+// Content //
+void VerifyGameContents(const std::string& game_path)
+{
+ QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
+ tr("Cancel"),
+ 0,
+ 100,
+ rootObject);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ const auto callback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ const auto result = ContentManager::VerifyGameContents(*system, game_path, callback);
+
+ switch (result) {
+ case ContentManager::GameVerificationResult::Success:
+ QtCommon::Frontend::Information(rootObject,
+ tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+ break;
+ case ContentManager::GameVerificationResult::Failed:
+ QtCommon::Frontend::Critical(rootObject,
+ tr("Integrity verification failed!"),
+ tr("File contents may be corrupt or missing."));
+ break;
+ case ContentManager::GameVerificationResult::NotImplemented:
+ QtCommon::Frontend::Warning(
+ rootObject,
+ tr("Integrity verification couldn't be performed"),
+ tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. "
+ "File contents could not be checked for validity."));
+ }
+}
+
+void InstallKeys()
+{
+ const QString key_source_location
+ = QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"),
+ {},
+ QStringLiteral("Decryption Keys (*.keys)"),
+ {},
+ QtCommon::Frontend::Option::ReadOnly);
+
+ if (key_source_location.isEmpty()) {
+ return;
+ }
+
+ FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location
+ .toStdString(),
+ "keys");
+
+ system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
+
+ switch (result) {
+ case FirmwareManager::KeyInstallResult::Success:
+ QtCommon::Frontend::Information(tr("Decryption Keys install succeeded"),
+ tr("Decryption Keys were successfully installed"));
+ break;
+ default:
+ QtCommon::Frontend::Critical(tr("Decryption Keys install failed"),
+ tr(FirmwareManager::GetKeyInstallResultString(result)));
+ break;
+ }
+}
+
+void VerifyInstalledContents() {
+ // Initialize a progress dialog.
+ QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ // Declare progress callback.
+ auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ const std::vector result =
+ ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback);
+ progress.close();
+
+ if (result.empty()) {
+ QtCommon::Frontend::Information(tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+ } else {
+ const auto failed_names =
+ QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
+ QtCommon::Frontend::Critical(
+ tr("Integrity verification failed!"),
+ tr("Verification failed for the following files:\n\n%1").arg(failed_names));
+ }
+}
+
+} // namespace QtCommon::Content
diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h
new file mode 100644
index 0000000000..b572c1c4a3
--- /dev/null
+++ b/src/qt_common/qt_content_util.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_CONTENT_UTIL_H
+#define QT_CONTENT_UTIL_H
+
+#include
+#include "common/common_types.h"
+
+namespace QtCommon::Content {
+
+//
+bool CheckGameFirmware(u64 program_id, QObject *parent);
+
+static constexpr std::array FIRMWARE_RESULTS
+ = {"Successfully installed firmware version %1",
+ "",
+ "Unable to locate potential firmware NCA files",
+ "Failed to delete one or more firmware files.",
+ "One or more firmware files failed to copy into NAND.",
+ "Firmware installation cancelled, firmware may be in a bad state or corrupted."
+ "Restart Eden or re-install firmware."};
+
+enum class FirmwareInstallResult {
+ Success,
+ NoOp,
+ NoNCAs,
+ FailedDelete,
+ FailedCopy,
+ FailedCorrupted,
+};
+
+inline constexpr const char *GetFirmwareInstallResultString(FirmwareInstallResult result)
+{
+ return FIRMWARE_RESULTS.at(static_cast(result));
+}
+
+void InstallFirmware(const QString &location, bool recursive);
+
+QString UnzipFirmwareToTmp(const QString &location);
+
+// Keys //
+void InstallKeys();
+
+// Content //
+void VerifyGameContents(const std::string &game_path);
+void VerifyInstalledContents();
+}
+#endif // QT_CONTENT_UTIL_H
diff --git a/src/qt_common/qt_frontend_util.cpp b/src/qt_common/qt_frontend_util.cpp
new file mode 100644
index 0000000000..d519669ad5
--- /dev/null
+++ b/src/qt_common/qt_frontend_util.cpp
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_frontend_util.h"
+#include "qt_common/qt_common.h"
+
+#ifdef YUZU_QT_WIDGETS
+#include
+#endif
+
+namespace QtCommon::Frontend {
+
+StandardButton ShowMessage(
+ Icon icon, const QString &title, const QString &text, StandardButtons buttons, QObject *parent)
+{
+#ifdef YUZU_QT_WIDGETS
+ QMessageBox *box = new QMessageBox(icon, title, text, buttons, (QWidget *) parent);
+ return static_cast(box->exec());
+#endif
+ // TODO(crueter): If Qt Widgets is disabled...
+ // need a way to reference icon/buttons too
+}
+
+const QString GetOpenFileName(const QString &title,
+ const QString &dir,
+ const QString &filter,
+ QString *selectedFilter,
+ Options options)
+{
+#ifdef YUZU_QT_WIDGETS
+ return QFileDialog::getOpenFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
+#endif
+}
+
+} // namespace QtCommon::Frontend
diff --git a/src/qt_common/qt_frontend_util.h b/src/qt_common/qt_frontend_util.h
new file mode 100644
index 0000000000..f86b9e1357
--- /dev/null
+++ b/src/qt_common/qt_frontend_util.h
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_FRONTEND_UTIL_H
+#define QT_FRONTEND_UTIL_H
+
+#include
+#include "qt_common/qt_common.h"
+
+#ifdef YUZU_QT_WIDGETS
+#include
+#include
+#include
+#endif
+
+/**
+ * manages common functionality e.g. message boxes and such for Qt/QML
+ */
+namespace QtCommon::Frontend {
+
+Q_NAMESPACE
+
+#ifdef YUZU_QT_WIDGETS
+using Options = QFileDialog::Options;
+using Option = QFileDialog::Option;
+
+using StandardButton = QMessageBox::StandardButton;
+using StandardButtons = QMessageBox::StandardButtons;
+
+using Icon = QMessageBox::Icon;
+#else
+enum Option {
+ ShowDirsOnly = 0x00000001,
+ DontResolveSymlinks = 0x00000002,
+ DontConfirmOverwrite = 0x00000004,
+ DontUseNativeDialog = 0x00000008,
+ ReadOnly = 0x00000010,
+ HideNameFilterDetails = 0x00000020,
+ DontUseCustomDirectoryIcons = 0x00000040
+};
+Q_ENUM_NS(Option)
+Q_DECLARE_FLAGS(Options, Option)
+Q_FLAG_NS(Options)
+
+enum StandardButton {
+ // keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton
+ NoButton = 0x00000000,
+ Ok = 0x00000400,
+ Save = 0x00000800,
+ SaveAll = 0x00001000,
+ Open = 0x00002000,
+ Yes = 0x00004000,
+ YesToAll = 0x00008000,
+ No = 0x00010000,
+ NoToAll = 0x00020000,
+ Abort = 0x00040000,
+ Retry = 0x00080000,
+ Ignore = 0x00100000,
+ Close = 0x00200000,
+ Cancel = 0x00400000,
+ Discard = 0x00800000,
+ Help = 0x01000000,
+ Apply = 0x02000000,
+ Reset = 0x04000000,
+ RestoreDefaults = 0x08000000,
+
+ FirstButton = Ok, // internal
+ LastButton = RestoreDefaults, // internal
+
+ YesAll = YesToAll, // obsolete
+ NoAll = NoToAll, // obsolete
+
+ Default = 0x00000100, // obsolete
+ Escape = 0x00000200, // obsolete
+ FlagMask = 0x00000300, // obsolete
+ ButtonMask = ~FlagMask // obsolete
+};
+Q_ENUM_NS(StandardButton)
+
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+typedef StandardButton Button;
+#endif
+Q_DECLARE_FLAGS(StandardButtons, StandardButton)
+Q_FLAG_NS(StandardButtons)
+
+enum Icon {
+ // keep this in sync with QMessageDialogOptions::StandardIcon
+ NoIcon = 0,
+ Information = 1,
+ Warning = 2,
+ Critical = 3,
+ Question = 4
+};
+Q_ENUM_NS(Icon)
+
+#endif
+
+// TODO(crueter) widgets-less impl, choices et al.
+StandardButton ShowMessage(Icon icon,
+ const QString &title,
+ const QString &text,
+ StandardButtons buttons = StandardButton::NoButton,
+ QObject *parent = nullptr);
+
+#define UTIL_OVERRIDES(level) \
+ inline StandardButton level(QObject *parent, \
+ const QString &title, \
+ const QString &text, \
+ StandardButtons buttons = StandardButton::Ok) \
+ { \
+ return ShowMessage(Icon::level, title, text, buttons, parent); \
+ } \
+ inline StandardButton level(QObject *parent, \
+ const char *title, \
+ const char *text, \
+ StandardButtons buttons \
+ = StandardButton::Ok) \
+ { \
+ return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \
+ } \
+ inline StandardButton level(const char *title, \
+ const char *text, \
+ StandardButtons buttons \
+ = StandardButton::Ok) \
+ { \
+ return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \
+ } \
+ inline StandardButton level(const QString title, \
+ const QString &text, \
+ StandardButtons buttons \
+ = StandardButton::Ok) \
+ { \
+ return ShowMessage(Icon::level, title, text, buttons, rootObject); \
+ }
+
+UTIL_OVERRIDES(Information)
+UTIL_OVERRIDES(Warning)
+UTIL_OVERRIDES(Critical)
+UTIL_OVERRIDES(Question)
+
+const QString GetOpenFileName(const QString &title,
+ const QString &dir,
+ const QString &filter,
+ QString *selectedFilter = nullptr,
+ Options options = Options());
+
+} // namespace QtCommon::Frontend
+#endif // QT_FRONTEND_UTIL_H
diff --git a/src/qt_common/qt_game_util.cpp b/src/qt_common/qt_game_util.cpp
new file mode 100644
index 0000000000..5d0b4d8ae7
--- /dev/null
+++ b/src/qt_common/qt_game_util.cpp
@@ -0,0 +1,577 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_game_util.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/hle/service/am/am_types.h"
+#include "frontend_common/content_manager.h"
+#include "qt_common.h"
+#include "qt_common/uisettings.h"
+#include "qt_frontend_util.h"
+#include "yuzu/util/util.h"
+
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include "common/scope_exit.h"
+#include "common/string_util.h"
+#include
+#include
+#else
+#include "fmt/ostream.h"
+#include
+#endif
+
+namespace QtCommon::Game {
+
+bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
+ const std::string& comment,
+ const std::filesystem::path& icon_path,
+ const std::filesystem::path& command,
+ const std::string& arguments,
+ const std::string& categories,
+ const std::string& keywords,
+ const std::string& name)
+try {
+#ifdef _WIN32 // Windows
+ HRESULT hr = CoInitialize(nullptr);
+ if (FAILED(hr)) {
+ LOG_ERROR(Frontend, "CoInitialize failed");
+ return false;
+ }
+ SCOPE_EXIT
+ {
+ CoUninitialize();
+ };
+ IShellLinkW* ps1 = nullptr;
+ IPersistFile* persist_file = nullptr;
+ SCOPE_EXIT
+ {
+ if (persist_file != nullptr) {
+ persist_file->Release();
+ }
+ if (ps1 != nullptr) {
+ ps1->Release();
+ }
+ };
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW,
+ reinterpret_cast(&ps1));
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
+ return false;
+ }
+ hres = ps1->SetPath(command.c_str());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set path");
+ return false;
+ }
+ if (!arguments.empty()) {
+ hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set arguments");
+ return false;
+ }
+ }
+ if (!comment.empty()) {
+ hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set description");
+ return false;
+ }
+ }
+ if (std::filesystem::is_regular_file(icon_path)) {
+ hres = ps1->SetIconLocation(icon_path.c_str(), 0);
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set icon location");
+ return false;
+ }
+ }
+ hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast(&persist_file));
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to get IPersistFile interface");
+ return false;
+ }
+ hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE);
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to save shortcut");
+ return false;
+ }
+ return true;
+#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX
+ std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
+ std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
+ if (!shortcut_stream.is_open()) {
+ LOG_ERROR(Frontend, "Failed to create shortcut");
+ return false;
+ }
+ // TODO: Migrate fmt::print to std::print in futures STD C++ 23.
+ fmt::print(shortcut_stream, "[Desktop Entry]\n");
+ fmt::print(shortcut_stream, "Type=Application\n");
+ fmt::print(shortcut_stream, "Version=1.0\n");
+ fmt::print(shortcut_stream, "Name={}\n", name);
+ if (!comment.empty()) {
+ fmt::print(shortcut_stream, "Comment={}\n", comment);
+ }
+ if (std::filesystem::is_regular_file(icon_path)) {
+ fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
+ }
+ fmt::print(shortcut_stream, "TryExec={}\n", command.string());
+ fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
+ if (!categories.empty()) {
+ fmt::print(shortcut_stream, "Categories={}\n", categories);
+ }
+ if (!keywords.empty()) {
+ fmt::print(shortcut_stream, "Keywords={}\n", keywords);
+ }
+ return true;
+#else // Unsupported platform
+ return false;
+#endif
+} catch (const std::exception& e) {
+ LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what());
+ return false;
+}
+
+bool MakeShortcutIcoPath(const u64 program_id,
+ const std::string_view game_file_name,
+ std::filesystem::path& out_icon_path)
+{
+ // Get path to Yuzu icons directory & icon extension
+ std::string ico_extension = "png";
+#if defined(_WIN32)
+ out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir);
+ ico_extension = "ico";
+#elif defined(__linux__) || defined(__FreeBSD__)
+ out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
+#endif
+ // Create icons directory if it doesn't exist
+ if (!Common::FS::CreateDirs(out_icon_path)) {
+ out_icon_path.clear();
+ return false;
+ }
+
+ // Create icon file path
+ out_icon_path /= (program_id == 0 ? fmt::format("eden-{}.{}", game_file_name, ico_extension)
+ : fmt::format("eden-{:016X}.{}", program_id, ico_extension));
+ return true;
+}
+
+void OpenEdenFolder(const Common::FS::EdenPath& path)
+{
+ QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(Common::FS::GetEdenPathString(path))));
+}
+
+void OpenRootDataFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::EdenDir);
+}
+
+void OpenNANDFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::NANDDir);
+}
+
+void OpenSDMCFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::SDMCDir);
+}
+
+void OpenModFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::LoadDir);
+}
+
+void OpenLogFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::LogDir);
+}
+
+static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type)
+{
+ switch (type) {
+ case QtCommon::Game::InstalledEntryType::Game:
+ return tr("Error Removing Contents");
+ case QtCommon::Game::InstalledEntryType::Update:
+ return tr("Error Removing Update");
+ case QtCommon::Game::InstalledEntryType::AddOnContent:
+ return tr("Error Removing DLC");
+ default:
+ return QStringLiteral("Error Removing ");
+ }
+}
+
+// Game Content //
+void RemoveBaseContent(u64 program_id, InstalledEntryType type)
+{
+ const auto res = ContentManager::RemoveBaseContent(system->GetFileSystemController(),
+ program_id);
+ if (res) {
+ QtCommon::Frontend::Information(rootObject,
+ "Successfully Removed",
+ "Successfully removed the installed base game.");
+ } else {
+ QtCommon::Frontend::Warning(
+ rootObject,
+ GetGameListErrorRemoving(type),
+ tr("The base game is not installed in the NAND and cannot be removed."));
+ }
+}
+
+void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
+{
+ const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
+ if (res) {
+ QtCommon::Frontend::Information(rootObject,
+ "Successfully Removed",
+ "Successfully removed the installed update.");
+ } else {
+ QtCommon::Frontend::Warning(rootObject,
+ GetGameListErrorRemoving(type),
+ tr("There is no update installed for this title."));
+ }
+}
+
+void RemoveAddOnContent(u64 program_id, InstalledEntryType type)
+{
+ const size_t count = ContentManager::RemoveAllDLC(*system, program_id);
+ if (count == 0) {
+ QtCommon::Frontend::Warning(rootObject,
+ GetGameListErrorRemoving(type),
+ tr("There are no DLCs installed for this title."));
+ return;
+ }
+
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed %1 installed DLC.").arg(count));
+}
+
+// Global Content //
+
+void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target)
+{
+ const auto target_file_name = [target] {
+ switch (target) {
+ case GameListRemoveTarget::GlShaderCache:
+ return "opengl.bin";
+ case GameListRemoveTarget::VkShaderCache:
+ return "vulkan.bin";
+ default:
+ return "";
+ }
+ }();
+ const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
+ const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id);
+ const auto target_file = shader_cache_folder_path / target_file_name;
+
+ if (!Common::FS::Exists(target_file)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Transferable Shader Cache"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+ if (Common::FS::RemoveFile(target_file)) {
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed the transferable shader cache."));
+ } else {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Transferable Shader Cache"),
+ tr("Failed to remove the transferable shader cache."));
+ }
+}
+
+void RemoveVulkanDriverPipelineCache(u64 program_id)
+{
+ static constexpr std::string_view target_file_name = "vulkan_pipelines.bin";
+
+ const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
+ const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id);
+ const auto target_file = shader_cache_folder_path / target_file_name;
+
+ if (!Common::FS::Exists(target_file)) {
+ return;
+ }
+ if (!Common::FS::RemoveFile(target_file)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Vulkan Driver Pipeline Cache"),
+ tr("Failed to remove the driver pipeline cache."));
+ }
+}
+
+void RemoveAllTransferableShaderCaches(u64 program_id)
+{
+ const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
+ const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id);
+
+ if (!Common::FS::Exists(program_shader_cache_dir)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Transferable Shader Caches"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+ if (Common::FS::RemoveDirRecursively(program_shader_cache_dir)) {
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed the transferable shader caches."));
+ } else {
+ QtCommon::Frontend::Warning(
+ rootObject,
+ tr("Error Removing Transferable Shader Caches"),
+ tr("Failed to remove the transferable shader cache directory."));
+ }
+}
+
+void RemoveCustomConfiguration(u64 program_id, const std::string& game_path)
+{
+ const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
+ const auto config_file_name = program_id == 0
+ ? Common::FS::PathToUTF8String(file_path.filename())
+ .append(".ini")
+ : fmt::format("{:016X}.ini", program_id);
+ const auto custom_config_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir)
+ / "custom" / config_file_name;
+
+ if (!Common::FS::Exists(custom_config_file_path)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Custom Configuration"),
+ tr("A custom configuration for this title does not exist."));
+ return;
+ }
+
+ if (Common::FS::RemoveFile(custom_config_file_path)) {
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed the custom game configuration."));
+ } else {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Custom Configuration"),
+ tr("Failed to remove the custom game configuration."));
+ }
+}
+
+void RemoveCacheStorage(u64 program_id)
+{
+ const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
+ auto vfs_nand_dir = vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir),
+ FileSys::OpenMode::Read);
+
+ const auto cache_storage_path
+ = FileSys::SaveDataFactory::GetFullPath({},
+ vfs_nand_dir,
+ FileSys::SaveDataSpaceId::User,
+ FileSys::SaveDataType::Cache,
+ 0 /* program_id */,
+ {},
+ 0);
+
+ const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
+
+ // Not an error if it wasn't cleared.
+ Common::FS::RemoveDirRecursively(path);
+}
+
+// Metadata //
+void ResetMetadata()
+{
+ const QString title = tr("Reset Metadata Cache");
+
+ if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir)
+ / "game_list/")) {
+ QtCommon::Frontend::Warning(rootObject, title, tr("The metadata cache is already empty."));
+ } else if (Common::FS::RemoveDirRecursively(
+ Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) {
+ QtCommon::Frontend::Information(rootObject,
+ title,
+ tr("The operation completed successfully."));
+ UISettings::values.is_game_list_reload_pending.exchange(true);
+ } else {
+ QtCommon::Frontend::Warning(
+ rootObject,
+ title,
+ tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
+ }
+}
+
+// Uhhh //
+
+// Messages in pre-defined message boxes for less code spaghetti
+inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title)
+{
+ int result = 0;
+ QMessageBox::StandardButtons buttons;
+ switch (imsg) {
+ case ShortcutMessages::Fullscreen:
+ buttons = QMessageBox::Yes | QMessageBox::No;
+ result
+ = QtCommon::Frontend::Information(tr("Create Shortcut"),
+ tr("Do you want to launch the game in fullscreen?"),
+ buttons);
+ return result == QMessageBox::Yes;
+ case ShortcutMessages::Success:
+ QtCommon::Frontend::Information(tr("Shortcut Created"),
+ tr("Successfully created a shortcut to %1").arg(game_title));
+ return false;
+ case ShortcutMessages::Volatile:
+ buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
+ result = QtCommon::Frontend::Warning(
+ tr("Shortcut may be Volatile!"),
+ tr("This will create a shortcut to the current AppImage. This may "
+ "not work well if you update. Continue?"),
+ buttons);
+ return result == QMessageBox::Ok;
+ default:
+ buttons = QMessageBox::Ok;
+ QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"),
+ tr("Failed to create a shortcut to %1").arg(game_title),
+ buttons);
+ return false;
+ }
+}
+
+void CreateShortcut(const std::string& game_path,
+ const u64 program_id,
+ const std::string& game_title_,
+ const ShortcutTarget &target,
+ std::string arguments_,
+ const bool needs_title)
+{
+ // Get path to Eden executable
+ std::filesystem::path command = GetEdenCommand();
+
+ // Shortcut path
+ std::filesystem::path shortcut_path = GetShortcutPath(target);
+
+ if (!std::filesystem::exists(shortcut_path)) {
+ CreateShortcutMessagesGUI(ShortcutMessages::Failed,
+ QString::fromStdString(shortcut_path.generic_string()));
+ LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string());
+ return;
+ }
+
+ const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(),
+ QtCommon::system->GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto loader =
+ Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read));
+
+ std::string game_title{game_title_};
+
+ // Delete illegal characters from title
+ if (needs_title) {
+ game_title = fmt::format("{:016X}", program_id);
+ if (control.first != nullptr) {
+ game_title = control.first->GetApplicationName();
+ } else {
+ loader->ReadTitle(game_title);
+ }
+ }
+
+ const std::string illegal_chars = "<>:\"/\\|?*.";
+ for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
+ if (illegal_chars.find(*it) != std::string::npos) {
+ game_title.erase(it.base() - 1);
+ }
+ }
+
+ const QString qgame_title = QString::fromStdString(game_title);
+
+ // Get icon from game file
+ std::vector icon_image_file{};
+ if (control.second != nullptr) {
+ icon_image_file = control.second->ReadAllBytes();
+ } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
+ LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
+ }
+
+ QImage icon_data =
+ QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size()));
+ std::filesystem::path out_icon_path;
+ if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
+ if (!SaveIconToFile(out_icon_path, icon_data)) {
+ LOG_ERROR(Frontend, "Could not write icon to file");
+ }
+ } else {
+ QtCommon::Frontend::Critical(
+ tr("Create Icon"),
+ tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
+ .arg(QString::fromStdString(out_icon_path.string())));
+ }
+
+#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
+ // Special case for AppImages
+ // Warn once if we are making a shortcut to a volatile AppImage
+ if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) {
+ if (!CreateShortcutMessagesGUI(ShortcutMessages::Volatile, qgame_title)) {
+ return;
+ }
+ UISettings::values.shortcut_already_warned = true;
+ }
+#endif
+
+ // Create shortcut
+ std::string arguments{arguments_};
+ if (CreateShortcutMessagesGUI(ShortcutMessages::Fullscreen, qgame_title)) {
+ arguments = "-f " + arguments;
+ }
+ const std::string comment = fmt::format("Start {:s} with the Eden Emulator", game_title);
+ const std::string categories = "Game;Emulator;Qt;";
+ const std::string keywords = "Switch;Nintendo;";
+
+ if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command,
+ arguments, categories, keywords, game_title)) {
+ CreateShortcutMessagesGUI(ShortcutMessages::Success,
+ qgame_title);
+ return;
+ }
+ CreateShortcutMessagesGUI(ShortcutMessages::Failed,
+ qgame_title);
+}
+
+constexpr std::string GetShortcutPath(ShortcutTarget target) {
+ {
+ std::string shortcut_path{};
+ if (target == ShortcutTarget::Desktop) {
+ shortcut_path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)
+ .toStdString();
+ } else if (target == ShortcutTarget::Applications) {
+ shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)
+ .toStdString();
+ }
+
+ return shortcut_path;
+ }
+}
+
+void CreateHomeMenuShortcut(ShortcutTarget target) {
+ constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch);
+ auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QtCommon::Frontend::Warning(tr("No firmware available"),
+ tr("Please install firmware to use the home menu."));
+ return;
+ }
+
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+ if (!qlaunch_nca) {
+ QtCommon::Frontend::Warning(tr("Home Menu Applet"),
+ tr("Home Menu is not available. Please reinstall firmware."));
+ return;
+ }
+
+ auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+ const auto game_path = qlaunch_applet_nca->GetFullPath();
+
+ // TODO(crueter): Make this use the Eden icon
+ CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
+}
+
+
+} // namespace QtCommon::Game
diff --git a/src/qt_common/qt_game_util.h b/src/qt_common/qt_game_util.h
new file mode 100644
index 0000000000..0a21208659
--- /dev/null
+++ b/src/qt_common/qt_game_util.h
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_GAME_UTIL_H
+#define QT_GAME_UTIL_H
+
+#include
+#include
+#include "common/fs/path_util.h"
+
+namespace QtCommon::Game {
+
+enum class InstalledEntryType {
+ Game,
+ Update,
+ AddOnContent,
+};
+
+enum class GameListRemoveTarget {
+ GlShaderCache,
+ VkShaderCache,
+ AllShaderCache,
+ CustomConfiguration,
+ CacheStorage,
+};
+
+enum class ShortcutTarget {
+ Desktop,
+ Applications,
+};
+
+enum class ShortcutMessages{
+ Fullscreen = 0,
+ Success = 1,
+ Volatile = 2,
+ Failed = 3
+};
+
+bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
+ const std::string& comment,
+ const std::filesystem::path& icon_path,
+ const std::filesystem::path& command,
+ const std::string& arguments,
+ const std::string& categories,
+ const std::string& keywords,
+ const std::string& name);
+
+bool MakeShortcutIcoPath(const u64 program_id,
+ const std::string_view game_file_name,
+ std::filesystem::path& out_icon_path);
+
+void OpenEdenFolder(const Common::FS::EdenPath &path);
+void OpenRootDataFolder();
+void OpenNANDFolder();
+void OpenSDMCFolder();
+void OpenModFolder();
+void OpenLogFolder();
+
+void RemoveBaseContent(u64 program_id, InstalledEntryType type);
+void RemoveUpdateContent(u64 program_id, InstalledEntryType type);
+void RemoveAddOnContent(u64 program_id, InstalledEntryType type);
+
+void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target);
+void RemoveVulkanDriverPipelineCache(u64 program_id);
+void RemoveAllTransferableShaderCaches(u64 program_id);
+void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
+void RemoveCacheStorage(u64 program_id);
+
+// Metadata //
+void ResetMetadata();
+
+// Shortcuts //
+void CreateShortcut(const std::string& game_path,
+ const u64 program_id,
+ const std::string& game_title_,
+ const ShortcutTarget& target,
+ std::string arguments_,
+ const bool needs_title);
+
+constexpr std::string GetShortcutPath(ShortcutTarget target);
+void CreateHomeMenuShortcut(ShortcutTarget target);
+
+}
+
+#endif // QT_GAME_UTIL_H
diff --git a/src/qt_common/qt_meta.cpp b/src/qt_common/qt_meta.cpp
new file mode 100644
index 0000000000..67ae659771
--- /dev/null
+++ b/src/qt_common/qt_meta.cpp
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_meta.h"
+#include "common/common_types.h"
+#include "core/core.h"
+#include "core/frontend/applets/cabinet.h"
+#include "core/frontend/applets/controller.h"
+#include "core/frontend/applets/profile_select.h"
+#include "core/frontend/applets/software_keyboard.h"
+#include "core/hle/service/am/frontend/applet_web_browser_types.h"
+
+namespace QtCommon::Meta {
+
+void RegisterMetaTypes()
+{
+ // Register integral and floating point types
+ qRegisterMetaType("u8");
+ qRegisterMetaType("u16");
+ qRegisterMetaType("u32");
+ qRegisterMetaType("u64");
+ qRegisterMetaType("u128");
+ qRegisterMetaType("s8");
+ qRegisterMetaType("s16");
+ qRegisterMetaType("s32");
+ qRegisterMetaType("s64");
+ qRegisterMetaType("f32");
+ qRegisterMetaType("f64");
+
+ // Register string types
+ qRegisterMetaType("std::string");
+ qRegisterMetaType("std::wstring");
+ qRegisterMetaType("std::u8string");
+ qRegisterMetaType("std::u16string");
+ qRegisterMetaType("std::u32string");
+ qRegisterMetaType("std::string_view");
+ qRegisterMetaType("std::wstring_view");
+ qRegisterMetaType("std::u8string_view");
+ qRegisterMetaType("std::u16string_view");
+ qRegisterMetaType("std::u32string_view");
+
+ // Register applet types
+
+ // Cabinet Applet
+ qRegisterMetaType("Core::Frontend::CabinetParameters");
+ qRegisterMetaType>(
+ "std::shared_ptr");
+
+ // Controller Applet
+ qRegisterMetaType("Core::Frontend::ControllerParameters");
+
+ // Profile Select Applet
+ qRegisterMetaType(
+ "Core::Frontend::ProfileSelectParameters");
+
+ // Software Keyboard Applet
+ qRegisterMetaType(
+ "Core::Frontend::KeyboardInitializeParameters");
+ qRegisterMetaType(
+ "Core::Frontend::InlineAppearParameters");
+ qRegisterMetaType("Core::Frontend::InlineTextParameters");
+ qRegisterMetaType("Service::AM::Frontend::SwkbdResult");
+ qRegisterMetaType(
+ "Service::AM::Frontend::SwkbdTextCheckResult");
+ qRegisterMetaType(
+ "Service::AM::Frontend::SwkbdReplyType");
+
+ // Web Browser Applet
+ qRegisterMetaType("Service::AM::Frontend::WebExitReason");
+
+ // Register loader types
+ qRegisterMetaType("Core::SystemResultStatus");
+}
+
+}
diff --git a/src/qt_common/qt_meta.h b/src/qt_common/qt_meta.h
new file mode 100644
index 0000000000..c0a37db983
--- /dev/null
+++ b/src/qt_common/qt_meta.h
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_META_H
+#define QT_META_H
+
+#include
+
+namespace QtCommon::Meta {
+
+//
+void RegisterMetaTypes();
+
+}
+#endif // QT_META_H
diff --git a/src/qt_common/qt_path_util.cpp b/src/qt_common/qt_path_util.cpp
new file mode 100644
index 0000000000..761e6e8405
--- /dev/null
+++ b/src/qt_common/qt_path_util.cpp
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_path_util.h"
+#include
+#include
+#include
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "qt_common/qt_frontend_util.h"
+#include
+
+namespace QtCommon::Path {
+
+bool OpenShaderCache(u64 program_id, QObject *parent)
+{
+ const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
+ const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)};
+ if (!Common::FS::CreateDirs(shader_cache_folder_path)) {
+ QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent);
+ }
+
+ const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};
+ const auto qt_shader_cache_path = QString::fromStdString(shader_path_string);
+ return QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
+}
+
+}
diff --git a/src/qt_common/qt_path_util.h b/src/qt_common/qt_path_util.h
new file mode 100644
index 0000000000..855b06caa9
--- /dev/null
+++ b/src/qt_common/qt_path_util.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_PATH_UTIL_H
+#define QT_PATH_UTIL_H
+
+#include "common/common_types.h"
+#include
+
+namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject *parent); }
+
+#endif // QT_PATH_UTIL_H
diff --git a/src/qt_common/qt_progress_dialog.cpp b/src/qt_common/qt_progress_dialog.cpp
new file mode 100644
index 0000000000..b4bf74c8bd
--- /dev/null
+++ b/src/qt_common/qt_progress_dialog.cpp
@@ -0,0 +1,4 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_progress_dialog.h"
diff --git a/src/qt_common/qt_progress_dialog.h b/src/qt_common/qt_progress_dialog.h
new file mode 100644
index 0000000000..17f6817ffa
--- /dev/null
+++ b/src/qt_common/qt_progress_dialog.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_PROGRESS_DIALOG_H
+#define QT_PROGRESS_DIALOG_H
+
+#include
+
+#ifdef YUZU_QT_WIDGETS
+#include
+#endif
+
+namespace QtCommon::Frontend {
+#ifdef YUZU_QT_WIDGETS
+
+using QtProgressDialog = QProgressDialog;
+
+// TODO(crueter): QML impl
+#else
+class QtProgressDialog
+{
+public:
+ QtProgressDialog(const QString &labelText,
+ const QString &cancelButtonText,
+ int minimum,
+ int maximum,
+ QObject *parent = nullptr,
+ Qt::WindowFlags f = Qt::WindowFlags());
+
+ bool wasCanceled() const;
+ void setWindowModality(Qt::WindowModality modality);
+ void setMinimumDuration(int durationMs);
+ void setAutoClose(bool autoClose);
+ void setAutoReset(bool autoReset);
+
+public slots:
+ void setLabelText(QString &text);
+ void setRange(int min, int max);
+ void setValue(int progress);
+ bool close();
+
+ void show();
+};
+#endif // YUZU_QT_WIDGETS
+
+}
+#endif // QT_PROGRESS_DIALOG_H
diff --git a/src/qt_common/qt_rom_util.cpp b/src/qt_common/qt_rom_util.cpp
new file mode 100644
index 0000000000..08ccb05a97
--- /dev/null
+++ b/src/qt_common/qt_rom_util.cpp
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_rom_util.h"
+
+#include
+
+namespace QtCommon::ROM {
+
+bool RomFSRawCopy(size_t total_size,
+ size_t& read_size,
+ QtProgressCallback callback,
+ const FileSys::VirtualDir& src,
+ const FileSys::VirtualDir& dest,
+ bool full)
+{
+ // TODO(crueter)
+ // if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ // return false;
+ // if (dialog.wasCanceled())
+ // return false;
+
+ // std::vector buffer(CopyBufferSize);
+ // auto last_timestamp = std::chrono::steady_clock::now();
+
+ // const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file,
+ // const FileSys::VirtualFile& dest_file) {
+ // if (src_file == nullptr || dest_file == nullptr) {
+ // return false;
+ // }
+ // if (!dest_file->Resize(src_file->GetSize())) {
+ // return false;
+ // }
+
+ // for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) {
+ // if (dialog.wasCanceled()) {
+ // dest_file->Resize(0);
+ // return false;
+ // }
+
+ // using namespace std::literals::chrono_literals;
+ // const auto new_timestamp = std::chrono::steady_clock::now();
+
+ // if ((new_timestamp - last_timestamp) > 33ms) {
+ // last_timestamp = new_timestamp;
+ // dialog.setValue(
+ // static_cast(std::min(read_size, total_size) * 100 / total_size));
+ // QCoreApplication::processEvents();
+ // }
+
+ // const auto read = src_file->Read(buffer.data(), buffer.size(), i);
+ // dest_file->Write(buffer.data(), read, i);
+
+ // read_size += read;
+ // }
+
+ // return true;
+ // };
+
+ // if (full) {
+ // for (const auto& file : src->GetFiles()) {
+ // const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
+ // if (!QtRawCopy(file, out))
+ // return false;
+ // }
+ // }
+
+ // for (const auto& dir : src->GetSubdirectories()) {
+ // const auto out = dest->CreateSubdirectory(dir->GetName());
+ // if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full))
+ // return false;
+ // }
+
+ // return true;
+ return true;
+}
+
+}
diff --git a/src/qt_common/qt_rom_util.h b/src/qt_common/qt_rom_util.h
new file mode 100644
index 0000000000..f76b09753d
--- /dev/null
+++ b/src/qt_common/qt_rom_util.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_ROM_UTIL_H
+#define QT_ROM_UTIL_H
+
+#include "qt_common/qt_common.h"
+#include
+
+namespace QtCommon::ROM {
+
+bool RomFSRawCopy(size_t total_size,
+ size_t& read_size,
+ QtProgressCallback callback,
+ const FileSys::VirtualDir& src,
+ const FileSys::VirtualDir& dest,
+ bool full);
+
+}
+#endif // QT_ROM_UTIL_H
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/qt_common/shared_translation.cpp
similarity index 91%
rename from src/yuzu/configuration/shared_translation.cpp
rename to src/qt_common/shared_translation.cpp
index 1137145659..8f31e07154 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/qt_common/shared_translation.cpp
@@ -7,23 +7,21 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "yuzu/configuration/shared_translation.h"
+#include "shared_translation.h"
#include
-#include
#include "common/settings.h"
#include "common/settings_enums.h"
#include "common/settings_setting.h"
#include "common/time_zone.h"
-#include "yuzu/uisettings.h"
+#include "qt_common/uisettings.h"
#include