Compare commits

..

45 Commits

Author SHA1 Message Date
_Neo_
d20dedda0e Update MainMenuBarView.axaml 2026-03-25 21:06:02 +02:00
_Neo_
b44cb07235 Physically move Manage File Types over to the Files menu 2026-03-25 21:01:07 +02:00
_Neo_
4c16ef0572 Move Manage File Types to MenuBar_File.json 2026-03-25 00:24:53 +02:00
_Neo_
a8ce2c845a Update MainMenuBarView.axaml 2026-03-24 23:43:13 +02:00
_Neo_
583cac31eb More fractured locales 2026-03-24 23:41:12 +02:00
_Neo_
ea3ee010f6 MenuBar_Actions.json fractured locales implementation 2026-03-24 23:18:53 +02:00
_Neo_
cfdfdb3e28 Fix some more typos in Locales.md 2026-03-24 22:28:27 +02:00
_Neo_
a710a8756f Revert Locales.md example 2026-03-24 22:26:15 +02:00
_Neo_
bb687b6523 Remove Cheat Manager from Actions entirely 2026-03-24 21:49:55 +02:00
_Neo_
7ec8844121 Fix one more 2026-03-24 21:47:21 +02:00
_Neo_
8b2c53fea2 Fix some locales 2026-03-24 21:43:00 +02:00
_Neo_
6c0e9a0c41 Update Fractured Locales with more readable IDs 2026-03-24 17:09:46 +02:00
_Neo_
2a7d476a85 Additional locale updates 2026-03-23 21:08:23 +02:00
_Neo_
189033b84c General layout adjustments and tiny bug fixes 2026-03-23 20:56:34 +02:00
_Neo_
fd2c71462e Updating more shortcuts... 2026-03-23 19:00:58 +02:00
_Neo_
efd5cdc706 Update MainMenuBarView.axaml 2026-03-23 18:58:04 +02:00
_Neo_
7b10b13d0d Update MainMenuBarView.axaml.cs 2026-03-23 18:55:49 +02:00
_Neo_
a8cb33a7ed Adjust some locales and add shortcuts 2026-03-23 18:54:16 +02:00
_Neo_
19b762e7b0 Remove transferred locales from Root.json 2026-03-23 17:13:27 +02:00
_Neo_
56a8892c12 General changes Pt.1 2026-03-23 17:05:40 +02:00
yeager
32ee806070 Updated Swedish translation (ryubing/ryujinx!289)
See merge request ryubing/ryujinx!289
2026-03-18 00:13:47 -05:00
sh0inx
4e81a4c2f4 [HLE] Added "null" check for isAtRest (ryubing/ryujinx!287)
See merge request ryubing/ryujinx!287
2026-03-15 09:46:36 -05:00
Coxxs
9cae62096a HLE: Implement CreateContextForSystem (ryubing/ryujinx!285)
See merge request ryubing/ryujinx!285
2026-03-14 13:57:49 -05:00
BowedCascade
648b609ebb Add restart emulation command (ryubing/ryujinx!276)
See merge request ryubing/ryujinx!276
2026-03-14 13:56:20 -05:00
BowedCascade
5ae86fc493 Fix keys file overwrite on installation and method name typo (ryubing/ryujinx!268)
See merge request ryubing/ryujinx!268
2026-03-14 13:52:58 -05:00
KeatonTheBot
6f90e47a73 UI: Restore FluentAvaloniaUI package, disable animations on app initialization (ryubing/ryujinx!256)
See merge request ryubing/ryujinx!256
2026-03-14 13:48:59 -05:00
sh0inx
ac5f9857e2 HLE: Implement IHidServer IsSixAxisSensorAtRest (ryubing/ryujinx!228)
See merge request ryubing/ryujinx!228
2026-03-14 13:25:55 -05:00
KeatonTheBot
4b42087bd4 Linux: Fix file picker not launching from disabling core dumps (ryubing/ryujinx!249)
See merge request ryubing/ryujinx!249
2026-03-06 19:04:42 -06:00
EscoDev
80cbf5d1fc Fix incorrect save button locale in user editor (ryubing/ryujinx!280)
See merge request ryubing/ryujinx!280
2026-03-01 15:48:29 -06:00
LotP
cc6d2dc162 fix nacp language buffer (ryubing/ryujinx!281)
See merge request ryubing/ryujinx!281
2026-02-25 13:58:31 -06:00
Daenorth
4ebc318da5 Add new RPC images (ryubing/ryujinx!279)
See merge request ryubing/ryujinx!279
2026-02-23 20:57:19 -06:00
KeatonTheBot
00dad0a5e2 Windows ARM (win-arm64) build now launches with trimming (ryubing/ryujinx!277)
See merge request ryubing/ryujinx!277
2026-02-21 20:10:22 -06:00
Joshua de Reeper
b70e2e44cb NFC Mifare Manager (ryubing/ryujinx!270)
See merge request ryubing/ryujinx!270
2026-02-21 05:45:00 -06:00
sh0inx
012d1d6886 Fixed spelling in LocalesValidationTask.cs (ryubing/ryujinx!269)
See merge request ryubing/ryujinx!269
2026-02-21 04:37:02 -06:00
BowedCascade
d1205dc95d Fix backslash key not mappable in controller settings (ryubing/ryujinx!265)
See merge request ryubing/ryujinx!265
2026-02-18 18:13:15 -06:00
Awesomeangotti
6f95172bb6 Compatability Data Update (ryubing/ryujinx!264)
See merge request ryubing/ryujinx!264
2026-02-17 19:24:01 -06:00
Princess Piplup
8208d43d9e compatiblity/2026-02-17 (ryubing/ryujinx!263)
See merge request ryubing/ryujinx!263
2026-02-17 18:57:50 -06:00
shinyoyo
1260f93aaf Updated ‌Simplified Chinese‌ translation. (ryubing/ryujinx!260)
See merge request ryubing/ryujinx!260
2026-02-09 01:07:22 -06:00
LotP
1b3bf1473d Fix Dual Joy-Con driver and InputView (ryubing/ryujinx!259)
See merge request ryubing/ryujinx!259
2026-01-31 23:12:29 -06:00
LotP
081cdcab0c remap joy-cons (ryubing/ryujinx!258)
See merge request ryubing/ryujinx!258
2026-01-31 17:58:31 -06:00
Coxxs
922775664c audio: Fix crash due to invalid Splitter size (ryubing/ryujinx!257)
See merge request ryubing/ryujinx!257
2026-01-31 11:22:14 -06:00
sh0inx
478b66fd49 HLE: Stubbed IUserLocalCommuniationService SetProtocol (106) (ryubing/ryujinx!253)
See merge request ryubing/ryujinx!253
2026-01-30 20:48:41 -06:00
Coxxs
a16a072155 HLE: Implement 10106 and 10107 in IPrepoService (ryubing/ryujinx!254)
See merge request ryubing/ryujinx!254
2026-01-29 13:45:35 -06:00
Babib3l
a4a0fcd4da General translations updates + fixes (ryubing/ryujinx!248)
See merge request ryubing/ryujinx!248
2026-01-28 07:01:39 -06:00
GreemDev
cc5b60bbca fix AppleHardwareDeviceDriver.IsSupported (no fancy check is needed; it's on any macOS version 10.5 (Leopard) and above) 2026-01-28 00:05:02 -06:00
67 changed files with 2812 additions and 1562 deletions

View File

@@ -3,13 +3,13 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.6" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Avalonia" Version="11.3.12" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
@@ -22,7 +22,7 @@
<PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" />
<PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
@@ -41,7 +41,7 @@
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.126" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
<PackageVersion Include="Gommon" Version="2.8.0.1" />
@@ -56,7 +56,6 @@
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
<PackageVersion Include="System.Management" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@@ -5,8 +5,8 @@ Ryubing Locales uses a custom format, which uses a file for defining the support
Each json file holds the locales for a specific part of the emulator, e.g. the Setup Wizard locales are in `SetupWizard.json`, and each locale entry in the file includes all the supported languages in the same place.
## Languages
in the `/assets/` folder you will find the `Languages.json` file, which defines all the languages supported by the emulator.
The file includes a table of the langauge codes and their langauge names.
In the `/assets/` folder you will find the `Languages.json` file, which defines all the languages supported by the emulator.
The file includes a table of the language codes and their language names.
#Example of the format for Languages.json
{
@@ -19,7 +19,7 @@ The file includes a table of the langauge codes and their langauge names.
}
## Locales
in the `/assets/Locales/` folder you will find the json files, which define all the locales supported by the emulator.
In the `/assets/Locales/` folder you will find the json files, which define all the locales supported by the emulator.
Each json file holds locales for a specific part of the emulator in a large array of locale objects.
Each locale is made up an ID used for lookup and a list of the languages and their matching translations.
Any empty string or null value will automatically use the English translation instead in the emulator.

View File

@@ -0,0 +1,329 @@
{
"Locales": [
{
"ID": "InstallFromFileDialogTitle",
"Translations": {
"ar_SA": "اختر ملف .XCI أو أرشيف .ZIP لتثبيت البرنامج الثابت منه",
"de_DE": "Wählen Sie eine .XCI-Datei oder ein .ZIP-Archiv aus, um die Firmware zu installieren",
"el_GR": "Επιλέξτε ένα αρχείο .XCI ή ένα αρχείο .ZIP για να εγκαταστήσετε το υλικολογισμικό",
"en_US": "Choose an .XCI file or a .ZIP archive to install firmware from",
"es_ES": "Elige un archivo .XCI o un archivo .ZIP para instalar el firmware",
"fr_FR": "Choisissez un fichier .XCI ou une archive .ZIP pour installer le firmware",
"he_IL": "בחר קובץ .XCI או ארכיון .ZIP להתקנת הקושחה ממנו",
"it_IT": "Scegli un file .XCI o un archivio .ZIP per installare il firmware",
"ja_JP": "ファームウェアをインストールするために .XCI ファイルまたは .ZIP アーカイブを選択",
"ko_KR": "펌웨어를 설치할 .XCI 파일 또는 .ZIP 아카이브를 선택하세요",
"no_NO": "Velg en .XCI-fil eller et .ZIP-arkiv for å installere firmware fra",
"pl_PL": "Wybierz plik .XCI lub archiwum .ZIP, z którego chcesz zainstalować firmware",
"pt_BR": "Escolha um arquivo .XCI ou um arquivo .ZIP para instalar o firmware",
"ru_RU": "Выберите файл .XCI или архив .ZIP для установки прошивки",
"sv_SE": "Välj en .XCI-fil eller ett .ZIP-arkiv för att installera firmware",
"th_TH": "เลือกไฟล์ .XCI หรือไฟล์เก็บถาวร .ZIP เพื่อติดตั้งเฟิร์มแวร์จาก",
"tr_TR": "Firmware yüklemek için bir .XCI dosyası veya .ZIP arşivi seçin",
"uk_UA": "Виберіть файл .XCI або архів .ZIP для встановлення прошивки",
"zh_CN": "选择一个 .XCI 文件或 .ZIP 存档来安装固件",
"zh_TW": "選擇一個 .XCI 檔案或 .ZIP 封存檔來安裝韌體"
}
},
{
"ID": "InstallFromFolderDialogTitle",
"Translations": {
"ar_SA": "اختر مجلد لتثبيت الكوشحة منه",
"de_DE": "Wählen Sie einen ORDNER aus, um die Firmware zu installieren",
"el_GR": "Επιλέξτε έναν ΦΆΚΕΛΟ για να εγκαταστήσετε το firmware",
"en_US": "Choose a FOLDER to install firmware from",
"es_ES": "Elige una CARPETA para instalar el firmware",
"fr_FR": "Choisissez un DOSSIER pour installer le firmware",
"he_IL": "בחר תיקיה להתקנת הקושחה ממנה",
"it_IT": "Scegli una CARTELLA per installare il firmware",
"ja_JP": "ファームウェアをインストールするフォルダを選択",
"ko_KR": "펌웨어를 설치할 폴더를 선택하세요",
"no_NO": "Velg en MAPPE for å installere firmware fra",
"pl_PL": "Wybierz FOLDER, z którego chcesz zainstalować firmware",
"pt_BR": "Escolha uma PASTA para instalar o firmware",
"ru_RU": "Выберите ПАПКУ для установки прошивки",
"sv_SE": "Välj en MAPP för att installera firmware från",
"th_TH": "เลือกโฟลเดอร์เพื่อติดตั้งเฟิร์มแวร์จากนั้น",
"tr_TR": "Firmware yüklemek için bir KLASÖR seçin",
"uk_UA": "Виберіть ПАПКУ для встановлення прошивки",
"zh_CN": "选择一个文件夹来安装固件",
"zh_TW": "選擇一個資料夾來安裝韌體"
}
},
{
"ID": "InstallerEmbeddedMessage",
"Translations": {
"ar_SA": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})",
"de_DE": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})",
"el_GR": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})",
"en_US": "Would you like to install the firmware embedded in this game? (Firmware {0})",
"es_ES": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})",
"fr_FR": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})",
"he_IL": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})",
"it_IT": "Vuoi installare il firmware incluso in questo gioco? (Firmware {0})",
"ja_JP": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})",
"ko_KR": "이 게임에 포함된 펌웨어를 설치하시겠습니까?(Firmware {0})",
"no_NO": "Ønsker du å installere fastvaren innebygd i dette spillet? (Firmware {0})",
"pl_PL": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})",
"pt_BR": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})",
"ru_RU": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})",
"sv_SE": "Vill du installera det firmware som är inbäddat i detta spel? (Firmware {0})",
"th_TH": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})",
"tr_TR": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})",
"uk_UA": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})",
"zh_CN": "要安装游戏文件中内嵌的系统固件吗?(固件版本 {0})",
"zh_TW": "您想安裝遊戲內建的韌體嗎? (韌體 {0})"
}
},
{
"ID": "InstallerEmbeddedMessageSuccess",
"Translations": {
"ar_SA": "لم يتم العثور على أي برنامج ثابت مثبت ولكن ريوجينكس كان قادرا على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.",
"de_DE": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.",
"el_GR": "Δεν βρέθηκε εγκατεστημένο υλικολογισμικό, αλλά το Ryujinx κατάφερε να εγκαταστήσει το υλικολογισμικό {0} από το παρεχόμενο παιχνίδι.\nΟ προσομοιωτής θα ξεκινήσει τώρα.",
"en_US": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.",
"es_ES": "No se encontró ningún firmware instalado, pero Ryujinx pudo instalar el firmware {0} del juego proporcionado.\nEl emulador iniciará.",
"fr_FR": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\nL'émulateur va maintenant démarrer.",
"he_IL": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \nהאמולטור יופעל כעת.",
"it_IT": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.",
"ja_JP": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\nエミュレータが開始します。",
"ko_KR": "설치된 펌웨어를 찾을 수 없지만 Ryujinx는 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있습니다.\n이제 에뮬레이터가 시작됩니다.",
"no_NO": "Det ble ikke funnet noen installert fastvare, men Ryujinx kunne installere fastvare {0} fra det oppgitte spillet.\nemulatoren vil nå starte.",
"pl_PL": "Nie znaleziono zainstalowanego oprogramowania, ale Ryujinx był w stanie zainstalować oprogramowanie {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.",
"pt_BR": "Nenhum firmware instalado foi encontrado, mas o Ryujinx conseguiu instalar o firmware {0} a partir do jogo fornecido.\nO emulador será iniciado agora.",
"ru_RU": "Установленной прошивки не было найдено, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь запустится эмулятор.",
"sv_SE": "Inget installerat firmware hittades men Ryujinx kunde installera firmware {0} från angiven spel.\nEmulatorn kommer nu att startas.",
"th_TH": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ Ryujinx จะติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\nขณะนี้โปรแกรมจำลองจะเริ่มทำงาน",
"tr_TR": "Yüklü bir firmware bulunamadı, ancak Ryujinx sağlanan oyundan firmware {0} yüklemeyi başardı.\nEmülatör şimdi başlatılacak.",
"uk_UA": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\nТепер запуститься емулятор.",
"zh_CN": "Ryujinx 模拟器已经从当前游戏文件中安装了系统固件 {0} 。\n模拟器现在可以正常运行了。",
"zh_TW": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}。\n模擬器現在可以執行。"
}
},
{
"ID": "InstallerNotInstalledMessage",
"Translations": {
"ar_SA": "لا يوجد برنامج ثابت مثبت",
"de_DE": "Keine Firmware installiert.",
"el_GR": "Δεν έχει εγκατασταθεί Firmware.",
"en_US": "No Firmware Installed.",
"es_ES": "No hay Firmware Instalado.",
"fr_FR": "Aucun Firmware Installé.",
"he_IL": "לא מותקנת קושחה.",
"it_IT": "Nessun firmware installato.",
"ja_JP": "ファームウェアがインストールされていません。",
"ko_KR": "펌웨어가 설치되어 있지 .않음",
"no_NO": "Ingen fastvare installert.",
"pl_PL": "Brak Zainstalowanego Firmware'u.",
"pt_BR": "Nenhum Firmware Instalado.",
"ru_RU": "Прошивка не установлена.",
"sv_SE": "Inget firmware installerat.",
"th_TH": "ไม่มีการติดตั้งเฟิร์มแวร์",
"tr_TR": "Yazılım Yüklü Değil.",
"uk_UA": "Прошивка не встановлена.",
"zh_CN": "未安装系统固件。",
"zh_TW": "未安裝韌體。"
}
},
{
"ID": "InstallerInstalledMessage",
"Translations": {
"ar_SA": "تم تثبيت البرنامج الثابت {0}",
"de_DE": "Firmware {0} wurde installiert",
"el_GR": "Το Firmware {0} εγκαταστάθηκε",
"en_US": "Firmware {0} was installed",
"es_ES": "Se Instaló el Firmware {0}",
"fr_FR": "Le firmware {0} a été installé",
"he_IL": "הקושחה {0} הותקנה",
"it_IT": "Il firmware {0} è stato installato",
"ja_JP": "ファームウェア {0} がインストールされました",
"ko_KR": "펌웨어 {0}이(가) 설치됨",
"no_NO": "fastvare {0} ble installert",
"pl_PL": "Firmware {0} został zainstalowany",
"pt_BR": "Firmware {0} foi instalado",
"ru_RU": "Прошивка {0} была установлена",
"sv_SE": "Firmware {0} installerades",
"th_TH": "เฟิร์มแวร์ {0} ติดตั้งแล้ว",
"tr_TR": "Yazılım {0} yüklendi",
"uk_UA": "Встановлено прошивку {0}",
"zh_CN": "已安装系统固件 {0}",
"zh_TW": "已安裝韌體{0}"
}
},
{
"ID": "InstallerFirmwareNotFound",
"Translations": {
"ar_SA": "لم يتم العثور على برنامج ثابت للنظام صالح في {0}.",
"de_DE": "Es wurde keine gültige System-Firmware gefunden in {0}.",
"el_GR": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.",
"en_US": "A valid system firmware was not found in {0}.",
"es_ES": "No se pudo encontrar un firmware válido en {0}.",
"fr_FR": "Un firmware valide n'a pas été trouvé dans {0}.",
"he_IL": "לא נמצאה קושחת מערכת תקפה ב-{0}.",
"it_IT": "Un firmware del sistema valido non è stato trovato in {0}.",
"ja_JP": "{0} には有効なシステムファームウェアがありません。",
"ko_KR": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.",
"no_NO": "En gyldig systemfastvare ble ikke funnet i {0}.",
"pl_PL": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.",
"pt_BR": "Um firmware de sistema válido não foi encontrado em {0}.",
"ru_RU": "Не удалось найти действительную системную прошивку в {0}.",
"sv_SE": "Ett giltigt systemfirmware hittades inte i {0}.",
"th_TH": "ไม่พบเฟิร์มแวร์ของระบบที่ถูกต้อง {0}.",
"tr_TR": "{0} da geçerli bir sistem firmware'i bulunamadı.",
"uk_UA": "Дійсна прошивка системи не знайдена в {0}.",
"zh_CN": "在路径 {0} 中找不到有效的 Switch 系统固件。",
"zh_TW": "在 {0} 中未發現有效的系統韌體。"
}
},
{
"ID": "InstallerTitle",
"Translations": {
"ar_SA": "تثبيت البرنامج الثابت {0}",
"de_DE": "Installiere Firmware {0}",
"el_GR": "Εγκατάσταση Firmware {0}",
"en_US": "Install Firmware {0}",
"es_ES": "Instalar Firmware {0}",
"fr_FR": "Installer le Firmware {0}",
"he_IL": "התקן קושחה {0}",
"it_IT": "Installa firmware {0}",
"ja_JP": "ファームウェア {0} をインストール",
"ko_KR": "펌웨어 {0} 설치",
"no_NO": "Installer fastvare {0}",
"pl_PL": "Zainstaluj Firmware {0}",
"pt_BR": "Instalar Firmware {0}",
"ru_RU": "Установить прошивку {0}",
"sv_SE": "Installera firmware {0}",
"th_TH": "ติดตั้งเฟิร์มแวร์ {0}",
"tr_TR": "Firmware {0} Yükle",
"uk_UA": "Встановити прошивку {0}",
"zh_CN": "安装系统固件 {0}",
"zh_TW": "安裝韌體 {0}"
}
},
{
"ID": "InstallerMainMessage",
"Translations": {
"ar_SA": "سيتم تثبيت إصدار النظام {0}.",
"de_DE": "Systemversion {0} wird jetzt installiert.",
"el_GR": "Θα εγκατασταθεί η έκδοση συστήματος {0}.",
"en_US": "System version {0} will be installed.",
"es_ES": "Se instalará la versión de sistema {0}.",
"fr_FR": "La version {0} du système sera installée.",
"he_IL": "גירסת המערכת {0} תותקן.",
"it_IT": "La versione del sistema {0} sarà installata.",
"ja_JP": "システムバージョン {0} がインストールされます。",
"ko_KR": "시스템 버전 {0}이(가) 설치됩니다.",
"no_NO": "Systemversjon {0} vil bli installert.",
"pl_PL": "Wersja systemu {0} zostanie zainstalowana.",
"pt_BR": "A versão do sistema {0} será instalada.",
"ru_RU": "Будет установлена версия прошивки {0}.",
"sv_SE": "Systemversion {0} kommer att installeras.",
"th_TH": "ระบบเวอร์ชั่น {0} ได้รับการติดตั้งเร็วๆ นี้",
"tr_TR": "Sistem sürümü {0} yüklenecek.",
"uk_UA": "Буде встановлено версію системи {0}.",
"zh_CN": "即将安装系统固件版本 {0} 。",
"zh_TW": "即將安裝系統韌體版本 {0}。"
}
},
{
"ID": "InstallerSubMessage",
"Translations": {
"ar_SA": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.",
"de_DE": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.",
"el_GR": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.",
"en_US": "\n\nThis will replace the current system version {0}.",
"es_ES": "\n\nEsto reemplazará la versión de sistema actual, {0}.",
"fr_FR": "\n\nCela remplacera la version actuelle du système {0}.",
"he_IL": "\n\nזה יחליף את גרסת המערכת הנוכחית {0}.",
"it_IT": "\n\nQuesta sostituirà l'attuale versione del sistema ({0}).",
"ja_JP": "\n\n現在のシステムバージョン {0} を置き換えます。",
"ko_KR": "\n\n현재 시스템 버전 {0}을(를) 대체합니다.",
"no_NO": "\n\nDette erstatter den gjeldende systemversjonen {0}.",
"pl_PL": "\n\nZastąpi to obecną wersję systemu {0}.",
"pt_BR": "\n\nIsso substituirá a versão do sistema atual {0}.",
"ru_RU": "\n\nЭто заменит текущую версию прошивки {0}.",
"sv_SE": "\n\nDetta kommer att ersätta aktuella systemversionen {0}.",
"th_TH": "\n\nสิ่งนี้จะแทนที่เวอร์ชั่นของระบบเวอร์ชั่นปัจจุบัน {0}.",
"tr_TR": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.",
"uk_UA": "\n\nЦе замінить поточну версію системи {0}.",
"zh_CN": "\n\n替换当前系统固件版本 {0} 。",
"zh_TW": "\n\n這將取代目前的系統韌體版本 {0}。"
}
},
{
"ID": "InstallerConfirmMessage",
"Translations": {
"ar_SA": "\nهل تريد المتابعة؟",
"de_DE": "\n\nMöchtest du fortfahren?",
"el_GR": "\n\nΘέλετε να συνεχίσετε;",
"en_US": "\n\nDo you want to continue?",
"es_ES": "\n\n¿Continuar?",
"fr_FR": "\n\nVoulez-vous continuer ?",
"he_IL": "\n\nהאם ברצונך להמשיך?",
"it_IT": "\n\nVuoi continuare?",
"ja_JP": "\n\n続けてよろしいですか?",
"ko_KR": "\n\n계속하시겠습니까?",
"no_NO": "\n\nVil du fortsette?",
"pl_PL": "\n\nCzy chcesz kontynuować?",
"pt_BR": "\n\nDeseja continuar?",
"ru_RU": "\n\nПродолжить?",
"sv_SE": "\n\nVill du fortsätta?",
"th_TH": "\n\nคุณต้องการดำเนินการต่อหรือไม่?",
"tr_TR": "\n\nDevam etmek istiyor musunuz?",
"uk_UA": "\n\nВи хочете продовжити?",
"zh_CN": "\n\n是否继续",
"zh_TW": "\n\n您確定要繼續嗎?"
}
},
{
"ID": "InstallerWaitMessage",
"Translations": {
"ar_SA": "تثبيت البرنامج الثابت...",
"de_DE": "Firmware wird installiert...",
"el_GR": "Εγκατάσταση Firmware...",
"en_US": "Installing Firmware...",
"es_ES": "Instalando Firmware...",
"fr_FR": "Installation du Firmware...",
"he_IL": "מתקין קושחה...",
"it_IT": "Installazione del firmware...",
"ja_JP": "ファームウェアをインストール中...",
"ko_KR": "펌웨어 설치 중...",
"no_NO": "Installerer fastvare...",
"pl_PL": "Instalowanie firmware'u...",
"pt_BR": "Instalando firmware...",
"ru_RU": "Установка прошивки...",
"sv_SE": "Installerar firmware...",
"th_TH": "กำลังติดตั้งเฟิร์มแวร์...",
"tr_TR": "Firmware yükleniyor...",
"uk_UA": "Встановлення прошивки...",
"zh_CN": "安装系统固件中...",
"zh_TW": "正在安裝韌體..."
}
},
{
"ID": "InstallerSuccessMessage",
"Translations": {
"ar_SA": "تم تثبيت إصدار النظام {0} بنجاح.",
"de_DE": "Systemversion {0} wurde erfolgreich installiert.",
"el_GR": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.",
"en_US": "System version {0} successfully installed.",
"es_ES": "Versión de sistema {0} instalada con éxito.",
"fr_FR": "Version du système {0} installée avec succès.",
"he_IL": "גרסת המערכת {0} הותקנה בהצלחה.",
"it_IT": "La versione del sistema {0} è stata installata.",
"ja_JP": "システムバージョン {0} が正常にインストールされました。",
"ko_KR": "시스템 버전 {0}이(가) 설치되었습니다.",
"no_NO": "Systemversjon {0} ble installert.",
"pl_PL": "Wersja systemu {0} została pomyślnie zainstalowana.",
"pt_BR": "Versão do sistema {0} instalada com sucesso.",
"ru_RU": "Прошивка версии {0} успешно установлена.",
"sv_SE": "Systemversion {0} har installerats.",
"th_TH": "ระบบเวอร์ชั่น {0} ติดตั้งเรียบร้อยแล้ว",
"tr_TR": "Sistem sürümü {0} başarıyla yüklendi.",
"uk_UA": "Версію системи {0} успішно встановлено.",
"zh_CN": "成功安装系统固件版本 {0}。",
"zh_TW": "成功安裝系統韌體版本 {0}。"
}
}
]
}

View File

@@ -0,0 +1,204 @@
{
"Locales": [
{
"ID": "InstallFromFileDialogTitle",
"Translations": {
"ar_SA": "اختر ملف .KEYS",
"de_DE": "Wählen Sie eine .KEYS-Datei aus",
"el_GR": "Επιλέξτε ένα αρχείο .KEYS",
"en_US": "Choose a .KEYS file",
"es_ES": "Elige un archivo .KEYS",
"fr_FR": "Choisissez un fichier .KEYS",
"he_IL": "בחר קובץ .KEYS",
"it_IT": "Scegli un file .KEYS",
"ja_JP": ".KEYS ファイルを選択",
"ko_KR": ".KEYS 파일을 선택하세요",
"no_NO": "Velg en .KEYS-fil",
"pl_PL": "Wybierz plik .KEYS",
"pt_BR": "Escolha um arquivo .KEYS",
"ru_RU": "Выберите файл .KEYS",
"sv_SE": "Välj en .KEYS-fil",
"th_TH": "เลือกไฟล์ .KEYS",
"tr_TR": ".KEYS dosyasını seçin",
"uk_UA": "Виберіть файл .KEYS",
"zh_CN": "选择一个 .KEYS 文件",
"zh_TW": "選擇一個 .KEYS 檔案"
}
},
{
"ID": "InstallFromFolderDialogTitle",
"Translations": {
"ar_SA": "اختر مجلد لتثبيت المفاتيح منه",
"de_DE": "Wählen Sie einen ORDNER aus, um die Schlüssel zu installieren",
"el_GR": "Επιλέξτε έναν ΦΆΚΕΛΟ για να εγκαταστήσετε τα κλειδιά",
"en_US": "Choose a FOLDER to install keys from",
"es_ES": "Elige una CARPETA para instalar las claves",
"fr_FR": "Choisissez un DOSSIER pour installer les clés",
"he_IL": "בחר תיקיה להתקנת המפתחות ממנו",
"it_IT": "Scegli una CARTELLA per installare le chiavi",
"ja_JP": "フォルダを選択してキーをインストール",
"ko_KR": "폴더를 선택하여 키를 설치하세요",
"no_NO": "Velg en MAPPE for å installere nøklene fra",
"pl_PL": "Wybierz FOLDER, aby zainstalować klucze",
"pt_BR": "Escolha uma PASTA para instalar as chaves",
"ru_RU": "Выберите ПАПКУ для установки ключей",
"sv_SE": "Välj en MAPP för att installera nycklar från",
"th_TH": "เลือกโฟลเดอร์เพื่อติดตั้งคีย์จาก",
"tr_TR": "KLASÖR seçin ve anahtarları yükleyin",
"uk_UA": "Виберіть ПАПКУ для встановлення ключів",
"zh_CN": "选择一个文件夹来安装密钥",
"zh_TW": "選擇一個資料夾來安裝密鑰"
}
},
{
"ID": "InstallerConfirmInstall",
"Translations": {
"ar_SA": "\nهل تريد المتابعة؟",
"de_DE": "\n\nMöchtest du fortfahren?",
"el_GR": "\n\nΘέλετε να συνεχίσετε;",
"en_US": "\n\nDo you want to continue?",
"es_ES": "\n\n¿Continuar?",
"fr_FR": "\n\nVoulez-vous continuer ?",
"he_IL": "\n\nהאם ברצונך להמשיך?",
"it_IT": "\n\nVuoi continuare?",
"ja_JP": "\n\n続けてよろしいですか?",
"ko_KR": "\n\n계속하시겠습니까?",
"no_NO": "\n\nVil du fortsette?",
"pl_PL": "\n\nCzy chcesz kontynuować?",
"pt_BR": "\n\nDeseja continuar?",
"ru_RU": "\n\nПродолжить?",
"sv_SE": "\n\nVill du fortsätta?",
"th_TH": "\n\nคุณต้องการดำเนินการต่อหรือไม่?",
"tr_TR": "\n\nDevam etmek istiyor musunuz?",
"uk_UA": "\n\nВи хочете продовжити?",
"zh_CN": "\n\n是否继续",
"zh_TW": "\n\n您確定要繼續嗎?"
}
},
{
"ID": "InstallerKeysNotFound",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "An invalid Keys file was found in {0}.",
"es_ES": "Se halló un archivo Keys inválido en {0}.",
"fr_FR": "Un fichier de Clés invalide a été trouvé dans {0}.",
"he_IL": "",
"it_IT": "È stato trovato un file di chiavi non valido in {0}.",
"ja_JP": "",
"ko_KR": "{0}에서 잘못된 키 파일이 발견.",
"no_NO": "En ugyldig Keys-fil ble funnet i {0}.",
"pl_PL": "",
"pt_BR": "Um arquivo Chaves inválido foi encontrado em {0}.",
"ru_RU": "В {0} найден некорректный файл ключей.",
"sv_SE": "En ogiltig nyckelfil hittades i {0}.",
"th_TH": "พบไฟล์ Keys ที่ไม่ถูกต้องใน {0}.",
"tr_TR": "",
"uk_UA": "Виявлено неправильний файл ключів у теці {0}.",
"zh_CN": "在 {0} 发现了一个无效的密匙文件。",
"zh_TW": "找到無效的金鑰檔案 {0}。"
}
},
{
"ID": "InstallerMainMessage",
"Translations": {
"ar_SA": "سيتم تثبيت ملف مفاتيح جديد.",
"de_DE": "Eine neue Schlüsseldatei wird installiert.",
"el_GR": "Ένα νέο αρχείο Κλειδιών θα εγκατασταθεί.",
"en_US": "New Keys file will be installed.",
"es_ES": "Un nuevo archivo de Claves será instalado.",
"fr_FR": "Nouveau fichier de Clés sera installé.",
"he_IL": "קובץ מפתחות חדש יותקן.",
"it_IT": "Un nuovo file di chiavi sarà installato.",
"ja_JP": "新しいキー ファイルがインストールされます。",
"ko_KR": "새로운 키 파일이 설치됩니다.",
"no_NO": "Ny Keys-fil vil bli installert.",
"pl_PL": "Nowy plik kluczy zostanie zainstalowany.",
"pt_BR": "O novo arquivo Chaves será instalado.",
"ru_RU": "Будут установлены новые ключи.",
"sv_SE": "Ny nyckelfil kommer att installeras.",
"th_TH": "กำลังติดตั้งไฟล์ Keys ใหม่",
"tr_TR": "Yeni anahtar dosyası yüklenecek.",
"uk_UA": "Новий файл Ключів буде встановлено.",
"zh_CN": "将会安装新密匙文件。",
"zh_TW": "將會安裝新增的金鑰檔案。"
}
},
{
"ID": "InstallerSubMessage",
"Translations": {
"ar_SA": "\n\nقد يحل هذا محل بعض المفاتيح المثبتة حاليًا.",
"de_DE": "\n\nDies könnte einige der derzeit installierten Schlüssel ersetzen.",
"el_GR": "\n\nΑυτό μπορεί να αντικαταστήσει μερικά από τα τρέχοντα εγκατεστημένα κλειδιά.",
"en_US": "\n\nThis may replace some of the current installed Keys.",
"es_ES": "\n\nEsto puede reemplazar algunas de las Keys actualmente instaladas.",
"fr_FR": "\n\nCela peut remplacer certaines des Clés actuellement installées.",
"he_IL": "\n\nזה עשוי להחליף חלק מהמפתחות המותקנים הנוכחיים.",
"it_IT": "\n\nAlcune delle chiavi già installate potrebbero essere sovrascritte.",
"ja_JP": "\n\nこれにより、現在インストールされているキーの一部が置き換えられる場合があります。",
"ko_KR": "\n\n이로 인해 현재 설치된 키 중 일부가 대체될 수 있습니다.",
"no_NO": "\n\nDette kan erstatte noen av de nåværende installerte nøklene.",
"pl_PL": "\n\nTo może zastąpić niektóre z aktualnie zainstalowanych kluczy.",
"pt_BR": "\n\nIsso pode substituir algumas das chaves instaladas atualmente.",
"ru_RU": "\n\nЭто может заменить некоторые из текущих установленных ключей.",
"sv_SE": "\n\nDetta kan ersätta några av de redan installerade nycklarna.",
"th_TH": "\n\nสิ่งนี้อาจทำให้ไฟล์ Keys บางส่วนที่ติดตั้งอยู่ถูกแทนที่",
"tr_TR": "\n\nBu, şu anda kurulu olan anahtarların bazılarının yerine geçebilir.",
"uk_UA": "\n\nЦе замінить собою поточні файли Ключів.",
"zh_CN": "\n\n这也许会替换掉一些当前已安装的密匙。",
"zh_TW": "\n\n這將取代部分已安裝的金鑰。"
}
},
{
"ID": "InstallerWaitMessage",
"Translations": {
"ar_SA": "جارٍ تثبيت المفاتيح...",
"de_DE": "Schlüssel werden installiert...",
"el_GR": "Εγκατάσταση κλειδιών...",
"en_US": "Installing Keys...",
"es_ES": "Instalando Claves...",
"fr_FR": "Installation des Clés...",
"he_IL": "מתקין מפתחות...",
"it_IT": "Installazione delle chiavi...",
"ja_JP": "キーをインストールしています...",
"ko_KR": "키 설치 중...",
"no_NO": "Installere nøkler...",
"pl_PL": "Instalowanie kluczy...",
"pt_BR": "Instalando Chaves...",
"ru_RU": "Установка ключей...",
"sv_SE": "Installerar nycklar...",
"th_TH": "กำลังดำเนินการติดตั้ง Keys...",
"tr_TR": "Anahtarlar yükleniyor...",
"uk_UA": "Встановлення Ключів...",
"zh_CN": "安装密匙中。。。",
"zh_TW": "正在安裝金鑰。。。"
}
},
{
"ID": "InstallerSuccessMessage",
"Translations": {
"ar_SA": "تم تثبيت ملف المفاتيح الجديد بنجاح.",
"de_DE": "Neue Schlüsseldatei erfolgreich installiert.",
"el_GR": "Το νέο αρχείο Κλειδιών εγκαταστάθηκε με επιτυχία.",
"en_US": "New Keys file successfully installed.",
"es_ES": "Nuevo archivo Keys instalado con éxito.",
"fr_FR": "Nouveau fichier de Clés a été installé.",
"he_IL": "הקובץ החדש של המפתחות הותקן בהצלחה.",
"it_IT": "Nuovo file di chiavi installato con successo.",
"ja_JP": "新しいキー ファイルが正常にインストールされました。",
"ko_KR": "새로운 키 파일이 성공적으로 설치되었습니다.",
"no_NO": "Ny Keys -fil installert.",
"pl_PL": "Nowy plik kluczy został pomyślnie zainstalowany.",
"pt_BR": "Novo arquivo de chaves instalado com sucesso.",
"ru_RU": "Новые ключи успешно установлены.",
"sv_SE": "Ny nyckelfil installerades.",
"th_TH": "การติดตั้งไฟล์ Keys ใหม่เสร็จสมบูรณ์แล้ว",
"tr_TR": "Yeni anahtar dosyası başarıyla yüklendi.",
"uk_UA": "Нові ключі встановлено.",
"zh_CN": "已成功安装新密匙文件。",
"zh_TW": "成功安裝新增的金鑰檔案。"
}
}
]
}

154
assets/Locales/Error.json Normal file
View File

@@ -0,0 +1,154 @@
{
"Locales": [
{
"ID": "NoKeysFound",
"Translations": {
"ar_SA": "المفاتيح غير موجودة.",
"de_DE": "Keys nicht gefunden.",
"el_GR": "Τα κλειδιά δεν βρέθηκαν.",
"en_US": "Keys not found.",
"es_ES": "No se encontraron claves.",
"fr_FR": "Clés non trouvées.",
"he_IL": "המפתחות לא נמצאו.",
"it_IT": "Chiavi non trovate.",
"ja_JP": "Keys がありません。",
"ko_KR": "키를 찾을 수 없음.",
"no_NO": "Finner ikke nøkler.",
"pl_PL": "Nie znaleziono kluczy.",
"pt_BR": "Chaves não encontradas.",
"ru_RU": "Ключи не найдены.",
"sv_SE": "Nycklarna hittades inte.",
"th_TH": "ไม่พบ คีย์",
"tr_TR": "Keys bulunamadı.",
"uk_UA": "Ключі не знайдено.",
"zh_CN": "找不到密钥。",
"zh_TW": "找不到金鑰。"
}
},
{
"ID": "NoKeysFoundDescription",
"Translations": {
"ar_SA": "لم يتمكن ريوجينكس من العثور على ملف \"prod.keys\" الخاص بك.",
"de_DE": "Ryujinx konnte deine \"prod.keys\" Datei nicht finden.",
"el_GR": "Το Ryujinx δεν κατάφερε να εντοπίσει το αρχείο \"prod.keys\".",
"en_US": "Ryujinx was unable to find your \"prod.keys\" file.",
"es_ES": "Ryujinx no pudo encontrar tus \"prod.keys\".",
"fr_FR": "Ryujinx n'a pas pu trouver votre fichier \"prod.keys\".",
"he_IL": "ריוג'ינקס לא הצליח למצוא את קובץ ה-\"prod.keys\" שלך.",
"it_IT": "Ryujinx non è riuscito a trovare il file \"prod.keys\".",
"ja_JP": "\"prod.keys\" が見つかりませんでした。",
"ko_KR": "Ryujinx가 '\"prod.keys\" 파일을 찾지 못함.",
"no_NO": "Ryujinx kunne ikke finne \"prod.keys\" filen din.",
"pl_PL": "Ryujinx nie mógł znaleźć twojego pliku \"prod.keys\".",
"pt_BR": "Ryujinx não conseguiu encontrar o seu arquivo '\"prod.keys\".",
"ru_RU": "Ryujinx не удалось найти ваш \"prod.keys\" файл.",
"sv_SE": "Ryujinx kunde inte hitta din \"prod.keys\"-fil.",
"th_TH": "Ryujinx ไม่พบไฟล์ '\"prod.keys\" ในเครื่องของคุณ",
"tr_TR": "Ryujinx \"prod.keys\" dosyasını bulamadı.",
"uk_UA": "Ryujinx не вдалося знайти ваш файл \"prod.keys\".",
"zh_CN": "Ryujinx 模拟器找不到“prod.keys”密钥文件。",
"zh_TW": "Ryujinx 無法找到您的「prod.keys」檔案。"
}
},
{
"ID": "NoFirmwareFound",
"Translations": {
"ar_SA": "لم يتم العثور على البرنامج الثابت",
"de_DE": "Firmware nicht gefunden",
"el_GR": "Το firmware δε βρέθηκε",
"en_US": "Firmware not found",
"es_ES": "No se encontró Firmware",
"fr_FR": "Firmware introuvable",
"he_IL": "קושחה לא נמצאה",
"it_IT": "Firmware non trovato",
"ja_JP": "ファームウェアがありません",
"ko_KR": "펌웨어를 찾을 수 없음",
"no_NO": "Fastvare ikke funnet",
"pl_PL": "Nie znaleziono firmware'u",
"pt_BR": "Firmware não encontrado",
"ru_RU": "Прошивка не найдена",
"sv_SE": "Firmware hittades inte",
"th_TH": "ไม่พบ เฟิร์มแวร์",
"tr_TR": "Firmware bulunamadı",
"uk_UA": "Прошивка не знайдена",
"zh_CN": "未安装系统固件",
"zh_TW": "找不到韌體"
}
},
{
"ID": "NoFirmwareFoundDescription",
"Translations": {
"ar_SA": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة.",
"de_DE": "Ryujinx konnte keine installierte Firmware finden!",
"el_GR": "Το Ryujinx δεν κατάφερε να εντοπίσει κανένα εγκατεστημένο firmware.",
"en_US": "Ryujinx was unable to find any firmwares installed.",
"es_ES": "Ryujinx no pudo encontrar un firmware instalado.",
"fr_FR": "Ryujinx n'a pas trouvé de firmware installé.",
"he_IL": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת.",
"it_IT": "Ryujinx non è riuscito a trovare alcun firmware installato.",
"ja_JP": "インストールされたファームウェアが見つかりませんでした。",
"ko_KR": "Ryujinx가 설치된 펌웨어를 찾을 수 없음.",
"no_NO": "Ryujinx kunne ikke finne noen fastvare installert.",
"pl_PL": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u.",
"pt_BR": "Ryujinx não conseguiu encontrar nenhum Firmware instalado.",
"ru_RU": "Ryujinx не удалось найти ни одной установленной прошивки.",
"sv_SE": "Ryujinx kunde inte hitta några installerade firmwares.",
"th_TH": "Ryujinx ไม่พบ เฟิร์มแวร์ที่ติดตั้งไว้ในเครื่องของคุณ",
"tr_TR": "Ryujinx yüklü herhangi firmware bulamadı.",
"uk_UA": "Ryujinx не вдалося знайти жодної встановленої прошивки.",
"zh_CN": "Ryujinx 模拟器未安装 Switch 系统固件。",
"zh_TW": "Ryujinx 無法找到已安裝的任何韌體。"
}
},
{
"ID": "FirmwareParsingFailed",
"Translations": {
"ar_SA": "خطأ في تحليل البرنامج الثابت",
"de_DE": "Firmware-Analysierung-Fehler",
"el_GR": "Σφάλμα ανάλυσης firmware",
"en_US": "Firmware parsing error",
"es_ES": "Error al analizar el Firmware",
"fr_FR": "Erreur d'analyse du firmware",
"he_IL": "שגיאת ניתוח קושחה",
"it_IT": "Errore di analisi del firmware",
"ja_JP": "ファームウェアのパーズエラー",
"ko_KR": "펌웨어 구문 분석 오류",
"no_NO": "Fastvare analysefeil",
"pl_PL": "Błąd parsowania firmware'u",
"pt_BR": "Erro de análise de firmware",
"ru_RU": "Ошибка извлечения прошивки",
"sv_SE": "Tolkningsfel i firmware",
"th_TH": "เกิดข้อผิดพลาดในการวิเคราะห์เฟิร์มแวร์",
"tr_TR": "Firmware çözümleme hatası",
"uk_UA": "Помилка аналізу прошивки",
"zh_CN": "固件文件解析出错",
"zh_TW": "韌體解析錯誤"
}
},
{
"ID": "FirmwareParsingFailedDescription",
"Translations": {
"ar_SA": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.",
"de_DE": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.",
"el_GR": "Το Ryujinx δεν κατάφερε να αναλύσει το συγκεκριμένο firmware. Αυτό συνήθως οφείλετε σε ξεπερασμένα/παλιά κλειδιά.",
"en_US": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.",
"es_ES": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.",
"fr_FR": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.",
"he_IL": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.",
"it_IT": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.",
"ja_JP": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.",
"ko_KR": "Ryujinx가 제공된 펌웨어를 구문 분석하지 못했습니다. 일반적으로 오래된 키로 인해 발생합니다.",
"no_NO": "Ryujinx klarte ikke å analysere levert fastvare. Dette er vanligvis forårsaket av utdaterte nøkler.",
"pl_PL": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.",
"pt_BR": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.",
"ru_RU": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.",
"sv_SE": "Ryujinx kunde inte tolka angiven firmware. Detta sker oftast med utdaterade nycklar.",
"th_TH": "Ryujinx ไม่สามารถวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่เก่าจนเกินไป",
"tr_TR": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.",
"uk_UA": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.",
"zh_CN": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。",
"zh_TW": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。"
}
}
]
}

View File

@@ -0,0 +1,579 @@
{
"Locales": [
{
"ID": "InstallKeysLabel",
"Translations": {
"ar_SA": "تثبيت المفاتيح",
"de_DE": "Schlüssel installieren",
"el_GR": "Εγκατάσταση Κλειδιών",
"en_US": "Install Keys",
"es_ES": "Instalar Claves",
"fr_FR": "Installer des Clés",
"he_IL": "התקנת מפתחות",
"it_IT": "Installa chiavi",
"ja_JP": "キーをインストール",
"ko_KR": "설치 키",
"no_NO": "Installere nøkler",
"pl_PL": "Zainstaluj klucze",
"pt_BR": "Instalar Chaves",
"ru_RU": "Установить ключи",
"sv_SE": "Installera nycklar",
"th_TH": "ติดตั้ง Keys",
"tr_TR": "Anahtarları Yükle",
"uk_UA": "Встановити Ключі",
"zh_CN": "安装密匙",
"zh_TW": "安裝金鑰"
}
},
{
"ID": "InstallKeysFromFileButton",
"Translations": {
"ar_SA": null,
"de_DE": null,
"el_GR": null,
"en_US": ".KEYS",
"es_ES": null,
"fr_FR": null,
"he_IL": null,
"it_IT": null,
"ja_JP": null,
"ko_KR": null,
"no_NO": null,
"pl_PL": null,
"pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": null,
"tr_TR": null,
"uk_UA": null,
"zh_CN": null,
"zh_TW": null
}
},
{
"ID": "InstallKeysFromFolderButton",
"Translations": {
"ar_SA": "مجلد",
"de_DE": "Verzeichnis",
"el_GR": "Φάκελος",
"en_US": "Folder",
"es_ES": "Carpeta",
"fr_FR": "Dossier",
"he_IL": "תיקיה",
"it_IT": "Cartella",
"ja_JP": "フォルダ",
"ko_KR": "폴더",
"no_NO": "Mappe",
"pl_PL": "Folder",
"pt_BR": "Diretório",
"ru_RU": "Папка",
"sv_SE": "Katalog",
"th_TH": "ไดเรกทอรี",
"tr_TR": "Klasör",
"uk_UA": "Тека",
"zh_CN": "文件夹",
"zh_TW": "資料夾"
}
},
{
"ID": "InstallFirmwareLabel",
"Translations": {
"ar_SA": "تثبيت البرنامج الثابت",
"de_DE": "Firmware installieren",
"el_GR": "Εγκατάσταση Firmware",
"en_US": "Install Firmware",
"es_ES": "Instalar Firmware",
"fr_FR": "Installer le Firmware",
"he_IL": "התקן קושחה",
"it_IT": "Installa firmware",
"ja_JP": "ファームウェアをインストール",
"ko_KR": "펌웨어 설치",
"no_NO": "Installer fastvare",
"pl_PL": "Zainstaluj oprogramowanie",
"pt_BR": "Instalar Firmware",
"ru_RU": "Установить прошивку",
"sv_SE": "Installera firmware",
"th_TH": "ติดตั้งเฟิร์มแวร์",
"tr_TR": "Yazılım Yükle",
"uk_UA": "Встановити прошивку",
"zh_CN": "安装系统固件",
"zh_TW": "安裝韌體"
}
},
{
"ID": "InstallFirmwareFromFileButton",
"Translations": {
"ar_SA": ".XCI أو .ZIP",
"de_DE": ".XCI oder .ZIP",
"el_GR": ".XCI ή .ZIP",
"en_US": ".XCI or .ZIP",
"es_ES": ".XCI o .ZIP",
"fr_FR": ".XCI ou .ZIP",
"he_IL": ".XCI או .ZIP",
"it_IT": ".XCI o .ZIP",
"ja_JP": ".XCI または .ZIP",
"ko_KR": ".XCI 또는 .ZIP",
"no_NO": ".XCI eller .ZIP",
"pl_PL": ".XCI lub .ZIP",
"pt_BR": ".XCI ou .ZIP",
"ru_RU": ".XCI или .ZIP",
"sv_SE": ".XCI eller .ZIP",
"th_TH": ".XCI หรือ .ZIP",
"tr_TR": ".XCI veya .ZIP",
"uk_UA": ".XCI або .ZIP",
"zh_CN": ".XCI 或 .ZIP",
"zh_TW": ".XCI 或 .ZIP"
}
},
{
"ID": "InstallFirmwareFromFolderButton",
"Translations": {
"ar_SA": "مجلد",
"de_DE": "Verzeichnis",
"el_GR": "Φάκελος",
"en_US": "Folder",
"es_ES": "Carpeta",
"fr_FR": "Dossier",
"he_IL": "תיקייה",
"it_IT": "Cartella",
"ja_JP": "フォルダー",
"ko_KR": "폴더",
"no_NO": "Mappe",
"pl_PL": "Katalog",
"pt_BR": "Diretório",
"ru_RU": "Папка",
"sv_SE": "Katalog",
"th_TH": "โฟลเดอร์",
"tr_TR": "Klasör",
"uk_UA": "Тека",
"zh_CN": "文件夹",
"zh_TW": "資料夾"
}
},
{
"ID": "ToolsLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Tools",
"es_ES": "Herramientas",
"fr_FR": "Outils",
"he_IL": "",
"it_IT": "Strumenti",
"ja_JP": "",
"ko_KR": "도구",
"no_NO": "",
"pl_PL": "",
"pt_BR": "Ferramentas",
"ru_RU": "Инструменты",
"sv_SE": "Verktyg",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "工具",
"zh_TW": "工具"
}
},
{
"ID": "MiiEditorButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Mii Editor",
"es_ES": "Editor de Mii",
"fr_FR": "Éditeur de Mii",
"he_IL": "",
"it_IT": "Editor di Mii",
"ja_JP": "",
"ko_KR": "Mii 편집기",
"no_NO": "Mii-redigerer",
"pl_PL": "Edytor Mii",
"pt_BR": "Editor de Mii",
"ru_RU": "Редактор Mii",
"sv_SE": "Mii-redigerare",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Редактор Mii",
"zh_CN": "Mii 编辑器",
"zh_TW": "Mii 編輯器"
}
},
{
"ID": "XCITrimmerButton",
"Translations": {
"ar_SA": "",
"de_DE": "XCI-Dateien trimmen",
"el_GR": "",
"en_US": "Trim XCI Files",
"es_ES": "Recortar Archivos XCI",
"fr_FR": "Réduire les Fichiers XCI",
"he_IL": "",
"it_IT": "Riduci dimensioni dei file XCI",
"ja_JP": "",
"ko_KR": "XCI 파일 트리머",
"no_NO": "Trim XCI-filer",
"pl_PL": "",
"pt_BR": "Reduzir Arquivos XCI",
"ru_RU": "Обрезать XCI файлы",
"sv_SE": "Optimera XCI-filer",
"th_TH": "ตัดแต่งไฟล์ XCI",
"tr_TR": "",
"uk_UA": "Обрізати XCI файли",
"zh_CN": "瘦身 XCI 文件",
"zh_TW": "修剪 XCI 檔案"
}
},
{
"ID": "PauseEmulationButton",
"Translations": {
"ar_SA": "إيقاف التشغيل مؤقتًا",
"de_DE": "Emulation pausieren",
"el_GR": "Παύση προσομοίωσης",
"en_US": "Pause Emulation",
"es_ES": "Pausar Emulación",
"fr_FR": "Pauser l'Émulation",
"he_IL": "השהיית האמולציה",
"it_IT": "Pausa emulazione",
"ja_JP": "エミュレーション一時停止",
"ko_KR": "에뮬레이션 일시중지",
"no_NO": "Pause Emulatoren",
"pl_PL": "Wstrzymaj emulację",
"pt_BR": "Pausar emulação",
"ru_RU": "Пауза эмуляции",
"sv_SE": "Pausa emuleringen",
"th_TH": "พักการจำลอง",
"tr_TR": "Emülasyonu Duraklat",
"uk_UA": "Пауза емуляції",
"zh_CN": "暂停模拟",
"zh_TW": "暫停模擬"
}
},
{
"ID": "ResumeEmulationButton",
"Translations": {
"ar_SA": "استئناف المحاكاة",
"de_DE": "Emulation fortsetzen",
"el_GR": "Συνέχιση προσομοίωσης",
"en_US": "Resume Emulation",
"es_ES": "Reanudar Emulación",
"fr_FR": "Reprendre l'Émulation",
"he_IL": "המשך האמולציה",
"it_IT": "Riprendi l'emulazione",
"ja_JP": "エミュレーション再開",
"ko_KR": "에뮬레이션 다시 시작",
"no_NO": "Gjenoppta emuleringen",
"pl_PL": "Wznów emulację",
"pt_BR": "Retomar emulação",
"ru_RU": "Продолжить эмуляцию",
"sv_SE": "Återuppta emuleringen",
"th_TH": "ดำเนินการจำลองต่อ",
"tr_TR": "Emülasyonu Sürdür",
"uk_UA": "Продовжити емуляцію",
"zh_CN": "继续模拟",
"zh_TW": "繼續模擬"
}
},
{
"ID": "StopEmulationButton",
"Translations": {
"ar_SA": "إيقاف المحاكاة",
"de_DE": "Emulation beenden",
"el_GR": "Διακοπή Εξομοίωσης",
"en_US": "Stop Emulation",
"es_ES": "Detener Emulación",
"fr_FR": "Arrêter l'Émulation",
"he_IL": "עצור אמולציה",
"it_IT": "Arresta l'emulazione",
"ja_JP": "エミュレーションを中止",
"ko_KR": "에뮬레이션 중지",
"no_NO": "Stopp Emulering",
"pl_PL": "Zatrzymaj emulację",
"pt_BR": "Parar a Emulação",
"ru_RU": "Остановить эмуляцию",
"sv_SE": "Stoppa emulering",
"th_TH": "หยุดการจำลอง",
"tr_TR": "Emülasyonu Durdur",
"uk_UA": "Зупинити емуляцію",
"zh_CN": "停止模拟",
"zh_TW": "停止模擬"
}
},
{
"ID": "RestartEmulationButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Restart Emulation",
"es_ES": "",
"fr_FR": "Redémarrer l'Émulation",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Перезапустить эмуляцию",
"sv_SE": "Starta om emulering",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "ScanAmiiboButton",
"Translations": {
"ar_SA": "مسح Amiibo",
"de_DE": "Amiibo scannen",
"el_GR": "Σάρωση Amiibo",
"en_US": "Scan Amiibo",
"es_ES": "Escanear Amiibo",
"fr_FR": "Scanner un Amiibo",
"he_IL": "סרוק אמיבו",
"it_IT": "Scansiona un Amiibo",
"ja_JP": "Amiibo をスキャン",
"ko_KR": "Amiibo 스캔",
"no_NO": "Skann en Amiibo",
"pl_PL": "Skanuj Amiibo",
"pt_BR": "Escanear um Amiibo",
"ru_RU": "Сканировать Amiibo",
"sv_SE": "Skanna en Amiibo",
"th_TH": "สแกนหา Amiibo",
"tr_TR": "Bir Amiibo Tara",
"uk_UA": "Сканувати Amiibo",
"zh_CN": "扫描 Amiibo",
"zh_TW": "掃描 Amiibo"
}
},
{
"ID": "ScanAmiiboFromBinButton",
"Translations": {
"ar_SA": "مسح Amiibo (.BIN)",
"de_DE": "Amiibo scannen (.BIN)",
"el_GR": "Σάρωση Amiibo (.BIN)",
"en_US": "Scan Amiibo (.BIN)",
"es_ES": "Escanear un Amiibo (.BIN)",
"fr_FR": "Scanner un Amiibo (.BIN)",
"he_IL": "סרוק Amiibo (.BIN)",
"it_IT": "Scansiona un Amiibo (.BIN)",
"ja_JP": "Amiibo をスキャン (.BIN)",
"ko_KR": "Amiibo 스캔 (.BIN)",
"no_NO": "Skann en Amiibo (.BIN)",
"pl_PL": "Skanuj Amiibo (.BIN)",
"pt_BR": "Escaneie um Amiibo (.BIN)",
"ru_RU": "Сканировать Amiibo (.BIN)",
"sv_SE": "Skanna en Amiibo (.BIN)",
"th_TH": "สแกนอามีโบ (.BIN)",
"tr_TR": "Amiibo Tara (.BIN)",
"uk_UA": "Сканувати Amiibo (.BIN)",
"zh_CN": "扫描 Amiibo (.BIN)",
"zh_TW": "掃瞄 Amiibo (.BIN)"
}
},
{
"ID": "ScanSkylanderButton",
"Translations": {
"ar_SA": "‫فحص Skylander",
"de_DE": "Skylander scannen",
"el_GR": "Σάρωση Skylander",
"en_US": "Scan Skylander",
"es_ES": "Escanear Skylander",
"fr_FR": "Scanner un Skylander",
"he_IL": "סרוק אמיבו",
"it_IT": "Scansiona un Skylander",
"ja_JP": "Skylander をスキャン",
"ko_KR": "Skylander 스캔",
"no_NO": "Skann en Skylander",
"pl_PL": "Skanuj Skylander",
"pt_BR": "Escanear um Skylander",
"ru_RU": "Сканировать Skylander",
"sv_SE": "Skanna en Skylander",
"th_TH": "สแกนหา Skylander",
"tr_TR": "Bir Skylander Tara",
"uk_UA": "Сканувати Skylander",
"zh_CN": "扫描 Skylander",
"zh_TW": "掃描 Skylander"
}
},
{
"ID": "RemoveSkylanderButton",
"Translations": {
"ar_SA": "إزالة Skylander",
"de_DE": "Skylander entfernen",
"el_GR": "Αφαίρεση Skylander",
"en_US": "Remove Skylander",
"es_ES": "Eliminar Skylander",
"fr_FR": "Supprimer un Skylander",
"he_IL": "הסר Skylander",
"it_IT": "Rimuovi Skylander",
"ja_JP": "Skylander を削除",
"ko_KR": "Skylander 제거",
"no_NO": "Fjern Skylander",
"pl_PL": "Usuń Skylander",
"pt_BR": "Remover um Skylander",
"ru_RU": "Удалить Skylander",
"sv_SE": "Ta bort Skylander",
"th_TH": "ลบ Skylander",
"tr_TR": "Skylander'ı Kaldır",
"uk_UA": "Видалити Skylander",
"zh_CN": "移除 Skylander",
"zh_TW": "移除 Skylander"
}
},
{
"ID": "TakeScreenshotButton",
"Translations": {
"ar_SA": "أخذ لقطة للشاشة",
"de_DE": "Screenshot aufnehmen",
"el_GR": "Λήψη Στιγμιότυπου",
"en_US": "Take Screenshot",
"es_ES": "Captura de Pantalla",
"fr_FR": "Prendre une Capture d'Écran",
"he_IL": "צלם מסך",
"it_IT": "Cattura uno screenshot",
"ja_JP": "スクリーンショットを撮影",
"ko_KR": "스크린샷 찍기",
"no_NO": "Ta skjermbilde",
"pl_PL": "Zrób zrzut ekranu",
"pt_BR": "Tirar Captura de tela",
"ru_RU": "Сделать снимок экрана",
"sv_SE": "Ta skärmbild",
"th_TH": "ถ่ายภาพหน้าจอ",
"tr_TR": "Ekran Görüntüsü Al",
"uk_UA": "Зробити знімок екрана",
"zh_CN": "保存截屏",
"zh_TW": "儲存擷取畫面"
}
},
{
"ID": "HideUiButton",
"Translations": {
"ar_SA": "إخفاء واجهة المستخدم",
"de_DE": "Oberfläche ausblenden",
"el_GR": "Απόκρυψη UI",
"en_US": "Hide UI",
"es_ES": "Ocultar Interfaz",
"fr_FR": "Masquer l'Interface",
"he_IL": "הסתר ממשק משתמש ",
"it_IT": "Nascondi l'interfaccia",
"ja_JP": "UIを隠す",
"ko_KR": "UI 숨기기",
"no_NO": "Skjul brukergrensesnitt",
"pl_PL": "Ukryj interfejs użytkownika",
"pt_BR": "Esconder Interface",
"ru_RU": "Скрыть интерфейс",
"sv_SE": "Dölj gränssnittet",
"th_TH": "ซ่อน UI",
"tr_TR": "Arayüzü Gizle",
"uk_UA": "Сховати інтерфейс",
"zh_CN": "隐藏菜单栏和状态栏",
"zh_TW": "隱藏 UI"
}
},
{
"ID": "StartRenderDocCaptureButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Start RenderDoc Frame Capture",
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
"fr_FR": "Démarrer une capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 시작",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "启动 RenderDoc 帧捕获",
"zh_TW": ""
}
},
{
"ID": "EndRenderDocCaptureButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "End RenderDoc Frame Capture",
"es_ES": "Detener la captura de fotograma de RenderDoc",
"fr_FR": "Arrêter la capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 종료",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "结束 RenderDoc 帧捕获",
"zh_TW": ""
}
},
{
"ID": "DiscardRenderDocCaptureButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Discard RenderDoc Frame Capture",
"es_ES": "Descartar la captura de fotograma de RenderDoc",
"fr_FR": "Supprimer la capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 폐기",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "丢弃 RenderDoc 帧捕获",
"zh_TW": ""
}
},
{
"ID": "DiscardRenderDocCaptureToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
"fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "현재 활성화된 RenderDoc 프레임 캡처를 종료하고 결과를 즉시 폐기합니다.",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
"zh_TW": ""
}
}
]
}

View File

@@ -0,0 +1,79 @@
{
"Locales": [
{
"ID": "ManageFileTypes",
"Translations": {
"ar_SA": "إدارة أنواع الملفات",
"de_DE": "Dateitypen verwalten",
"el_GR": "Διαχείριση τύπων αρχείων",
"en_US": "Manage File Types",
"es_ES": "Administrar Tipos de Archivo",
"fr_FR": "Gérer les Types de Fichiers",
"he_IL": "ניהול סוגי קבצים",
"it_IT": "Gestisci i tipi di file",
"ja_JP": "ファイル形式を管理",
"ko_KR": "파일 형식 관리",
"no_NO": "Behandle filtyper",
"pl_PL": "Zarządzaj rodzajami plików",
"pt_BR": "Gerenciar Tipos de Arquivos",
"ru_RU": "Управление типами файлов",
"sv_SE": "Hantera filtyper",
"th_TH": "จัดการประเภทไฟล์",
"tr_TR": "Dosya uzantılarını yönet",
"uk_UA": "Керувати типами файлів",
"zh_CN": "管理文件扩展名",
"zh_TW": "管理檔案類型"
}
},
{
"ID": "InstallFileTypes",
"Translations": {
"ar_SA": "تثبيت أنواع الملفات",
"de_DE": "Dateitypen installieren",
"el_GR": "Εγκαταστήσετε τύπους αρχείων.",
"en_US": "Install File Types",
"es_ES": "Instalar Tipos de Archivo",
"fr_FR": "Installer des Types de Fichiers",
"he_IL": "סוגי קבצי התקנה",
"it_IT": "Installa i tipi di file",
"ja_JP": "ファイル形式をインストール",
"ko_KR": "파일 형식 설치",
"no_NO": "Installer filtyper",
"pl_PL": "Typy plików instalacyjnych",
"pt_BR": "Instalar tipos de arquivos",
"ru_RU": "Установить типы файлов",
"sv_SE": "Installera filtyper",
"th_TH": "ติดตั้งประเภทไฟล์",
"tr_TR": "Dosya uzantılarını yükle",
"uk_UA": "Встановити типи файлів",
"zh_CN": "关联文件扩展名",
"zh_TW": "安裝檔案類型"
}
},
{
"ID": "UninstallFileTypes",
"Translations": {
"ar_SA": "إزالة أنواع الملفات",
"de_DE": "Dateitypen deinstallieren",
"el_GR": "Απεγκαταστήσετε τύπους αρχείων",
"en_US": "Uninstall File Types",
"es_ES": "Desinstalar Tipos de Archivo",
"fr_FR": "Désinstaller des Types de Fichiers",
"he_IL": "סוגי קבצי הסרה",
"it_IT": "Disinstalla i tipi di file",
"ja_JP": "ファイル形式をアンインストール",
"ko_KR": "파일 형식 제거",
"no_NO": "Avinstaller filtyper",
"pl_PL": "Typy plików dezinstalacyjnych",
"pt_BR": "Desinstalar tipos de arquivos",
"ru_RU": "Удалить типы файлов",
"sv_SE": "Avinstallera filtyper",
"th_TH": "ถอนการติดตั้งประเภทไฟล์",
"tr_TR": "Dosya uzantılarını kaldır",
"uk_UA": "Видалити типи файлів",
"zh_CN": "取消关联扩展名",
"zh_TW": "移除檔案類型"
}
}
]
}

View File

@@ -1,104 +0,0 @@
{
"Locales": [
{
"ID": "MenuBarActions_StartCapture",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Start RenderDoc Frame Capture",
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
"fr_FR": "Démarrer une capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 시작",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "MenuBarActions_EndCapture",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "End RenderDoc Frame Capture",
"es_ES": "Detener la captura de fotograma de RenderDoc",
"fr_FR": "Arrêter la capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 종료",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "MenuBarActions_DiscardCapture",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Discard RenderDoc Frame Capture",
"es_ES": "Descartar la captura de fotograma de RenderDoc",
"fr_FR": "Supprimer la capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 폐기",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "MenuBarActions_DiscardCapture_ToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
"fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "현재 활성화된 RenderDoc 프레임 캡처를 종료하고 결과를 즉시 폐기합니다.",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
{
"Locales": [
{
"ID": "FirmwareVersion",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Firmware Version: {0}",
"es_ES": "Versión del Firmware: {0}",
"fr_FR": "Version du Firmware : {0}",
"he_IL": "",
"it_IT": "Versione firmware: {0}",
"ja_JP": "",
"ko_KR": "펌웨어 버전 : {0}",
"no_NO": "Fastvareversjon: {0}",
"pl_PL": "",
"pt_BR": "Versão do Firmware: {0}",
"ru_RU": "Версия прошивки: {0}",
"sv_SE": "Firmware-version: {0}",
"th_TH": "เวอร์ชันเฟิร์มแวร์: {0}",
"tr_TR": "",
"uk_UA": "Версія прошивки: {0}",
"zh_CN": "系统固件版本:{0}",
"zh_TW": "系統韌體版本: {0}"
}
}
]
}

View File

@@ -2050,7 +2050,9 @@
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
010057D00ECE4000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
010037A0170D2000,"NINTENDO 64™ Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
@@ -2638,6 +2640,7 @@
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
@@ -3307,6 +3310,7 @@
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
0100BFC01D976000,"Virtual Boy Nintendo Classics",services,nothing,2026-02-17 11:26:59
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
1 title_id game_name labels status last_updated
2050 010003C00B868000 Ninjin: Clash of Carrots online-broken playable 2024-07-10 05:12:26
2051 0100746010E4C000 NinNinDays playable 2022-11-20 15:17:29
2052 0100C9A00ECE6000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2053 010057D00ECE4000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2054 0100e0601c632000 Nintendo 64™ – Nintendo Switch Online: MATURE 17+ ingame 2025-02-03 22:27:00
2055 010037A0170D2000 NINTENDO 64™ – Nintendo Switch Online 18+ ingame 2025-02-03 22:27:00
2056 0100D870045B6000 Nintendo Entertainment System™ - Nintendo Switch Online online playable 2022-07-01 15:45:06
2057 0100C4B0034B2000 Nintendo Labo Toy-Con 01 Variety Kit gpu ingame 2022-08-07 12:56:07
2058 01001E9003502000 Nintendo Labo Toy-Con 03 Vehicle Kit services;crash menus 2022-08-03 17:20:11
2640 0100B16009C10000 SINNER: Sacrifice for Redemption nvdec;UE4;vulkan-backend-bug playable 2022-08-12 20:37:33
2641 0100E9201410E000 Sir Lovelot playable 2021-04-05 16:21:46
2642 0100134011E32000 Skate City playable 2022-11-04 11:37:39
2643 0100a8501b66e000 Skateboard Drifting with Maxwell Cat: The Game Simulator playable 2026-02-17 19:05:00
2644 0100B2F008BD8000 Skee-Ball playable 2020-11-16 04:44:07
2645 01001A900F862000 Skelattack playable 2021-06-09 15:26:26
2646 01008E700F952000 Skelittle: A Giant Party! playable 2021-06-09 19:08:34
3310 0100AFA011068000 Voxel Pirates playable 2022-09-28 22:55:02
3311 0100BFB00D1F4000 Voxel Sword playable 2022-08-30 14:57:27
3312 01004E90028A2000 Vroom in the night sky Needs Update;vulkan-backend-bug playable 2023-02-20 02:32:29
3313 0100BFC01D976000 Virtual Boy – Nintendo Classics services nothing 2026-02-17 11:26:59
3314 0100C7C00AE6C000 VSR: Void Space Racing playable 2021-01-27 14:08:59
3315 0100B130119D0000 Waifu Uncovered crash ingame 2023-02-27 01:17:46
3316 0100E29010A4A000 Wanba Warriors playable 2020-10-04 17:56:22

View File

@@ -83,39 +83,7 @@ namespace Ryujinx.Audio.Backends.Apple
}
}
public static bool IsSupported => IsSupportedInternal();
private static bool IsSupportedInternal()
{
if (!OperatingSystem.IsMacOS()) return false;
try
{
AudioStreamBasicDescription format =
GetAudioFormat(SampleFormat.PcmInt16, Constants.TargetSampleRate, 2);
int result = AudioQueueNewOutput(
ref format,
nint.Zero,
nint.Zero,
nint.Zero,
nint.Zero,
0,
out nint testQueue);
if (result == 0)
{
AudioQueueDispose(testQueue, true);
return true;
}
return false;
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Audio, $"Failed to check if AudioToolbox is supported: {e.Message}\n{e.StackTrace}");
return false;
}
}
public static bool IsSupported => OperatingSystem.IsMacOSVersionAtLeast(10, 5);
public ManualResetEvent GetUpdateRequiredEvent()
=> _updateRequiredEvent;

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Common
public uint MixesSize;
public uint SinksSize;
public uint PerformanceBufferSize;
public uint Unknown24;
public uint SplitterSize;
public uint RenderInfoSize;
#pragma warning disable IDE0051, CS0169 // Remove unused field

View File

@@ -433,8 +433,12 @@ namespace Ryujinx.Audio.Renderer.Server
public ResultCode UpdateSplitter(SplitterContext context)
{
long initialInputConsumed = _inputReader.Consumed;
if (context.Update(ref _inputReader))
{
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SplitterSize);
return ResultCode.Success;
}

View File

@@ -107,12 +107,12 @@ namespace Ryujinx.BuildValidationTasks
{
locale.Translations[langCode] = string.Empty;
Console.WriteLine(
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
}
else
{
Console.WriteLine(
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
}
}

View File

@@ -10,7 +10,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" />
<PackageReference Include="Humanizer" />
<PackageReference Include="Gommon" />
</ItemGroup>

View File

@@ -184,6 +184,7 @@ namespace Ryujinx.Common
"01001b300b9be000", // Diablo III: Eternal Collection
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
"01008c8012920000", // Dying Light Platinum Edition
"0100d11013e6a000", // Eschatos
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"0100f7e00c70e000", // Hogwarts Legacy
@@ -193,9 +194,15 @@ namespace Ryujinx.Common
"0100d71004694000", // Minecraft
"01007430037f6000", // Monopoly
"0100853015e86000", // No Man's Sky
"0100f85014ed0000", // No More Heroes
"0100463014ed4000", // No More Heroes 2
"0100e570094e8000", // Owlboy
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01009f100bc52000", // Psikyo Collection 1
"01009d400c4a8000", // Psikyo Collection 2
"01008e200c5c2000", // Muse Dash
"01005ff002e2a000", // Rayman Legends
"01007820196a6000", // Red Dead Redemption
"0100e8300a67a000", // Risk
"01002f7013224000", // Rune Factory 5

View File

@@ -22,10 +22,11 @@ namespace Ryujinx.Common.Utilities
}
// "dumpable" attribute of the calling process
private const int PR_GET_DUMPABLE = 3;
private const int PR_SET_DUMPABLE = 4;
[DllImport("libc", SetLastError = true)]
private static extern int prctl(int option, int arg2);
[LibraryImport("libc", SetLastError = true)]
private static partial int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable)
{
@@ -36,5 +37,13 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(result == 0);
}
}
// Use the below line to display dumpable status in the console:
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
public static bool IsCoreDumpable()
{
int result = prctl(PR_GET_DUMPABLE, 0);
return result == 1;
}
}
}

View File

@@ -488,6 +488,8 @@ namespace Ryujinx.HLE.FileSystem
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
List<string> failedFiles = new();
foreach (string filePath in keyPaths)
{
try
@@ -497,17 +499,20 @@ namespace Ryujinx.HLE.FileSystem
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, e.Message);
failedFiles.Add(Path.GetFileName(filePath));
continue;
}
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
if (File.Exists(destPath))
File.Delete(destPath);
File.Copy(filePath, destPath, true);
}
if (failedFiles.Count > 0)
{
throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
}
return;
}
@@ -518,8 +523,6 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(keysSource);
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
@@ -534,10 +537,6 @@ namespace Ryujinx.HLE.FileSystem
string dest = Path.Combine(installDirectory, info.Name);
if (File.Exists(dest))
File.Delete(dest);
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
File.Copy(keysSource, dest, true);
}
@@ -1059,7 +1058,7 @@ namespace Ryujinx.HLE.FileSystem
}
}
public static bool AreKeysAlredyPresent(string pathToCheck)
public static bool AreKeysAlreadyPresent(string pathToCheck)
{
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
foreach (string file in fileNames)

View File

@@ -20,6 +20,7 @@ using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
using Ryujinx.HLE.HOS.Services.Nv;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
@@ -66,6 +67,8 @@ namespace Ryujinx.HLE.HOS
internal List<NfpDevice> NfpDevices { get; private set; }
internal List<NfcDevice> NfcDevices { get; private set; }
internal SmRegistry SmRegistry { get; private set; }
internal ServerBase SmServer { get; private set; }
@@ -132,6 +135,7 @@ namespace Ryujinx.HLE.HOS
PerformanceState = new PerformanceState();
NfpDevices = [];
NfcDevices = [];
// Note: This is not really correct, but with HLE of services, the only memory
// region used that is used is Application, so we can use the other ones for anything.
@@ -372,6 +376,15 @@ namespace Ryujinx.HLE.HOS
}
}
public void ScanSkylander(int nfcDeviceId, byte[] data)
{
if (NfcDevices[nfcDeviceId].State == NfcDeviceState.SearchingForTag)
{
NfcDevices[nfcDeviceId].State = NfcDeviceState.TagFound;
NfcDevices[nfcDeviceId].Data = data;
}
}
public bool SearchingForAmiibo(out int nfpDeviceId)
{
nfpDeviceId = default;
@@ -389,6 +402,53 @@ namespace Ryujinx.HLE.HOS
return false;
}
public bool SearchingForSkylander(out int nfcDeviceId)
{
nfcDeviceId = default;
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.SearchingForTag)
{
nfcDeviceId = i;
return true;
}
}
return false;
}
public bool HasSkylander(out int nfcDeviceId)
{
nfcDeviceId = default;
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.TagFound)
{
nfcDeviceId = i;
return true;
}
}
return false;
}
public void RemoveSkylander()
{
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.TagFound)
{
NfcDevices[i].State = NfcDeviceState.Initialized;
NfcDevices[i].SignalDeactivate();
Thread.Sleep(100); // NOTE: Simulate skylander scanning delay.
}
}
}
public void SignalDisplayResolutionChange()
{
DisplayResolutionChangeEvent.ReadableEvent.Signal();

View File

@@ -56,6 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_activeCount = 0;
JoyHold = NpadJoyHoldType.Vertical;
SixAxisActive = false;
}
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
@@ -580,6 +581,29 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return needUpdateRight;
}
public bool isAtRest(int playerNumber)
{
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
if (currentNpad.StyleSet == NpadStyleTag.None)
{
return true; // it will always be at rest because it cannot move.
}
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
float acceleration = Math.Abs(storage.Acceleration.X)
+ Math.Abs(storage.Acceleration.Y)
+ Math.Abs(storage.Acceleration.Z);
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
+ Math.Abs(storage.AngularVelocity.Y)
+ Math.Abs(storage.AngularVelocity.Z);
// TODO: check against config deadzone and add sensitivity setting
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
}
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{

View File

@@ -602,19 +602,33 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandCmif(82)]
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
{
int sixAxisSensorHandle = context.RequestData.ReadInt32();
// 4 byte struct w/ 4-byte alignment
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
// 32bit sign extension padding -> if = 0, + offset, else - offset
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
bool isAtRest = true;
context.ResponseData.Write(isAtRest);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
// We currently do not support stopping or starting SixAxisTracking.
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
return ResultCode.Success;
}
@@ -629,7 +643,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
return ResultCode.Success;
}

View File

@@ -5,6 +5,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
@@ -14,6 +15,7 @@ using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
@@ -487,6 +489,23 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
return ResultCode.Success;
}
[CommandCmif(106)] // 20.0.0+
// SetProtocol
public ResultCode SetProtocol(ServiceCtx context)
{
uint protocolValue = context.RequestData.ReadUInt32();
// On NX only input value 1 or 3 is allowed, with an error being thrown otherwise.
if (protocolValue != 1 && protocolValue != 3)
{
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
}
Logger.Stub?.PrintStub(LogClass.ServiceLdn, $"Protocol value: {protocolValue}");
return ResultCode.Success;
}
[CommandCmif(200)]
// OpenAccessPoint()
public ResultCode OpenAccessPoint(ServiceCtx context)

View File

@@ -1,8 +1,19 @@
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
{
[Service("nfc:mf:u")]
class IUserManager : IpcService
{
public IUserManager(ServiceCtx context) { }
[CommandCmif(0)]
// CreateUserInterface() -> object<nn::nfc::mf::IUser>
public ResultCode CreateUserInterface(ServiceCtx context)
{
MakeObject(context, new IMifare());
return ResultCode.Success;
}
}
}

View File

@@ -0,0 +1,477 @@
using Ryujinx.Common.Memory;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
class IMifare : IpcService
{
private State _state;
private KEvent _availabilityChangeEvent;
private CancellationTokenSource _cancelTokenSource;
public IMifare()
{
_state = State.NonInitialized;
}
[CommandCmif(0)]
public ResultCode Initialize(ServiceCtx context)
{
_state = State.Initialized;
NfcDevice devicePlayer1 = new()
{
NpadIdType = NpadIdType.Player1,
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
State = NfcDeviceState.Initialized,
};
context.Device.System.NfcDevices.Add(devicePlayer1);
return ResultCode.Success;
}
[CommandCmif(1)]
public ResultCode Finalize(ServiceCtx context)
{
if (_state == State.Initialized)
{
_cancelTokenSource?.Cancel();
// NOTE: All events are destroyed here.
context.Device.System.NfcDevices.Clear();
_state = State.NonInitialized;
}
return ResultCode.Success;
}
[CommandCmif(2)]
public ResultCode GetListDevices(ServiceCtx context)
{
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
ulong outputSize = context.Request.RecvListBuff[0].Size;
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfcDevices[i].Handle);
}
context.ResponseData.Write(context.Device.System.NfcDevices.Count);
return ResultCode.Success;
}
[CommandCmif(3)]
public ResultCode StartDetection(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfcDevices[i].State = NfcDeviceState.SearchingForTag;
break;
}
}
_cancelTokenSource = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
{
if (_cancelTokenSource.Token.IsCancellationRequested)
{
break;
}
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
{
context.Device.System.NfcDevices[i].SignalActivate();
Thread.Sleep(125); // NOTE: Simulate skylander scanning delay.
break;
}
}
}
}, _cancelTokenSource.Token);
return ResultCode.Success;
}
[CommandCmif(4)]
public ResultCode StopDetection(ServiceCtx context)
{
_cancelTokenSource?.Cancel();
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfcDevices[i].State = NfcDeviceState.Initialized;
Array.Clear(context.Device.System.NfcDevices[i].Data);
context.Device.System.NfcDevices[i].SignalDeactivate();
break;
}
}
return ResultCode.Success;
}
[CommandCmif(5)]
public ResultCode ReadMifare(ServiceCtx context)
{
if (context.Request.ReceiveBuff.Count == 0 || context.Request.SendBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] readBlockParameter = new byte[inputSize];
context.Memory.Read(inputPosition, readBlockParameter);
var span = MemoryMarshal.Cast<byte, NfcMifareReadBlockParameter>(readBlockParameter);
var list = new List<NfcMifareReadBlockParameter>(span.Length);
foreach (var item in span)
list.Add(item);
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
return ResultCode.TagNotFound;
}
else
{
for (int p = 0; p < list.Count; p++)
{
NfcMifareReadBlockData blockData = new()
{
SectorNumber = list[p].SectorNumber,
Reserved = new Array7<byte>(),
};
byte[] data = new byte[16];
switch (list[p].SectorKey.MifareCommand)
{
case NfcMifareCommand.NfcMifareCommand_Read:
case NfcMifareCommand.NfcMifareCommand_AuthA:
if (IsCurrentBlockKeyBlock(list[p].SectorNumber))
{
Array.Copy(context.Device.System.NfcDevices[i].Data, (16 * list[p].SectorNumber) + 6, data, 6, 4);
}
else
{
Array.Copy(context.Device.System.NfcDevices[i].Data, 16 * list[p].SectorNumber, data, 0, 16);
}
data.CopyTo(blockData.Data.AsSpan());
context.Memory.Write(outputPosition + ((uint)(p * Unsafe.SizeOf<NfcMifareReadBlockData>())), blockData);
break;
}
}
}
}
}
return ResultCode.Success;
}
[CommandCmif(6)]
public ResultCode WriteMifare(ServiceCtx context)
{
if (context.Request.SendBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] writeBlockParameter = new byte[inputSize];
context.Memory.Read(inputPosition, writeBlockParameter);
var span = MemoryMarshal.Cast<byte, NfcMifareWriteBlockParameter>(writeBlockParameter);
var list = new List<NfcMifareWriteBlockParameter>(span.Length);
foreach (var item in span)
list.Add(item);
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
return ResultCode.TagNotFound;
}
else
{
for (int p = 0; p < list.Count; p++)
{
switch (list[p].SectorKey.MifareCommand)
{
case NfcMifareCommand.NfcMifareCommand_Write:
case NfcMifareCommand.NfcMifareCommand_AuthA:
list[p].Data.AsSpan().CopyTo(context.Device.System.NfcDevices[i].Data.AsSpan(list[p].SectorNumber * 16, 16));
break;
}
}
}
}
}
return ResultCode.Success;
}
[CommandCmif(7)]
public ResultCode GetTagInfo(ServiceCtx context)
{
ResultCode resultCode = ResultCode.Success;
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagMounted || context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
{
TagInfo tagInfo = new()
{
UuidLength = 4,
Reserved1 = new Array21<byte>(),
Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol
TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type
Reserved2 = new Array6<byte>(),
};
byte[] uuid = new byte[4];
Array.Copy(context.Device.System.NfcDevices[i].Data, 0, uuid, 0, 4);
uuid.CopyTo(tagInfo.Uuid.AsSpan());
context.Memory.Write(outputPosition, tagInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(8)]
public ResultCode AttachActivateEvent(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.Device.System.NfcDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(9)]
public ResultCode AttachDeactivateEvent(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.Device.System.NfcDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(10)]
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write((int)_state);
return ResultCode.Success;
}
[CommandCmif(11)]
public ResultCode GetDeviceState(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
if (context.Device.System.NfcDevices[i].State > NfcDeviceState.Finalized)
{
throw new InvalidOperationException($"{nameof(context.Device.System.NfcDevices)} contains an invalid state for device {i}: {context.Device.System.NfcDevices[i].State}");
}
context.ResponseData.Write((uint)context.Device.System.NfcDevices[i].State);
return ResultCode.Success;
}
}
context.ResponseData.Write((uint)NfcDeviceState.Unavailable);
return ResultCode.DeviceNotFound;
}
[CommandCmif(12)]
public ResultCode GetNpadId(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfcDevices[i].Handle));
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(13)]
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
{
_availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
return ResultCode.Success;
}
private bool IsCurrentBlockKeyBlock(byte block)
{
return ((block + 1) % 4) == 0;
}
}
}

View File

@@ -0,0 +1,21 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
class NfcDevice
{
public KEvent ActivateEvent;
public KEvent DeactivateEvent;
public void SignalActivate() => ActivateEvent.ReadableEvent.Signal();
public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
public NfcDeviceState State = NfcDeviceState.Unavailable;
public PlayerIndex Handle;
public NpadIdType NpadIdType;
public byte[] Data;
}
}

View File

@@ -0,0 +1,14 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcMifareCommand : byte
{
NfcMifareCommand_Read = 0x30,
NfcMifareCommand_AuthA = 0x60,
NfcMifareCommand_AuthB = 0x61,
NfcMifareCommand_Write = 0xA0,
NfcMifareCommand_Transfer = 0xB0,
NfcMifareCommand_Decrement = 0xC0,
NfcMifareCommand_Increment = 0xC1,
NfcMifareCommand_Store = 0xC2,
}
}

View File

@@ -0,0 +1,13 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
struct NfcMifareReadBlockData
{
public Array16<byte> Data;
public byte SectorNumber;
public Array7<byte> Reserved;
}
}

View File

@@ -0,0 +1,13 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
struct NfcMifareReadBlockParameter
{
public byte SectorNumber;
public Array7<byte> Reserved;
public NfcSectorKey SectorKey;
}
}

View File

@@ -0,0 +1,14 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
struct NfcMifareWriteBlockParameter
{
public Array16<byte> Data;
public byte SectorNumber;
public Array7<byte> Reserved;
public NfcSectorKey SectorKey;
}
}

View File

@@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcProtocol : byte
{
NfcProtocol_None = 0b_0000_0000,
NfcProtocol_TypeA = 0b_0000_0001, ///< ISO14443A
NfcProtocol_TypeB = 0b_0000_0010, ///< ISO14443B
NfcProtocol_TypeF = 0b_0000_0100, ///< Sony FeliCa
NfcProtocol_All = 0xFF,
}
}

View File

@@ -0,0 +1,16 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct NfcSectorKey
{
public NfcMifareCommand MifareCommand;
public byte Unknown;
public Array6<byte> Reserved1;
public Array6<byte> SectorKey;
public Array2<byte> Reserved2;
}
}

View File

@@ -0,0 +1,15 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcTagType : byte
{
NfcTagType_None = 0b_0000_0000,
NfcTagType_Type1 = 0b_0000_0001, ///< ISO14443A RW. Topaz
NfcTagType_Type2 = 0b_0000_0010, ///< ISO14443A RW. Ultralight, NTAGX, ST25TN
NfcTagType_Type3 = 0b_0000_0100, ///< ISO14443A RW/RO. Sony FeliCa
NfcTagType_Type4A = 0b_0000_1000, ///< ISO14443A RW/RO. DESFire
NfcTagType_Type4B = 0b_0001_0000, ///< ISO14443B RW/RO. DESFire
NfcTagType_Type5 = 0b_0010_0000, ///< ISO15693 RW/RO. SLI, SLIX, ST25TV
NfcTagType_Mifare = 0b_0100_0000, ///< Mifare clasic. Skylanders
NfcTagType_All = 0xFF,
}
}

View File

@@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcDeviceState : byte
{
Initialized = 0,
SearchingForTag = 1,
TagFound = 2,
TagRemoved = 3,
TagMounted = 4,
Unavailable = 5,
Finalized = 6,
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum State
{
NonInitialized,
Initialized,
}
}

View File

@@ -0,0 +1,16 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x58)]
struct TagInfo
{
public Array10<byte> Uuid;
public byte UuidLength;
public Array21<byte> Reserved1;
public uint Protocol;
public uint TagType;
public Array6<byte> Reserved2;
}
}

View File

@@ -0,0 +1,17 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
{
public enum ResultCode
{
ModuleId = 161,
ErrorCodeShift = 9,
Success = 0,
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, // 0x80A1
WrongArgument = (65 << ErrorCodeShift) | ModuleId, // 0x82A1
WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, // 0x92A1
NfcDisabled = (80 << ErrorCodeShift) | ModuleId, // 0xA0A1
TagNotFound = (97 << ErrorCodeShift) | ModuleId, // 0xC2A1
MifareAccessError = (288 << ErrorCodeShift) | ModuleId, // 0x240a1
}
}

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
public ISslService(ServiceCtx context) { }
[CommandCmif(0)]
// CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext>
// CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext>
public ResultCode CreateContext(ServiceCtx context)
{
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
@@ -126,14 +126,18 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
}
[CommandCmif(100)]
// CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64)
// CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContextForSystem>
public ResultCode CreateContextForSystem(ServiceCtx context)
{
ulong pid = context.RequestData.ReadUInt64();
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong pidPlaceholder = context.RequestData.ReadUInt64();
#pragma warning restore IDE0059
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder });
// Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class.
MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
return ResultCode.Success;
}

View File

@@ -20,5 +20,7 @@ namespace Ryujinx.HLE.HOS.SystemState
SimplifiedChinese,
TraditionalChinese,
BrazilianPortuguese,
Polish,
Thai,
}
}

View File

@@ -23,7 +23,9 @@ namespace Ryujinx.HLE.HOS.SystemState
"es-419",
"zh-Hans",
"zh-Hant",
"pt-BR"
"pt-BR",
"pl",
"th"
];
internal long DesiredKeyboardLayout { get; private set; }

View File

@@ -18,5 +18,7 @@ namespace Ryujinx.HLE.HOS.SystemState
TraditionalChinese,
SimplifiedChinese,
BrazilianPortuguese,
Polish,
Thai,
}
}

View File

@@ -33,7 +33,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
[CmifCommand(10100)] // 1.0.0-5.1.0
[CmifCommand(10102)] // 6.0.0-9.2.0
[CmifCommand(10104)] // 10.0.0+
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
public Result SaveReportOld([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
return Result.Success;
}
[CmifCommand(10106)] // 21.0.0+
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid, bool optInCheckEnabled)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
@@ -48,7 +61,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
[CmifCommand(10101)] // 1.0.0-5.1.0
[CmifCommand(10103)] // 6.0.0-9.2.0
[CmifCommand(10105)] // 10.0.0+
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
public Result SaveReportWithUserOld(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
return Result.Success;
}
[CmifCommand(10107)] // 21.0.0+
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid, bool optInCheckEnabled)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{

View File

@@ -1,12 +1,19 @@
using Ryujinx.Common.Memory;
using System;
using System.Buffers.Binary;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Horizon.Sdk.Ns
{
public struct ApplicationControlProperty
{
public Array16<ApplicationTitle> Title;
/// <summary>
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
/// </summary>
public Array16<ApplicationTitle> TitleBlock;
public Array37<byte> Isbn;
public StartupUserAccountValue StartupUserAccount;
public UserAccountSwitchLockValue UserAccountSwitchLock;
@@ -58,7 +65,10 @@ namespace Ryujinx.Horizon.Sdk.Ns
public RepairFlagValue RepairFlag;
public byte ProgramIndex;
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
public Array4<byte> Reserved3214;
public byte ApplicationErrorCodePrefix;
public TitleCompressionValue TitleCompression;
public byte AcdIndex;
public byte ApparentPlatform;
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
public ApplicationJitConfiguration JitConfiguration;
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
@@ -74,6 +84,47 @@ namespace Ryujinx.Horizon.Sdk.Ns
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
private const int TitleCount = 32;
private const int TitleEntrySize = 0x300;
/// <summary>
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
/// </summary>
public readonly ApplicationTitle[] Title
{
get
{
var titles = new ApplicationTitle[TitleCount];
if (TitleCompression != TitleCompressionValue.Enable)
{
TitleBlock.AsSpan().CopyTo(titles);
return titles;
}
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
}
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
return titles;
}
}
public struct ApplicationTitle
{
@@ -130,6 +181,8 @@ namespace Ryujinx.Horizon.Sdk.Ns
TraditionalChinese = 13,
SimplifiedChinese = 14,
BrazilianPortuguese = 15,
Polish = 16,
Thai = 17,
}
public enum Organization
@@ -302,5 +355,11 @@ namespace Ryujinx.Horizon.Sdk.Ns
Deny = 0,
Allow = 1,
}
public enum TitleCompressionValue : byte
{
Disable = 0,
Enable = 1,
}
}
}

View File

@@ -8,8 +8,10 @@ namespace Ryujinx.Horizon.Sdk.Prepo
{
interface IPrepoService : IServiceObject
{
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReportOld(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, bool optInCheckEnabled);
Result SaveReportWithUserOld(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, bool optInCheckEnabled);
Result RequestImmediateTransmission();
Result GetTransmissionStatus(out int status);
Result GetSystemSessionId(out ulong systemSessionId);

View File

@@ -9,10 +9,20 @@ using static SDL.SDL3;
namespace Ryujinx.Input.SDL3
{
public unsafe class SDL3GamepadDriver : IGamepadDriver
{
private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping;
private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds;
/// <summary>
/// Unlinked joy-cons
/// </summary>
private readonly Dictionary<SDL_JoystickID, string> _joyConsIds;
/// <summary>
/// Linked joy-cons, remove dual joy-con from <c>_gamepadsIds</c> when a linked joy-con is removed
/// </summary>
private readonly Dictionary<SDL_JoystickID,string> _linkedJoyConsIds;
private readonly Lock _lock = new();
public ReadOnlySpan<string> GamepadsIds
@@ -21,7 +31,11 @@ namespace Ryujinx.Input.SDL3
{
lock (_lock)
{
return _gamepadsIds.Values.ToArray();
List<string> temp = [];
temp.AddRange(_gamepadsIds.Values);
temp.AddRange(_joyConsIds.Values);
temp.AddRange(_linkedJoyConsIds.Values);
return temp.ToArray();
}
}
}
@@ -35,6 +49,8 @@ namespace Ryujinx.Input.SDL3
{
_gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>();
_gamepadsIds = [];
_joyConsIds = [];
_linkedJoyConsIds = [];
SDL3Driver.Instance.Initialize();
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
@@ -92,7 +108,7 @@ namespace Ryujinx.Input.SDL3
int guidIndex = 0;
id = guidIndex + "-" + guidString;
while (_gamepadsIds.ContainsValue(id))
while (_gamepadsIds.ContainsValue(id) || _joyConsIds.ContainsValue(id) || _linkedJoyConsIds.ContainsValue(id))
{
id = (++guidIndex) + "-" + guidString;
}
@@ -104,16 +120,47 @@ namespace Ryujinx.Input.SDL3
private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId)
{
bool joyConPairDisconnected = false;
string fakeId = null;
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
return;
lock (_lock)
{
_gamepadsIds.Remove(joystickInstanceId);
if (!SDL3JoyConPair.IsCombinable(_gamepadsIds))
if (!_linkedJoyConsIds.ContainsKey(joystickInstanceId))
{
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
if (!_joyConsIds.Remove(joystickInstanceId))
{
_gamepadsIds.Remove(joystickInstanceId);
}
}
else
{
foreach (string matchId in _gamepadsIds.Values)
{
if (matchId.Contains(id))
{
fakeId = matchId;
break;
}
}
string leftId = fakeId!.Split('_')[0];
string rightId = fakeId!.Split('_')[1];
if (leftId == id)
{
_linkedJoyConsIds.Remove(GetInstanceIdFromId(rightId));
_joyConsIds.Add(GetInstanceIdFromId(rightId), rightId);
}
else
{
_linkedJoyConsIds.Remove(GetInstanceIdFromId(leftId));
_joyConsIds.Add(GetInstanceIdFromId(leftId), leftId);
}
_linkedJoyConsIds.Remove(joystickInstanceId);
_gamepadsIds.Remove(GetInstanceIdFromId(fakeId));
joyConPairDisconnected = true;
}
}
@@ -121,13 +168,14 @@ namespace Ryujinx.Input.SDL3
OnGamepadDisconnected?.Invoke(id);
if (joyConPairDisconnected)
{
OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id);
OnGamepadDisconnected?.Invoke(fakeId);
}
}
private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId)
{
bool joyConPairConnected = false;
string fakeId = null;
if (SDL_IsGamepad(joystickInstanceId))
{
@@ -149,27 +197,40 @@ namespace Ryujinx.Input.SDL3
{
lock (_lock)
{
_gamepadsIds.Add(joystickInstanceId, id);
if (SDL3JoyConPair.IsCombinable(_gamepadsIds))
if (!SDL3JoyCon.IsJoyCon(joystickInstanceId))
{
// TODO - It appears that you can only have one joy con pair connected at a time?
// This was also the behavior before SDL3
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
uint fakeInstanceID = uint.MaxValue;
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceID, SDL3JoyConPair.Id))
_gamepadsIds.Add(joystickInstanceId, id);
}
else
{
if (SDL3JoyConPair.IsCombinable(joystickInstanceId, _joyConsIds, out SDL_JoystickID match))
{
fakeInstanceID--;
_joyConsIds.Remove(match, out string matchId);
_linkedJoyConsIds.Add(joystickInstanceId, id);
_linkedJoyConsIds.Add(match, matchId);
uint fakeInstanceId = uint.MaxValue;
fakeId = SDL3JoyCon.IsLeftJoyCon(joystickInstanceId)
? $"{id}_{matchId}"
: $"{matchId}_{id}";
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceId, fakeId))
{
fakeInstanceId--;
}
_gamepadsInstanceIdsMapping.Add((SDL_JoystickID)fakeInstanceId, fakeId);
joyConPairConnected = true;
}
else
{
_joyConsIds.Add(joystickInstanceId, id);
}
joyConPairConnected = true;
}
}
OnGamepadConnected?.Invoke(id);
if (joyConPairConnected)
{
OnGamepadConnected?.Invoke(SDL3JoyConPair.Id);
OnGamepadConnected?.Invoke(fakeId);
}
}
}
@@ -193,10 +254,22 @@ namespace Ryujinx.Input.SDL3
{
OnGamepadDisconnected?.Invoke(gamepad.Value);
}
foreach (var gamepad in _joyConsIds)
{
OnGamepadDisconnected?.Invoke(gamepad.Value);
}
foreach (var gamepad in _linkedJoyConsIds)
{
OnGamepadDisconnected?.Invoke(gamepad.Value);
}
lock (_lock)
{
_gamepadsIds.Clear();
_joyConsIds.Clear();
_linkedJoyConsIds.Clear();
}
SDL3Driver.Instance.Dispose();
@@ -215,11 +288,27 @@ namespace Ryujinx.Input.SDL3
public IGamepad GetGamepad(string id)
{
if (id == SDL3JoyConPair.Id)
// joy-con pair ids is the combined ids of its parts which are split using a '_'
if (id.Contains('_'))
{
lock (_lock)
{
return SDL3JoyConPair.GetGamepad(_gamepadsIds);
string leftId = id.Split('_')[0];
string rightId = id.Split('_')[1];
SDL_JoystickID leftInstanceId = GetInstanceIdFromId(leftId);
SDL_JoystickID rightInstanceId = GetInstanceIdFromId(rightId);
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad(leftInstanceId);
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad(rightInstanceId);
if (leftGamepadHandle == null || rightGamepadHandle == null)
{
return null;
}
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, leftId),
new SDL3JoyCon(rightGamepadHandle, rightId));
}
}
@@ -232,7 +321,7 @@ namespace Ryujinx.Input.SDL3
return null;
}
if (SDL_GetGamepadName(gamepadHandle).StartsWith(SDL3JoyCon.Prefix))
if (SDL3JoyCon.IsJoyCon(instanceId))
{
return new SDL3JoyCon(gamepadHandle, id);
}
@@ -249,6 +338,22 @@ namespace Ryujinx.Input.SDL3
yield return GetGamepad(gamepad.Value);
}
}
lock (_joyConsIds)
{
foreach (var gamepad in _joyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
lock (_linkedJoyConsIds)
{
foreach (var gamepad in _linkedJoyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
}
}
}

View File

@@ -24,10 +24,10 @@ namespace Ryujinx.Input.SDL3
private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _leftButtonsDriverMapping = new()
{
{GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
{GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
{GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
{GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
@@ -37,10 +37,10 @@ namespace Ryujinx.Input.SDL3
private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _rightButtonsDriverMapping = new()
{
{GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
{GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
{GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
@@ -398,5 +398,15 @@ namespace Ryujinx.Input.SDL3
return SDL_GetGamepadButton(_gamepadHandle, button);
}
public static bool IsJoyCon(SDL_JoystickID gamepadsId)
{
return SDL_GetGamepadNameForID(gamepadsId) is LeftName or RightName;
}
public static bool IsLeftJoyCon(SDL_JoystickID gamepadsId)
{
return SDL_GetGamepadNameForID(gamepadsId) is LeftName;
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Input.SDL3
public const string Id = "JoyConPair";
string IGamepad.Id => Id;
public string Name => "* Nintendo Switch Joy-Con (L/R)";
public string Name => "Nintendo Switch Dual Joy-Con (L/R)";
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
public void Dispose()
@@ -96,44 +96,23 @@ namespace Ryujinx.Input.SDL3
right.SetTriggerThreshold(triggerThreshold);
}
public static bool IsCombinable(Dictionary<SDL_JoystickID, string> gamepadsIds)
public static bool IsCombinable(SDL_JoystickID joyCon1, Dictionary<SDL_JoystickID, string> joyConIds, out SDL_JoystickID match)
{
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
return leftIndex >= 0 && rightIndex >= 0;
}
bool isLeft = SDL3JoyCon.IsLeftJoyCon(joyCon1);
string matchName = isLeft ? SDL3JoyCon.RightName : SDL3JoyCon.LeftName;
match = 0;
private static (int leftIndex, int rightIndex) DetectJoyConPair(Dictionary<SDL_JoystickID, string> gamepadsIds)
{
Dictionary<string, SDL_JoystickID> gamepadNames = gamepadsIds
.Where(gamepadId => gamepadId.Value != Id && SDL_GetGamepadNameForID(gamepadId.Key) is SDL3JoyCon.LeftName or SDL3JoyCon.RightName)
.Select(gamepad => (SDL_GetGamepadNameForID(gamepad.Key), gamepad.Key))
.ToDictionary();
SDL_JoystickID idx;
int leftIndex = gamepadNames.TryGetValue(SDL3JoyCon.LeftName, out idx) ? (int)idx : -1;
int rightIndex = gamepadNames.TryGetValue(SDL3JoyCon.RightName, out idx) ? (int)idx : -1;
return (leftIndex, rightIndex);
}
public unsafe static IGamepad GetGamepad(Dictionary<SDL_JoystickID, string> gamepadsIds)
{
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
if (leftIndex <= 0 || rightIndex <= 0)
foreach (var joyConId in joyConIds.Keys)
{
return null;
if (SDL_GetGamepadNameForID(joyConId) == matchName)
{
match = joyConId;
return true;
}
}
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)leftIndex);
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)rightIndex);
if (leftGamepadHandle == null || rightGamepadHandle == null)
{
return null;
}
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, gamepadsIds[(SDL_JoystickID)leftIndex]),
new SDL3JoyCon(rightGamepadHandle, gamepadsIds[(SDL_JoystickID)rightIndex]));
return false;
}
}
}

View File

@@ -141,7 +141,7 @@ namespace Ryujinx.Ava.Input
AvaKey.OemComma,
AvaKey.OemPeriod,
AvaKey.OemQuestion,
AvaKey.OemBackslash,
AvaKey.OemPipe,
// NOTE: invalid
AvaKey.None

View File

@@ -42,6 +42,7 @@ namespace Ryujinx.Ava
public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
public static bool CoreDumpArg { get; private set; }
private const uint MbIconwarning = 0x30;
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
CoreDumpArg = coreDumpArg;
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
// This is undesirable and causes very odd behavior during development (the process stops responding,
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.

View File

@@ -28,11 +28,6 @@
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-arm64'">
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
<!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
@@ -54,7 +49,7 @@
<PackageReference Include="Svg.Controls.Avalonia" />
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI.NoAnim" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="CommandLineParser" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="DiscordRichPresence" />

View File

@@ -476,10 +476,10 @@ namespace Ryujinx.Ava.Systems
TouchScreenManager.Initialize(Device);
_viewModel.IsGameRunning = true;
Dispatcher.UIThread.InvokeAsync(() =>
{
_viewModel.IsGameRunning = true;
_viewModel.IsPaused = false;
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
});
@@ -578,6 +578,7 @@ namespace Ryujinx.Ava.Systems
public void Stop()
{
_isActive = false;
_viewModel.IsPaused = false;
_playTimer.Stop();
}
@@ -704,8 +705,8 @@ namespace Ryujinx.Ava.Systems
if (userError is UserError.NoFirmware)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallerNotInstalledMessage],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.Dialog_Firmware_InstallerEmbeddedMessage, firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
string.Empty);
@@ -735,8 +736,8 @@ namespace Ryujinx.Ava.Systems
_viewModel.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString),
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString),
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.Dialog_Firmware_InstallerInstalledMessage, firmwareVersion.VersionString),
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.Dialog_Firmware_InstallerEmbeddedMessageSuccess, firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
string.Empty,
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);

View File

@@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Name))
{
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{
if (!controlTitle.NameString.IsEmpty())
{
@@ -1417,7 +1417,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Developer))
{
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{
if (!controlTitle.PublisherString.IsEmpty())
{

View File

@@ -24,6 +24,8 @@ namespace Ryujinx.Ava.Systems.Configuration.System
SimplifiedChinese,
TraditionalChinese,
BrazilianPortuguese,
Polish,
Thai,
}
public static class LanguageEnumHelper

View File

@@ -14,9 +14,9 @@ namespace Ryujinx.Ava.UI.Helpers
private static string GetErrorTitle(UserError error) =>
error switch
{
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware],
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailed],
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.Error_NoKeysFound],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.Error_NoFirmwareFound],
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.Error_FirmwareParsingFailed],
UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFound],
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown],
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined],
@@ -25,9 +25,9 @@ namespace Ryujinx.Ava.UI.Helpers
private static string GetErrorDescription(UserError error) =>
error switch
{
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription],
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailedDescription],
UserError.NoKeys => LocaleManager.Instance[LocaleKeys.Error_NoKeysFoundDescription],
UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.Error_NoFirmwareFoundDescription],
UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.Error_FirmwareParsingFailedDescription],
UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFoundDescription],
UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription],
_ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription],

View File

@@ -5,6 +5,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Windowing;
using Gommon;
using Ryujinx.Ava.Common.Locale;
@@ -53,6 +54,9 @@ namespace Ryujinx.Ava
{
Name = FormatTitle();
// Disable menu animations
FAUISettings.SetAnimationsEnabledAtAppLevel(false);
AvaloniaXamlLoader.Load(this);
if (OperatingSystem.IsMacOS())

View File

@@ -184,7 +184,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_controller = 0;
}
if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1)
if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1)
{
ControllerType controller = Controllers[_controller].Type;
@@ -467,7 +467,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
IsModified = true;
RevertChanges();
FindPairedDeviceInConfigFile();
_isChangeTrackingActive = true; // Enable configuration change tracking
}
@@ -521,7 +521,17 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1)
{
Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType);
int controllerIndex = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType);
// Avalonia bug: setting a newly instanced ComboBox to 0
// causes the selected item to show up blank
// Workaround: set the box to 1 and then 0
if (controllerIndex == 0)
{
Controller = 1;
}
Controller = controllerIndex;
}
else
{
@@ -576,7 +586,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
DeviceList.Clear();
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
int controllerNumber = 0;
foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
{
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
@@ -593,6 +603,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (gamepad != null)
{
int controllerNumber = 0;
string name = GetUniqueGamepadName(gamepad, ref controllerNumber);
Devices.Add((DeviceType.Controller, id, name));
}
@@ -950,8 +961,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
LoadDevice();
_isLoaded = false;
LoadConfiguration();
LoadDevice();
_isLoaded = true;
OnPropertyChanged();
IsModified = false;

View File

@@ -174,6 +174,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _screenshotKey = "F8";
private float _volume;
private ApplicationData _currentApplicationData;
private bool _pendingRestart;
private readonly AutoResetEvent _rendererWaitEvent;
private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy;
@@ -370,6 +371,39 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
public bool IsSkylanderRequested
{
get => field && _isGameRunning;
set
{
field = value;
OnPropertyChanged();
}
}
public bool HasSkylander
{
get => field && _isGameRunning;
set
{
field = value;
OnPropertyChanged();
}
}
public bool ShowSkylanderActions
{
get => field && _isGameRunning;
set
{
field = value;
OnPropertyChanged();
}
}
public bool ShowLoadProgress
{
get;
@@ -907,25 +941,25 @@ namespace Ryujinx.Ava.UI.ViewModels
{
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename));
LocaleKeys.Dialog_Firmware_InstallerFirmwareNotFound, filename));
return;
}
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString);
LocaleKeys.Dialog_Firmware_InstallerTitle, firmwareVersion.VersionString);
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString);
LocaleKeys.Dialog_Firmware_InstallerMainMessage, firmwareVersion.VersionString);
SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion();
if (currentVersion != null)
{
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString);
LocaleKeys.Dialog_Firmware_InstallerSubMessage, currentVersion.VersionString);
}
dialogMessage +=
LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage];
LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallerConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
dialogTitle,
@@ -935,7 +969,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UpdateWaitWindow waitingDialog = new(dialogTitle,
LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallerWaitMessage]);
if (result == UserResult.Yes)
{
@@ -957,7 +991,7 @@ namespace Ryujinx.Ava.UI.ViewModels
waitingDialog.Close();
string message = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage,
LocaleKeys.Dialog_Firmware_InstallerSuccessMessage,
firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(
@@ -1025,18 +1059,18 @@ namespace Ryujinx.Ava.UI.ViewModels
}
string dialogTitle =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle);
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.MenuBar_Actions_InstallKeysLabel);
string dialogMessage =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.Dialog_Keys_InstallerMainMessage);
if (ContentManager.AreKeysAlredyPresent(systemDirectory))
if (ContentManager.AreKeysAlreadyPresent(systemDirectory))
{
dialogMessage +=
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
.DialogKeysInstallerKeysInstallSubMessage);
.Dialog_Keys_InstallerSubMessage);
}
dialogMessage += LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallConfirmMessage];
dialogMessage += LocaleManager.Instance[LocaleKeys.Dialog_Keys_InstallerConfirmInstall];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
dialogTitle,
@@ -1046,7 +1080,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UpdateWaitWindow waitingDialog = new(dialogTitle,
LocaleManager.Instance[LocaleKeys.DialogKeysInstallerKeysInstallWaitMessage]);
LocaleManager.Instance[LocaleKeys.Dialog_Keys_InstallerWaitMessage]);
if (result == UserResult.Yes)
{
@@ -1069,7 +1103,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string message =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
.DialogKeysInstallerKeysInstallSuccessMessage);
.Dialog_Keys_InstallerSuccessMessage);
await ContentDialogHelper.CreateInfoDialog(
dialogTitle,
@@ -1091,7 +1125,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (ex is FormatException)
{
message = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogKeysInstallerKeysNotFoundErrorMessage, filename);
LocaleKeys.Dialog_Keys_InstallerKeysNotFound, filename);
}
await ContentDialogHelper.CreateErrorDialog(message);
@@ -1217,6 +1251,14 @@ namespace Ryujinx.Ava.UI.ViewModels
await LoadApplication(_currentApplicationData);
}
else if (_pendingRestart)
{
_pendingRestart = false;
Logger.Info?.Print(LogClass.Application, $"Restarting emulation for '{_currentApplicationData.Name}'");
await LoadApplication(_currentApplicationData);
}
else
{
// Otherwise, clear state.
@@ -1225,6 +1267,21 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public void RestartEmulation()
{
if (AppHost is null || _currentApplicationData is null)
{
Logger.Warning?.Print(LogClass.Application, "RestartEmulation called but no application is running.");
return;
}
Logger.Info?.Print(LogClass.Application, $"Restart requested for '{_currentApplicationData.Name}'");
_pendingRestart = true;
AppHost.Stop();
}
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{
if (ShowMenuAndStatusBar && !ShowLoadProgress)
@@ -1357,9 +1414,10 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallFromFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
Patterns = ["*.xci", "*.zip"],
AppleUniformTypeIdentifiers = ["com.ryujinx.xci", "public.zip-archive"],
@@ -1388,7 +1446,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task InstallFirmwareFromFolder()
{
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync();
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallFromFolderDialogTitle]
});
if (result.HasValue)
{
@@ -1400,6 +1461,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.Dialog_Keys_InstallFromFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{
new("KEYS")
@@ -1419,7 +1481,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task InstallKeysFromFolder()
{
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync();
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.Dialog_Keys_InstallFromFolderDialogTitle]
});
if (result.HasValue)
{
@@ -1773,14 +1838,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (version != null)
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion,
version.VersionString);
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_FirmwareVersion, version.VersionString);
hasApplet = version.Major > 3;
}
else
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "NaN");
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_FirmwareVersion, "NaN");
}
IsAppletMenuActive = hasApplet;
@@ -1864,6 +1928,46 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
public async Task OpenSkylanderWindow()
{
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
{
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"],
},
},
});
if (result.HasValue)
{
// Open reading stream from the first file.
await using var stream = await result.Value.OpenReadAsync();
using var streamReader = new BinaryReader(stream);
// Reads all the content of file as a text.
byte[] data = new byte[1024];
var count = streamReader.Read(data, 0, 1024);
if (count < 1024)
{
return;
}
else
{
AppHost.Device.System.ScanSkylander(deviceId, data);
}
}
}
}
public async Task RemoveSkylander()
{
AppHost.Device.System.RemoveSkylander();
}
public void ReloadRenderDocApi()
{

View File

@@ -47,18 +47,13 @@
</StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
<ui:ColorPickerButton
<ColorPicker
Margin="5"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False"
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
ColorChanged="ColorPickerButton_OnColorChanged"
AttachedToVisualTree="ColorPicker_OnAttachedToVisualTree"
ColorChanged="ColorPicker_OnColorChanged"
Color="{Binding LedColor, Mode=TwoWay}">
</ui:ColorPickerButton>
</ColorPicker>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -1,4 +1,5 @@
using Avalonia;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
@@ -30,19 +31,17 @@ namespace Ryujinx.UI.Views.Input
InitializeComponent();
}
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
private void ColorPicker_OnColorChanged(object sender, ColorChangedEventArgs args)
{
if (!args.NewColor.HasValue)
return;
if (!ViewModel.EnableLedChanging)
return;
if (ViewModel.TurnOffLed)
return;
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.ToUInt32());
}
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
private void ColorPicker_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (!ViewModel.EnableLedChanging)
return;

View File

@@ -63,6 +63,11 @@
Command="{Binding OpenScreenshotsFolder}"
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
Icon="{ext:Icon fa-solid fa-image}" />
<Separator IsVisible="{Binding ManageFileTypesVisible}" />
<MenuItem Header="{ext:Locale MenuBar_File_ManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}" Icon="{ext:Icon fa-solid fa-clipboard}">
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBar_File_InstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" Icon="{ext:Icon fa-solid fa-square-plus}" />
<MenuItem Name="UninstallFileTypesMenuItem" Header="{ext:Locale MenuBar_File_UninstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered}" Icon="{ext:Icon fa-solid fa-square-minus}" />
</MenuItem>
<Separator />
<MenuItem
Name="CloseRyujinxMenuItem"
@@ -149,100 +154,120 @@
IsVisible="{Binding !EnableNonGameRunningControls}">
<MenuItem
Name="PauseEmulationMenuItem"
Header="{ext:Locale MenuBarOptionsPauseEmulation}"
Header="{ext:Locale MenuBar_Actions_PauseEmulationButton}"
Icon="{ext:Icon fa-solid fa-pause}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" />
<MenuItem
Name="ResumeEmulationMenuItem"
Header="{ext:Locale MenuBarOptionsResumeEmulation}"
Header="{ext:Locale MenuBar_Actions_ResumeEmulationButton}"
Icon="{ext:Icon fa-solid fa-play}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" />
<MenuItem
Name="StopEmulationMenuItem"
Header="{ext:Locale MenuBarOptionsStopEmulation}"
Header="{ext:Locale MenuBar_Actions_StopEmulationButton}"
Icon="{ext:Icon fa-solid fa-stop}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" />
<Separator />
<MenuItem
Name="RestartEmulationMenuItem"
Header="{ext:Locale MenuBar_Actions_RestartEmulationButton}"
Icon="{ext:Icon fa-solid fa-rotate-right}"
InputGesture="Ctrl + R"
IsEnabled="{Binding IsGameRunning}" />
<Separator/>
<MenuItem
Command="{Binding OpenAmiiboWindow}"
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Header="{ext:Locale MenuBarActionsScanAmiibo}"
Icon="{ext:Icon fa-solid fa-cube}"
Header="{ext:Locale MenuBar_Actions_ScanAmiiboButton}"
Icon="{ext:Icon fa-solid fa-chess-rook}"
InputGesture="Ctrl + A"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{Binding OpenBinFile}"
AttachedToVisualTree="ScanBinAmiiboMenuItem_AttachedToVisualTree"
Header="{ext:Locale MenuBarActionsScanAmiiboBin}"
Icon="{ext:Icon fa-solid fa-cube}"
Header="{ext:Locale MenuBar_Actions_ScanAmiiboFromBinButton}"
Icon="{ext:Icon fa-solid fa-chess-rook}"
IsVisible="{Binding CanScanAmiiboBinaries}"
InputGesture="Ctrl + B"
IsEnabled="{Binding IsAmiiboBinRequested}" />
<Separator IsVisible="{Binding CanScanAmiiboBinaries}"/>
<MenuItem
Command="{Binding OpenSkylanderWindow}"
AttachedToVisualTree="ScanSkylanderMenuItem_AttachedToVisualTree"
Header="{ext:Locale MenuBar_Actions_ScanSkylanderButton}"
Icon="{ext:Icon fa-solid fa-dragon}"
IsVisible="{Binding ShowSkylanderActions}"
InputGesture="Ctrl + S"
IsEnabled="{Binding IsSkylanderRequested}" />
<MenuItem
Command="{Binding RemoveSkylander}"
AttachedToVisualTree="RemoveSkylanderMenuItem_AttachedToVisualTree"
Header="{ext:Locale MenuBar_Actions_RemoveSkylanderButton}"
Icon="{ext:Icon fa-solid fa-dragon}"
IsVisible="{Binding ShowSkylanderActions}"
InputGesture="Ctrl + D"
IsEnabled="{Binding HasSkylander}" />
<Separator IsVisible="{Binding ShowSkylanderActions}"/>
<MenuItem
Command="{Binding SimulateWakeUpMessage}"
Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}"
Icon="{ext:Icon fa-solid fa-sun}"
InputGesture="Ctrl + M" />
<Separator />
<MenuItem
Command="{Binding TakeScreenshot}"
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
Header="{ext:Locale MenuBar_Actions_TakeScreenshotButton}"
Icon="{ext:Icon fa-solid fa-camera}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{Binding HideUi}"
Header="{ext:Locale MenuBarFileToolsHideUi}"
Header="{ext:Locale MenuBar_Actions_HideUiButton}"
Icon="{ext:Icon fa-solid fa-eye-slash}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Name="CheatManagerMenuItem"
Header="{ext:Locale GameListContextMenuManageCheat}"
Icon="{ext:Icon fa-solid fa-code}"
IsEnabled="{Binding IsGameRunning}" />
<Separator IsVisible="{Binding RenderDocIsAvailable}" />
<MenuItem
IsVisible="{Binding ShowStartCaptureButton}"
Command="{Binding StartRenderDocCapture}"
CommandParameter="{Binding}"
Header="{ext:Locale RenderDoc_MenuBarActions_StartCapture}"
Header="{ext:Locale MenuBar_Actions_StartRenderDocCaptureButton}"
Icon="{ext:Icon fa-solid fa-video}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
IsVisible="{Binding ShowEndCaptureButton}"
Command="{Binding EndRenderDocCapture}"
CommandParameter="{Binding}"
Header="{ext:Locale RenderDoc_MenuBarActions_EndCapture}"
Header="{ext:Locale MenuBar_Actions_EndRenderDocCaptureButton}"
Icon="{ext:Icon fa-solid fa-video-slash}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
IsVisible="{Binding ShowEndCaptureButton}"
Command="{Binding DiscardRenderDocCapture}"
CommandParameter="{Binding}"
Header="{ext:Locale RenderDoc_MenuBarActions_DiscardCapture}"
ToolTip.Tip="{ext:Locale RenderDoc_MenuBarActions_DiscardCapture_ToolTip}"
Header="{ext:Locale MenuBar_Actions_DiscardRenderDocCaptureButton}"
ToolTip.Tip="{ext:Locale MenuBar_Actions_DiscardRenderDocCaptureToolTip}"
Icon="{ext:Icon fa-solid fa-video-slash}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarActions}" IsVisible="{Binding EnableNonGameRunningControls}">
<MenuItem Header="{ext:Locale MenuBarActionsInstallKeys}" Icon="{ext:Icon fa-solid fa-key}">
<MenuItem Command="{Binding InstallKeysFromFile}" Header="{ext:Locale MenuBarFileActionsInstallKeysFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
<MenuItem Command="{Binding InstallKeysFromFolder}" Header="{ext:Locale MenuBarFileActionsInstallKeysFromFolder}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
<MenuItem Header="{ext:Locale MenuBar_Actions_InstallKeysLabel}" Icon="{ext:Icon fa-solid fa-key}">
<MenuItem Command="{Binding InstallKeysFromFile}" Header="{ext:Locale MenuBar_Actions_InstallKeysFromFileButton}" Icon="{ext:Icon fa-solid fa-file-code}" />
<MenuItem Command="{Binding InstallKeysFromFolder}" Header="{ext:Locale MenuBar_Actions_InstallKeysFromFolderButton}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
</MenuItem>
<MenuItem Header="{ext:Locale MenuBarActionsInstallFirmware}" Icon="{ext:Icon fa-solid fa-floppy-disk}">
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromDirectory}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
</MenuItem>
<MenuItem Header="{ext:Locale MenuBarActionsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}" Icon="{ext:Icon fa-solid fa-clipboard}">
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsInstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" Icon="{ext:Icon fa-solid fa-square-plus}" />
<MenuItem Name="UninstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsUninstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered}" Icon="{ext:Icon fa-solid fa-square-minus}" />
<MenuItem Header="{ext:Locale MenuBar_Actions_InstallFirmwareLabel}" Icon="{ext:Icon fa-solid fa-floppy-disk}">
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBar_Actions_InstallFirmwareFromFileButton}" Icon="{ext:Icon fa-solid fa-file-code}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBar_Actions_InstallFirmwareFromFolderButton}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
</MenuItem>
<Separator />
<MenuItem Header="{ext:Locale MenuBarActionsTools}" Icon="{ext:Icon fa-solid fa-toolbox}">
<MenuItem Header="{ext:Locale MenuBar_Actions_ToolsLabel}" Icon="{ext:Icon fa-solid fa-toolbox}">
<MenuItem
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBarActionsOpenMiiEditor}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" ToolTip.Tip="{ext:Locale MenuBarActionsOpenMiiEditorToolTip}" />
<MenuItem Name="XciTrimmerMenuItem" Header="{ext:Locale MenuBarActionsXCITrimmer}" Icon="{ext:Icon fa-solid fa-scissors}" />
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" />
<MenuItem Name="XciTrimmerMenuItem" Header="{ext:Locale MenuBar_Actions_XCITrimmerButton}" Icon="{ext:Icon fa-solid fa-scissors}" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}">

View File

@@ -43,7 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Main
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp);
RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation());
InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes);
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);
XciTrimmerMenuItem.Command = Commands.Create(XciTrimmerView.Show);
@@ -163,24 +163,6 @@ namespace Ryujinx.Ava.UI.Views.Main
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
public async Task OpenCheatManagerForCurrentApp()
{
if (!ViewModel.IsGameRunning)
return;
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await StyleableAppWindow.ShowAsync(
new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
ViewModel.SelectedApplication.Path)
);
ViewModel.AppHost.Device.EnableCheats();
}
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
@@ -193,6 +175,20 @@ namespace Ryujinx.Ava.UI.Views.Main
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
}
private void ScanSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
ViewModel.IsSkylanderRequested = ViewModel.AppHost.Device.System.SearchingForSkylander(out _);
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
}
private void RemoveSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
ViewModel.HasSkylander = ViewModel.AppHost.Device.System.HasSkylander(out _);
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
}
private async Task InstallFileTypes()
{
ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install();

View File

@@ -329,7 +329,7 @@
Margin="5, 0, 0, 0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{ext:Locale StatusBarSystemVersion}" />
Text="{ext:Locale StatusBar_FirmwareVersion}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -108,7 +108,7 @@
<Button
Name="SaveButton"
Click="SaveButton_Click">
<TextBlock Text="{ext:Locale UserProfilesSetProfileImage}" />
<TextBlock Text="{ext:Locale UserProfilesSave}" />
</Button>
</StackPanel>
</Grid>

View File

@@ -78,22 +78,16 @@
Spacing="10"
Margin="0 24 0 0"
HorizontalAlignment="Right">
<ui:ColorPickerButton
FlyoutPlacement="Top"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
<ColorPicker
IsAlphaEnabled="False"
Color="{Binding BackgroundColor, Mode=TwoWay}"
Name="ColorButton">
<ui:ColorPickerButton.Styles>
<ColorPicker.Styles>
<Style Selector="Grid#Root > DockPanel > Grid">
<Setter Property="IsVisible" Value="False" />
</Style>
</ui:ColorPickerButton.Styles>
</ui:ColorPickerButton>
</ColorPicker.Styles>
</ColorPicker>
<Button
Content="{ext:Locale AvatarChoose}"
Height="35"

View File

@@ -41,8 +41,10 @@
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
<KeyBinding Gesture="Ctrl+M" Command="{Binding SimulateWakeUpMessage}" />
</Window.KeyBindings>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*">
<helpers:OffscreenTextBox IsEnabled="False" Opacity="0" Name="HiddenTextBox" IsHitTestVisible="False" IsTabStop="False" />

View File

@@ -1,5 +1,7 @@
using Avalonia.Platform.Storage;
using Gommon;
using Ryujinx.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -11,29 +13,42 @@ namespace Ryujinx.Ava.Utilities
extension(IStorageProvider storageProvider)
{
public Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false))
CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false)))
.Then(folders => folders.FindFirst());
public Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false))
CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false)))
.Then(files => files.FindFirst());
public Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true))
CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true)))
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
public Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true))
CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true)))
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
}
private static async Task<T> CoreDumpable<T>(Func<Task<T>> picker)
{
OsUtils.SetCoreDumpable(true);
try
{
return await picker();
}
finally
{
if (!Program.CoreDumpArg)
OsUtils.SetCoreDumpable(false);
}
}
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
{
if (openOptions is null)
return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple;
return openOptions;
}
@@ -43,7 +58,6 @@ namespace Ryujinx.Ava.Utilities
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple;
return openOptions;
}
}

View File

@@ -1,6 +1,5 @@
using Ryujinx.Common.Logging;
using System;
using System.Management;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -11,7 +10,7 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
{
internal WindowsSystemInfo()
{
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameFromRegistry()} ; {LogicalCoreCount} logical";
(RamTotal, RamAvailable) = GetMemoryStats();
}
@@ -28,25 +27,26 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
return (0, 0);
}
private static string GetCpuNameWMI()
private static string GetCpuNameFromRegistry()
{
ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor");
if (cpuObjs != null)
try
{
foreach (ManagementBaseObject cpuObj in cpuObjs)
{
return cpuObj["Name"].ToString().Trim();
}
}
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
return key?.GetValue("ProcessorNameString")?.ToString()?.Trim();
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Registry CPU name lookup failed: {ex.Message}");
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
}
}
[StructLayout(LayoutKind.Sequential)]
private struct MemoryStatusEx
private struct MemoryStatusEx()
{
public uint Length;
public uint Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
public uint MemoryLoad;
public ulong TotalPhys;
public ulong AvailPhys;
@@ -55,33 +55,10 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
public ulong TotalVirtual;
public ulong AvailVirtual;
public ulong AvailExtendedVirtual;
public MemoryStatusEx()
{
Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
}
}
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer);
private static ManagementObjectCollection GetWMIObjects(string scope, string query)
{
try
{
return new ManagementObjectSearcher(scope, query).Get();
}
catch (PlatformNotSupportedException ex)
{
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
}
catch (COMException ex)
{
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
}
return null;
}
}
}