mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-07-11 09:54:02 +02:00
Compare commits
76 Commits
Canary-1.3.290
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b19e2ac65d | |||
| 6953128ff2 | |||
| b72990bf53 | |||
| 732e882524 | |||
| 68432d210b | |||
| e47417a161 | |||
| d665de1f4f | |||
| 680b5b719a | |||
| b236732713 | |||
| 97aed15d19 | |||
| 134773e95f | |||
| 5072904162 | |||
| 6fb71b08ae | |||
| 59a6a3a5f7 | |||
| 8b7d27a071 | |||
| 45c66eaf66 | |||
| 905a41a643 | |||
| 54e9b40cd7 | |||
| 737b951ee9 | |||
| 5566e752a4 | |||
| a5f72136b2 | |||
| aa5d32a7b1 | |||
| be5881f100 | |||
| 8b1b015572 | |||
| c2e0cf2fd5 | |||
| 6eb6b1fc81 | |||
| 0ce1bf0b33 | |||
| c698a10b23 | |||
| 6d67a86efd | |||
| e777e3f93b | |||
| 7b19e041cb | |||
| e7a3c94b9c | |||
| 223f20868a | |||
| 8fe1a9c672 | |||
| aad1a71f68 | |||
| 25120667f1 | |||
| 9061c3a2b3 | |||
| 963f024838 | |||
| c263c211bd | |||
| 77c9a50f59 | |||
| a9ce288632 | |||
| 9b88fee78f | |||
| 04574103cf | |||
| 153d632ee2 | |||
| 824643e143 | |||
| 7be1708146 | |||
| 279c4cf3b4 | |||
| fa72b5a298 | |||
| e02081c09e | |||
| de70f66a27 | |||
| 6c1692ed60 | |||
| fb7c1fde11 | |||
| 18226decf1 | |||
| b2310823c9 | |||
| b62c58c2fe | |||
| cc67e586ec | |||
| 8d53b02fa8 | |||
| 4631e0a9e1 | |||
| e477ec7149 | |||
| 81468c1d25 | |||
| 004a12005e | |||
| a3eda287b5 | |||
| ce340e5d0b | |||
| d94b759e89 | |||
| b7772462f1 | |||
| 3473044c6e | |||
| e756ad4556 | |||
| 58bd19a2f3 | |||
| 48888bd014 | |||
| d307afc454 | |||
| 134453e62b | |||
| 298b6c3959 | |||
| c96433eb13 | |||
| c0078088dd | |||
| dcac29680a | |||
| ec07e51807 |
@@ -28,7 +28,7 @@ jobs:
|
||||
configuration: [Release]
|
||||
platform:
|
||||
- { name: win-x64, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, zip_os_name: win_arm64 }
|
||||
- { name: win-arm64, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, zip_os_name: linux_arm64 }
|
||||
#- { name: osx-x64, zip_os_name: osx_x64 }
|
||||
|
||||
@@ -32,9 +32,9 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop
|
||||
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/app.ryujinx.Ryujinx.desktop
|
||||
shell: bash
|
||||
|
||||
- name: Create output dir
|
||||
|
||||
@@ -26,9 +26,9 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
|
||||
#- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
|
||||
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ ipch/
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# .NET
|
||||
.dotnet-home/
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
|
||||
+16
-16
@@ -3,12 +3,12 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.3.15" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.15" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.48.0" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.18" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.49.1" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
|
||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
@@ -27,7 +27,7 @@
|
||||
<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" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.18.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.19.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
@@ -39,29 +39,29 @@
|
||||
<!-- OpenTk.Audio.OpenAL has moved to OpenTk.Audio -->
|
||||
<!--<PackageVersion Include="OpenTK.Audio" Version="5.0.0-pre.15" />-->
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.9.4" />
|
||||
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
|
||||
<!-- Ryujinx.Audio.OpenAL.Dependencies is from the original project, last updated 12/30/20 -->
|
||||
<!--<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />-->
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.1" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.2" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.4-build6" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.4" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="2.0.6" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="2.0.6" />
|
||||
<PackageVersion Include="Gommon" Version="2.8.1.2" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="Sep" Version="0.13.0" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
||||
<PackageVersion Include="Sep" Version="0.15.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Win32" Version="2.88.9" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.9" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.9" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -40,8 +40,14 @@
|
||||
|
||||
## Usage
|
||||
|
||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
||||
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
||||
To run this emulator, your PC must be equipped with at least:
|
||||
- 8GiB of RAM
|
||||
- 6 cores
|
||||
- A GPU released within the last 10 years.
|
||||
- OpenGL 4.6 | Vulkan 1.4
|
||||
- Windows 10 version 20H1 | macOS Big Sur (Apple Silicon)
|
||||
|
||||
Failing to meet these requirements may result in a poor gameplay experience or unexpected crashes.
|
||||
|
||||
## Latest build
|
||||
|
||||
|
||||
+3
-3
@@ -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.
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "WindowTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "أميبو",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Amiibo",
|
||||
"es_ES": null,
|
||||
"fr_FR": null,
|
||||
"he_IL": "אמיבו",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": null,
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": null,
|
||||
"ru_RU": null,
|
||||
"sv_SE": null,
|
||||
"th_TH": "อมิโบ้",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": null,
|
||||
"zh_TW": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SeriesLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "سلسلة",
|
||||
"de_DE": "Serie",
|
||||
"el_GR": "Σειρά",
|
||||
"en_US": "Series",
|
||||
"es_ES": "Serie",
|
||||
"fr_FR": "Séries",
|
||||
"he_IL": "סדרה",
|
||||
"it_IT": "Serie",
|
||||
"ja_JP": "シリーズ",
|
||||
"ko_KR": "시리즈",
|
||||
"no_NO": "Serie",
|
||||
"pl_PL": "Seria",
|
||||
"pt_BR": "Série",
|
||||
"ru_RU": "Серия",
|
||||
"sv_SE": "Serie",
|
||||
"th_TH": "ซีรีส์",
|
||||
"tr_TR": "Seri",
|
||||
"uk_UA": "Серія",
|
||||
"zh_CN": "系列",
|
||||
"zh_TW": "系列"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ShowAllAmiiboLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "إظهار الكل Amiibo",
|
||||
"de_DE": "Alle Amiibo anzeigen",
|
||||
"el_GR": "Εμφάνιση Όλων των Amiibo",
|
||||
"en_US": "Show All Amiibo",
|
||||
"es_ES": "Mostrar Todas las Amiibo",
|
||||
"fr_FR": "Afficher Tous les Amiibo",
|
||||
"he_IL": "הצג הכל Amiibo",
|
||||
"it_IT": "Mostra tutti gli Amiibo",
|
||||
"ja_JP": "すべてのAmiiboを表示",
|
||||
"ko_KR": "모든 Amiibo 표시",
|
||||
"no_NO": "Vis alle Amiibo",
|
||||
"pl_PL": "Pokaż Wszystkie Amiibo",
|
||||
"pt_BR": "Mostrar Todos os Amiibo",
|
||||
"ru_RU": "Показывать все Amiibo",
|
||||
"sv_SE": "Visa alla Amiibo",
|
||||
"th_TH": "แสดง Amiibo ทั้งหมด",
|
||||
"tr_TR": "Tüm Amiibo'ları Göster",
|
||||
"uk_UA": "Показати всі Amiibo",
|
||||
"zh_CN": "显示所有 Amiibo",
|
||||
"zh_TW": "顯示所有 Amiibo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UseRandomUuidLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "استخدام UUID عشوائي",
|
||||
"de_DE": "Zufällige UUID verwenden",
|
||||
"el_GR": "Χρήση τυχαίου UUID",
|
||||
"en_US": "Use Random UUID",
|
||||
"es_ES": "Usar UUID aleatorio",
|
||||
"fr_FR": "Utiliser un UUID aléatoire",
|
||||
"he_IL": "השתמש ב-UUID אקראי",
|
||||
"it_IT": "Usa UUID casuale",
|
||||
"ja_JP": "ランダム UUID を使用",
|
||||
"ko_KR": "무작위 UUID 사용",
|
||||
"no_NO": "Bruk tilfeldig UUID",
|
||||
"pl_PL": "Użyj losowego UUID",
|
||||
"pt_BR": "Usar UUID aleatório",
|
||||
"ru_RU": "Использовать случайный UUID",
|
||||
"sv_SE": "Använd slumpmässig UUID",
|
||||
"th_TH": "ใช้ UUID แบบสุ่ม",
|
||||
"tr_TR": "Rastgele UUID kullan",
|
||||
"uk_UA": "Використовувати випадковий UUID",
|
||||
"zh_CN": "使用随机 UUID",
|
||||
"zh_TW": "使用隨機 UUID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "PauseEmulationWhileScanningAmiibo",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Emulation während des Scannens pausieren",
|
||||
"el_GR": "",
|
||||
"en_US": "Pause Emulation While Scanning",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Приостанавливать эмуляцию во время сканирования",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UsageLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "الاستخدام:",
|
||||
"de_DE": "Nutzung:",
|
||||
"el_GR": "Χρήση:",
|
||||
"en_US": "Usage:",
|
||||
"es_ES": "Uso:",
|
||||
"fr_FR": "Utilisation :",
|
||||
"he_IL": "שימוש:",
|
||||
"it_IT": "Utilizzo:",
|
||||
"ja_JP": "使用法:",
|
||||
"ko_KR": "사용법:",
|
||||
"no_NO": "Forbruk:",
|
||||
"pl_PL": "Użycie:",
|
||||
"pt_BR": "Uso:",
|
||||
"ru_RU": "Применение:",
|
||||
"sv_SE": "Användning:",
|
||||
"th_TH": "การใช้งาน:",
|
||||
"tr_TR": "Kullanım:",
|
||||
"uk_UA": "Використання:",
|
||||
"zh_CN": "用法:",
|
||||
"zh_TW": "用途:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "WritableLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "الاستخدام (يمكنه حفظ البيانات):",
|
||||
"de_DE": "Nutzung (Kann Daten speichern):",
|
||||
"el_GR": "Χρήση (Μπορεί να αποθηκεύσει δεδομένα):",
|
||||
"en_US": "Usage (Can Save Data):",
|
||||
"es_ES": "Uso (Puede guardar datos):",
|
||||
"fr_FR": "Utilisation (Peut enregistrer des données) :",
|
||||
"he_IL": "שימוש (יכול לשמור נתונים):",
|
||||
"it_IT": "Utilizzo (Può salvare dati):",
|
||||
"ja_JP": "使用法(データを保存できます):",
|
||||
"ko_KR": "사용법 (데이터 저장 가능):",
|
||||
"no_NO": "Bruk (Kan lagre data):",
|
||||
"pl_PL": "Użycie (Może zapisywać dane):",
|
||||
"pt_BR": "Uso (Pode salvar dados):",
|
||||
"ru_RU": "Применение (Может сохранять данные):",
|
||||
"sv_SE": "Användning (Kan spara data):",
|
||||
"th_TH": "การใช้งาน (สามารถบันทึกข้อมูลได้):",
|
||||
"tr_TR": "Kullanım (Veri kaydedebilir):",
|
||||
"uk_UA": "Використання (Може зберігати дані):",
|
||||
"zh_CN": "用法(可保存数据):",
|
||||
"zh_TW": "用途(可儲存資料):"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UnknownLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "غير معروف",
|
||||
"de_DE": "Unbekannt",
|
||||
"el_GR": "Άγνωστο",
|
||||
"en_US": "Unknown",
|
||||
"es_ES": "Desconocido",
|
||||
"fr_FR": "Inconnu",
|
||||
"he_IL": "לא ידוע",
|
||||
"it_IT": "Sconosciuto",
|
||||
"ja_JP": "不明",
|
||||
"ko_KR": "알 수 없음",
|
||||
"no_NO": "Ukjent",
|
||||
"pl_PL": "Nieznane",
|
||||
"pt_BR": "Desconhecido",
|
||||
"ru_RU": "Неизвестно",
|
||||
"sv_SE": "Okänt",
|
||||
"th_TH": "ไม่รู้จัก",
|
||||
"tr_TR": "Bilinmeyen",
|
||||
"uk_UA": "Невідомо",
|
||||
"zh_CN": "未知",
|
||||
"zh_TW": "未知"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ScanButton",
|
||||
"Translations": {
|
||||
"ar_SA": "فحص Amiibo",
|
||||
"de_DE": "Amiibo einscannen",
|
||||
"el_GR": "Σαρώστε το Amiibo",
|
||||
"en_US": "Scan Amiibo",
|
||||
"es_ES": "Escanear Amiibo",
|
||||
"fr_FR": "Scanner l'Amiibo",
|
||||
"he_IL": "סרוק את ה-Amiibo",
|
||||
"it_IT": "Scansiona l'Amiibo",
|
||||
"ja_JP": "Amiiboをスキャン",
|
||||
"ko_KR": "Amiibo 스캔하기",
|
||||
"no_NO": "Skanne Amiibo",
|
||||
"pl_PL": "Zeskanuj Amiibo",
|
||||
"pt_BR": "Escanear Amiibo",
|
||||
"ru_RU": "Сканировать Amiibo",
|
||||
"sv_SE": "Skanna Amiibo",
|
||||
"th_TH": "สแกน Amiibo",
|
||||
"tr_TR": "Amiibo'yu tara",
|
||||
"uk_UA": "Сканувати Amiibo",
|
||||
"zh_CN": "扫描 Amiibo",
|
||||
"zh_TW": "掃描 Amiibo"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "Cancel",
|
||||
"Translations": {
|
||||
"ar_SA": "إلغاء",
|
||||
"de_DE": "Abbrechen",
|
||||
"el_GR": "Ακύρωση",
|
||||
"en_US": "Cancel",
|
||||
"es_ES": "Cancelar",
|
||||
"fr_FR": "Annuler",
|
||||
"he_IL": "ביטול",
|
||||
"it_IT": "Annulla",
|
||||
"ja_JP": "キャンセル",
|
||||
"ko_KR": "취소",
|
||||
"no_NO": "Avbryt",
|
||||
"pl_PL": "Anuluj",
|
||||
"pt_BR": "Cancelar",
|
||||
"ru_RU": "Отмена",
|
||||
"sv_SE": "Avbryt",
|
||||
"th_TH": "ยกเลิก",
|
||||
"tr_TR": "İptal",
|
||||
"uk_UA": "Скасувати",
|
||||
"zh_CN": "取消",
|
||||
"zh_TW": "取消"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "AllSupportedFormats",
|
||||
"Translations": {
|
||||
"ar_SA": "كل التنسيقات المدعومة",
|
||||
"de_DE": "Alle unterstützten Formate",
|
||||
"el_GR": "Όλες Οι Υποστηριζόμενες Μορφές",
|
||||
"en_US": "All Supported Formats",
|
||||
"es_ES": "Todos los Formatos Soportados",
|
||||
"fr_FR": "Tous les Formats Supportés",
|
||||
"he_IL": "כל הפורמטים הנתמכים",
|
||||
"it_IT": "Tutti i formati supportati",
|
||||
"ja_JP": "すべての対応フォーマット",
|
||||
"ko_KR": "지원되는 모든 형식",
|
||||
"no_NO": "Alle støttede formater",
|
||||
"pl_PL": "Wszystkie Obsługiwane Formaty",
|
||||
"pt_BR": "Todos os Formatos Suportados",
|
||||
"ru_RU": "Все поддерживаемые форматы",
|
||||
"sv_SE": "Alla format som stöds",
|
||||
"th_TH": "รูปแบบที่รองรับทั้งหมด",
|
||||
"tr_TR": "Tüm Desteklenen Formatlar",
|
||||
"uk_UA": "Усі підтримувані формати",
|
||||
"zh_CN": "所有支持的格式",
|
||||
"zh_TW": "所有支援的格式"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "SearchAmiiboWatermark",
|
||||
"Translations": {
|
||||
"ar_SA": "بحث Amiibo...",
|
||||
"de_DE": "Amiibo suchen...",
|
||||
"el_GR": "Αναζήτηση Amiibo...",
|
||||
"en_US": "Search Amiibo...",
|
||||
"es_ES": "Buscar Amiibo...",
|
||||
"fr_FR": "Rechercher Amiibo...",
|
||||
"he_IL": "חפש Amiibo...",
|
||||
"it_IT": "Cerca Amiibo...",
|
||||
"ja_JP": "Amiiboを検索...",
|
||||
"ko_KR": "Amiibo 검색...",
|
||||
"no_NO": "Søk Amiibo...",
|
||||
"pl_PL": "Wyszukaj Amiibo...",
|
||||
"pt_BR": "Buscar Amiibo...",
|
||||
"ru_RU": "Поиск Amiibo...",
|
||||
"sv_SE": "Sök Amiibo...",
|
||||
"th_TH": "ค้นหา Amiibo...",
|
||||
"tr_TR": "Amiibo Ara...",
|
||||
"uk_UA": "Пошук Amiibo...",
|
||||
"zh_CN": "搜索 Amiibo...",
|
||||
"zh_TW": "搜尋 Amiibo..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SearchWatermark",
|
||||
"Translations": {
|
||||
"ar_SA": "بحث",
|
||||
"de_DE": "Suche",
|
||||
"el_GR": "Αναζήτηση",
|
||||
"en_US": "Search",
|
||||
"es_ES": "Buscar",
|
||||
"fr_FR": "Rechercher",
|
||||
"he_IL": "חפש",
|
||||
"it_IT": "Cerca",
|
||||
"ja_JP": "検索",
|
||||
"ko_KR": "찾기",
|
||||
"no_NO": "Søk",
|
||||
"pl_PL": "Wyszukaj",
|
||||
"pt_BR": "Buscar",
|
||||
"ru_RU": "Поиск",
|
||||
"sv_SE": "Sök",
|
||||
"th_TH": "ค้นหา",
|
||||
"tr_TR": "Ara",
|
||||
"uk_UA": "Пошук",
|
||||
"zh_CN": "搜索",
|
||||
"zh_TW": "搜尋"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "NameLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "الاسم",
|
||||
"de_DE": "",
|
||||
"el_GR": "Όνομα",
|
||||
"en_US": "Name",
|
||||
"es_ES": "Nombre",
|
||||
"fr_FR": "Nom",
|
||||
"he_IL": "שם",
|
||||
"it_IT": "Nome",
|
||||
"ja_JP": "名称",
|
||||
"ko_KR": "이름",
|
||||
"no_NO": "Navn",
|
||||
"pl_PL": "Nazwa",
|
||||
"pt_BR": "Nome",
|
||||
"ru_RU": "Название",
|
||||
"sv_SE": "Namn",
|
||||
"th_TH": "ชื่อ",
|
||||
"tr_TR": "İsim",
|
||||
"uk_UA": "Назва",
|
||||
"zh_CN": "名称",
|
||||
"zh_TW": "名稱"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SavingsLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "التوفير",
|
||||
"de_DE": "Einsparung",
|
||||
"el_GR": "Εξοικονόμηση",
|
||||
"en_US": "Savings",
|
||||
"es_ES": "Ahorro",
|
||||
"fr_FR": "Économies",
|
||||
"he_IL": "חיסכון",
|
||||
"it_IT": "Risparmio",
|
||||
"ja_JP": "節約",
|
||||
"ko_KR": "절약",
|
||||
"no_NO": "Besparelse",
|
||||
"pl_PL": "Oszczędność",
|
||||
"pt_BR": "Economia",
|
||||
"ru_RU": "Экономия",
|
||||
"sv_SE": "Besparing",
|
||||
"th_TH": "การประหยัด",
|
||||
"tr_TR": "Tasarruf",
|
||||
"uk_UA": "Економія",
|
||||
"zh_CN": "节省",
|
||||
"zh_TW": "節省"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "TrimStatusLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Trimmstatus",
|
||||
"el_GR": "",
|
||||
"en_US": "Trim Status",
|
||||
"es_ES": "Estado del recorte",
|
||||
"fr_FR": "État de réduction",
|
||||
"he_IL": "",
|
||||
"it_IT": "Stato della riduzione",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Статус обрезки",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Статус обрізки",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "OrderAscending",
|
||||
"Translations": {
|
||||
"ar_SA": "تصاعدي",
|
||||
"de_DE": "Aufsteigend",
|
||||
"el_GR": "Αύξουσα",
|
||||
"en_US": "Ascending",
|
||||
"es_ES": "Ascendente",
|
||||
"fr_FR": "Croissant",
|
||||
"he_IL": "סדר עולה",
|
||||
"it_IT": "Crescente",
|
||||
"ja_JP": "昇順",
|
||||
"ko_KR": "오름차순",
|
||||
"no_NO": "Stigende",
|
||||
"pl_PL": "Rosnąco",
|
||||
"pt_BR": "Ascendente",
|
||||
"ru_RU": "По Возрастанию",
|
||||
"sv_SE": "Stigande",
|
||||
"th_TH": "จากน้อยไปมาก",
|
||||
"tr_TR": "Artan",
|
||||
"uk_UA": "За зростанням",
|
||||
"zh_CN": "升序",
|
||||
"zh_TW": "從小到大"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "OrderDescending",
|
||||
"Translations": {
|
||||
"ar_SA": "تنازلي",
|
||||
"de_DE": "Absteigend",
|
||||
"el_GR": "Φθίνουσα",
|
||||
"en_US": "Descending",
|
||||
"es_ES": "Descendente",
|
||||
"fr_FR": "Décroissant",
|
||||
"he_IL": "סדר יורד",
|
||||
"it_IT": "Decrescente",
|
||||
"ja_JP": "降順",
|
||||
"ko_KR": "내림차순",
|
||||
"no_NO": "Synkende",
|
||||
"pl_PL": "Malejąco",
|
||||
"pt_BR": "Descendente",
|
||||
"ru_RU": "По Убыванию",
|
||||
"sv_SE": "Fallande",
|
||||
"th_TH": "จากมากไปน้อย",
|
||||
"tr_TR": "Azalan",
|
||||
"uk_UA": "За спаданням",
|
||||
"zh_CN": "降序",
|
||||
"zh_TW": "從大到小"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "ScanAmiiboFromBinFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle eine BIN-Datei aus, um ein Amiibo zu scannen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a BIN file to scan an Amiibo from",
|
||||
"es_ES": "Selecciona un archivo BIN para escanear un Amiibo",
|
||||
"fr_FR": "Sélectionnez un fichier BIN pour scanner un Amiibo",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona un file BIN da cui scansionare un Amiibo",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione um arquivo BIN para escanear um Amiibo",
|
||||
"ru_RU": "Выберите BIN-файл для сканирования Amiibo",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть BIN-файл для сканування Amiibo",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "APITitle",
|
||||
"Translations": {
|
||||
"ar_SA": "أميبو API",
|
||||
"de_DE": "Amiibo-API",
|
||||
"el_GR": "API για Amiibo.",
|
||||
"en_US": "Amiibo API",
|
||||
"es_ES": "API Amiibo",
|
||||
"fr_FR": "API Amiibo",
|
||||
"he_IL": "ממשק תכנות אמיבו",
|
||||
"it_IT": "API Amiibo",
|
||||
"ja_JP": "",
|
||||
"ko_KR": null,
|
||||
"no_NO": "",
|
||||
"pl_PL": "API Amiibo",
|
||||
"pt_BR": "API Amiibo",
|
||||
"ru_RU": "API Amiibo",
|
||||
"sv_SE": "Amiibo-API",
|
||||
"th_TH": "อามิโบ้ API",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "API Amiibo",
|
||||
"zh_CN": null,
|
||||
"zh_TW": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "APIFailFetchMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "حدث خطأ أثناء جلب المعلومات من API.",
|
||||
"de_DE": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.",
|
||||
"el_GR": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.",
|
||||
"en_US": "An error occured while fetching information from the API.",
|
||||
"es_ES": "Ocurrió un error al recibir información de la API.",
|
||||
"fr_FR": "Une erreur est survenue lors de la récupération des informations de l'API.",
|
||||
"he_IL": "אירעה שגיאה בעת שליפת מידע מהממשק.",
|
||||
"it_IT": "Si è verificato un errore durante il recupero delle informazioni dall'API.",
|
||||
"ja_JP": "API からの情報取得中にエラーが発生しました.",
|
||||
"ko_KR": "API에서 정보를 가져오는 중에 오류가 발생했습니다.",
|
||||
"no_NO": "En feil oppstod under henting av informasjon fra API.",
|
||||
"pl_PL": "Wystąpił błąd podczas pobierania informacji z API.",
|
||||
"pt_BR": "Um erro ocorreu ao tentar obter informações da API.",
|
||||
"ru_RU": "Произошла ошибка при получении информации из API.",
|
||||
"sv_SE": "Ett fel inträffade vid hämtning av information från API.",
|
||||
"th_TH": "เกิดข้อผิดพลาดขณะเรียกข้อมูลจาก API",
|
||||
"tr_TR": "API'dan bilgi alırken bir hata oluştu.",
|
||||
"uk_UA": "Під час отримання інформації з API сталася помилка.",
|
||||
"zh_CN": "从 API 获取信息时出错。",
|
||||
"zh_TW": "從 API 取得資訊時出現錯誤。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "APIConnectErrorMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "غير قادر على الاتصال بخادم API أميبو. قد تكون الخدمة معطلة أو قد تحتاج إلى التحقق من اتصالك بالإنترنت.",
|
||||
"de_DE": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.",
|
||||
"el_GR": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.",
|
||||
"en_US": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.",
|
||||
"es_ES": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.",
|
||||
"fr_FR": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.",
|
||||
"he_IL": "לא ניתן להתחבר לממשק שרת האמיבו. ייתכן שהשירות מושבת או שתצטרך לוודא שהחיבור לאינטרנט שלך מקוון.",
|
||||
"it_IT": "Impossibile connettersi al server dell'API Amiibo. Il servizio potrebbe non essere disponibile o potresti non essere connesso a Internet.",
|
||||
"ja_JP": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.",
|
||||
"ko_KR": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 온라인 상태인지 확인이 필요합니다.",
|
||||
"no_NO": "Kan ikke koble til Amiibo API server. Tjenesten kan være nede, eller du må kanskje verifisere at din internettforbindelse er tilkoblet.",
|
||||
"pl_PL": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.",
|
||||
"pt_BR": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.",
|
||||
"ru_RU": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна или вам может потребоваться проверить ваше интернет-соединение.",
|
||||
"sv_SE": "Kunde inte ansluta till Amiibo API-server. Tjänsten kanske är nere eller så behöver du kontrollera att din internetanslutning fungerar.",
|
||||
"th_TH": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ Amiibo API บางบริการอาจหยุดทำงาน หรือไม่คุณต้องทำการตรวจสอบว่าอินเทอร์เน็ตของคุณอยู่ในสถานะเชื่อมต่ออยู่หรือไม่",
|
||||
"tr_TR": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.",
|
||||
"uk_UA": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.",
|
||||
"zh_CN": "无法连接到 Amiibo API 服务器,服务可能已关闭,或者没有连接互联网。",
|
||||
"zh_TW": "無法連接 Amiibo API 伺服器。服務可能已停機,或者您可能需要確認網際網路連線是否在線上。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RenameAmiiboTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Amiibo umbenennen",
|
||||
"el_GR": "",
|
||||
"en_US": "Rename Amiibo",
|
||||
"es_ES": "Renombrar Amiibo",
|
||||
"fr_FR": "Renommer l'Amiibo",
|
||||
"he_IL": "",
|
||||
"it_IT": "Rinomina Amiibo",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Renomear Amiibo",
|
||||
"ru_RU": "Переименовать Amiibo",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Перейменувати Amiibo",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ScanAmiiboTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "فحص Amiibo",
|
||||
"de_DE": "Amiibo einscannen",
|
||||
"el_GR": "Σαρώστε το Amiibo",
|
||||
"en_US": "Scan Amiibo",
|
||||
"es_ES": "Escanear Amiibo",
|
||||
"fr_FR": "Scanner l'Amiibo",
|
||||
"he_IL": "סרוק את ה-Amiibo",
|
||||
"it_IT": "Scansiona l'Amiibo",
|
||||
"ja_JP": "Amiiboをスキャン",
|
||||
"ko_KR": "Amiibo 스캔하기",
|
||||
"no_NO": "Skanne Amiibo",
|
||||
"pl_PL": "Zeskanuj Amiibo",
|
||||
"pt_BR": "Escanear Amiibo",
|
||||
"ru_RU": "Сканировать Amiibo",
|
||||
"sv_SE": "Skanna Amiibo",
|
||||
"th_TH": "สแกน Amiibo",
|
||||
"tr_TR": "Amiibo'yu tara",
|
||||
"uk_UA": "Сканувати Amiibo",
|
||||
"zh_CN": "扫描 Amiibo",
|
||||
"zh_TW": "掃描 Amiibo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ScanAmiiboMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Bitte scanne jetzt dein Amiibo.",
|
||||
"el_GR": "",
|
||||
"en_US": "Please scan your Amiibo now.",
|
||||
"es_ES": "Escanea tu Amiibo ahora.",
|
||||
"fr_FR": "Veuillez scannez votre Amiibo.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Scansiona ora il tuo Amiibo.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "지금 Amiibo를 스캔하세요.",
|
||||
"no_NO": "Vennligst skann Amiiboene dine nå.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Por favor, escaneie seu Amiibo agora.",
|
||||
"ru_RU": "Пожалуйста, сканируйте ваш Amiibo.",
|
||||
"sv_SE": "Skanna din Amiibo nu.",
|
||||
"th_TH": "กรุณาสแกน Amiibo ของคุณตอนนี้",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Будь ласка, проскануйте Ваш Amiibo.",
|
||||
"zh_CN": "请现在扫描你的 Amiibo",
|
||||
"zh_TW": "請掃描你的 Amiibo。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "UpdatesAddedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Updates hinzugefügt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "Updates Added: {0}",
|
||||
"es_ES": "Actualizaciones Añadidas: {0}",
|
||||
"fr_FR": "Mises à Jour Ajoutées : {0}",
|
||||
"he_IL": "",
|
||||
"it_IT": "Aggiornamenti aggiunti: {0}",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "추가된 업데이트: {0}",
|
||||
"no_NO": "Oppdateringer lagt til: {0}",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Atualizações adicionadas: {0}",
|
||||
"ru_RU": "Добавлено обновлений: {0}",
|
||||
"sv_SE": "Tillagda uppdateringar: {0}",
|
||||
"th_TH": "การอัปเดตที่เพิ่มเข้ามา: {0}",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Додані оновлення: {0}",
|
||||
"zh_CN": "已添加更新:{0}",
|
||||
"zh_TW": "已新增更新:{0}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UpdatesRemovedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Updates entfernt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "Updates Removed: {0}",
|
||||
"es_ES": "Actualizaciones Eliminadas: {0}",
|
||||
"fr_FR": "Mises à Jour Supprimées : {0}",
|
||||
"he_IL": "",
|
||||
"it_IT": "Aggiornamenti rimossi: {0}",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "제거된 업데이트: {0}",
|
||||
"no_NO": "Fjernede oppdateringer: {0}",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Atualizações removidas: {0}",
|
||||
"ru_RU": "Удалено обновлений: {0}",
|
||||
"sv_SE": "Borttagna uppdateringar: {0}",
|
||||
"th_TH": "การอัปเดตที่ถูกลบ: {0}",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Видалені оновлення: {0}",
|
||||
"zh_CN": "已移除更新:{0}",
|
||||
"zh_TW": "已移除更新:{0}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DLCAddedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "DLC hinzugefügt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "DLC Added: {0}",
|
||||
"es_ES": "DLC Añadidos: {0}",
|
||||
"fr_FR": "DLC Ajoutés : {0}",
|
||||
"he_IL": "",
|
||||
"it_IT": "DLC aggiunti: {0}",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "추가된 DLC: {0}",
|
||||
"no_NO": "DLC lagt til: {0}",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "DLC adicionados: {0}",
|
||||
"ru_RU": "Добавлено DLC: {0}",
|
||||
"sv_SE": "Tillagda DLC: {0}",
|
||||
"th_TH": "DLC ที่เพิ่มเข้ามา: {0}",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Додані DLC: {0}",
|
||||
"zh_CN": "已添加 DLC:{0}",
|
||||
"zh_TW": "已新增 DLC:{0}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DLCRemovedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "DLC entfernt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "DLC Removed: {0}",
|
||||
"es_ES": "DLC Eliminados: {0}",
|
||||
"fr_FR": "DLC Supprimés : {0}",
|
||||
"he_IL": "",
|
||||
"it_IT": "DLC rimossi: {0}",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "제거된 DLC: {0}",
|
||||
"no_NO": "DLC fjernet: {0}",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "DLC removidos: {0}",
|
||||
"ru_RU": "Удалено DLC: {0}",
|
||||
"sv_SE": "DLC borttaget: {0}",
|
||||
"th_TH": "ลบ DLC: {0}",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Видалено DLC: {0}",
|
||||
"zh_CN": "已移除 DLC:{0}",
|
||||
"zh_TW": "已移除 DLC:{0}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "LoadApplicationFromFileFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle eine Switch-Anwendungsdatei zum Laden aus",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a Switch application file to load",
|
||||
"es_ES": "Selecciona un archivo de aplicación de Switch para cargar",
|
||||
"fr_FR": "Sélectionnez un fichier d’application Switch à charger",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona un file applicazione Switch da caricare",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "로드할 Switch 애플리케이션 파일을 선택하세요",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione um arquivo de aplicativo Switch para carregar",
|
||||
"ru_RU": "Выберите файл приложения Switch для загрузки",
|
||||
"sv_SE": "Välj en Switch-applikationsfil att läsa in",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть файл застосунку Switch для завантаження",
|
||||
"zh_CN": "请选择要加载的 Switch 应用程序文件",
|
||||
"zh_TW": "請選擇要載入的 Switch 應用程式檔案"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "LoadUnpackedApplicationFromFolderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle einen Ordner mit einer entpackten Switch-Anwendung zum Laden",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a folder containing an unpacked Switch application to load",
|
||||
"es_ES": "Selecciona una carpeta que contenga una aplicación de Switch descomprimida para cargar",
|
||||
"fr_FR": "Sélectionnez un dossier contenant une application Switch décompressée à charger",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona una cartella contenente un’applicazione Switch non compressa da caricare",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "압축 해제된 Switch 애플리케이션이 포함된 폴더를 선택하여 로드하세요",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione uma pasta que contenha um aplicativo Switch descompactado para carregar",
|
||||
"ru_RU": "Выберите папку, содержащую распакованное приложение Switch, для загрузки",
|
||||
"sv_SE": "Välj en mapp som innehåller en uppackad Switch-applikation att läsa in",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть папку, що містить розпакований застосунок Switch, для завантаження",
|
||||
"zh_CN": "请选择包含未打包 Switch 应用程序的文件夹以加载",
|
||||
"zh_TW": "請選擇包含未解壓縮 Switch 應用程式的資料夾以載入"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "LoadUpdatesAndDLCFromFolderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle einen oder mehrere Ordner zum Laden von Updates und DLC aus",
|
||||
"el_GR": "",
|
||||
"en_US": "Select one or more folders to bulk load updates and DLC from",
|
||||
"es_ES": "Selecciona una o más carpetas para cargar de forma masiva actualizaciones y DLC",
|
||||
"fr_FR": "Sélectionnez un ou plusieurs dossiers pour charger en masse des mises à jour et des DLC",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona una o più cartelle da cui caricare in blocco aggiornamenti e DLC",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "업데이트 및 DLC를 대량으로 로드할 폴더를 하나 이상 선택하세요",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione uma ou mais pastas para carregar em massa atualizações e DLC",
|
||||
"ru_RU": "Выберите одну или несколько папок для массовой загрузки обновлений и DLC",
|
||||
"sv_SE": "Välj en eller flera mappar för att massinläsa uppdateringar och DLC",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть одну або кілька папок для масового завантаження оновлень і DLC",
|
||||
"zh_CN": "请选择一个或多个文件夹以批量加载更新和 DLC",
|
||||
"zh_TW": "請選擇一個或多個資料夾以批次載入更新與 DLC"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "AssociationSuccessMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "تم ربط أنواع الملفات بنجاح.",
|
||||
"de_DE": "Dateitypen erfolgreich zugeordnet.",
|
||||
"el_GR": "Οι τύποι αρχείων συσχετίστηκαν με επιτυχία.",
|
||||
"en_US": "Successfully associated file types.",
|
||||
"es_ES": "¡Tipos de archivo asociados con éxito.",
|
||||
"fr_FR": "Types de fichiers associés avec succès.",
|
||||
"he_IL": "שיוך סוגי הקבצים בוצע בהצלחה.",
|
||||
"it_IT": "Tipi di file associati con successo.",
|
||||
"ja_JP": "ファイルの種類の関連付けに成功しました。",
|
||||
"ko_KR": "파일 형식 연결에 성공했습니다.",
|
||||
"no_NO": "Filtyper ble tilknyttet.",
|
||||
"pl_PL": "Pomyślnie powiązano typy plików.",
|
||||
"pt_BR": "Tipos de arquivo associados com sucesso.",
|
||||
"ru_RU": "Типы файлов успешно связаны.",
|
||||
"sv_SE": "Filtyper har kopplats.",
|
||||
"th_TH": "เชื่อมโยงประเภทไฟล์สำเร็จแล้ว",
|
||||
"tr_TR": "Dosya türleri başarıyla ilişkilendirildi.",
|
||||
"uk_UA": "Типи файлів успішно пов’язані.",
|
||||
"zh_CN": "文件类型关联成功。",
|
||||
"zh_TW": "檔案類型關聯成功。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RemoveAssociationSuccessMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "تمت إزالة ارتباطات أنواع الملفات بنجاح.",
|
||||
"de_DE": "Dateitypzuordnungen erfolgreich entfernt.",
|
||||
"el_GR": "Οι συσχετίσεις τύπων αρχείων αφαιρέθηκαν με επιτυχία.",
|
||||
"en_US": "Successfully removed file type associations.",
|
||||
"es_ES": "Asociaciones de tipos de archivo eliminadas con éxito.",
|
||||
"fr_FR": "Associations de types de fichiers supprimées avec succès.",
|
||||
"he_IL": "שיוכי סוגי הקבצים הוסרו בהצלחה.",
|
||||
"it_IT": "Associazioni dei tipi di file rimosse con successo.",
|
||||
"ja_JP": "ファイルの種類の関連付けの削除に成功しました。",
|
||||
"ko_KR": "파일 형식 연결이 성공적으로 제거되었습니다.",
|
||||
"no_NO": "Filtype-tilknytninger ble fjernet.",
|
||||
"pl_PL": "Pomyślnie usunięto skojarzenia typów plików.",
|
||||
"pt_BR": "Associações de tipos de arquivo removidas com sucesso.",
|
||||
"ru_RU": "Связи типов файлов успешно удалены.",
|
||||
"sv_SE": "Filtypsassociationer har tagits bort.",
|
||||
"th_TH": "ลบการเชื่อมโยงประเภทไฟล์สำเร็จแล้ว",
|
||||
"tr_TR": "Dosya türü ilişkilendirmeleri başarıyla kaldırıldı.",
|
||||
"uk_UA": "Прив’язки типів файлів успішно видалено.",
|
||||
"zh_CN": "文件类型关联已成功移除。",
|
||||
"zh_TW": "檔案類型關聯已成功移除。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "AssociationFailedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "فشل ربط أنواع الملفات.",
|
||||
"de_DE": "Fehler beim Zuordnen der Dateitypen.",
|
||||
"el_GR": "Απέτυχε η συσχέτιση των τύπων αρχείων.",
|
||||
"en_US": "Failed to associate file types.",
|
||||
"es_ES": "No se pudieron asociar los tipos de archivo.",
|
||||
"fr_FR": "Échec de l’association des types de fichiers.",
|
||||
"he_IL": "שיוך סוגי הקבצים נכשל.",
|
||||
"it_IT": "Associazione dei tipi di file non riuscita.",
|
||||
"ja_JP": "ファイルの種類の関連付けに失敗しました。",
|
||||
"ko_KR": "파일 형식 연결에 실패했습니다.",
|
||||
"no_NO": "Klarte ikke å tilknytte filtyper.",
|
||||
"pl_PL": "Nie udało się powiązać typów plików.",
|
||||
"pt_BR": "Falha ao associar tipos de arquivo.",
|
||||
"ru_RU": "Не удалось связать типы файлов.",
|
||||
"sv_SE": "Det gick inte att koppla filtyper.",
|
||||
"th_TH": "เชื่อมโยงประเภทไฟล์ไม่สำเร็จ",
|
||||
"tr_TR": "Dosya türleri ilişkilendirilemedi.",
|
||||
"uk_UA": "Не вдалося пов’язати типи файлів.",
|
||||
"zh_CN": "文件类型关联失败。",
|
||||
"zh_TW": "檔案類型關聯失敗。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RemoveAssociationFailedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "فشلت إزالة ارتباطات أنواع الملفات.",
|
||||
"de_DE": "Das Entfernen der Dateitypzuordnungen ist fehlgeschlagen.",
|
||||
"el_GR": "Η αφαίρεση των συσχετίσεων τύπων αρχείων απέτυχε.",
|
||||
"en_US": "Failed to remove file type associations.",
|
||||
"es_ES": "No se pudieron eliminar las asociaciones de tipos de archivo.",
|
||||
"fr_FR": "Échec de la suppression des associations de types de fichiers.",
|
||||
"he_IL": "הסרת שיוכי סוגי הקבצים נכשלה.",
|
||||
"it_IT": "Rimozione delle associazioni dei tipi di file non riuscita.",
|
||||
"ja_JP": "ファイルの種類の関連付けの削除に失敗しました。",
|
||||
"ko_KR": "파일 형식 연결 제거에 실패했습니다.",
|
||||
"no_NO": "Fjerning av filtype-tilknytninger mislyktes.",
|
||||
"pl_PL": "Nie udało się usunąć skojarzeń typów plików.",
|
||||
"pt_BR": "Falha ao remover associações de tipos de arquivo.",
|
||||
"ru_RU": "Не удалось удалить связи типов файлов.",
|
||||
"sv_SE": "Det gick inte att ta bort filtypsassociationer.",
|
||||
"th_TH": "ไม่สามารถลบการเชื่อมโยงประเภทไฟล์ได้",
|
||||
"tr_TR": "Dosya türü ilişkilendirmeleri kaldırılamadı.",
|
||||
"uk_UA": "Не вдалося видалити прив’язки типів файлів.",
|
||||
"zh_CN": "文件类型关联移除失败。",
|
||||
"zh_TW": "無法移除檔案類型關聯。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "InstallFromFileFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "حدد ملف XCI أو أرشيف ZIP لتثبيت البرنامج الثابت منه",
|
||||
"de_DE": "Wählen Sie eine XCI-Datei oder ein ZIP-Archiv zur Firmware-Installation aus",
|
||||
"el_GR": "Επιλέξτε ένα αρχείο XCI ή ένα αρχείο ZIP για εγκατάσταση υλικολογισμικού",
|
||||
"en_US": "Select an XCI file or a ZIP archive to install firmware from",
|
||||
"es_ES": "Selecciona un archivo XCI o un archivo ZIP para instalar el firmware",
|
||||
"fr_FR": "Sélectionnez un fichier XCI ou une archive ZIP pour installer le firmware",
|
||||
"he_IL": "בחר קובץ XCI או ארכיון ZIP להתקנת קושחה",
|
||||
"it_IT": "Seleziona un file XCI o un archivio ZIP da cui installare il firmware",
|
||||
"ja_JP": "ファームウェアをインストールする XCI ファイルまたは ZIP アーカイブを選択してください",
|
||||
"ko_KR": "펌웨어를 설치할 XCI 파일 또는 ZIP 아카이브를 선택하세요",
|
||||
"no_NO": "Velg en XCI-fil eller et ZIP-arkiv for å installere fastvare fra",
|
||||
"pl_PL": "Wybierz plik XCI lub archiwum ZIP, z którego chcesz zainstalować firmware",
|
||||
"pt_BR": "Selecione 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 från",
|
||||
"th_TH": "เลือกไฟล์ XCI หรือไฟล์ ZIP เพื่อติดตั้งเฟิร์มแวร์",
|
||||
"tr_TR": "Üretici yazılımını 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": "InstallFromFolderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "حدد مجلدًا لتثبيت البرنامج الثابت منه",
|
||||
"de_DE": "Wählen Sie einen Ordner aus, um die Firmware zu installieren",
|
||||
"el_GR": "Επιλέξτε έναν φάκελο για να εγκαταστήσετε το firmware από αυτόν",
|
||||
"en_US": "Select a folder to install firmware from",
|
||||
"es_ES": "Selecciona una carpeta para instalar el firmware desde ella",
|
||||
"fr_FR": "Sélectionnez un dossier pour installer le firmware à partir de celui-ci",
|
||||
"he_IL": "בחר תיקיה שממנה תותקן הקושחה",
|
||||
"it_IT": "Seleziona una cartella da cui installare il firmware",
|
||||
"ja_JP": "ファームウェアをインストールするフォルダを選択してください",
|
||||
"ko_KR": "펌웨어를 설치할 폴더를 선택하세요",
|
||||
"no_NO": "Velg en mappe å installere fastvaren fra",
|
||||
"pl_PL": "Wybierz folder, z którego chcesz zainstalować oprogramowanie układowe",
|
||||
"pt_BR": "Selecione uma pasta para instalar o firmware a partir dela",
|
||||
"ru_RU": "Выберите папку, из которой будет установлена прошивка",
|
||||
"sv_SE": "Välj en mapp 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": "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}。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "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": "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": "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": "InstallerEmbeddedSuccessMessage",
|
||||
"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模擬器現在可以執行。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "InstallFromFileFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "حدد ملف KEYS لتثبيت المفاتيح منه",
|
||||
"de_DE": "Wählen Sie eine KEYS-Datei zur Schlüsselinstallation aus",
|
||||
"el_GR": "Επιλέξτε ένα αρχείο KEYS για εγκατάσταση κλειδιών",
|
||||
"en_US": "Select a KEYS file to install keys from",
|
||||
"es_ES": "Selecciona un archivo KEYS para instalar las claves",
|
||||
"fr_FR": "Sélectionnez un fichier KEYS pour installer les clés",
|
||||
"he_IL": "בחר קובץ KEYS להתקנת מפתחות",
|
||||
"it_IT": "Seleziona un file KEYS da cui installare le chiavi",
|
||||
"ja_JP": "キーをインストールする KEYS ファイルを選択してください",
|
||||
"ko_KR": "키를 설치할 KEYS 파일을 선택하세요",
|
||||
"no_NO": "Velg en KEYS-fil for å installere nøkler fra",
|
||||
"pl_PL": "Wybierz plik KEYS, z którego chcesz zainstalować klucze",
|
||||
"pt_BR": "Selecione um arquivo KEYS para instalar as chaves",
|
||||
"ru_RU": "Выберите KEYS файл для установки ключей",
|
||||
"sv_SE": "Välj en KEYS-fil för att installera nycklar från",
|
||||
"th_TH": "เลือกไฟล์ KEYS เพื่อติดตั้งคีย์",
|
||||
"tr_TR": "Anahtarları yüklemek için bir KEYS dosyası seçin",
|
||||
"uk_UA": "Виберіть KEYS файл для встановлення ключів",
|
||||
"zh_CN": "选择一个用于安装密钥的 KEYS 文件",
|
||||
"zh_TW": "選擇一個用於安裝金鑰的 KEYS 檔案"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "InstallFromFolderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "حدد مجلدًا لتثبيت المفاتيح منه",
|
||||
"de_DE": "Wählen Sie einen Ordner aus, um die Schlüssel zu installieren",
|
||||
"el_GR": "Επιλέξτε έναν φάκελο για να εγκαταστήσετε τα κλειδιά",
|
||||
"en_US": "Select a folder to install keys from",
|
||||
"es_ES": "Selecciona una carpeta para instalar las claves",
|
||||
"fr_FR": "Sélectionnez un dossier pour installer les clés",
|
||||
"he_IL": "בחר תיקיה להתקין ממנה את המפתחות",
|
||||
"it_IT": "Seleziona una cartella da cui installare le chiavi",
|
||||
"ja_JP": "キーをインストールするフォルダを選択してください",
|
||||
"ko_KR": "키를 설치할 폴더를 선택하세요",
|
||||
"no_NO": "Velg en mappe å installere nøklene fra",
|
||||
"pl_PL": "Wybierz folder, z którego zainstalować klucze",
|
||||
"pt_BR": "Selecione uma pasta para instalar as chaves",
|
||||
"ru_RU": "Выберите папку, из которой установить ключи",
|
||||
"sv_SE": "Välj en mapp att installera nycklar från",
|
||||
"th_TH": "เลือกโฟลเดอร์เพื่อติดตั้งคีย์",
|
||||
"tr_TR": "Anahtarları yüklemek için bir klasör seçin",
|
||||
"uk_UA": "Виберіть папку, з якої встановити ключі",
|
||||
"zh_CN": "选择一个文件夹以安装密钥",
|
||||
"zh_TW": "選擇一個資料夾以安裝密鑰"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "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": "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": "成功安裝新增的金鑰檔案。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "InstallerInvalidKeysFoundMessage",
|
||||
"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}。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "ScanSkylanderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle eine BIN-, SKY-, DMP- oder DUMP-Datei aus, um einen Skylander zu scannen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a BIN, SKY, DMP, or DUMP file to scan a Skylander from",
|
||||
"es_ES": "Selecciona un archivo BIN, SKY, DMP o DUMP para escanear un Skylander",
|
||||
"fr_FR": "Sélectionnez un fichier BIN, SKY, DMP ou DUMP pour scanner un Skylander",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona un file BIN, SKY, DMP o DUMP da cui scansionare uno Skylander",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione um arquivo BIN, SKY, DMP ou DUMP para escanear um Skylander",
|
||||
"ru_RU": "Выберите файл BIN, SKY, DMP или DUMP для сканирования Skylander",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть файл BIN, SKY, DMP або DUMP для сканування Skylander",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "PrimaryMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Entfernt ungenutzten Speicherplatz aus der XCI-Datei, um ihre Dateigröße zu verringern.",
|
||||
"el_GR": "",
|
||||
"en_US": "Removes unused space from the XCI to reduce its file size.",
|
||||
"es_ES": "Elimina el espacio no utilizado del XCI para reducir su tamaño.",
|
||||
"fr_FR": "Supprime l’espace inutilisé du XCI afin de réduire sa taille.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Rimuove lo spazio inutilizzato dall'XCI per ridurne le dimensioni.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI에서 사용되지 않는 공간을 제거하여 파일 크기를 줄입니다.",
|
||||
"no_NO": "Fjerner ubrukt plass fra XCI-filen for å redusere filstørrelsen.",
|
||||
"pl_PL": "Usuwa nieużywane miejsce z pliku XCI, aby zmniejszyć jego rozmiar.",
|
||||
"pt_BR": "Remove o espaço não utilizado do XCI para reduzir seu tamanho.",
|
||||
"ru_RU": "Удаляет неиспользуемое пространство из XCI, уменьшая размер файла.",
|
||||
"sv_SE": "Tar bort oanvänt utrymme från XCI-filen för att minska filstorleken.",
|
||||
"th_TH": "ลบพื้นที่ที่ไม่ได้ใช้งานออกจาก XCI เพื่อลดขนาดไฟล์",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Видаляє невикористаний простір із XCI, зменшуючи розмір файлу.",
|
||||
"zh_CN": "移除 XCI 中未使用的空间以减小文件大小。",
|
||||
"zh_TW": "移除 XCI 中未使用的空間以減少檔案大小。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SecondaryMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Datei: {0:n} MB • Spiel: {1:n} MB\n\nErsparnis: {2:n} MB",
|
||||
"el_GR": "",
|
||||
"en_US": "File: {0:n} MB • Game: {1:n} MB\n\nSavings: {2:n} MB",
|
||||
"es_ES": "Archivo: {0:n} MB • Juego: {1:n} MB\n\nAhorro: {2:n} MB",
|
||||
"fr_FR": "Fichier: {0:n} Mo • Jeu: {1:n} Mo\n\nÉconomies: {2:n} Mo",
|
||||
"he_IL": "",
|
||||
"it_IT": "File: {0:n} MB • Gioco: {1:n} MB\n\nRisparmio: {2:n} MB",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "파일: {0:n} MB • 게임: {1:n} MB\n\n절약: {2:n} MB",
|
||||
"no_NO": "Fil: {0:n} MB • Spill: {1:n} MB\n\nBesparelse: {2:n} MB",
|
||||
"pl_PL": "Plik: {0:n} MB • Gra: {1:n} MB\n\nOszczędności: {2:n} MB",
|
||||
"pt_BR": "Arquivo: {0:n} MB • Jogo: {1:n} MB\n\nEconomia: {2:n} MB",
|
||||
"ru_RU": "Файл: {0:n} МБ • Игра: {1:n} МБ\n\nЭкономия: {2:n} МБ",
|
||||
"sv_SE": "Fil: {0:n} MB • Spel: {1:n} MB\n\nBesparing: {2:n} MB",
|
||||
"th_TH": "ไฟล์: {0:n} MB • เกม: {1:n} MB\n\nการประหยัด: {2:n} MB",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Файл: {0:n} МБ • Гра: {1:n} МБ\n\nЕкономія: {2:n} МБ",
|
||||
"zh_CN": "文件: {0:n} MB • 游戏: {1:n} MB\n\n节省: {2:n} MB",
|
||||
"zh_TW": "檔案: {0:n} MB • 遊戲: {1:n} MB\n\n節省: {2:n} MB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NoTrimNecessaryMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI muss nicht getrimmt werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI does not require trimming. Check logs for details.",
|
||||
"es_ES": "El XCI no necesita ser recortado. Verifica los logs para detalles.",
|
||||
"fr_FR": "Le XCI n’a pas besoin d’être réduit. Référez-vous aux journaux pour détails.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Non è necessario ridurre la dimensione del XCI. Controlla i log per dettagli.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI는 트리밍할 필요가 없습니다. 자세한 내용은 로그를 확인.",
|
||||
"no_NO": "XCI trenger ikke å trimmes. Sjekk loggene for detaljer.",
|
||||
"pl_PL": "XCI nie wymaga przycinania. Sprawdź dzienniki, aby uzyskać szczegóły.",
|
||||
"pt_BR": "O XCI não precisa ser reduzido. Verifique os logs para detalhes.",
|
||||
"ru_RU": "XCI не требует обрезки. Проверьте логи для подробностей.",
|
||||
"sv_SE": "XCI behöver inte optimeras. Kontrollera loggen för detaljer.",
|
||||
"th_TH": "XCI ไม่จำเป็นต้องถูกตัดแต่ง โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "XCI не потребує обрізання. Перевірте журнали для отримання деталей.",
|
||||
"zh_CN": "XCI 不需要被瘦身。查看日志以获得更多细节。",
|
||||
"zh_TW": "XCI 不需要修剪。檢查日誌以取得更多資訊。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NoUntrimPossibleMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Das Trimmen der XCI kann nicht rückgängig gemacht werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI cannot be untrimmed. Check logs for details.",
|
||||
"es_ES": "El recorte del XCI no puede ser deshecho. Verifica los registros para detalles.",
|
||||
"fr_FR": "Le XCI ne peut être restauré. Référez-vous aux journaux pour détails.",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI non può essere ripristinato. Controlla i log per dettagli.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI는 복원할 수 없습니다. 자세한 내용은 로그를 확인.",
|
||||
"no_NO": "XCI kan ikke gjenopprettes. Sjekk loggene for detaljer.",
|
||||
"pl_PL": "XCI nie może zostać przywrócone. Sprawdź dzienniki, aby uzyskać szczegóły.",
|
||||
"pt_BR": "XCI não pode ser desfeito. Verifique os logs para detalhes.",
|
||||
"ru_RU": "XCI не может быть восстановлен. Проверьте журналы для подробностей.",
|
||||
"sv_SE": "XCI kan inte återställas. Kontrollera loggen för detaljer.",
|
||||
"th_TH": "ไม่สามารถคืนค่า XCI ได้ โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "XCI не можна відновити. Перевірте журнали для деталей.",
|
||||
"zh_CN": "XCI 不能恢复。查看日志以获取详情。",
|
||||
"zh_TW": "XCI 無法恢復。檢查日誌以取得詳情。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ReadOnlyFileCannotFixMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI ist schreibgeschützt und konnte nicht schreibbar gemacht werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI is read-only and could not be made writable. Check logs for details.",
|
||||
"es_ES": "XCI es solo lectura y no se puede escribir. Verifica los registros para detalles.",
|
||||
"fr_FR": "XCI en lecture seule et n'a pas pu être rendu écrivable. Référez-vous aux journaux pour détails.",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI è solo lettura e non può essere scritto. Controlla i log per dettagli.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 파일은 읽기 전용이며 쓰기 불가. 로그를 확인하십시오.",
|
||||
"no_NO": "XCI er skrivebeskyttet og kunne ikke gjøres skrivbar. Sjekk loggene for detaljer.",
|
||||
"pl_PL": "XCI jest tylko do odczytu i nie można zapisać. Sprawdź logi dla szczegółów.",
|
||||
"pt_BR": "XCI é somente leitura e não pode ser gravado. Verifique os logs para detalhes.",
|
||||
"ru_RU": "XCI только для чтения и не стал доступен для записи. Проверьте журналы для подробностей.",
|
||||
"sv_SE": "XCI-filen är skrivskyddad och kunde inte göras skrivbar. Kontrollera loggen för mer information",
|
||||
"th_TH": "XCI เป็นอ่านอย่างเดียวและไม่สามารถเขียนได้ ตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "XCI тільки для читання і не можна записати. Перевірте логи для деталей.",
|
||||
"zh_CN": "XCI 只读,无法写入。查看日志以获取详情。",
|
||||
"zh_TW": "XCI 檔案唯讀,無法寫入。檢查日誌以取得詳情。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SizeChangedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI-Größe hat sich seit dem letzten Scan geändert. Stellen Sie sicher, dass die Datei nicht beschrieben wird, und versuchen Sie es erneut.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI size changed since last scan. Ensure the file is not being written to and try again.",
|
||||
"es_ES": "El tamaño de XCI ha cambiado desde que fue escaneado. Verifica que no se esté escribiendo al archivo y vuelve a intentarlo.",
|
||||
"fr_FR": "La taille de XCI a changé depuis son analyse. Vérifiez que le fichier n’est pas en cours d’écriture, puis réessayez.",
|
||||
"he_IL": "",
|
||||
"it_IT": "La dimensione di XCI è cambiata da quando è stato scansionato. Controlla che il file non sia scritto e riprova.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 크기가 스캔 후 변경되었습니다. 파일이 쓰여지고 있지 않은지 확인하고 다시 시도하세요.",
|
||||
"no_NO": "XCI har endret størrelse siden den ble skannet. Kontroller at det ikke skrives til filen, og prøv på nytt.",
|
||||
"pl_PL": "Rozmiar XCI zmienił się od momentu zeskanowania. Sprawdź, czy plik nie jest zapisywany, a następnie spróbuj ponownie.",
|
||||
"pt_BR": "O tamanho de XCI mudou desde que foi escaneado. Verifique se o arquivo não está sendo gravado e tente novamente.",
|
||||
"ru_RU": "Размер XCI изменился после сканирования. Проверьте, не записывается ли файл, и попробуйте снова.",
|
||||
"sv_SE": "XCI har ändrats i storlek sedan den lästes av. Kontrollera att filen inte skrivs till och försök igen.",
|
||||
"th_TH": "ขนาด XCI เปลี่ยนไปตั้งแต่การสแกนครั้งล่าสุด ตรวจสอบว่าไฟล์ไม่ได้ถูกเขียน และลองใหม่",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Розмір XCI змінився з моменту сканування. Перевірте, чи не записується файл, та спробуйте знову.",
|
||||
"zh_CN": "XCI 在扫描后大小发生了变化。请检查文件是否未被写入,然后重试。",
|
||||
"zh_TW": "XCI 檔案大小自上次掃描以來已經改變。請檢查檔案是否未被寫入,然後再嘗試。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "FreeSpaceCheckFailedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI enthält Daten im freien Speicherbereich. Es ist unsicher zu trimmen.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI has data in the free space area. It is not safe to trim.",
|
||||
"es_ES": "XCI tiene datos en el área de espacio libre. No es seguro recortarlo.",
|
||||
"fr_FR": "XCI contient des données dans la zone d'espace libre. Il n'est pas sûr de le réduire.",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI contiene dati nell'area di spazio libero. Non è sicuro ridurre la sua dimensione.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 파일에 여유 공간 영역에 데이터가 있으므로 트리밍하는 것이 안전하지 않습니다.",
|
||||
"no_NO": "XCI har data i den ledige plassen. Det er ikke trygt å trimme den.",
|
||||
"pl_PL": "XCI zawiera dane w obszarze wolnego miejsca. Nie jest bezpieczne go przycinać.",
|
||||
"pt_BR": "XCI tem dados na área de espaço livre. Não é seguro reduzi-lo.",
|
||||
"ru_RU": "XCI содержит данные в свободной области. Его обрезка небезопасна.",
|
||||
"sv_SE": "XCI har data i det lediga utrymmet. Det är inte säkert att optimera.",
|
||||
"th_TH": "XCI มีข้อมูลในพื้นที่ว่าง จึงไม่ปลอดภัยที่จะทำการตัดแต่ง",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "XCI містить дані в зоні вільного простору. Тому обрізка небезпечна.",
|
||||
"zh_CN": "XCI 文件的空闲区域内有数据。不能安全瘦身。",
|
||||
"zh_TW": "XCI 檔案有數據儲存於空閒區域。修剪不安全。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "InvalidDataMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI enthält ungültige Daten. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI contains invalid data. Check logs for details.",
|
||||
"es_ES": "XCI contiene datos inválidos. Lee el registro para detalles.",
|
||||
"fr_FR": "XCI contient des données invalides. Référez-vous aux journaux pour détails.",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI contiene dati non validi. Controlla i log per dettagli.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 파일에 유효하지 않은 데이터가 포함되어 있습니다. 로그를 확인하세요.",
|
||||
"no_NO": "XCI-filen inneholder ugyldige data. Sjekk loggene for detaljer.",
|
||||
"pl_PL": "XCI zawiera nieprawidłowe dane. Sprawdź dzienniki, aby uzyskać szczegóły.",
|
||||
"pt_BR": "XCI contém dados inválidos. Verifique os logs para detalhes.",
|
||||
"ru_RU": "XCI содержит недопустимые данные. Проверьте журналы для подробностей.",
|
||||
"sv_SE": "XCI-filen innehåller ogiltig data. Kontrollera loggen för detaljer.",
|
||||
"th_TH": "XCI มีข้อมูลที่ไม่ถูกต้อง โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "XCI містить недійсні дані. Перевірте журнали для деталей.",
|
||||
"zh_CN": "XCI 文件含有无效数据。查看日志以获得更多细节。",
|
||||
"zh_TW": "XCI 檔案帶有無效的數據。檢查日誌以取得更多資訊"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "WriteErrorMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI konnte nicht zum Schreiben geöffnet werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI could not be opened for writing. Check logs for details.",
|
||||
"es_ES": "XCI no se puede abrir para escribir. Lee el registro para detalles.",
|
||||
"fr_FR": "XCI n'a pas pu être ouvert pour écriture. Consultez les journaux pour détails.",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI non può essere aperto in scrittura. Controlla i log per dettagli.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI를 쓰기 위해 열 수 없습니다. 로그를 확인하세요.",
|
||||
"no_NO": "XCI kunne ikke åpnes for skriving. Sjekk loggene for detaljer.",
|
||||
"pl_PL": "Nie można otworzyć XCI do zapisu. Sprawdź dzienniki, aby uzyskać szczegóły.",
|
||||
"pt_BR": "XCI não pôde ser aberto para gravação. Verifique os logs para detalhes.",
|
||||
"ru_RU": "Не удалось открыть XCI для записи. Проверьте журналы для подробностей.",
|
||||
"sv_SE": "XCI kunde inte öppnas för skrivning. Kontrollera loggen för detaljer.",
|
||||
"th_TH": "ไม่สามารถเปิด XCI เพื่อเขียนข้อมูลได้ โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Не вдалося відкрити XCI для запису. Перевірте журнали для деталей.",
|
||||
"zh_CN": "XCI 不能写入。查看日志以获得更多细节。",
|
||||
"zh_TW": "XCI 無法開啟以進行寫入。請檢查日誌以取得更多資訊。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "TrimFailedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Trimmen der XCI fehlgeschlagen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Failed to trim XCI.",
|
||||
"es_ES": "El recorte del XCI falló.",
|
||||
"fr_FR": "La réduction du XCI a échoué.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Riduzione del XCI fallita.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 트리밍에 실패했습니다.",
|
||||
"no_NO": "Trimming av XCI mislyktes.",
|
||||
"pl_PL": "Nie udało się przyciąć XCI.",
|
||||
"pt_BR": "A redução do XCI falhou.",
|
||||
"ru_RU": "Обрезка XCI не удалась.",
|
||||
"sv_SE": "Optimering av XCI misslyckades.",
|
||||
"th_TH": "การตัดแต่ง XCI ล้มเหลว",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Не вдалося обрізати XCI.",
|
||||
"zh_CN": "XCI 瘦身失败。",
|
||||
"zh_TW": "修剪 XCI 失敗。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "TrimCancelledMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Der Vorgang wurde abgebrochen.",
|
||||
"el_GR": "",
|
||||
"en_US": "The operation was cancelled.",
|
||||
"es_ES": "La operación fue cancelada.",
|
||||
"fr_FR": "L'opération a été annulée.",
|
||||
"he_IL": "",
|
||||
"it_IT": "L'operazione è stata annullata.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "작업이 취소되었습니다.",
|
||||
"no_NO": "Operasjonen ble avbrutt.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "A operação foi cancelada.",
|
||||
"ru_RU": "Операция была отменена.",
|
||||
"sv_SE": "Åtgärden avbröts.",
|
||||
"th_TH": "การดำเนินการถูกยกเลิกแล้ว.",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Операцію перервано.",
|
||||
"zh_CN": "操作已取消。",
|
||||
"zh_TW": "操作已取消。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NoOperationPerformedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Es wurde keine Aktion durchgeführt.",
|
||||
"el_GR": "",
|
||||
"en_US": "No operation was performed.",
|
||||
"es_ES": "No se realizó ninguna operación.",
|
||||
"fr_FR": "Aucune opération n'a été effectuée.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Non è stata effettuata alcuna operazione.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "작업이 수행되지 않았습니다.",
|
||||
"no_NO": "Ingen operasjon ble utført.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Nenhuma operação foi realizada.",
|
||||
"ru_RU": "Операция не была выполнена.",
|
||||
"sv_SE": "Ingen åtgärd genomfördes.",
|
||||
"th_TH": "ไม่มีการดำเนินการใด ๆ",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Операцію не було виконано.",
|
||||
"zh_CN": "未执行任何操作。",
|
||||
"zh_TW": "沒有執行任何操作。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "NoApplicationFoundInFile",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Keine Anwendungen in ausgewählter Datei gefunden.",
|
||||
"el_GR": "",
|
||||
"en_US": "No applications found in selected file.",
|
||||
"es_ES": "No se encontraron aplicaciones en el archivo seleccionado.",
|
||||
"fr_FR": "Aucune application trouvée dans le fichier sélectionné.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Nessuna applicazione trovata nel file selezionato.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "선택한 파일에서 앱을 찾을 수 없습니다.",
|
||||
"no_NO": "Ingen apper ble funnet i valgt fil.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Nenhum aplicativo encontrado no arquivo selecionado.",
|
||||
"ru_RU": "Приложений в выбранном файле не найдены",
|
||||
"sv_SE": "Inga applikationer hittades i vald fil.",
|
||||
"th_TH": "ไม่พบแอปพลิเคชั่นจากไฟล์ที่เลือก",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "У вибраному файлі не знайдено жодних додатків.",
|
||||
"zh_CN": "未发现应用",
|
||||
"zh_TW": "未能從已選擇的檔案中找到應用程式。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NoUnpackedApplicationFoundInFolder",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Bitte wähle einen entpackten Anwendungsordner mit gültigem ExeFS oder NSO/NRO aus.",
|
||||
"el_GR": "",
|
||||
"en_US": "Please select an unpacked application folder with a valid ExeFS or NSO/NRO.",
|
||||
"es_ES": "",
|
||||
"fr_FR": "Veuillez sélectionner un répertoire d’application décompressée contenant un ExeFS valide ou NSO/NRO.",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Пожалуйста, выберите папку распакованного приложения с корректным ExeFS или NSO/NRO",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"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 無法解析所提供的韌體。這通常是由於金鑰過時造成的。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "TrimXCIButton",
|
||||
"Translations": {
|
||||
"ar_SA": "تقليم XCI",
|
||||
"de_DE": "Zuschneiden der XCI",
|
||||
"el_GR": "Κοπή XCI",
|
||||
"en_US": "Trim XCI",
|
||||
"es_ES": "Recortar XCI",
|
||||
"fr_FR": "Réduire le XCI",
|
||||
"he_IL": "חתוך XCI",
|
||||
"it_IT": "Riduci il XCI",
|
||||
"ja_JP": "XCIをトリム",
|
||||
"ko_KR": "XCI 트림",
|
||||
"no_NO": "Trim XCI-filen",
|
||||
"pl_PL": "Przytnij XCI",
|
||||
"pt_BR": "Reduzir o XCI",
|
||||
"ru_RU": "Обрезать XCI",
|
||||
"sv_SE": "Optimera XCI",
|
||||
"th_TH": "ลดขนาด XCI",
|
||||
"tr_TR": "XCI'yi Kırp",
|
||||
"uk_UA": "Нарізка XCI",
|
||||
"zh_CN": "精简 XCI",
|
||||
"zh_TW": "修剪 XCI"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,629 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "ActionsLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "_الإجراءات",
|
||||
"de_DE": "_Aktionen",
|
||||
"el_GR": "_Δράσεις",
|
||||
"en_US": "_Actions",
|
||||
"es_ES": "_Acciones",
|
||||
"fr_FR": null,
|
||||
"he_IL": "_פעולות",
|
||||
"it_IT": "_Azioni",
|
||||
"ja_JP": "アクション(_A)",
|
||||
"ko_KR": "동작(_A)",
|
||||
"no_NO": "_Handlinger",
|
||||
"pl_PL": "_Akcje",
|
||||
"pt_BR": "_Ações",
|
||||
"ru_RU": "_Действия",
|
||||
"sv_SE": "_Åtgärder",
|
||||
"th_TH": "_การดำเนินการ",
|
||||
"tr_TR": "_Eylemler",
|
||||
"uk_UA": "_Дії",
|
||||
"zh_CN": "操作(_A)",
|
||||
"zh_TW": "動作(_A)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "KEYS...",
|
||||
"de_DE": null,
|
||||
"el_GR": null,
|
||||
"en_US": "KEYS...",
|
||||
"es_ES": null,
|
||||
"fr_FR": null,
|
||||
"he_IL": "KEYS...",
|
||||
"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": "Katalog...",
|
||||
"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": "Werkzeuge",
|
||||
"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": "Mii-Editor",
|
||||
"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-Trimmer",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI Trimmer",
|
||||
"es_ES": "Recortador de XCI",
|
||||
"fr_FR": "Réducteur de XCI",
|
||||
"he_IL": "",
|
||||
"it_IT": "Trimmer XCI",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 파일 트리머",
|
||||
"no_NO": "Trim XCI-filer",
|
||||
"pl_PL": "Przycinacz XCI",
|
||||
"pt_BR": "Cortador de XCI",
|
||||
"ru_RU": "Триммер XCI",
|
||||
"sv_SE": "XCI-trimmer",
|
||||
"th_TH": "",
|
||||
"tr_TR": "XCI Kesici",
|
||||
"uk_UA": "Тример XCI",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "Emulation neustarten",
|
||||
"el_GR": "",
|
||||
"en_US": "Restart Emulation",
|
||||
"es_ES": "Reiniciar Emulación",
|
||||
"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": "SimulateWakeUpMessageButton",
|
||||
"Translations": {
|
||||
"ar_SA": "محاكاة رسالة الاستيقاظ",
|
||||
"de_DE": "Aufwachnachricht simulieren",
|
||||
"el_GR": "Προσομοίωση Μηνύματος Αφύπνισης",
|
||||
"en_US": "Simulate Wake-Up Message",
|
||||
"es_ES": "Simular Mensaje de Reactivación",
|
||||
"fr_FR": "Simuler un Message de Réveil",
|
||||
"he_IL": "דמה הודעת השכמה",
|
||||
"it_IT": "Simula messaggio di risveglio",
|
||||
"ja_JP": "スリープ復帰メッセージをシミュレート",
|
||||
"ko_KR": "절전 모드 해제 메시지 시뮬레이션",
|
||||
"no_NO": "Simuler oppvåknings-melding",
|
||||
"pl_PL": "Symuluj wiadomość wybudzania",
|
||||
"pt_BR": "Simular Mensagem de Acordar o Console",
|
||||
"ru_RU": "Имитировать сообщение пробуждения",
|
||||
"sv_SE": "Simulera uppvakningsmeddelande",
|
||||
"th_TH": "จำลองการปลุกอุปกรณ์ให้ทำงาน",
|
||||
"tr_TR": "Uyandırma Mesajı Simüle Et",
|
||||
"uk_UA": "Симулювати повідомлення про пробудження",
|
||||
"zh_CN": "模拟唤醒消息",
|
||||
"zh_TW": "模擬喚醒訊息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "RenderDoc Frame-Aufnahme starten",
|
||||
"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": "Запустить захват кадра RenderDoc",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启动 RenderDoc 帧捕获",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "EndRenderDocCaptureButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme beenden",
|
||||
"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": "Завершить захват кадра RenderDoc",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "结束 RenderDoc 帧捕获",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DiscardRenderDocCaptureButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme verwerfen",
|
||||
"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": "Отменить захват кадра RenderDoc",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "丢弃 RenderDoc 帧捕获",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DiscardRenderDocCaptureToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Beendet die jetzige RenderDoc Frame-Aufnahme, verwirft sofort das Ergebnis.",
|
||||
"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": "Завершает текущий активный захват кадра RenderDoc и немедленно удаляет его результат.",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "FileLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "_ملف",
|
||||
"de_DE": "_Datei",
|
||||
"el_GR": "_Αρχείο",
|
||||
"en_US": "_File",
|
||||
"es_ES": "_Archivo",
|
||||
"fr_FR": "_Fichier",
|
||||
"he_IL": "_קובץ",
|
||||
"it_IT": "_Archivio",
|
||||
"ja_JP": "ファイル(_F)",
|
||||
"ko_KR": "파일(_F)",
|
||||
"no_NO": "_Fil",
|
||||
"pl_PL": "_Plik",
|
||||
"pt_BR": "_Arquivo",
|
||||
"ru_RU": "_Файл",
|
||||
"sv_SE": "_Arkiv",
|
||||
"th_TH": "ไฟล์",
|
||||
"tr_TR": "_Dosya",
|
||||
"uk_UA": "_Файл",
|
||||
"zh_CN": "文件(_F)",
|
||||
"zh_TW": "檔案(_F)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "LoadApplicationFromFileButton",
|
||||
"Translations": {
|
||||
"ar_SA": "_تحميل التطبيق...",
|
||||
"de_DE": "_Anwendung laden...",
|
||||
"el_GR": "_Φόρτωση εφαρμογής...",
|
||||
"en_US": "_Load Application...",
|
||||
"es_ES": "_Cargar Aplicación...",
|
||||
"fr_FR": "_Charger l’Application...",
|
||||
"he_IL": "_טען יישום...",
|
||||
"it_IT": "_Carica applicazione...",
|
||||
"ja_JP": "アプリケーションをロード(_L)...",
|
||||
"ko_KR": "앱 불러오기(_L)...",
|
||||
"no_NO": "_Last inn program...",
|
||||
"pl_PL": "_Załaduj aplikację...",
|
||||
"pt_BR": "_Carregar aplicativo...",
|
||||
"ru_RU": "_Загрузить приложение...",
|
||||
"sv_SE": "_Läs in applikation...",
|
||||
"th_TH": "_โหลดแอปพลิเคชัน...",
|
||||
"tr_TR": "_Uygulamayı yükle...",
|
||||
"uk_UA": "_Завантажити застосунок...",
|
||||
"zh_CN": "加载应用程序(_L)...",
|
||||
"zh_TW": "載入應用程式(_L)..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "LoadUnpackedGameFromFolderButton",
|
||||
"Translations": {
|
||||
"ar_SA": "تحميل لُعْبَة غير محزومة...",
|
||||
"de_DE": "_Entpacktes Spiel laden...",
|
||||
"el_GR": "_Φόρτωση Απακετάριστου Παιχνιδιού...",
|
||||
"en_US": "Load _Unpacked Game...",
|
||||
"es_ES": "Cargar Juego _Desempaquetado...",
|
||||
"fr_FR": "Charger un Jeu Décompressé...",
|
||||
"he_IL": "טען משחק _שאינו ארוז...",
|
||||
"it_IT": "Carica gioco _estratto...",
|
||||
"ja_JP": "_展開されたゲームをロード...",
|
||||
"ko_KR": "압축 푼 게임 불러오기(_U)...",
|
||||
"no_NO": "Last inn _upakket spill...",
|
||||
"pl_PL": "Załaduj _rozpakowaną grę...",
|
||||
"pt_BR": "Abrir Jogo _Extraído...",
|
||||
"ru_RU": "Загрузить _распакованную игру...",
|
||||
"sv_SE": "Läs in _uppackat spel...",
|
||||
"th_TH": "โหลดเกมที่แตกไฟล์แล้ว...",
|
||||
"tr_TR": "_Sıkıştırılmamış Oyun Yükle...",
|
||||
"uk_UA": "Завантажити _розпаковану гру...",
|
||||
"zh_CN": "加载解包后的游戏(_U)...",
|
||||
"zh_TW": "載入未封裝的遊戲(_U)..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "LoadTitleUpdatesAndDLCFromFolderButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Updates/DLC laden...",
|
||||
"el_GR": "",
|
||||
"en_US": "Load Updates/DLC...",
|
||||
"es_ES": "Cargar Actualizaciones/DLC...",
|
||||
"fr_FR": "Charger les Mises à Jour/DLC...",
|
||||
"he_IL": "",
|
||||
"it_IT": "Carica aggiornamenti/DLC...",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "업데이트/DLC 불러오기...",
|
||||
"no_NO": "Last inn oppdateringer/DLC...",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Carregar atualizações/DLC...",
|
||||
"ru_RU": "Загрузить обновления/DLC...",
|
||||
"sv_SE": "Läs in uppdateringar/DLC...",
|
||||
"th_TH": "โหลดอัปเดต/DLC...",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Завантажити оновлення/DLC...",
|
||||
"zh_CN": "加载更新/DLC...",
|
||||
"zh_TW": "載入更新/DLC..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "OpenRyuijnxFolderButton",
|
||||
"Translations": {
|
||||
"ar_SA": "فتح مجلد Ryujinx",
|
||||
"de_DE": "Ryujinx-Ordner öffnen",
|
||||
"el_GR": "Άνοιγμα Φακέλου Ryujinx",
|
||||
"en_US": "Open Ryujinx Folder",
|
||||
"es_ES": "Abrir Carpeta de Ryujinx",
|
||||
"fr_FR": "Ouvrir le Dossier Ryujinx",
|
||||
"he_IL": "פתח את תיקיית ריוג'ינקס",
|
||||
"it_IT": "Apri la cartella di Ryujinx",
|
||||
"ja_JP": "Ryujinx フォルダを開く",
|
||||
"ko_KR": "Ryujinx 폴더 열기",
|
||||
"no_NO": "Åpne Ryujinx mappe",
|
||||
"pl_PL": "Otwórz folder Ryujinx",
|
||||
"pt_BR": "Abrir Pasta do Ryujinx",
|
||||
"ru_RU": "Открыть папку Ryujinx",
|
||||
"sv_SE": "Öppna Ryujinx-mapp",
|
||||
"th_TH": "เปิดโฟลเดอร์ Ryujinx",
|
||||
"tr_TR": "Ryujinx Klasörünü aç",
|
||||
"uk_UA": "Відкрити теку Ryujinx",
|
||||
"zh_CN": "打开 Ryujinx 系统目录",
|
||||
"zh_TW": "開啟 Ryujinx 資料夾"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "OpenLogsFolderButton",
|
||||
"Translations": {
|
||||
"ar_SA": "فتح مجلد السجلات",
|
||||
"de_DE": "Logs-Ordner öffnen",
|
||||
"el_GR": "Άνοιγμα Φακέλου Καταγραφής",
|
||||
"en_US": "Open Logs Folder",
|
||||
"es_ES": "Abrir Carpeta de Registros",
|
||||
"fr_FR": "Ouvrir le Dossier des Journaux",
|
||||
"he_IL": "פתח את תיקיית קבצי הלוג",
|
||||
"it_IT": "Apri la cartella dei log",
|
||||
"ja_JP": "ログフォルダを開く",
|
||||
"ko_KR": "로그 폴더 열기",
|
||||
"no_NO": "Åpne Logg mappen",
|
||||
"pl_PL": "Otwórz folder plików dziennika zdarzeń",
|
||||
"pt_BR": "Abrir Pasta de _Logs",
|
||||
"ru_RU": "Открыть папку журналов",
|
||||
"sv_SE": "Öppna loggmapp",
|
||||
"th_TH": "เปิดโฟลเดอร์ Logs",
|
||||
"tr_TR": "Logs Klasörünü aç",
|
||||
"uk_UA": "Відкрити теку журналів змін",
|
||||
"zh_CN": "打开日志目录",
|
||||
"zh_TW": "開啟日誌資料夾"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "OpenScreenshotsFolderButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Bildschirmfoto-Ordner öffnen",
|
||||
"el_GR": "",
|
||||
"en_US": "Open Screenshots Folder",
|
||||
"es_ES": "Abrir Carpeta de Capturas de Pantalla",
|
||||
"fr_FR": "Ouvrir le Dossier des Captures d’Écran",
|
||||
"he_IL": "",
|
||||
"it_IT": "Apri la cartella degli screenshots",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "스크린샷 폴더 열기",
|
||||
"no_NO": "Åpne Skjermbilde Mappen",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Abrir Pasta de Capturas de Tela",
|
||||
"ru_RU": "Открыть папку снимков экрана",
|
||||
"sv_SE": "Öppna skärmbildsmappen",
|
||||
"th_TH": "เปิดโฟลเดอร์ที่เก็บภาพหน้าจอ",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відкрити теку скріншотів",
|
||||
"zh_CN": "打开截图文件夹",
|
||||
"zh_TW": "開啟螢幕擷取畫面資料夾"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "AssociateFileTypesButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Dateitypen zuordnen",
|
||||
"el_GR": "",
|
||||
"en_US": "Associate File Types",
|
||||
"es_ES": "",
|
||||
"fr_FR": "Associer Les Types de Fichiers",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Связать файлы с приложением",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RemoveFileTypeAssociationsButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Dateityp-Zuordnungen entfernen",
|
||||
"el_GR": "",
|
||||
"en_US": "Remove File Type Associations",
|
||||
"es_ES": "",
|
||||
"fr_FR": "Dissocier Les Types de Fichiers",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Удалить ассоциации файлов",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "FileTypeAssociationsToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Ordnet unterstützte Dateitypen (NSP, XCI, NCA, NRO, NSO) Ryujinx zu, sodass sie beim Doppelklick im Dateimanager automatisch geöffnet werden. Das Entfernen der Dateityp-Zuordnungen deaktiviert dieses Verhalten.",
|
||||
"el_GR": "",
|
||||
"en_US": "Associates supported file types (NSP, XCI, NCA, NRO, NSO) with Ryujinx so they open automatically when double-clicked in your file manager. Removing file type associations stops this behavior.",
|
||||
"es_ES": "",
|
||||
"fr_FR": "Associe les types de fichiers pris en charge (NSP, XCI, NCA, NRO, NSO) à Ryujinx afin qu’ils s’ouvrent automatiquement par double-clic dans votre gestionnaire de fichiers. Les dissocier désactive ce comportement",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Связывает поддерживаемые типы файлов (NSP, XCI, NCA, NRO, NSO) с Ryujinx, чтобы они автоматически открывались при двойном щелчке в файловом менеджере. Удаление ассоциаций файлов отключает это поведение.",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ExitButton",
|
||||
"Translations": {
|
||||
"ar_SA": "_خروج",
|
||||
"de_DE": "_Beenden",
|
||||
"el_GR": "_Έξοδος",
|
||||
"en_US": "_Exit",
|
||||
"es_ES": "_Salir",
|
||||
"fr_FR": "_Quitter",
|
||||
"he_IL": "_יציאה",
|
||||
"it_IT": "_Esci",
|
||||
"ja_JP": "終了(_E)",
|
||||
"ko_KR": "종료(_E)",
|
||||
"no_NO": "_Avslutt",
|
||||
"pl_PL": "_Wyjdź",
|
||||
"pt_BR": "_Sair",
|
||||
"ru_RU": "_Выйти",
|
||||
"sv_SE": "_Avsluta",
|
||||
"th_TH": "_ออก",
|
||||
"tr_TR": "_Çıkış",
|
||||
"uk_UA": "_Вихід",
|
||||
"zh_CN": "退出(_E)",
|
||||
"zh_TW": "結束(_E)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "OptionsLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "_خيارات",
|
||||
"de_DE": "_Optionen",
|
||||
"el_GR": "_Επιλογές",
|
||||
"en_US": "_Options",
|
||||
"es_ES": "_Opciones",
|
||||
"fr_FR": null,
|
||||
"he_IL": "_אפשרויות",
|
||||
"it_IT": "_Opzioni",
|
||||
"ja_JP": "オプション(_O)",
|
||||
"ko_KR": "옵션(_O)",
|
||||
"no_NO": "_Alternativer",
|
||||
"pl_PL": "_Opcje",
|
||||
"pt_BR": "_Opções",
|
||||
"ru_RU": "_Настройки",
|
||||
"sv_SE": "_Inställningar",
|
||||
"th_TH": "_ตัวเลือก",
|
||||
"tr_TR": "_Seçenekler",
|
||||
"uk_UA": "_Параметри",
|
||||
"zh_CN": "选项(_O)",
|
||||
"zh_TW": "選項(_O)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "LanguageButton",
|
||||
"Translations": {
|
||||
"ar_SA": "اللغة",
|
||||
"de_DE": "Sprache",
|
||||
"el_GR": "Γλώσσα",
|
||||
"en_US": "Language",
|
||||
"es_ES": "Idioma",
|
||||
"fr_FR": "Langue",
|
||||
"he_IL": "שפה",
|
||||
"it_IT": "Lingua",
|
||||
"ja_JP": "言語",
|
||||
"ko_KR": "언어",
|
||||
"no_NO": "Språk",
|
||||
"pl_PL": "Język",
|
||||
"pt_BR": "Idioma",
|
||||
"ru_RU": "Язык",
|
||||
"sv_SE": "Språk",
|
||||
"th_TH": "ภาษา",
|
||||
"tr_TR": "Dil",
|
||||
"uk_UA": "Мова",
|
||||
"zh_CN": "语言",
|
||||
"zh_TW": "語言"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UserProfilesButton",
|
||||
"Translations": {
|
||||
"ar_SA": "_ملفات المستخدمين",
|
||||
"de_DE": "_Benutzerprofile",
|
||||
"el_GR": "_Προφίλ Χρηστών",
|
||||
"en_US": "_User Profiles",
|
||||
"es_ES": "_Perfiles de Usuario",
|
||||
"fr_FR": "_Profils d'Utilisateurs",
|
||||
"he_IL": "_פרופילי משתמש",
|
||||
"it_IT": "_Profili utente",
|
||||
"ja_JP": "ユーザプロファイル(_M)",
|
||||
"ko_KR": "사용자 프로필(_M)",
|
||||
"no_NO": "_Brukerprofiler",
|
||||
"pl_PL": "_Profile użytkowników",
|
||||
"pt_BR": "_Perfis de usuário",
|
||||
"ru_RU": "_Профили пользователей",
|
||||
"sv_SE": "_Användarprofiler",
|
||||
"th_TH": "_โปรไฟล์ผู้ใช้งาน",
|
||||
"tr_TR": "_Kullanıcı Profilleri",
|
||||
"uk_UA": "_Профілі користувачів",
|
||||
"zh_CN": "用户配置文件(_M)",
|
||||
"zh_TW": "使用者設定檔(_M)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsButton",
|
||||
"Translations": {
|
||||
"ar_SA": "_الإعدادات",
|
||||
"de_DE": "_Einstellungen",
|
||||
"el_GR": "_Ρυθμίσεις",
|
||||
"en_US": "_Settings",
|
||||
"es_ES": "_Configuración",
|
||||
"fr_FR": "_Paramètres",
|
||||
"he_IL": "_הגדרות",
|
||||
"it_IT": "_Impostazioni",
|
||||
"ja_JP": "設定(_S)",
|
||||
"ko_KR": "설정(_S)",
|
||||
"no_NO": "_Innstillinger",
|
||||
"pl_PL": "_Ustawienia",
|
||||
"pt_BR": "_Configurações",
|
||||
"ru_RU": "_Параметры",
|
||||
"sv_SE": "_Inställningar",
|
||||
"th_TH": "_ตั้งค่า",
|
||||
"tr_TR": "_Seçenekler",
|
||||
"uk_UA": "_Налаштування",
|
||||
"zh_CN": "设置(_S)",
|
||||
"zh_TW": "設定(_S)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "ToggleFullscreenButton",
|
||||
"Translations": {
|
||||
"ar_SA": "التبديل إلى وضع ملء الشاشة",
|
||||
"de_DE": "Vollbild",
|
||||
"el_GR": "Λειτουργία Πλήρους Οθόνης",
|
||||
"en_US": "Toggle Fullscreen",
|
||||
"es_ES": "Cambiar a Pantalla Completa.",
|
||||
"fr_FR": "Basculer en Plein Écran",
|
||||
"he_IL": "שנה מצב- מסך מלא",
|
||||
"it_IT": "Schermo intero",
|
||||
"ja_JP": "全画面切り替え",
|
||||
"ko_KR": "전체 화면 전환",
|
||||
"no_NO": "Fullskjermsvisning av/på",
|
||||
"pl_PL": "Przełącz na tryb pełnoekranowy",
|
||||
"pt_BR": "Mudar para Tela Cheia",
|
||||
"ru_RU": "Переключить полноэкранный режим",
|
||||
"sv_SE": "Växla helskärm",
|
||||
"th_TH": "สลับเป็นโหมดเต็มหน้าจอ",
|
||||
"tr_TR": "Tam Ekran Modunu Aç",
|
||||
"uk_UA": "На весь екран",
|
||||
"zh_CN": "切换全屏",
|
||||
"zh_TW": "切換全螢幕模式"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ShowConsoleButton",
|
||||
"Translations": {
|
||||
"ar_SA": "عرض وحدة التحكم",
|
||||
"de_DE": "Zeige Konsole",
|
||||
"el_GR": "Εμφάνιση Κονσόλας",
|
||||
"en_US": "Show Console",
|
||||
"es_ES": "Mostrar Consola",
|
||||
"fr_FR": "Afficher la Console",
|
||||
"he_IL": "הצג שורת פקודות",
|
||||
"it_IT": "Mostra console",
|
||||
"ja_JP": "コンソールを表示",
|
||||
"ko_KR": "콘솔 표시",
|
||||
"no_NO": "Vis konsoll",
|
||||
"pl_PL": "Pokaż Konsolę",
|
||||
"pt_BR": "Exibir Console",
|
||||
"ru_RU": "Показать консоль",
|
||||
"sv_SE": "Visa konsoll",
|
||||
"th_TH": "แสดง คอนโซล",
|
||||
"tr_TR": "Konsol'u Göster",
|
||||
"uk_UA": "Показати консоль",
|
||||
"zh_CN": "显示控制台",
|
||||
"zh_TW": "顯示控制台"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "MenuBarActions_StartCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme starten",
|
||||
"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": "啟動 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_EndCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme beenden",
|
||||
"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": "停止 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_DiscardCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "RenderDoc Frame-Aufnahme verwerfen",
|
||||
"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": "捨棄 RenderDoc 畫格擷取"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarActions_DiscardCapture_ToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Beendet die jetzige RenderDoc Frame-Aufnahme, verwirft sofort das Ergebnis.",
|
||||
"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": "停止正在執行的 RenderDoc 畫格擷取,且立即捨棄其結果。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+494
-5594
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "StartGamesInFullscreenCheckboxLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "ابدأ الألعاب بملء الشاشة",
|
||||
"de_DE": "Spiele im Vollbild starten",
|
||||
"el_GR": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη",
|
||||
"en_US": "Start Games in Fullscreen",
|
||||
"es_ES": "Iniciar Juegos en Pantalla Completa",
|
||||
"fr_FR": "Démarrer les Jeux en Plein Écran",
|
||||
"he_IL": "התחל משחקים במסך מלא",
|
||||
"it_IT": "Avvia i giochi a schermo intero",
|
||||
"ja_JP": "全画面でゲームを開始",
|
||||
"ko_KR": "전체 화면으로 게임 시작",
|
||||
"no_NO": "Start spill i fullskjerm",
|
||||
"pl_PL": "Uruchamiaj gry na pełnym ekranie",
|
||||
"pt_BR": "Iniciar Jogos em Tela Cheia",
|
||||
"ru_RU": "Запускать игры в полноэкранном режиме",
|
||||
"sv_SE": "Starta spel i helskärm",
|
||||
"th_TH": "เริ่มเกมแบบเต็มหน้าจอ",
|
||||
"tr_TR": "Oyunları Tam Ekranda Başlat",
|
||||
"uk_UA": "Запускати ігри в повноекранному режимі",
|
||||
"zh_CN": "全屏启动游戏",
|
||||
"zh_TW": "以全螢幕啟動遊戲"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "StartGamesWithoutUICheckboxLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Benutzeroberfläche beim Spielstart ausblenden",
|
||||
"el_GR": "",
|
||||
"en_US": "Hide UI on Game Start",
|
||||
"es_ES": "Ocultar la Interfaz al Iniciar el Juego",
|
||||
"fr_FR": "Masquer l’Interface au Lancement du Jeu",
|
||||
"he_IL": "",
|
||||
"it_IT": "Nascondi l’interfaccia all’avvio del gioco",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "게임 시작 시 UI 숨기기",
|
||||
"no_NO": "Skjul brukergrensesnitt ved spillstart",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Ocultar a interface ao iniciar o jogo",
|
||||
"ru_RU": "Скрывать интерфейс при запуске игры",
|
||||
"sv_SE": "Dölj användargränssnitt vid spelstart",
|
||||
"th_TH": "ซ่อน UI เมื่อเริ่มเกม",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Приховувати інтерфейс під час запуску гри",
|
||||
"zh_CN": "启动游戏时隐藏界面",
|
||||
"zh_TW": "啟動遊戲時隱藏介面"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "StartGamesWithoutUICheckboxLabelToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Blendet die Benutzeroberfläche von Ryujinx automatisch aus, sobald ein Spiel gestartet wird. Während des Spiels kann die Oberfläche mit F4 eingeblendet werden.",
|
||||
"el_GR": "",
|
||||
"en_US": "Automatically hides Ryujinx's UI whenever a game launches. While in-game, press F4 to show the UI.",
|
||||
"es_ES": "",
|
||||
"fr_FR": "Masque automatiquement l'interface de Ryujinx au lancement d'un jeu. En jeu, appuyez sur F4 pour afficher l'interface.",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Автоматически скрывает интерфейс Ryujinx при запуске игры. Во время игры нажмите F4, чтобы показать интерфейс.",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "TrimmingXCILabel",
|
||||
"Translations": {
|
||||
"ar_SA": "جاري تقليم: {0}",
|
||||
"de_DE": "Schneide: {0}",
|
||||
"el_GR": "Κόβει το: {0}",
|
||||
"en_US": "Trimming: {0}",
|
||||
"es_ES": "Recortando: {0}",
|
||||
"fr_FR": "Réduction de: {0}",
|
||||
"he_IL": "חיתוך: {0}",
|
||||
"it_IT": "Riduzione di: {0}",
|
||||
"ja_JP": "{0} をトリミング中:",
|
||||
"ko_KR": "{0} 트리밍:",
|
||||
"no_NO": "Trimming av: {0}",
|
||||
"pl_PL": "Przycinanie: {0}",
|
||||
"pt_BR": "Reduzindo: {0}",
|
||||
"ru_RU": "Обрезка: {0}",
|
||||
"sv_SE": "Trimmar: {0}",
|
||||
"th_TH": "กำลังตัด: {0}",
|
||||
"tr_TR": "{0} Kısaltılıyor:",
|
||||
"uk_UA": "Обрізка: {0}",
|
||||
"zh_CN": "正在修剪: {0}",
|
||||
"zh_TW": "正在修剪: {0}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "FirmwareVersionLabel",
|
||||
"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}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "StatusCountLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI ausgewählt: {0}/{1}",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI Selected: {0}/{1}",
|
||||
"es_ES": "XCI Seleccionados: {0}/{1}",
|
||||
"fr_FR": "XCI Sélectionnés : {0}/{1}",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI Selezionati: {0}/{1}",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 선택됨: {0}/{1}",
|
||||
"no_NO": "XCI Valgt: {0}/{1}",
|
||||
"pl_PL": "XCI Wybrane: {0}/{1}",
|
||||
"pt_BR": "XCI Selecionados: {0}/{1}",
|
||||
"ru_RU": "Выбрано XCI: {0}/{1}",
|
||||
"sv_SE": "XCI Valda: {0}/{1}",
|
||||
"th_TH": "XCI เลือกแล้ว: {0}/{1}",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Вибрано XCI: {0}/{1}",
|
||||
"zh_CN": "XCI 已选: {0}/{1}",
|
||||
"zh_TW": "XCI 已選擇: {0}/{1}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "StatusCountWithFilterLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI ausgewählt: {0}/{1} (Sichtbar: {2})",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI Selected: {0}/{1} (Displayed: {2})",
|
||||
"es_ES": "XCI Seleccionados: {0}/{1} (Mostrados: {2})",
|
||||
"fr_FR": "XCI Sélectionnés : {0}/{1} (Affichés : {2})",
|
||||
"he_IL": "",
|
||||
"it_IT": "XCI Selezionati: {0}/{1} (Visualizzati: {2})",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "XCI 선택됨: {0}/{1} (표시됨: {2})",
|
||||
"no_NO": "XCI Valgt: {0}/{1} (Vises: {2})",
|
||||
"pl_PL": "XCI Wybrane: {0}/{1} (Wyświetlone: {2})",
|
||||
"pt_BR": "XCI Selecionados: {0}/{1} (Exibidos: {2})",
|
||||
"ru_RU": "Выбрано XCI: {0}/{1} (Отображается: {2})",
|
||||
"sv_SE": "XCI Valda: {0}/{1} (Visas: {2})",
|
||||
"th_TH": "XCI เลือกแล้ว: {0}/{1} (แสดง: {2})",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Вибрано XCI: {0}/{1} (Відображається: {2})",
|
||||
"zh_CN": "XCI 已选: {0}/{1}(显示: {2})",
|
||||
"zh_TW": "XCI 已選擇: {0}/{1}(顯示: {2})"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "StatusTrimmingLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Trimmen: {0}/{1}...",
|
||||
"el_GR": "",
|
||||
"en_US": "Trimming: {0}/{1}...",
|
||||
"es_ES": "Recortando: {0}/{1}...",
|
||||
"fr_FR": "Réduction : {0}/{1}...",
|
||||
"he_IL": "",
|
||||
"it_IT": "Riduzione: {0}/{1}...",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "트리밍: {0}/{1}...",
|
||||
"no_NO": "Trimmer: {0}/{1}...",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Recortando: {0}/{1}...",
|
||||
"ru_RU": "Обрезка: {0}/{1}...",
|
||||
"sv_SE": "Trimmar: {0}/{1}...",
|
||||
"th_TH": "กำลังตัดแต่ง: {0}/{1}...",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Обрізання: {0}/{1}...",
|
||||
"zh_CN": "正在修剪:{0}/{1}...",
|
||||
"zh_TW": "正在修剪:{0}/{1}..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "StatusUntrimmingLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Trimmung von {0} wieder aufheben...",
|
||||
"el_GR": "",
|
||||
"en_US": "Untrimming {0}...",
|
||||
"es_ES": "Deshaciendo el recorte {0}...",
|
||||
"fr_FR": "Restauration de {0}...",
|
||||
"he_IL": "",
|
||||
"it_IT": "Ripristino di {0}...",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "{0} 복원 중...",
|
||||
"no_NO": "Gjenoppretter {0}...",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Restaurando {0}...",
|
||||
"ru_RU": "Восстановление {0}...",
|
||||
"sv_SE": "Återställer {0}...",
|
||||
"th_TH": "กำลังกู้คืน {0}...",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відновлення {0}...",
|
||||
"zh_CN": "正在恢复 {0}...",
|
||||
"zh_TW": "正在還原 {0}..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SelectAllButton",
|
||||
"Translations": {
|
||||
"ar_SA": "اختر الكل",
|
||||
"de_DE": "Alles auswählen",
|
||||
"el_GR": "Επιλογή όλων",
|
||||
"en_US": "Select All",
|
||||
"es_ES": "Seleccionar Todo",
|
||||
"fr_FR": "Sélectionner Tout",
|
||||
"he_IL": "בחר הכל",
|
||||
"it_IT": "Seleziona tutto",
|
||||
"ja_JP": "すべて選択",
|
||||
"ko_KR": "모두 선택",
|
||||
"no_NO": "Velg alle",
|
||||
"pl_PL": "Zaznacz wszystko",
|
||||
"pt_BR": "Selecionar tudo",
|
||||
"ru_RU": "Выбрать все",
|
||||
"sv_SE": "Markera alla",
|
||||
"th_TH": "เลือกทั้งหมด",
|
||||
"tr_TR": "Hepsini seç",
|
||||
"uk_UA": "Вибрати все",
|
||||
"zh_CN": "选择全部",
|
||||
"zh_TW": "選擇全部"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ClearSelectionButton",
|
||||
"Translations": {
|
||||
"ar_SA": "مسح التحديد",
|
||||
"de_DE": "Auswahl aufheben",
|
||||
"el_GR": "Εκκαθάριση επιλογής",
|
||||
"en_US": "Clear Selection",
|
||||
"es_ES": "Borrar selección",
|
||||
"fr_FR": "Effacer la sélection",
|
||||
"he_IL": "נקה בחירה",
|
||||
"it_IT": "Cancella selezione",
|
||||
"ja_JP": "選択をクリア",
|
||||
"ko_KR": "선택 해제",
|
||||
"no_NO": "Fjern utvalg",
|
||||
"pl_PL": "Wyczyść zaznaczenie",
|
||||
"pt_BR": "Limpar seleção",
|
||||
"ru_RU": "Очистить выделение",
|
||||
"sv_SE": "Rensa markering",
|
||||
"th_TH": "ล้างการเลือก",
|
||||
"tr_TR": "Seçimi temizle",
|
||||
"uk_UA": "Очистити вибір",
|
||||
"zh_CN": "清除选择",
|
||||
"zh_TW": "清除選擇"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "TrimmedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Getrimmt",
|
||||
"el_GR": "",
|
||||
"en_US": "Trimmed",
|
||||
"es_ES": "Recortado",
|
||||
"fr_FR": "Réduit",
|
||||
"he_IL": "",
|
||||
"it_IT": "Dim. ridotta",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "트리밍됨",
|
||||
"no_NO": "Trimmet",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Reduzido",
|
||||
"ru_RU": "Обрезан",
|
||||
"sv_SE": "Optimerad",
|
||||
"th_TH": "ตัดแต่งแล้ว",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Обрізані",
|
||||
"zh_CN": "经过瘦身的",
|
||||
"zh_TW": "已修剪"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UntrimmedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Ungetrimmt",
|
||||
"el_GR": "",
|
||||
"en_US": "Untrimmed",
|
||||
"es_ES": "Sin Recortar",
|
||||
"fr_FR": "Non Réduit",
|
||||
"he_IL": "",
|
||||
"it_IT": "Dim. originale",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "트리밍되지 않음",
|
||||
"no_NO": "Ikke trimmet",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Não Reduzido",
|
||||
"ru_RU": "Не обрезан",
|
||||
"sv_SE": "Orörd",
|
||||
"th_TH": "ยังไม่ได้ตัดแต่ง",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Необрізані",
|
||||
"zh_CN": "没有瘦身的",
|
||||
"zh_TW": "未修剪"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "PartialLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Teilweise",
|
||||
"el_GR": "",
|
||||
"en_US": "Partial",
|
||||
"es_ES": "Parcial",
|
||||
"fr_FR": "Partiel",
|
||||
"he_IL": "",
|
||||
"it_IT": "Parziale",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "일부",
|
||||
"no_NO": "Delvis",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Parcial",
|
||||
"ru_RU": "Частично",
|
||||
"sv_SE": "Delvis",
|
||||
"th_TH": "ยังไม่สมบูรณ์",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Часткові",
|
||||
"zh_CN": "分区",
|
||||
"zh_TW": "部分"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "FailedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Fehlgeschlagen",
|
||||
"el_GR": "",
|
||||
"en_US": "Failed",
|
||||
"es_ES": "Fallido",
|
||||
"fr_FR": "Échoué",
|
||||
"he_IL": "",
|
||||
"it_IT": "Fallito",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "실패",
|
||||
"no_NO": "Mislyktes",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Falhou",
|
||||
"ru_RU": "Ошибка",
|
||||
"sv_SE": "Misslyckades",
|
||||
"th_TH": "ล้มเหลว",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Невдача",
|
||||
"zh_CN": "失败",
|
||||
"zh_TW": "失敗"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UnknownLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "مجهول",
|
||||
"de_DE": "Unbekannt",
|
||||
"el_GR": "Άγνωστο",
|
||||
"en_US": "Unknown",
|
||||
"es_ES": "Desconocido",
|
||||
"fr_FR": "Inconnu",
|
||||
"he_IL": "לא ידוע",
|
||||
"it_IT": "Sconosciuto",
|
||||
"ja_JP": "不明",
|
||||
"ko_KR": "알 수 없음",
|
||||
"no_NO": "Ukjent",
|
||||
"pl_PL": "Nieznany",
|
||||
"pt_BR": "Desconhecido",
|
||||
"ru_RU": "Неизвестно",
|
||||
"sv_SE": "Okänd",
|
||||
"th_TH": "ไม่รู้จัก",
|
||||
"tr_TR": "Bilinmeyen",
|
||||
"uk_UA": "Невідомо",
|
||||
"zh_CN": "未知",
|
||||
"zh_TW": "未知"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "CalculatedSavingsLabel",
|
||||
"Translations": {
|
||||
"ar_SA": null,
|
||||
"de_DE": null,
|
||||
"el_GR": null,
|
||||
"en_US": "{0} MB ({1}%)",
|
||||
"es_ES": null,
|
||||
"fr_FR": "{0} Mo ({1} %)",
|
||||
"he_IL": null,
|
||||
"it_IT": null,
|
||||
"ja_JP": "{0}MB({1}%)",
|
||||
"ko_KR": "{0}MB ({1}%)",
|
||||
"no_NO": null,
|
||||
"pl_PL": null,
|
||||
"pt_BR": null,
|
||||
"ru_RU": "{0} МБ ({1}%)",
|
||||
"sv_SE": null,
|
||||
"th_TH": null,
|
||||
"tr_TR": null,
|
||||
"uk_UA": "{0} МБ ({1}%)",
|
||||
"zh_CN": "{0} MB({1}%)",
|
||||
"zh_TW": "{0} MB({1}%)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SavedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Gespeichert:",
|
||||
"el_GR": "",
|
||||
"en_US": "Saved:",
|
||||
"es_ES": "Ahorrado:",
|
||||
"fr_FR": "Économies :",
|
||||
"he_IL": "",
|
||||
"it_IT": "Risparmiato:",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "Zaoszczędzone:",
|
||||
"pt_BR": "Economizado:",
|
||||
"ru_RU": "Сохранено:",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Збережено:",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RemainingLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "المتبقية:",
|
||||
"de_DE": "Verbleibend:",
|
||||
"el_GR": "Απομένουν:",
|
||||
"en_US": "Remaining:",
|
||||
"es_ES": "Restante:",
|
||||
"fr_FR": "Restant :",
|
||||
"he_IL": "נותרו:",
|
||||
"it_IT": "Rimanenti:",
|
||||
"ja_JP": "残り:",
|
||||
"ko_KR": "남은:",
|
||||
"no_NO": "Gjenstående:",
|
||||
"pl_PL": "Pozostało:",
|
||||
"pt_BR": "Restante:",
|
||||
"ru_RU": "Осталось:",
|
||||
"sv_SE": "Kvar:",
|
||||
"th_TH": "เหลือ:",
|
||||
"tr_TR": "Kalan:",
|
||||
"uk_UA": "Залишилося:",
|
||||
"zh_CN": "剩余:",
|
||||
"zh_TW": "剩餘:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MBLabel",
|
||||
"Translations": {
|
||||
"ar_SA": null,
|
||||
"de_DE": null,
|
||||
"el_GR": null,
|
||||
"en_US": "{0} MB",
|
||||
"es_ES": null,
|
||||
"fr_FR": "{0} Mo",
|
||||
"he_IL": null,
|
||||
"it_IT": null,
|
||||
"ja_JP": "{0}MB",
|
||||
"ko_KR": "{0}MB",
|
||||
"no_NO": null,
|
||||
"pl_PL": null,
|
||||
"pt_BR": null,
|
||||
"ru_RU": "{0} МБ",
|
||||
"sv_SE": null,
|
||||
"th_TH": null,
|
||||
"tr_TR": null,
|
||||
"uk_UA": "{0} МБ",
|
||||
"zh_CN": null,
|
||||
"zh_TW": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "TrimButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Trimmen",
|
||||
"el_GR": "",
|
||||
"en_US": "Trim",
|
||||
"es_ES": "Recortar",
|
||||
"fr_FR": "Réduire",
|
||||
"he_IL": "",
|
||||
"it_IT": "Riduci",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "트림",
|
||||
"no_NO": "",
|
||||
"pl_PL": "Przytnij",
|
||||
"pt_BR": "Reduzir",
|
||||
"ru_RU": "Обрезать",
|
||||
"sv_SE": "Trimma",
|
||||
"th_TH": "ตัด",
|
||||
"tr_TR": "Kırp",
|
||||
"uk_UA": "Обрізати",
|
||||
"zh_CN": "瘦身",
|
||||
"zh_TW": "修剪"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UntrimButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Trimmen aufheben",
|
||||
"el_GR": "",
|
||||
"en_US": "Untrim",
|
||||
"es_ES": "Desrecortar",
|
||||
"fr_FR": "Dé-Réduire",
|
||||
"he_IL": "",
|
||||
"it_IT": "Ripristina",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "언트림",
|
||||
"no_NO": "Utrim",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Restaurar",
|
||||
"ru_RU": "Восстановить",
|
||||
"sv_SE": "Avoptimera",
|
||||
"th_TH": "กู้คืน",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Відновити",
|
||||
"zh_CN": "取消精简",
|
||||
"zh_TW": "反修剪"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,23 +1,11 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL3" ]; then
|
||||
RYUJINX_BIN="Ryujinx.Headless.SDL3"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
|
||||
RYUJINX_BIN="Ryujinx"
|
||||
fi
|
||||
|
||||
if [ -z "$RYUJINX_BIN" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1"
|
||||
|
||||
if command -v gamemoderun > /dev/null 2>&1; then
|
||||
COMMAND="$COMMAND gamemoderun"
|
||||
fi
|
||||
|
||||
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||
exec $COMMAND "$SCRIPT_DIR/Ryujinx" "$@"
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Version=1.5
|
||||
Name=Ryujinx
|
||||
Type=Application
|
||||
Icon=Ryujinx
|
||||
Icon=app.ryujinx.Ryujinx
|
||||
Exec=Ryujinx.sh %f
|
||||
Comment=A Nintendo Switch Emulator
|
||||
GenericName=Nintendo Switch Emulator
|
||||
Terminal=false
|
||||
Categories=Game;Emulator;
|
||||
MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
|
||||
Keywords=Switch;Nintendo;Emulator;
|
||||
StartupWMClass=Ryujinx
|
||||
PrefersNonDefaultGPU=true
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
CURRENTDIR="$(readlink -f "$(dirname "$0")")"
|
||||
exec "$CURRENTDIR"/usr/bin/Ryujinx.sh "$@"
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>app.ryujinx.Ryujinx</id>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<name>Ryujinx</name>
|
||||
<summary>Nintendo Switch 1 emulator written in C#</summary>
|
||||
<description>
|
||||
<p>Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">app.ryujinx.Ryujinx.desktop</launchable>
|
||||
<url type="homepage">https://ryujinx.app/</url>
|
||||
<developer id="app.ryujinx">
|
||||
<name>Ryubing</name>
|
||||
</developer>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://git.ryujinx.app/projects/Ryubing/raw/branch/master/docs/shell.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<provides>
|
||||
<id>app.ryujinx.Ryujinx.desktop</id>
|
||||
</provides>
|
||||
<content_rating type="oars-1.1" />
|
||||
</component>
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
ROOTDIR="$(readlink -f "$(dirname "$0")")"/../../../
|
||||
@@ -7,20 +7,33 @@ cd "$ROOTDIR"
|
||||
BUILDDIR=${BUILDDIR:-publish}
|
||||
OUTDIR=${OUTDIR:-publish_appimage}
|
||||
|
||||
# AppStream
|
||||
|
||||
rm -rf AppDir
|
||||
mkdir -p AppDir/usr/bin
|
||||
mkdir -p AppDir/usr/lib AppDir/usr/bin
|
||||
mkdir -p AppDir/usr/share/metainfo AppDir/usr/share/applications
|
||||
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps/
|
||||
|
||||
cp distribution/linux/Ryujinx.desktop AppDir/Ryujinx.desktop
|
||||
cp distribution/linux/appimage/AppRun AppDir/AppRun
|
||||
cp distribution/misc/Logo.svg AppDir/Ryujinx.svg
|
||||
cp -r "$BUILDDIR"/* AppDir/usr/lib/
|
||||
|
||||
cp distribution/linux/appimage/app.ryujinx.Ryujinx.appdata.xml AppDir/usr/share/metainfo/app.ryujinx.Ryujinx.appdata.xml
|
||||
cp distribution/linux/app.ryujinx.Ryujinx.desktop AppDir/usr/share/applications/app.ryujinx.Ryujinx.desktop
|
||||
cp distribution/misc/Logo.png AppDir/usr/share/icons/hicolor/256x256/apps/app.ryujinx.Ryujinx.png
|
||||
ln -s ../lib/Ryujinx AppDir/usr/bin/Ryujinx
|
||||
|
||||
cp -r "$BUILDDIR"/* AppDir/usr/bin/
|
||||
# AppImage Root
|
||||
|
||||
ln -s ./usr/share/applications/app.ryujinx.Ryujinx.desktop AppDir/app.ryujinx.Ryujinx.desktop
|
||||
ln -s ./usr/share/icons/hicolor/256x256/apps/app.ryujinx.Ryujinx.png AppDir/.DirIcon
|
||||
ln -s ./usr/share/icons/hicolor/256x256/apps/app.ryujinx.Ryujinx.png AppDir/app.ryujinx.Ryujinx.png
|
||||
ln -s ./usr/lib/Ryujinx.sh AppDir/AppRun
|
||||
|
||||
# Ensure necessary bins are set as executable
|
||||
chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# The "-n" flag removes the appstream checks during build, in case the main website is down.
|
||||
# Run "appstreamcli validate --explain AppDir/usr/share/metainfo/app.ryujinx.Ryujinx.appdata.xml" to check manually
|
||||
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
|
||||
AppDir "$OUTDIR"/Ryujinx.AppImage
|
||||
AppDir "$OUTDIR"/Ryujinx.AppImage
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 1.2 MiB |
+13
-3
@@ -1061,6 +1061,7 @@
|
||||
0100BCA016636000,"eBaseball Powerful Pro Yakyuu 2022",gpu;services-horizon;crash,nothing,2024-05-26 23:07:19
|
||||
01001F20100B8000,"Eclipse: Edge of Light",,playable,2020-08-11 23:06:29
|
||||
0100E0A0110F4000,"eCrossminton",,playable,2020-07-11 18:24:27
|
||||
010054601D54C000,"Emio – The Smiling Man: Famicom Detective Club (DEMO)",demo,playable,2026-05-13 18:32:12
|
||||
0100ABE00DB4E000,"Edna & Harvey: Harvey's New Eyes",nvdec,playable,2021-01-26 14:36:08
|
||||
01004F000B716000,"Edna & Harvey: The Breakout – Anniversary Edition",crash;nvdec,ingame,2022-08-01 16:59:56
|
||||
01002550129F0000,"Effie",,playable,2022-10-27 14:36:39
|
||||
@@ -1204,7 +1205,7 @@
|
||||
01003B200E440000,"Five Nights at Freddy's: Sister Location",,playable,2023-10-06 09:00:58
|
||||
010038200E088000,"Flan",crash;regression,ingame,2021-11-17 07:39:28
|
||||
01000A0004C50000,"FLASHBACK™",nvdec,playable,2020-05-14 13:57:29
|
||||
0100C53004C52000,"Flat Heroes",gpu,ingame,2022-07-26 19:37:37
|
||||
0100C53004C52000,"Flat Heroes",,playable,2026-02-27 17:00:00
|
||||
0100B54012798000,"Flatland: Prologue",,playable,2020-12-11 20:41:12
|
||||
0100307004B4C000,"Flinthook",online,playable,2021-03-25 20:42:29
|
||||
010095A004040000,"Flip Wars",services;ldn-untested,ingame,2022-05-02 15:39:18
|
||||
@@ -1394,6 +1395,7 @@
|
||||
0100c3c012718000,"Grand Theft Auto: III – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
|
||||
0100182014022000,"Grand Theft Auto: Vice City – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
|
||||
010065a014024000,"Grand Theft Auto: San Andreas – The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
|
||||
0100EB500D92E000,"GROOVE COASTER WAI WAI PARTY!!!!",gpu,ingame,2026-05-13 18:32:12
|
||||
0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05
|
||||
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
|
||||
0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21
|
||||
@@ -1832,6 +1834,7 @@
|
||||
010055200E87E000,"Metamorphosis",UE4;audout;gpu;nvdec,ingame,2021-06-16 16:18:11
|
||||
0100D4900E82C000,"Metro 2033 Redux",gpu,ingame,2022-11-09 10:53:13
|
||||
0100F0400E850000,"Metro: Last Light Redux",slow;nvdec;vulkan-backend-bug,ingame,2023-11-01 11:53:52
|
||||
010019A01E2F2000,"Metroid Prime 4: Beyond",,ingame,2026-05-13 18:32:12
|
||||
010012101468C000,"Metroid Prime™ Remastered",gpu;Needs Update;vulkan-backend-bug;opengl-backend-bug,ingame,2024-05-07 22:48:15
|
||||
010093801237C000,"Metroid™ Dread",,playable,2023-11-13 04:02:36
|
||||
0100A1200F20C000,"Midnight Evil",,playable,2022-10-18 22:55:19
|
||||
@@ -1945,6 +1948,7 @@
|
||||
0100C3E00ACAA000,"Mutant Football League: Dynasty Edition",online-broken,playable,2022-08-05 17:01:51
|
||||
01004BE004A86000,"Mutant Mudds Collection",,playable,2022-08-05 17:11:38
|
||||
0100E6B00DEA4000,"Mutant Year Zero: Road to Eden - Deluxe Edition",nvdec;UE4,playable,2022-09-10 13:31:10
|
||||
010037501F864000,"Mute Crimson DX",,ingame,2026-05-13 18:32:12
|
||||
0100161009E5C000,"MX Nitro: Unleashed",,playable,2022-09-27 22:34:33
|
||||
0100218011E7E000,"MX vs ATV All Out",nvdec;UE4;vulkan-backend-bug,playable,2022-10-25 19:51:46
|
||||
0100D940063A0000,"MXGP3 - The Official Motocross Videogame",UE4;gpu;nvdec,ingame,2020-12-16 14:00:20
|
||||
@@ -2268,6 +2272,7 @@
|
||||
010086F0064CE000,"Poi: Explorer Edition",nvdec,playable,2021-01-21 19:32:00
|
||||
0100EB6012FD2000,"Poison Control",,playable,2021-05-16 14:01:54
|
||||
010072400E04A000,"Pokémon Café ReMix",,playable,2021-08-17 20:00:04
|
||||
01005B7008C52800,"Pokémon Champions",Needs Update;services;online-broke,menus,2026-05-13 18:32:12
|
||||
010008c01e742000,"Pokémon Friends",crash;services,menus,2025-07-24 13:32:00
|
||||
01003D200BAA2000,"Pokémon Mystery Dungeon™: Rescue Team DX",mac-bug,playable,2024-01-21 00:16:32
|
||||
01008DB008C2C000,"Pokémon Shield + Pokémon Shield Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-12 07:20:22
|
||||
@@ -2275,6 +2280,8 @@
|
||||
01009AD008C4C000,"Pokémon: Let's Go, Pikachu! demo",slow;demo,playable,2023-11-26 11:23:20
|
||||
0100000011D90000,"Pokémon™ Brilliant Diamond",gpu;ldn-works,ingame,2024-08-28 13:26:35
|
||||
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
|
||||
100554023408000,"Pokémon FireRed Version",crashes,nothing,2026-05-13 18:32:12
|
||||
010034D02340E000,"Pokémon LeafGreen Version",crashes,nothing,2026-05-13 18:32:12
|
||||
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
|
||||
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
|
||||
0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00
|
||||
@@ -2660,7 +2667,8 @@
|
||||
0100C52011460000,"Sky: Children of the Light",cpu;online-broken,nothing,2023-02-23 10:57:10
|
||||
010041C01014E000,"Skybolt Zack",,playable,2021-04-12 18:28:00
|
||||
0100A0A00D1AA000,"SKYHILL",,playable,2021-03-05 15:19:11
|
||||
0100CCC0002E6000,"Skylanders Imaginators",crash;services,boots,2020-05-30 18:49:18
|
||||
010048D000BFC000,"Skylanders Imaginators",,playable,2026-07-02 22:43:30
|
||||
0100CCC0002E6000,"Skylanders Imaginators",,playable,2026-07-02 22:43:30
|
||||
010021A00ABEE000,"SKYPEACE",,playable,2020-05-29 14:14:30
|
||||
0100EA400BF44000,"SkyScrappers",,playable,2020-05-28 22:11:25
|
||||
0100F3C00C400000,"SkyTime",slow,ingame,2020-05-30 09:24:51
|
||||
@@ -2866,7 +2874,7 @@
|
||||
0100277011F1A000,"Super Mario Bros.™ 35",online-broken,menus,2022-08-07 16:27:25
|
||||
010015100B514000,"Super Mario Bros.™ Wonder",amd-vendor-bug,playable,2024-09-06 13:21:21
|
||||
01009B90006DC000,"Super Mario Maker™ 2",online-broken;ldn-broken,playable,2024-08-25 11:05:19
|
||||
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
|
||||
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug;amd-vendor-bug,playable,2026-05-13 18:32:12
|
||||
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
|
||||
0100965017338000,"Super Mario Party Jamboree",mac-bug;gpu,ingame,2025-02-17 02:09:20
|
||||
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
|
||||
@@ -3163,6 +3171,8 @@
|
||||
0100E2E00CB14000,"Tokyo School Life",,playable,2022-09-16 20:25:54
|
||||
010024601BB16000,"Tomb Raider I-III Remastered Starring Lara Croft",gpu;opengl,ingame,2024-09-27 12:32:04
|
||||
0100D7F01E49C000,"Tomba! Special Edition",services-horizon,nothing,2024-09-15 21:59:54
|
||||
010051F0207B2000,"Tomodachi Life: Living the Dream",amd-vendor-bug;gpu;intel-vendor-bug;ldn-broken,ingame,2026-05-13 18:32:12
|
||||
0100CA502552A000,"Tomodachi Life: Living the Dream – Welcome Edtion",amd-vendor-bug;demo,playable,2026-05-13 18:32:12
|
||||
0100D400100F8000,"Tonight We Riot",,playable,2021-02-26 15:55:09
|
||||
0100CC00102B4000,"Tony Hawk's™ Pro Skater™ 1 + 2",gpu;Needs Update,ingame,2024-09-24 08:18:14
|
||||
010093F00E818000,"Tools Up!",crash,ingame,2020-07-21 12:58:17
|
||||
|
||||
|
@@ -8,7 +8,7 @@ namespace ARMeilleure.CodeGen
|
||||
/// <summary>
|
||||
/// Represents a compiled function.
|
||||
/// </summary>
|
||||
readonly struct CompiledFunction
|
||||
public readonly struct CompiledFunction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the machine code of the <see cref="CompiledFunction"/>.
|
||||
@@ -44,10 +44,11 @@ namespace ARMeilleure.CodeGen
|
||||
/// <typeparamref name="T"/> pointing to the mapped function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of delegate</typeparam>
|
||||
/// <param name="jitCache">The jit cache to map the function into</param>
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T Map<T>()
|
||||
public T Map<T>(JitCache jitCache)
|
||||
{
|
||||
return MapWithPointer<T>(out _);
|
||||
return MapWithPointer<T>(jitCache, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,11 +56,12 @@ namespace ARMeilleure.CodeGen
|
||||
/// <typeparamref name="T"/> pointing to the mapped function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of delegate</typeparam>
|
||||
/// <param name="jitCache">The jit cache to map the function into</param>
|
||||
/// <param name="codePointer">Pointer to the function code in memory</param>
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T MapWithPointer<T>(out nint codePointer)
|
||||
public T MapWithPointer<T>(JitCache jitCache, out nint codePointer)
|
||||
{
|
||||
codePointer = JitCache.Map(this);
|
||||
codePointer = jitCache.Map(this);
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
|
||||
/// <summary>
|
||||
/// Represents a relocation.
|
||||
/// </summary>
|
||||
readonly struct RelocEntry
|
||||
public readonly struct RelocEntry
|
||||
{
|
||||
public const int Stride = 13; // Bytes.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
|
||||
/// <summary>
|
||||
/// Represents relocation information about a <see cref="CompiledFunction"/>.
|
||||
/// </summary>
|
||||
readonly struct RelocInfo
|
||||
public readonly struct RelocInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="RelocInfo"/>.
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
|
||||
/// <summary>
|
||||
/// Represents a symbol.
|
||||
/// </summary>
|
||||
readonly struct Symbol
|
||||
public readonly struct Symbol
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
|
||||
/// <summary>
|
||||
/// Types of <see cref="Symbol"/>.
|
||||
/// </summary>
|
||||
enum SymbolType : byte
|
||||
public enum SymbolType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Refers to nothing, i.e no symbol.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.Unwinding
|
||||
{
|
||||
struct UnwindInfo
|
||||
public struct UnwindInfo
|
||||
{
|
||||
public const int Stride = 4; // Bytes.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.Unwinding
|
||||
{
|
||||
enum UnwindPseudoOp
|
||||
public enum UnwindPseudoOp
|
||||
{
|
||||
PushReg = 0,
|
||||
SetFrame = 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.CodeGen.Unwinding
|
||||
{
|
||||
struct UnwindPushEntry
|
||||
public struct UnwindPushEntry
|
||||
{
|
||||
public const int Stride = 16; // Bytes.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ARMeilleure.Common
|
||||
public readonly struct AddressTableLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the index of the <see cref="Level"/> in the guest address.
|
||||
/// Gets the index of the <see cref="Index"/> in the guest address.
|
||||
/// </summary>
|
||||
public int Index { get; }
|
||||
|
||||
|
||||
@@ -19,46 +19,22 @@ namespace ARMeilleure.Common
|
||||
new( 7, 8),
|
||||
new( 1, 6)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels64BitSparseTiny =
|
||||
|
||||
private static readonly AddressTableLevel[] _monoSparse64Bit =
|
||||
[
|
||||
new( 11, 28),
|
||||
new( 2, 9)
|
||||
new( 2, 37)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels32BitSparseTiny =
|
||||
private static readonly AddressTableLevel[] _monoSparse32Bit =
|
||||
[
|
||||
new( 10, 22),
|
||||
new( 1, 9)
|
||||
new( 1, 31)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels64BitSparseGiant =
|
||||
[
|
||||
new( 38, 1),
|
||||
new( 2, 36)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
|
||||
[
|
||||
new( 31, 1),
|
||||
new( 1, 30)
|
||||
];
|
||||
|
||||
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
|
||||
//low power will never run worse than non-sparse, but for most systems it won't be necessary
|
||||
//high power is always used, but I've left low power in here for future reference
|
||||
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
|
||||
|
||||
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse)
|
||||
{
|
||||
if (sparse)
|
||||
{
|
||||
if (lowPower)
|
||||
{
|
||||
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
|
||||
}
|
||||
else
|
||||
{
|
||||
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
|
||||
}
|
||||
return for64Bits ? _monoSparse64Bit : _monoSparse32Bit;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
|
||||
using System.Linq;
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else if (table.Sparse)
|
||||
{
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
|
||||
// Deliberately attempts to avoid branches.
|
||||
|
||||
Operand tableBase = !context.HasPtc ?
|
||||
@@ -247,18 +247,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
hostAddress = tableBase;
|
||||
|
||||
for (int i = 0; i < table.Levels.Length; i++)
|
||||
{
|
||||
AddressTableLevel level = table.Levels[i];
|
||||
int clearBits = 64 - (level.Index + level.Length);
|
||||
AddressTableLevel level = table.Levels.Last();
|
||||
int clearBits = 64 - (level.Index + level.Length);
|
||||
|
||||
Operand index = context.ShiftLeft(
|
||||
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
|
||||
Const(3)
|
||||
);
|
||||
Operand index = context.ShiftLeft(
|
||||
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
|
||||
Const(3)
|
||||
);
|
||||
|
||||
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
|
||||
}
|
||||
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace ARMeilleure.Memory
|
||||
{
|
||||
public class ReservedRegion
|
||||
public class ReservedRegion : IDisposable
|
||||
{
|
||||
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
@@ -17,7 +18,7 @@ namespace ARMeilleure.Signal
|
||||
public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState);
|
||||
public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr);
|
||||
|
||||
public static DebugPartialUnmap GenerateDebugPartialUnmap()
|
||||
public static DebugPartialUnmap GenerateDebugPartialUnmap(JitCache jitCache)
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
@@ -31,10 +32,10 @@ namespace ARMeilleure.Signal
|
||||
|
||||
OperandType[] argTypes = [OperandType.I64];
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>();
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>(jitCache);
|
||||
}
|
||||
|
||||
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(nint structPtr)
|
||||
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(JitCache jitCache, nint structPtr)
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
@@ -48,10 +49,10 @@ namespace ARMeilleure.Signal
|
||||
|
||||
OperandType[] argTypes = [OperandType.I64];
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>();
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>(jitCache);
|
||||
}
|
||||
|
||||
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop()
|
||||
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop(JitCache jitCache)
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
@@ -77,7 +78,7 @@ namespace ARMeilleure.Signal
|
||||
|
||||
OperandType[] argTypes = [OperandType.I64];
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>();
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>(jitCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
readonly struct CacheEntry : IComparable<CacheEntry>
|
||||
public readonly struct CacheEntry : IComparable<CacheEntry>
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
||||
@@ -22,12 +22,31 @@ namespace ARMeilleure.Translation.Cache
|
||||
return Offset.CompareTo(other.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int _regionSize;
|
||||
private int _regionCount;
|
||||
|
||||
private readonly List<MemoryBlock> _blocks = [];
|
||||
|
||||
public CacheMemoryAllocator(int capacity)
|
||||
public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
|
||||
{
|
||||
_blocks.Add(new MemoryBlock(0, capacity));
|
||||
_regionCount = 0;
|
||||
_regionSize = regionSize;
|
||||
|
||||
for (; initialRegionCount > 0; initialRegionCount--)
|
||||
{
|
||||
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
|
||||
_regionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewBlocks(int count)
|
||||
{
|
||||
for (; count > 0; count--)
|
||||
{
|
||||
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
|
||||
_regionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public int Allocate(int size)
|
||||
@@ -65,13 +84,14 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
int endOffs = block.Offset + block.Size;
|
||||
|
||||
if (index < _blocks.Count)
|
||||
// Don't merge blocks from different allocations
|
||||
if (index < _blocks.Count && endOffs % _regionSize != 0)
|
||||
{
|
||||
MemoryBlock next = _blocks[index];
|
||||
|
||||
int endOffs = block.Offset + block.Size;
|
||||
|
||||
if (next.Offset == endOffs)
|
||||
{
|
||||
block = new MemoryBlock(block.Offset, block.Size + next.Size);
|
||||
@@ -79,7 +99,8 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
}
|
||||
|
||||
if (index > 0)
|
||||
// Don't merge blocks from different allocations
|
||||
if (index > 0 && block.Offset % _regionSize != 0)
|
||||
{
|
||||
MemoryBlock prev = _blocks[index - 1];
|
||||
|
||||
|
||||
@@ -14,62 +14,35 @@ using System.Threading;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
static partial class JitCache
|
||||
public partial class JitCache : IDisposable
|
||||
{
|
||||
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 256 * 1024 * 1024;
|
||||
private const uint CacheSize = 256 * 1024 * 1024;
|
||||
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
private readonly JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static readonly List<CacheMemoryAllocator> _cacheAllocators = [];
|
||||
private readonly CacheMemoryAllocator _cacheAllocator;
|
||||
|
||||
private static readonly List<CacheEntry> _cacheEntries = [];
|
||||
private readonly List<CacheEntry> _cacheEntries = [];
|
||||
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private static readonly List<ReservedRegion> _jitRegions = [];
|
||||
private static int _activeRegionIndex = 0;
|
||||
private readonly List<ReservedRegion> _jitRegions = [];
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
||||
private static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
||||
|
||||
public static void Initialize(IJitMemoryAllocator allocator)
|
||||
public JitCache(IJitMemoryAllocator allocator)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.RemoveFunctionTableHandler(
|
||||
_jitRegions[0].Pointer);
|
||||
}
|
||||
_jitRegions.Add(new(allocator, CacheSize));
|
||||
|
||||
for (int i = 0; i < _jitRegions.Count; i++)
|
||||
{
|
||||
_jitRegions[i].Dispose();
|
||||
}
|
||||
|
||||
_jitRegions.Clear();
|
||||
_cacheAllocators.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
_activeRegionIndex = 0;
|
||||
|
||||
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
|
||||
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
|
||||
_cacheAllocators.Add(firstCacheAllocator);
|
||||
_cacheAllocator = new((int)CacheSize);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
@@ -79,23 +52,20 @@ namespace ARMeilleure.Translation.Cache
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.InstallFunctionTableHandler(
|
||||
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
||||
this, _jitRegions[0].Pointer, CacheSize, _jitRegions[0].Pointer + Allocate(_pageSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static nint Map(CompiledFunction func)
|
||||
public nint Map(CompiledFunction func)
|
||||
{
|
||||
byte[] code = func.Code;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||
nint funcPtr = GetFunctionPtr(funcOffset);
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -109,9 +79,9 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
|
||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -129,25 +99,24 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unmap(nint pointer)
|
||||
public void Unmap(nint pointer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
foreach (ReservedRegion region in _jitRegions)
|
||||
for (int i = 0; i < _jitRegions.Count; i++)
|
||||
{
|
||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||
ReservedRegion jitRegion = _jitRegions[i];
|
||||
if (pointer.ToInt64() < jitRegion.Pointer.ToInt64() ||
|
||||
pointer.ToInt64() >= (jitRegion.Pointer + (nint)CacheSize).ToInt64())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||
int funcOffset = (int)(pointer.ToInt64() - jitRegion.Pointer.ToInt64() + i * CacheSize);
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
|
||||
@@ -156,53 +125,67 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
private void ReprotectAsWritable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
|
||||
int regionEnd = endOffs % (int)CacheSize == 0
|
||||
? (((int)CacheSize) + _pageMask) & ~_pageMask
|
||||
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
private void ReprotectAsExecutable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
|
||||
int regionEnd = endOffs % (int)CacheSize == 0
|
||||
? (((int)CacheSize) + _pageMask) & ~_pageMask
|
||||
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
private int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
_cacheAllocator.AddNewBlocks(1);
|
||||
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
|
||||
|
||||
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
|
||||
if (allocOffsetNew < 0)
|
||||
|
||||
allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
if (allocOffset < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||
}
|
||||
|
||||
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||
return allocOffsetNew;
|
||||
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
private nint GetFunctionPtr(int offset)
|
||||
{
|
||||
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
|
||||
}
|
||||
|
||||
private ReservedRegion GetRegion(int offset)
|
||||
{
|
||||
int index = offset / (int)CacheSize;
|
||||
|
||||
return _jitRegions[index];
|
||||
}
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
@@ -210,7 +193,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
||||
}
|
||||
|
||||
private static void Add(int offset, int size, UnwindInfo unwindInfo)
|
||||
private void Add(int offset, int size, UnwindInfo unwindInfo)
|
||||
{
|
||||
CacheEntry entry = new(offset, size, unwindInfo);
|
||||
|
||||
@@ -224,25 +207,22 @@ namespace ARMeilleure.Translation.Cache
|
||||
_cacheEntries.Insert(index, entry);
|
||||
}
|
||||
|
||||
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
|
||||
public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (ReservedRegion _ in _jitRegions)
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
}
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,5 +230,18 @@ namespace ARMeilleure.Translation.Cache
|
||||
entryIndex = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.RemoveFunctionTableHandler(_jitRegions[0].Pointer);
|
||||
}
|
||||
|
||||
foreach (ReservedRegion jitRegion in _jitRegions)
|
||||
{
|
||||
jitRegion.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using ARMeilleure.CodeGen.Unwinding;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -26,6 +28,29 @@ namespace ARMeilleure.Translation.Cache
|
||||
public byte FrameRegister;
|
||||
public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize];
|
||||
}
|
||||
|
||||
private unsafe struct InternalFunctionHandler
|
||||
{
|
||||
public InternalFunctionHandler(JitCache jitCache, nint workBufferPtr)
|
||||
{
|
||||
_jitCache = jitCache;
|
||||
|
||||
_runtimeFunction = (RuntimeFunction*)workBufferPtr;
|
||||
|
||||
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction);
|
||||
}
|
||||
|
||||
readonly JitCache _jitCache;
|
||||
|
||||
readonly RuntimeFunction* _runtimeFunction;
|
||||
|
||||
readonly UnwindInfo* _unwindInfo;
|
||||
|
||||
public RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
|
||||
{
|
||||
return JitUnwindWindows.FunctionTableHandler(_jitCache, _runtimeFunction, _unwindInfo, controlPc, context);
|
||||
}
|
||||
}
|
||||
|
||||
private enum UnwindOp
|
||||
{
|
||||
@@ -59,27 +84,28 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
|
||||
|
||||
private static int _sizeOfRuntimeFunction;
|
||||
private static readonly int _sizeOfRuntimeFunction;
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, InternalFunctionHandler> _functionTableHandlers = new();
|
||||
|
||||
private unsafe static RuntimeFunction* _runtimeFunction;
|
||||
static JitUnwindWindows()
|
||||
{
|
||||
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
|
||||
}
|
||||
|
||||
private unsafe static UnwindInfo* _unwindInfo;
|
||||
|
||||
public static void InstallFunctionTableHandler(nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
|
||||
public static void InstallFunctionTableHandler(JitCache jitCache, nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
|
||||
{
|
||||
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
|
||||
|
||||
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
|
||||
|
||||
bool result;
|
||||
|
||||
InternalFunctionHandler handler;
|
||||
|
||||
unsafe
|
||||
{
|
||||
_runtimeFunction = (RuntimeFunction*)workBufferPtr;
|
||||
handler = new InternalFunctionHandler(jitCache, workBufferPtr);
|
||||
|
||||
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction);
|
||||
|
||||
_getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler);
|
||||
_getRuntimeFunctionCallback = handler.FunctionTableHandler;
|
||||
|
||||
result = RtlInstallFunctionTableCallback(
|
||||
codeCachePtr | 3,
|
||||
@@ -94,6 +120,8 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
throw new InvalidOperationException("Failure installing function table callback.");
|
||||
}
|
||||
|
||||
_functionTableHandlers.TryAdd(codeCachePtr, handler);
|
||||
}
|
||||
|
||||
public static void RemoveFunctionTableHandler(nint codeCachePointer)
|
||||
@@ -111,24 +139,26 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
throw new InvalidOperationException("Failure removing function table callback.");
|
||||
}
|
||||
|
||||
_functionTableHandlers.Remove(codeCachePtr, out _);
|
||||
}
|
||||
|
||||
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
|
||||
private static unsafe RuntimeFunction* FunctionTableHandler(JitCache jitCache, RuntimeFunction* runtimeFunction, UnwindInfo* unwindInfo, ulong controlPc, nint context)
|
||||
{
|
||||
int offset = (int)((long)controlPc - context.ToInt64());
|
||||
|
||||
if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _))
|
||||
if (!jitCache.TryFind(offset, out CacheEntry funcEntry, out _))
|
||||
{
|
||||
return null; // Not found.
|
||||
}
|
||||
|
||||
CodeGen.Unwinding.UnwindInfo unwindInfo = funcEntry.UnwindInfo;
|
||||
CodeGen.Unwinding.UnwindInfo funcUnwindInfo = funcEntry.UnwindInfo;
|
||||
|
||||
int codeIndex = 0;
|
||||
|
||||
for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--)
|
||||
for (int index = funcUnwindInfo.PushEntries.Length - 1; index >= 0; index--)
|
||||
{
|
||||
UnwindPushEntry entry = unwindInfo.PushEntries[index];
|
||||
UnwindPushEntry entry = funcUnwindInfo.PushEntries[index];
|
||||
|
||||
switch (entry.PseudoOp)
|
||||
{
|
||||
@@ -140,14 +170,14 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
if (stackOffset <= 0xFFFF0)
|
||||
{
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -161,18 +191,18 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
if (allocSize <= 128)
|
||||
{
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
|
||||
}
|
||||
else if (allocSize <= 0x7FFF8)
|
||||
{
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -180,7 +210,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
case UnwindPseudoOp.PushReg:
|
||||
{
|
||||
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
|
||||
unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -192,16 +222,16 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
Debug.Assert(codeIndex <= MaxUnwindCodesArraySize);
|
||||
|
||||
_unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler.
|
||||
_unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize;
|
||||
_unwindInfo->CountOfUnwindCodes = (byte)codeIndex;
|
||||
_unwindInfo->FrameRegister = 0;
|
||||
unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler.
|
||||
unwindInfo->SizeOfProlog = (byte)funcUnwindInfo.PrologSize;
|
||||
unwindInfo->CountOfUnwindCodes = (byte)codeIndex;
|
||||
unwindInfo->FrameRegister = 0;
|
||||
|
||||
_runtimeFunction->BeginAddress = (uint)funcEntry.Offset;
|
||||
_runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size);
|
||||
_runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction;
|
||||
runtimeFunction->BeginAddress = (uint)funcEntry.Offset;
|
||||
runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size);
|
||||
runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction;
|
||||
|
||||
return _runtimeFunction;
|
||||
return runtimeFunction;
|
||||
}
|
||||
|
||||
private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo)
|
||||
|
||||
@@ -4,6 +4,7 @@ using ARMeilleure.CodeGen.Unwinding;
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using Humanizer;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@@ -33,7 +34,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7018; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@@ -50,6 +51,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
||||
|
||||
public PtcProfiler Profiler { get; }
|
||||
|
||||
private readonly JitCache _jitCache;
|
||||
|
||||
// Carriers.
|
||||
private MemoryStream _infosStream;
|
||||
@@ -81,7 +84,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private volatile int _translateTotalCount;
|
||||
public event Action<PtcLoadingState, int, int> PtcStateChanged;
|
||||
|
||||
public Ptc()
|
||||
public Ptc(JitCache jitCache)
|
||||
{
|
||||
Profiler = new PtcProfiler(this);
|
||||
|
||||
@@ -92,6 +95,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
_waitEvent = new ManualResetEvent(true);
|
||||
|
||||
_jitCache = jitCache;
|
||||
|
||||
_disposed = false;
|
||||
|
||||
TitleIdText = TitleIdTextDefault;
|
||||
@@ -110,7 +115,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
Profiler.Wait();
|
||||
Profiler.ClearEntries();
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled}).");
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache v{InternalVersion}\n\t\t (title: {titleIdText}, version: '{displayVersion}', selector: '{cacheSelector}', enabled: {enabled}).");
|
||||
|
||||
if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
|
||||
{
|
||||
@@ -129,8 +134,6 @@ namespace ARMeilleure.Translation.PTC
|
||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||
_memoryMode = memoryMode;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}");
|
||||
|
||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
||||
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
||||
|
||||
@@ -784,7 +787,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return new UnwindInfo(pushEntries, prologueSize);
|
||||
}
|
||||
|
||||
private static TranslatedFunction FastTranslate(
|
||||
private TranslatedFunction FastTranslate(
|
||||
byte[] code,
|
||||
Counter<uint> callCounter,
|
||||
ulong guestSize,
|
||||
@@ -792,7 +795,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
bool highCq)
|
||||
{
|
||||
CompiledFunction cFunc = new(code, unwindInfo, RelocInfo.Empty);
|
||||
GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(out nint gFuncPointer);
|
||||
GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(_jitCache, out nint gFuncPointer);
|
||||
|
||||
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace ARMeilleure.Translation
|
||||
private readonly IJitMemoryAllocator _allocator;
|
||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||
|
||||
public readonly JitCache JitCache;
|
||||
private readonly Ptc _ptc;
|
||||
|
||||
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||
@@ -43,16 +44,17 @@ namespace ARMeilleure.Translation
|
||||
|
||||
_oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>();
|
||||
|
||||
_ptc = new Ptc();
|
||||
JitCache = new JitCache(allocator);
|
||||
|
||||
_ptc = new Ptc(JitCache);
|
||||
|
||||
Queue = new TranslatorQueue();
|
||||
|
||||
JitCache.Initialize(allocator);
|
||||
|
||||
CountTable = new EntryTable<uint>();
|
||||
Functions = new TranslatorCache<TranslatedFunction>();
|
||||
FunctionTable = functionTable;
|
||||
Stubs = new TranslatorStubs(FunctionTable);
|
||||
Stubs = new TranslatorStubs(JitCache, FunctionTable);
|
||||
|
||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
}
|
||||
@@ -167,6 +169,7 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
|
||||
ClearJitCache();
|
||||
JitCache.Dispose();
|
||||
|
||||
Stubs.Dispose();
|
||||
FunctionTable.Dispose();
|
||||
@@ -305,7 +308,7 @@ namespace ARMeilleure.Translation
|
||||
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
|
||||
}
|
||||
|
||||
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out nint funcPointer);
|
||||
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(JitCache, out nint funcPointer);
|
||||
|
||||
Allocators.ResetAll();
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
class TranslatorStubs : IDisposable
|
||||
{
|
||||
private readonly JitCache _jitCache;
|
||||
|
||||
private readonly Lazy<nint> _slowDispatchStub;
|
||||
|
||||
private bool _disposed;
|
||||
@@ -83,11 +85,14 @@ namespace ARMeilleure.Translation
|
||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||
/// <see cref="Translator"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="jitCache">Jit cache to map functions in</param>
|
||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||
public TranslatorStubs(IAddressTable<ulong> functionTable)
|
||||
public TranslatorStubs(JitCache jitCache, IAddressTable<ulong> functionTable)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(functionTable);
|
||||
|
||||
_jitCache = jitCache;
|
||||
|
||||
_functionTable = functionTable;
|
||||
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
|
||||
@@ -115,12 +120,12 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
if (_dispatchStub.IsValueCreated)
|
||||
{
|
||||
JitCache.Unmap(_dispatchStub.Value);
|
||||
_jitCache.Unmap(_dispatchStub.Value);
|
||||
}
|
||||
|
||||
if (_dispatchLoop.IsValueCreated)
|
||||
{
|
||||
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
|
||||
_jitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
@@ -156,7 +161,7 @@ namespace ARMeilleure.Translation
|
||||
context.BranchIfTrue(lblFallback, masked);
|
||||
|
||||
Operand index = default;
|
||||
Operand page = Const((long)_functionTable.Base);
|
||||
Operand page = Const(_functionTable.Base);
|
||||
|
||||
for (int i = 0; i < _functionTable.Levels.Length; i++)
|
||||
{
|
||||
@@ -188,7 +193,7 @@ namespace ARMeilleure.Translation
|
||||
OperandType retType = OperandType.I64;
|
||||
OperandType[] argTypes = [OperandType.I64];
|
||||
|
||||
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>();
|
||||
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
|
||||
|
||||
return Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
@@ -213,7 +218,7 @@ namespace ARMeilleure.Translation
|
||||
OperandType retType = OperandType.I64;
|
||||
OperandType[] argTypes = [OperandType.I64];
|
||||
|
||||
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>();
|
||||
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
|
||||
|
||||
return Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
@@ -290,7 +295,7 @@ namespace ARMeilleure.Translation
|
||||
OperandType retType = OperandType.None;
|
||||
OperandType[] argTypes = [OperandType.I64, OperandType.I64];
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(_jitCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -314,7 +319,7 @@ namespace ARMeilleure.Translation
|
||||
OperandType retType = OperandType.I64;
|
||||
OperandType[] argTypes = [OperandType.I64, OperandType.I64];
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>(_jitCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
@@ -59,7 +60,7 @@ namespace ARMeilleure.Translation
|
||||
return context.VectorExtract(OperandType.I32, vec, 0);
|
||||
}
|
||||
|
||||
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest()
|
||||
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest(JitCache jitCache)
|
||||
{
|
||||
EmitterContext context = new();
|
||||
|
||||
@@ -141,7 +142,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
OperandType[] argTypes = [OperandType.I64];
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>();
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>(jitCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
sw.Write($"{jsonString}\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class AssignedInputDevice
|
||||
{
|
||||
public AssignedInputDeviceType Type { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
public string ProfileName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<AssignedInputDeviceType>))]
|
||||
public enum AssignedInputDeviceType
|
||||
{
|
||||
Keyboard,
|
||||
Controller,
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
/// Enable Rumble
|
||||
/// </summary>
|
||||
public bool EnableRumble { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable HD Rumble support
|
||||
/// </summary
|
||||
public bool UseHDRumble { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
/// </summary>
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Allow a keyboard configuration to temporarily promote to a connected gamepad,
|
||||
/// while preserving the existing keyboard fallback path when that gamepad disappears.
|
||||
/// </summary>
|
||||
public bool EnableDynamicGamepadSwap { get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||
{
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PhysicalKey>))]
|
||||
public enum PhysicalKey
|
||||
{
|
||||
Unknown,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
ControlLeft,
|
||||
ControlRight,
|
||||
AltLeft,
|
||||
AltRight,
|
||||
WinLeft,
|
||||
WinRight,
|
||||
Menu,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
F25,
|
||||
F26,
|
||||
F27,
|
||||
F28,
|
||||
F29,
|
||||
F30,
|
||||
F31,
|
||||
F32,
|
||||
F33,
|
||||
F34,
|
||||
F35,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Enter,
|
||||
Escape,
|
||||
Space,
|
||||
Tab,
|
||||
BackSpace,
|
||||
Insert,
|
||||
Delete,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
PrintScreen,
|
||||
Pause,
|
||||
NumLock,
|
||||
Clear,
|
||||
Keypad0,
|
||||
Keypad1,
|
||||
Keypad2,
|
||||
Keypad3,
|
||||
Keypad4,
|
||||
Keypad5,
|
||||
Keypad6,
|
||||
Keypad7,
|
||||
Keypad8,
|
||||
Keypad9,
|
||||
KeypadDivide,
|
||||
KeypadMultiply,
|
||||
KeypadSubtract,
|
||||
KeypadAdd,
|
||||
KeypadDecimal,
|
||||
KeypadEnter,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Number0,
|
||||
Number1,
|
||||
Number2,
|
||||
Number3,
|
||||
Number4,
|
||||
Number5,
|
||||
Number6,
|
||||
Number7,
|
||||
Number8,
|
||||
Number9,
|
||||
Tilde,
|
||||
Grave,
|
||||
Minus,
|
||||
Plus,
|
||||
BracketLeft,
|
||||
BracketRight,
|
||||
Semicolon,
|
||||
Quote,
|
||||
Comma,
|
||||
Period,
|
||||
Slash,
|
||||
BackSlash,
|
||||
Unbound,
|
||||
|
||||
Count,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class PlayerInputAssignment
|
||||
{
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
public bool EnableDynamicInputSwap { get; set; }
|
||||
|
||||
public List<AssignedInputDevice> Devices { get; set; } = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public static class PlayerInputAssignmentHelper
|
||||
{
|
||||
public static AssignedInputDevice CreatePrimaryDevice(InputConfig inputConfig)
|
||||
{
|
||||
if (inputConfig == null || string.IsNullOrWhiteSpace(inputConfig.Id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AssignedInputDevice
|
||||
{
|
||||
Type = inputConfig is StandardKeyboardInputConfig
|
||||
? AssignedInputDeviceType.Keyboard
|
||||
: AssignedInputDeviceType.Controller,
|
||||
Id = inputConfig.Id,
|
||||
};
|
||||
}
|
||||
|
||||
public static PlayerInputAssignment Normalize(PlayerInputAssignment assignment, AssignedInputDevice preferredDevice = null)
|
||||
{
|
||||
if (assignment == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PlayerInputAssignment normalized = new()
|
||||
{
|
||||
PlayerIndex = assignment.PlayerIndex,
|
||||
EnableDynamicInputSwap = assignment.EnableDynamicInputSwap,
|
||||
};
|
||||
|
||||
List<AssignedInputDevice> distinctDevices = Deduplicate(assignment.Devices);
|
||||
|
||||
if (assignment.EnableDynamicInputSwap)
|
||||
{
|
||||
normalized.Devices.AddRange(distinctDevices.Select(Clone));
|
||||
return normalized;
|
||||
}
|
||||
|
||||
AssignedInputDevice primaryDevice = SelectPrimaryDevice(distinctDevices, preferredDevice) ?? Clone(preferredDevice);
|
||||
|
||||
if (primaryDevice != null)
|
||||
{
|
||||
normalized.Devices.Add(Clone(primaryDevice));
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static bool AreEquivalent(
|
||||
PlayerInputAssignment left,
|
||||
PlayerInputAssignment right,
|
||||
AssignedInputDevice leftPreferredDevice = null,
|
||||
AssignedInputDevice rightPreferredDevice = null)
|
||||
{
|
||||
if (left == null || right == null)
|
||||
{
|
||||
return left == right;
|
||||
}
|
||||
|
||||
PlayerInputAssignment normalizedLeft = Normalize(left, leftPreferredDevice);
|
||||
PlayerInputAssignment normalizedRight = Normalize(right, rightPreferredDevice);
|
||||
|
||||
if (normalizedLeft.EnableDynamicInputSwap != normalizedRight.EnableDynamicInputSwap)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<(AssignedInputDeviceType Type, string Id, string ProfileName)> leftDevices = normalizedLeft.Devices
|
||||
.Select(device => (Type: device.Type, Id: device.Id, ProfileName: device.ProfileName ?? string.Empty))
|
||||
.OrderBy(device => device.Type)
|
||||
.ThenBy(device => device.Id, StringComparer.Ordinal)
|
||||
.ThenBy(device => device.ProfileName, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
List<(AssignedInputDeviceType Type, string Id, string ProfileName)> rightDevices = normalizedRight.Devices
|
||||
.Select(device => (Type: device.Type, Id: device.Id, ProfileName: device.ProfileName ?? string.Empty))
|
||||
.OrderBy(device => device.Type)
|
||||
.ThenBy(device => device.Id, StringComparer.Ordinal)
|
||||
.ThenBy(device => device.ProfileName, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
return leftDevices.SequenceEqual(rightDevices);
|
||||
}
|
||||
|
||||
private static List<AssignedInputDevice> Deduplicate(IEnumerable<AssignedInputDevice> devices)
|
||||
{
|
||||
List<AssignedInputDevice> result = [];
|
||||
|
||||
if (devices == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (AssignedInputDevice device in devices)
|
||||
{
|
||||
if (device == null || string.IsNullOrWhiteSpace(device.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int existingIndex = result.FindIndex(existing =>
|
||||
existing.Type == device.Type &&
|
||||
string.Equals(existing.Id, device.Id, StringComparison.Ordinal));
|
||||
|
||||
if (existingIndex == -1)
|
||||
{
|
||||
result.Add(Clone(device));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(device.ProfileName) ||
|
||||
string.IsNullOrWhiteSpace(result[existingIndex].ProfileName))
|
||||
{
|
||||
result[existingIndex].ProfileName = device.ProfileName;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static AssignedInputDevice SelectPrimaryDevice(List<AssignedInputDevice> devices, AssignedInputDevice preferredDevice)
|
||||
{
|
||||
if (devices == null || devices.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preferredDevice != null)
|
||||
{
|
||||
AssignedInputDevice matchedDevice = devices.FirstOrDefault(device =>
|
||||
device.Type == preferredDevice.Type &&
|
||||
string.Equals(device.Id, preferredDevice.Id, StringComparison.Ordinal));
|
||||
|
||||
if (matchedDevice != null)
|
||||
{
|
||||
return matchedDevice;
|
||||
}
|
||||
}
|
||||
|
||||
return devices[0];
|
||||
}
|
||||
|
||||
private static AssignedInputDevice Clone(AssignedInputDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AssignedInputDevice
|
||||
{
|
||||
Type = device.Type,
|
||||
Id = device.Id,
|
||||
ProfileName = device.ProfileName,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ namespace Ryujinx.Common.Helper
|
||||
|
||||
public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows());
|
||||
|
||||
// NOTE: On macOS, users have a more robust file association system (via Right-Click > Get Info > "Open with:" > Ryujinx > "Change All...)
|
||||
// Custom file association isn't strictly necessary and will not provide any additional benefit to macOS users.
|
||||
|
||||
public static bool AreMimeTypesRegistered
|
||||
{
|
||||
get
|
||||
@@ -38,8 +41,6 @@ namespace Ryujinx.Common.Helper
|
||||
return AreMimeTypesRegisteredWindows();
|
||||
}
|
||||
|
||||
// TODO: Add macOS support.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -173,8 +174,6 @@ namespace Ryujinx.Common.Helper
|
||||
return InstallWindowsMimeTypes();
|
||||
}
|
||||
|
||||
// TODO: Add macOS support.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -190,8 +189,6 @@ namespace Ryujinx.Common.Helper
|
||||
return InstallWindowsMimeTypes(true);
|
||||
}
|
||||
|
||||
// TODO: Add macOS support.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Ryujinx.Common
|
||||
//Dragon Quest Franchise
|
||||
"010008900705c000", // Dragon Quest Builders
|
||||
"010042000a986000", // Dragon Quest Builders 2
|
||||
"010021701c45e000", // Dragon Quest VII Reimagined Demo
|
||||
|
||||
//Fire Emblem Franchise
|
||||
"0100a6301214e000", // Fire Emblem Engage
|
||||
@@ -33,7 +34,6 @@ namespace Ryujinx.Common
|
||||
"01007e3006dda000", // Kirby Star Allies
|
||||
"01003fb00c5a8000", // Super Kirby Clash
|
||||
|
||||
|
||||
//The Zelda Franchise
|
||||
"01000b900d8b0000", // Cadence of Hyrule
|
||||
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
|
||||
@@ -43,8 +43,7 @@ namespace Ryujinx.Common
|
||||
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
||||
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
||||
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
||||
|
||||
|
||||
|
||||
//Luigi Franchise
|
||||
"010048701995e000", // Luigi's Mansion 2 HD
|
||||
"0100dca0064a6000", // Luigi's Mansion 3
|
||||
@@ -59,6 +58,7 @@ namespace Ryujinx.Common
|
||||
|
||||
//Mario Franchise
|
||||
"010021d00812a000", // Arcade Archives VS. SUPER MARIO BROS.
|
||||
"01007fe0221d8000", // Hello, Mario!
|
||||
"01006d0017f7a000", // Mario & Luigi: Brothership
|
||||
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
|
||||
"010067300059a000", // Mario + Rabbids: Kingdom Battle
|
||||
@@ -70,17 +70,17 @@ namespace Ryujinx.Common
|
||||
"0100bde00862a000", // Mario Tennis Aces
|
||||
"0100b99019412000", // Mario vs. Donkey Kong
|
||||
"010049900f546000", // Super Mario 3D All-Stars
|
||||
"010049900f546001", // Super Mario 3D All-Stars | Super Mario 64
|
||||
"010049900f546002", // Super Mario 3D All-Stars | Super Mario Sunshine
|
||||
"010049900f546003", // Super Mario 3D All-Stars | Super Mario Galaxy
|
||||
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
||||
"010049900F546001", // Super Mario 64
|
||||
"0100ea80032ea000", // Super Mario Bros. U Deluxe
|
||||
"010015100b514000", // Super Mario Bros. Wonder
|
||||
"010049900F546003", // Super Mario Galaxy
|
||||
"01009b90006dc000", // Super Mario Maker 2
|
||||
"0100000000010000", // SUPER MARIO ODYSSEY
|
||||
"010036b0034e4000", // Super Mario Party
|
||||
"0100965017338000", // Super Mario Party Jamboree
|
||||
"0100bc0018138000", // Super Mario RPG
|
||||
"010049900F546002", // Super Mario Sunshine
|
||||
"0100a3900c3e2000", // Paper Mario: The Origami King
|
||||
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
||||
"01009cC022b96000", // Super Mario Galaxy
|
||||
@@ -107,6 +107,11 @@ namespace Ryujinx.Common
|
||||
"0100187003a36000", // Pokémon: Let's Go Eevee!
|
||||
"010003f003a34000", // Pokémon: Let's Go Pikachu!
|
||||
"0100f43008c44000", // Pokémon Legends: Z-A
|
||||
"0100554023408000", // Pokémon FireRed Version (EN)
|
||||
"01006fa0233f8000", // Pokémon FireRed Version (JP)
|
||||
"0100fd6023430000", // Pokémon LeafGreen Version (DE)
|
||||
"0100f1e0233fa000", // Pokémon LeafGreen Version (JP)
|
||||
"01005b7008c52000", // Pokémon Champions
|
||||
|
||||
//Splatoon Franchise
|
||||
"0100f8f0000a2000", // Splatoon 2 (EU)
|
||||
@@ -116,13 +121,14 @@ namespace Ryujinx.Common
|
||||
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
||||
|
||||
//NSO Membership games
|
||||
"0100d870045b6000", // NES - Nintendo Switch Online
|
||||
"01008d300c50c000", // SNES - Nintendo Switch Online
|
||||
"0100c62011050000", // GB - Nintendo Switch Online
|
||||
"010012f017576000", // GBA - Nintendo Switch Online
|
||||
"0100c9a00ece6000", // N64 - Nintendo Switch Online
|
||||
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
|
||||
"0100d870045b6000", // NES - Nintendo Switch Online
|
||||
"0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online
|
||||
"01008d300c50c000", // SNES - Nintendo Switch Online
|
||||
"0100bfc01d976000", // Virtual Boy - Nintendo Switch Online
|
||||
"0100ccf019c8c000", // F-ZERO 99
|
||||
"0100ad9012510000", // PAC-MAN 99
|
||||
"010040600c5ce000", // Tetris 99
|
||||
@@ -137,16 +143,23 @@ namespace Ryujinx.Common
|
||||
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
||||
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
||||
"010067b017588000", // Endless Ocean Luminous
|
||||
"01003da010e8a000", // Miitopia
|
||||
"01006b5012b32000", // Part Time UFO
|
||||
"0100107027310000", // Rhythm Heave Groove: Starter Demo
|
||||
"0100704000B3A000", // Snipperclips
|
||||
"01006a800016e000", // Super Smash Bros. Ultimate
|
||||
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
|
||||
"0100ca502552a000", // Tomodachi Life: Living the Dream - Welcome Edition
|
||||
"010051f0207b2000", // Tomodachi Life: Living the Dream
|
||||
|
||||
//Bayonetta Franchise
|
||||
"010076f0049a2000", // Bayonetta
|
||||
"01007960049a0000", // Bayonetta 2
|
||||
"01004a4010fea000", // Bayonetta 3
|
||||
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
|
||||
|
||||
// Famicom Detective Club Franchise
|
||||
"010054601d54c000", // Emio - The Smiling Man: Famicom Detective Series (DEMO)
|
||||
|
||||
//Persona Franchise
|
||||
"0100dcd01525a000", // Persona 3 Portable
|
||||
@@ -160,7 +173,7 @@ namespace Ryujinx.Common
|
||||
"01004ad014bf0000", // Sonic Frontiers
|
||||
"01009aa000faa000", // Sonic Mania
|
||||
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
|
||||
"01005ea01c0fc001", // ^
|
||||
"01005ea01c0fc001", // ^ TODO: (Needs to be differentiated like 3D All Stars)
|
||||
"01006e001823c000", // Sonic Racing: CrossWorlds
|
||||
"010064b0242be000", // Sonic Racing: CrossWorlds - Demo
|
||||
|
||||
@@ -171,7 +184,9 @@ namespace Ryujinx.Common
|
||||
"0100453019aa8000", // Xenoblade Chronicles: X Definitive Edition
|
||||
|
||||
//Misc Games
|
||||
"01003670066de000", // 36 Fragments of Midnight
|
||||
"010056e00853a000", // A Hat in Time
|
||||
"0100c9f00aaee000", // Ascendence
|
||||
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
||||
"01008c2019598000", // Bluey: The Video Game
|
||||
"010096f00ff22000", // Borderlands 2: Game of the Year Edition
|
||||
@@ -181,21 +196,27 @@ namespace Ryujinx.Common
|
||||
"0100744001588000", // Cars 3: Driven to Win
|
||||
"0100b41013c82000", // Cruis'n Blast
|
||||
"010085900337e000", // Death Squared
|
||||
"0100a0d022a68000", // DELTARUNE
|
||||
"01001b300b9be000", // Diablo III: Eternal Collection
|
||||
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
|
||||
"01008c8012920000", // Dying Light Platinum Edition
|
||||
"0100d11013e6a000", // Eschatos
|
||||
"01000490067ae000", // Frederic 2: Evil Strikes Back
|
||||
"01001cc01b2d4000", // Goat Simulator 3
|
||||
"01003620068ea000", // Hand of Fate 2
|
||||
"01007ac00e012000", // HEXAGRAVITY
|
||||
"0100f7e00c70e000", // Hogwarts Legacy
|
||||
"010013c00e930000", // Hollow Knight: Silksong
|
||||
"010085500130a000", // Lego City: Undercover
|
||||
"010073c01af34000", // LEGO Horizon Adventures
|
||||
"0100653026dde000", // MARVEL Cosmic Invasion Demo
|
||||
"0100419014456000", // Mary Skelter Finale
|
||||
"0100d71004694000", // Minecraft
|
||||
"01007430037f6000", // Monopoly
|
||||
"0100853015e86000", // No Man's Sky
|
||||
"0100f85014ed0000", // No More Heroes
|
||||
"0100463014ed4000", // No More Heroes 2
|
||||
"0100f7d00a1bc000", // NO THING
|
||||
"0100e570094e8000", // Owlboy
|
||||
"01007bb017812000", // Portal
|
||||
"0100abd01785c000", // Portal 2
|
||||
@@ -204,11 +225,16 @@ namespace Ryujinx.Common
|
||||
"01008e200c5c2000", // Muse Dash
|
||||
"01005ff002e2a000", // Rayman Legends
|
||||
"01007820196a6000", // Red Dead Redemption
|
||||
"01007a800d520000", // REFUNCT
|
||||
"0100e8300a67a000", // Risk
|
||||
"01002f7013224000", // Rune Factory 5
|
||||
"01008d100d43e000", // Saints Row IV
|
||||
"0100de600beee000", // Saints Row: The Third - The Full Package
|
||||
"01001180021fa000", // Shovel Knight: Specter of Torment
|
||||
"0100ccc0002e6000", // Skylanders Imaginators (US)
|
||||
"010048d000bfc000", // Skylanders Imaginators (EU)
|
||||
"010079f00671c000", // Sparkle 2: Evo
|
||||
"010077b00e046000", // Spyro: Reignited Trilogy
|
||||
"0100e1D01eb2e000", // Squeakross: Home Squeak Home
|
||||
"0100e65002bb8000", // Stardew Valley
|
||||
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
||||
|
||||
@@ -81,12 +81,13 @@ namespace ARMeilleure.Common
|
||||
|
||||
private bool _disposed;
|
||||
private TEntry** _table;
|
||||
private TEntry* _sparseTable;
|
||||
private readonly List<AddressTablePage> _pages;
|
||||
private TEntry _fill;
|
||||
|
||||
private readonly MemoryBlock _sparseFill;
|
||||
private readonly SparseMemoryBlock _fillBottomLevel;
|
||||
private readonly TEntry* _fillBottomLevelPtr;
|
||||
private MemoryBlock _sparseFill;
|
||||
private SparseMemoryBlock _fillBottomLevel;
|
||||
private TEntry* _fillBottomLevelPtr;
|
||||
|
||||
private readonly List<TableSparseBlock> _sparseReserved;
|
||||
private readonly ReaderWriterLockSlim _sparseLock;
|
||||
@@ -122,16 +123,23 @@ namespace ARMeilleure.Common
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
lock (_pages)
|
||||
if (Sparse)
|
||||
{
|
||||
return (nint)GetRootPage();
|
||||
return (nint)_sparseTable;
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_pages)
|
||||
{
|
||||
return (nint)GetRootPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
||||
/// <see cref="Level"/>.
|
||||
/// <see cref="AddressTableLevel"/>.
|
||||
/// </summary>
|
||||
/// <param name="levels">Levels for the address table</param>
|
||||
/// <param name="sparse">True if the bottom page should be sparsely mapped</param>
|
||||
@@ -156,18 +164,8 @@ namespace ARMeilleure.Common
|
||||
if (sparse)
|
||||
{
|
||||
// If the address table is sparse, allocate a fill block
|
||||
|
||||
_sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul
|
||||
|
||||
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
|
||||
|
||||
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
||||
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
||||
|
||||
_sparseReserved = [];
|
||||
_sparseLock = new ReaderWriterLockSlim();
|
||||
|
||||
_sparseBlockSize = bottomLevelSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,13 +207,24 @@ namespace ARMeilleure.Common
|
||||
public void SignalCodeRange(ulong address, ulong size)
|
||||
{
|
||||
AddressTableLevel bottom = Levels.Last();
|
||||
ulong bottomLevelEntries = 1ul << bottom.Length;
|
||||
|
||||
ulong entryIndex = address >> bottom.Index;
|
||||
|
||||
ulong entries = size >> bottom.Index;
|
||||
entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries);
|
||||
|
||||
if (Sparse)
|
||||
{
|
||||
ulong bottomLevelSize = (ulong)BitUtils.Pow2RoundUp((int)entries) * (ulong)sizeof(TEntry);
|
||||
|
||||
_sparseFill = new MemoryBlock(bottomLevelSize, MemoryAllocationFlags.Mirrorable);
|
||||
|
||||
_sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry));
|
||||
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
||||
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
||||
|
||||
_sparseBlockSize = bottomLevelSize;
|
||||
|
||||
_sparseTable = (TEntry*)Allocate((int)entries, Fill, leaf: true);
|
||||
|
||||
_sparseTable -= Levels.Last().GetValue(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -234,15 +243,26 @@ namespace ARMeilleure.Common
|
||||
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
|
||||
}
|
||||
|
||||
lock (_pages)
|
||||
if (Sparse)
|
||||
{
|
||||
TEntry* page = GetPage(address);
|
||||
long index = Levels.Last().GetValue(address);
|
||||
|
||||
EnsureMapped((nint)(_sparseTable + index));
|
||||
|
||||
return ref _sparseTable[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_pages)
|
||||
{
|
||||
TEntry* page = GetPage(address);
|
||||
|
||||
long index = Levels[^1].GetValue(address);
|
||||
long index = Levels.Last().GetValue(address);
|
||||
|
||||
EnsureMapped((nint)(page + index));
|
||||
EnsureMapped((nint)(page + index));
|
||||
|
||||
return ref page[index];
|
||||
return ref page[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu.AppleHv.Arm;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
@@ -17,9 +18,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
return _impl.ElrEl1;
|
||||
}
|
||||
return _impl.Pc;
|
||||
}
|
||||
}
|
||||
@@ -69,9 +68,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,11 +79,13 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private readonly IHvExecutionContext _shadowContext;
|
||||
private IHvExecutionContext _impl;
|
||||
private int _shouldStep;
|
||||
|
||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||
|
||||
private int _interruptRequested;
|
||||
|
||||
// GPU Sync control
|
||||
private int _syncCounter;
|
||||
private int _strongSyncCounter;
|
||||
|
||||
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
_counter = counter;
|
||||
@@ -108,38 +107,17 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
/// <inheritdoc/>
|
||||
public void SetV(int index, V128 value) => _impl.SetV(index, value);
|
||||
|
||||
private void InterruptHandler()
|
||||
{
|
||||
_exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void BreakHandler(ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void StepHandler()
|
||||
{
|
||||
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void UndefinedHandler(ulong address, int opCode)
|
||||
{
|
||||
_exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
|
||||
}
|
||||
private void InterruptHandler() => _exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||
private void BreakHandler(ulong address, int imm) => _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
private void StepHandler() => _exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
private void SupervisorCallHandler(ulong address, int imm) => _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
private void UndefinedHandler(ulong address, int opCode) => _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
|
||||
{
|
||||
impl.RequestInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetAndClearInterruptRequested()
|
||||
@@ -161,13 +139,9 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
_impl.ElrEl1 = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_impl.Pc = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +155,11 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
public unsafe void Execute(HvMemoryManager memoryManager, ulong address)
|
||||
{
|
||||
HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext);
|
||||
|
||||
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
|
||||
|
||||
_syncCounter = 0;
|
||||
_strongSyncCounter = 0;
|
||||
|
||||
while (Running)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
|
||||
@@ -192,16 +168,23 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||
spsr |= (1 << 21);
|
||||
spsr |= (1U << 21);
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pstate |= (1 << 21);
|
||||
Pstate |= (1U << 21);
|
||||
}
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
|
||||
}
|
||||
|
||||
// Adaptive GPU synchronization to prevent 0 FPS
|
||||
if (++_syncCounter % 12 == 0)
|
||||
{
|
||||
TryGpuSync();
|
||||
_syncCounter = 0;
|
||||
}
|
||||
|
||||
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
||||
|
||||
HvExitReason reason = vcpu.ExitInfo->Reason;
|
||||
@@ -212,9 +195,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
|
||||
|
||||
if (hvEc != ExceptionClass.HvcAarch64)
|
||||
{
|
||||
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
|
||||
}
|
||||
|
||||
address = SynchronousException(memoryManager, ref vcpu);
|
||||
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
|
||||
@@ -245,10 +226,31 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
HvVcpuPool.Instance.Destroy(vcpu, SwapContext);
|
||||
}
|
||||
|
||||
// TryGpuSync() is called periodically in the main Execute() loop. The "syncing" value can be tuned based on gameplay results.
|
||||
// This feature it to be followed-up and further completed in a future PR.
|
||||
private void TryGpuSync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.Yield();
|
||||
|
||||
if (++_strongSyncCounter % 6 == 0)
|
||||
{
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_strongSyncCounter % 100 == 0)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"[AppleHv] GPU sync issue: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu)
|
||||
{
|
||||
ulong vcpuHandle = vcpu.Handle;
|
||||
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
|
||||
|
||||
@@ -259,16 +261,20 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
case ExceptionClass.DataAbortLowerEl:
|
||||
DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr);
|
||||
break;
|
||||
|
||||
case ExceptionClass.TrappedMsrMrsSystem:
|
||||
InstructionTrap((uint)esr);
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, elr + 4UL).ThrowOnError();
|
||||
break;
|
||||
|
||||
case ExceptionClass.SvcAarch64:
|
||||
ReturnToPool(vcpu);
|
||||
ushort id = (ushort)esr;
|
||||
SupervisorCallHandler(elr - 4UL, id);
|
||||
Thread.Yield(); // MoltenVK causes extremely frequent SVC exits, and HVF handles them in a busy loop. Hypervisor.Framework accelerates the guest CPU, and without periodic yielding/flushing, MoltenVK's presentation queue can starve, causing permanent 0 FPS deadlock.
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
|
||||
case ExceptionClass.SoftwareStepLowerEl:
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||
spsr &= ~((ulong)(1 << 21));
|
||||
@@ -278,21 +284,23 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
StepHandler();
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
|
||||
case ExceptionClass.BrkAarch64:
|
||||
ReturnToPool(vcpu);
|
||||
BreakHandler(elr, (ushort)esr);
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception($"Unhandled guest exception {ec}.");
|
||||
}
|
||||
|
||||
// Make sure we will continue running at EL0.
|
||||
if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending())
|
||||
{
|
||||
|
||||
// TODO: Invalidate only the range that was modified?
|
||||
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
||||
}
|
||||
|
||||
return HvAddressSpace.KernelRegionEretAddress;
|
||||
}
|
||||
|
||||
@@ -305,7 +313,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
if (farValid)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpu, HvSysReg.FAR_EL1, out ulong far).ThrowOnError();
|
||||
|
||||
ulong size = 1UL << accessSizeLog2;
|
||||
|
||||
if (!tracking.VirtualMemoryEvent(far, size, write))
|
||||
@@ -349,9 +356,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private void WriteRt(uint rt, ulong value)
|
||||
{
|
||||
if (rt < 31)
|
||||
{
|
||||
SetX((int)rt, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool(HvVcpu vcpu)
|
||||
@@ -369,8 +374,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
_impl = newContext;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
@@ -14,8 +16,31 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private static readonly SetSimdFpReg _setSimdFpReg;
|
||||
private static readonly nint _setSimdFpRegNativePtr;
|
||||
|
||||
public static bool AggressiveMode { get; set; } = false;
|
||||
private bool _earlyBootPhase = true;
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
private readonly ulong[] _x = new ulong[32];
|
||||
private readonly V128[] _v = new V128[32];
|
||||
|
||||
private ulong _pc;
|
||||
private ulong _elrEl1;
|
||||
private ulong _esrEl1;
|
||||
private ulong _tpidrEl0;
|
||||
private ulong _tpidrroEl0;
|
||||
private ulong _fpcr;
|
||||
private ulong _fpsr;
|
||||
private ulong _pstateRaw;
|
||||
|
||||
private long _fallbackCount;
|
||||
private long _lastWarningTicks;
|
||||
private const long WarningCooldownTicks = 500_000_000; // 0.5 seconds
|
||||
|
||||
private readonly ulong _vcpu;
|
||||
private int _interruptRequested;
|
||||
private readonly object _registerLock = new object();
|
||||
|
||||
static HvExecutionContextVcpu()
|
||||
{
|
||||
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
||||
@@ -33,155 +58,293 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
}
|
||||
}
|
||||
|
||||
public HvExecutionContextVcpu(ulong vcpu)
|
||||
{
|
||||
_vcpu = vcpu;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (_registerLock)
|
||||
{
|
||||
_pstateRaw = 0x80000000UL;
|
||||
_pc = 0;
|
||||
_elrEl1 = 0;
|
||||
_esrEl1 = 0;
|
||||
_tpidrEl0 = 0;
|
||||
_tpidrroEl0 = 0;
|
||||
_fpcr = 0;
|
||||
_fpsr = 0;
|
||||
|
||||
Array.Clear(_x, 0, _x.Length);
|
||||
Array.Clear(_v, 0, _v.Length);
|
||||
|
||||
_fallbackCount = 0;
|
||||
_lastWarningTicks = 0;
|
||||
_interruptRequested = 0;
|
||||
_earlyBootPhase = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void LogHvWarning(string operation, string regName, string extra = "")
|
||||
{
|
||||
if (AggressiveMode) return;
|
||||
|
||||
long now = DateTime.UtcNow.Ticks;
|
||||
if (now - _lastWarningTicks <= WarningCooldownTicks) return;
|
||||
|
||||
string msg = $"[AppleHv] BadArgument on {operation} {regName} | PC=0x{_pc:X16}";
|
||||
if (!string.IsNullOrEmpty(extra)) msg += $" | {extra}";
|
||||
msg += $" | Total: {Interlocked.Read(ref _fallbackCount)}";
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, msg);
|
||||
_lastWarningTicks = now;
|
||||
}
|
||||
|
||||
public ulong Pc
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.PC, out ulong pc).ThrowOnError();
|
||||
return pc;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.PC, value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return GetRegCached(HvReg.PC, ref _pc, "PC"); }
|
||||
set { lock (_registerLock) SetRegCached(HvReg.PC, value, ref _pc, "PC"); }
|
||||
}
|
||||
|
||||
public ulong ElrEl1
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
|
||||
return elr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ELR_EL1, value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return GetSysRegCached(HvSysReg.ELR_EL1, ref _elrEl1, "ELR_EL1"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.ELR_EL1, value, ref _elrEl1, "ELR_EL1"); }
|
||||
}
|
||||
|
||||
public ulong EsrEl1
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
|
||||
return esr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ESR_EL1, value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return GetSysRegCached(HvSysReg.ESR_EL1, ref _esrEl1, "ESR_EL1"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.ESR_EL1, value, ref _esrEl1, "ESR_EL1"); }
|
||||
}
|
||||
|
||||
public long TpidrEl0
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, out ulong tpidrEl0).ThrowOnError();
|
||||
return (long)tpidrEl0;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, (ulong)value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDR_EL0, ref _tpidrEl0, "TPIDR_EL0"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDR_EL0, (ulong)value, ref _tpidrEl0, "TPIDR_EL0"); }
|
||||
}
|
||||
|
||||
public long TpidrroEl0
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, out ulong tpidrroEl0).ThrowOnError();
|
||||
return (long)tpidrroEl0;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, (ulong)value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDRRO_EL0, ref _tpidrroEl0, "TPIDRRO_EL0"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDRRO_EL0, (ulong)value, ref _tpidrroEl0, "TPIDRRO_EL0"); }
|
||||
}
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong cpsr).ThrowOnError();
|
||||
return (uint)cpsr;
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("Get", "CPSR (Pstate)");
|
||||
return (uint)_pstateRaw;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_pstateRaw = val;
|
||||
return (uint)val;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, (ulong)value).ThrowOnError();
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("Set", "CPSR (Pstate)", $"value=0x{value:X}");
|
||||
}
|
||||
else res.ThrowOnError();
|
||||
_pstateRaw = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Fpcr
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPCR, out ulong fpcr).ThrowOnError();
|
||||
return (uint)fpcr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPCR, (ulong)value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPCR, ref _fpcr, "FPCR"); }
|
||||
set { lock (_registerLock) SetRegCached(HvReg.FPCR, value, ref _fpcr, "FPCR"); }
|
||||
}
|
||||
|
||||
public uint Fpsr
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPSR, out ulong fpsr).ThrowOnError();
|
||||
return (uint)fpsr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPSR, (ulong)value).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ulong _vcpu;
|
||||
private int _interruptRequested;
|
||||
|
||||
public HvExecutionContextVcpu(ulong vcpu)
|
||||
{
|
||||
_vcpu = vcpu;
|
||||
get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPSR, ref _fpsr, "FPSR"); }
|
||||
set { lock (_registerLock) SetRegCached(HvReg.FPSR, value, ref _fpsr, "FPSR"); }
|
||||
}
|
||||
|
||||
public ulong GetX(int index)
|
||||
{
|
||||
if (index == 31)
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out ulong value).ThrowOnError();
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out ulong value).ThrowOnError();
|
||||
return value;
|
||||
ulong value;
|
||||
string regName = index == 31 ? "SP_EL0" : $"X{index}";
|
||||
|
||||
if (index == 31)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetX", regName);
|
||||
return _x[31];
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return _x[31] = value;
|
||||
}
|
||||
|
||||
if ((uint)index > 30) return 0;
|
||||
|
||||
if (index == 0 && _earlyBootPhase && _pc == 0)
|
||||
{
|
||||
return _x[0];
|
||||
}
|
||||
|
||||
HvResult resX = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out value);
|
||||
if (resX == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetX", regName);
|
||||
return _x[index];
|
||||
}
|
||||
resX.ThrowOnError();
|
||||
return _x[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetX(int index, ulong value)
|
||||
{
|
||||
if (index == 31)
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value).ThrowOnError();
|
||||
}
|
||||
else
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value).ThrowOnError();
|
||||
string regName = index == 31 ? "SP_EL0" : $"X{index}";
|
||||
|
||||
if (index == 31)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetX", regName, $"value=0x{value:X16}");
|
||||
_x[31] = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_x[31] = value;
|
||||
}
|
||||
else if ((uint)index <= 30)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetX", regName, $"value=0x{value:X16}");
|
||||
_x[index] = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_x[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public V128 GetV(int index)
|
||||
{
|
||||
HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 value).ThrowOnError();
|
||||
return new V128(value.Low, value.High);
|
||||
lock (_registerLock)
|
||||
{
|
||||
if ((uint)index > 31) return default;
|
||||
|
||||
HvResult res = HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetV", $"Q{index}");
|
||||
return _v[index];
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return _v[index] = new V128(val.Low, val.High);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetV(int index, V128 value)
|
||||
{
|
||||
_setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr).ThrowOnError();
|
||||
lock (_registerLock)
|
||||
{
|
||||
if ((uint)index > 31) return;
|
||||
|
||||
HvResult res = _setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetV", $"Q{index}");
|
||||
_v[index] = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_v[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetRegCached(HvReg reg, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, reg, out ulong val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetReg", name);
|
||||
return cached;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return cached = val;
|
||||
}
|
||||
|
||||
private void SetRegCached(HvReg reg, ulong value, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, reg, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetReg", name, $"value=0x{value:X16}");
|
||||
cached = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
cached = value;
|
||||
}
|
||||
|
||||
private ulong GetSysRegCached(HvSysReg reg, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, reg, out ulong val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetSysReg", name);
|
||||
return cached;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return cached = val;
|
||||
}
|
||||
|
||||
private void SetSysRegCached(HvSysReg reg, ulong value, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, reg, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetSysReg", name, $"value=0x{value:X16}");
|
||||
cached = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
cached = value;
|
||||
}
|
||||
|
||||
public long GetFallbackCount() => Interlocked.Read(ref _fallbackCount);
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
|
||||
|
||||
@@ -3,6 +3,7 @@ using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -189,7 +190,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
}
|
||||
else if (inlineLookup)
|
||||
{
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
|
||||
|
||||
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||
|
||||
@@ -203,18 +204,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
// Index into the table.
|
||||
asm.Mov(rn, tableBase);
|
||||
|
||||
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||
{
|
||||
AddressTableLevel level = funcTable.Levels[i];
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
AddressTableLevel level = funcTable.Levels.Last();
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
}
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
|
||||
if (tempGuestAddress != -1)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -355,7 +356,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
}
|
||||
else if (inlineLookup)
|
||||
{
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
|
||||
|
||||
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||
|
||||
@@ -369,18 +370,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
// Index into the table.
|
||||
asm.Mov(rn, tableBase);
|
||||
|
||||
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||
{
|
||||
AddressTableLevel level = funcTable.Levels[i];
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
AddressTableLevel level = funcTable.Levels.Last();
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
}
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
|
||||
if (tempGuestAddress != -1)
|
||||
{
|
||||
|
||||
@@ -23,12 +23,31 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
return Offset.CompareTo(other.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int _regionSize;
|
||||
private int _regionCount;
|
||||
|
||||
private readonly List<MemoryBlock> _blocks = [];
|
||||
|
||||
public CacheMemoryAllocator(int capacity)
|
||||
public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
|
||||
{
|
||||
_blocks.Add(new MemoryBlock(0, capacity));
|
||||
_regionCount = 0;
|
||||
_regionSize = regionSize;
|
||||
|
||||
for (; initialRegionCount > 0; initialRegionCount--)
|
||||
{
|
||||
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
|
||||
_regionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewBlocks(int count)
|
||||
{
|
||||
for (; count > 0; count--)
|
||||
{
|
||||
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
|
||||
_regionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public int Allocate(int size)
|
||||
@@ -100,13 +119,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
int endOffs = block.Offset + block.Size;
|
||||
|
||||
if (index < _blocks.Count)
|
||||
// Don't merge blocks from different allocations
|
||||
if (index < _blocks.Count && endOffs % _regionSize != 0)
|
||||
{
|
||||
MemoryBlock next = _blocks[index];
|
||||
|
||||
int endOffs = block.Offset + block.Size;
|
||||
|
||||
if (next.Offset == endOffs)
|
||||
{
|
||||
block = new MemoryBlock(block.Offset, block.Size + next.Size);
|
||||
@@ -114,7 +134,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
}
|
||||
|
||||
if (index > 0)
|
||||
// Don't merge blocks from different allocations
|
||||
if (index > 0 && block.Offset % _regionSize != 0)
|
||||
{
|
||||
MemoryBlock prev = _blocks[index - 1];
|
||||
|
||||
|
||||
@@ -11,76 +11,45 @@ using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
static partial class JitCache
|
||||
partial class JitCache : IDisposable
|
||||
{
|
||||
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
// TODO: JIT Cache size should be application dependent, not global.
|
||||
private const int CacheSize = 1024 * (1024 * 1024); // Megabytes * Size of Megabytes (since its in bytes).
|
||||
private const uint CacheSize = 256 * 1024 * 1024; // Megabytes * Size of Megabytes (since its in bytes).
|
||||
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
private readonly JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
private readonly CacheMemoryAllocator _cacheAllocator;
|
||||
|
||||
private static readonly List<CacheEntry> _cacheEntries = [];
|
||||
private readonly List<CacheEntry> _cacheEntries = [];
|
||||
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
private static readonly List<ReservedRegion> _jitRegions = [];
|
||||
private static int _activeRegionIndex = 0;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
[LibraryImport("libSystem.dylib", EntryPoint = "sys_icache_invalidate")]
|
||||
internal static partial void SysICacheInvalidate(nint start, nuint len);
|
||||
private readonly List<ReservedRegion> _jitRegions = [];
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[LibraryImport("libgcc_s.so.1", EntryPoint = "__clear_cache")]
|
||||
internal static partial void ClearCache(nint begin, nint end);
|
||||
|
||||
public static void Initialize(IJitMemoryAllocator allocator)
|
||||
public JitCache(IJitMemoryAllocator allocator)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
_activeRegionIndex = 0;
|
||||
|
||||
_jitRegions.Add(new(allocator, CacheSize));
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator((int)CacheSize);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||
}
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static nint Map(ReadOnlySpan<byte> code)
|
||||
public nint Map(ReadOnlySpan<byte> code)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||
nint funcPtr = GetFunctionPtr(funcOffset);
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -94,9 +63,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
|
||||
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||
}
|
||||
@@ -107,21 +76,20 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unmap(nint pointer)
|
||||
public void Unmap(nint pointer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
foreach (ReservedRegion region in _jitRegions)
|
||||
for (int i = 0; i < _jitRegions.Count; i++)
|
||||
{
|
||||
ReservedRegion region = _jitRegions[i];
|
||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||
pointer.ToInt64() >= (region.Pointer + (nint)CacheSize).ToInt64())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64() + i * CacheSize);
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
@@ -134,59 +102,63 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
private void ReprotectAsWritable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
|
||||
int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
private void ReprotectAsExecutable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
|
||||
int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
private int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
_activeRegionIndex = i;
|
||||
return allocOffset;
|
||||
}
|
||||
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
|
||||
_cacheAllocator.AddNewBlocks(1);
|
||||
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
int newRegionNumber = _activeRegionIndex;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
|
||||
if (allocOffsetNew < 0)
|
||||
allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
if (allocOffset < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||
}
|
||||
|
||||
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||
return allocOffsetNew;
|
||||
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
private nint GetFunctionPtr(int offset)
|
||||
{
|
||||
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
|
||||
}
|
||||
|
||||
private ReservedRegion GetRegion(int offset)
|
||||
{
|
||||
int index = offset / (int)CacheSize;
|
||||
|
||||
return _jitRegions[index];
|
||||
}
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
@@ -194,7 +166,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
||||
}
|
||||
|
||||
private static void Add(int offset, int size)
|
||||
private void Add(int offset, int size)
|
||||
{
|
||||
CacheEntry entry = new(offset, size);
|
||||
|
||||
@@ -208,7 +180,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
_cacheEntries.Insert(index, entry);
|
||||
}
|
||||
|
||||
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
|
||||
public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -231,5 +203,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
entryIndex = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (ReservedRegion jitRegion in _jitRegions)
|
||||
{
|
||||
jitRegion.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
private static bool IsNoWxPlatform => false;
|
||||
|
||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||
private readonly JitCache _jitCache;
|
||||
private readonly NoWxCache _noWxCache;
|
||||
private bool _disposed;
|
||||
|
||||
@@ -39,12 +40,12 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
}
|
||||
else
|
||||
{
|
||||
JitCache.Initialize(new JitMemoryAllocator(forJit: true));
|
||||
_jitCache = new(new JitMemoryAllocator(forJit: true));
|
||||
}
|
||||
|
||||
Functions = new TranslatorCache<TranslatedFunction>();
|
||||
FunctionTable = functionTable;
|
||||
Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
|
||||
Stubs = new TranslatorStubs(_jitCache, FunctionTable, _noWxCache);
|
||||
|
||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
|
||||
@@ -100,7 +101,7 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
|
||||
if (oldFunc != func)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPointer);
|
||||
_jitCache.Unmap(func.FuncPointer);
|
||||
func = oldFunc;
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
private TranslatedFunction Translate(ulong address, ExecutionMode mode)
|
||||
{
|
||||
CompiledFunction func = Compile(address, mode);
|
||||
nint funcPointer = JitCache.Map(func.Code);
|
||||
nint funcPointer = _jitCache.Map(func.Code);
|
||||
|
||||
return new TranslatedFunction(funcPointer, (ulong)func.GuestCodeLength);
|
||||
}
|
||||
@@ -164,14 +165,14 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
|
||||
foreach (TranslatedFunction func in functions)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPointer);
|
||||
_jitCache.Unmap(func.FuncPointer);
|
||||
}
|
||||
|
||||
Functions.Clear();
|
||||
|
||||
while (_oldFuncs.TryDequeue(out KeyValuePair<ulong, TranslatedFunction> kv))
|
||||
{
|
||||
JitCache.Unmap(kv.Value.FuncPointer);
|
||||
_jitCache.Unmap(kv.Value.FuncPointer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +189,7 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
else
|
||||
{
|
||||
ClearJitCache();
|
||||
_jitCache.Dispose();
|
||||
}
|
||||
|
||||
Stubs.Dispose();
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
{
|
||||
private delegate ulong GetFunctionAddressDelegate(nint framePointer, ulong address);
|
||||
|
||||
private readonly JitCache _jitCache;
|
||||
|
||||
private readonly Lazy<nint> _slowDispatchStub;
|
||||
|
||||
private bool _disposed;
|
||||
@@ -76,13 +78,16 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||
/// <see cref="Translator"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="jitCache">Jit cache to map functions in</param>
|
||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||
/// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||
public TranslatorStubs(IAddressTable<ulong> functionTable, NoWxCache noWxCache)
|
||||
public TranslatorStubs(JitCache jitCache, IAddressTable<ulong> functionTable, NoWxCache noWxCache)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(functionTable);
|
||||
|
||||
_jitCache = jitCache;
|
||||
|
||||
_functionTable = functionTable;
|
||||
_noWxCache = noWxCache;
|
||||
_getFunctionAddressRef = NativeInterface.GetFunctionAddress;
|
||||
@@ -113,12 +118,12 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
{
|
||||
if (_dispatchStub.IsValueCreated)
|
||||
{
|
||||
JitCache.Unmap(_dispatchStub.Value);
|
||||
_jitCache.Unmap(_dispatchStub.Value);
|
||||
}
|
||||
|
||||
if (_dispatchLoop.IsValueCreated)
|
||||
{
|
||||
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
|
||||
_jitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +368,7 @@ namespace Ryujinx.Cpu.LightningJit
|
||||
}
|
||||
else
|
||||
{
|
||||
return JitCache.Map(code);
|
||||
return _jitCache.Map(code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Ryujinx.Cpu.Signal
|
||||
public SignalHandlerRangeArray Ranges;
|
||||
}
|
||||
|
||||
static class NativeSignalHandler
|
||||
public static class NativeSignalHandler
|
||||
{
|
||||
private static readonly nint _handlerConfig;
|
||||
private static nint _signalHandlerPtr;
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsShaderBallot;
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
public readonly bool SupportsShaderNonUniformIndexing;
|
||||
public readonly bool SupportsTextureGatherOffsets;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsVertexStoreAndAtomics;
|
||||
@@ -110,6 +111,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsShaderBallot,
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsShaderNonUniformIndexing,
|
||||
bool supportsTextureGatherOffsets,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsVertexStoreAndAtomics,
|
||||
@@ -172,6 +174,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsShaderBallot = supportsShaderBallot;
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
|
||||
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
||||
|
||||
@@ -49,12 +49,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private const int MinCountForDeletion = 32;
|
||||
private const int MaxCapacity = 2048;
|
||||
private const ulong GiB = 1024 * 1024 * 1024;
|
||||
private ulong MaxTextureSizeCapacity = 4UL * GiB;
|
||||
private ulong MaxTextureSizeCapacity = 2 * GiB;
|
||||
private const ulong MinTextureSizeCapacity = 512 * 1024 * 1024;
|
||||
private const ulong DefaultTextureSizeCapacity = 1 * GiB;
|
||||
private const ulong TextureSizeCapacity6GiB = 4 * GiB;
|
||||
private const ulong TextureSizeCapacity8GiB = 6 * GiB;
|
||||
private const ulong TextureSizeCapacity12GiB = 12 * GiB;
|
||||
|
||||
private const float MemoryScaleFactor = 0.50f;
|
||||
private ulong _maxCacheMemoryUsage = DefaultTextureSizeCapacity;
|
||||
@@ -73,31 +70,22 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <remarks>
|
||||
/// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`.
|
||||
///
|
||||
/// Reads the current Device total CPU Memory, to determine the maximum amount of Vram available. Capped to 50% of Current GPU Memory.
|
||||
/// Reads the current Device total CPU Memory, to determine the maximum amount of VRAM available. Capped to 50% of Current GPU Memory.
|
||||
/// </remarks>
|
||||
/// <param name="context">The GPU context that the cache belongs to</param>
|
||||
/// <param name="cpuMemorySize">The amount of physical CPU Memory Avaiable on the device.</param>
|
||||
/// <param name="cpuMemorySize">The amount of physical CPU Memory available on the device.</param>
|
||||
public void Initialize(GpuContext context, ulong cpuMemorySize)
|
||||
{
|
||||
ulong cpuMemorySizeGiB = cpuMemorySize / GiB;
|
||||
ulong MaximumGpuMemoryGiB = context.Capabilities.MaximumGpuMemory / GiB;
|
||||
ulong TextureSizeCapacity = cpuMemorySize - (2 * GiB);
|
||||
|
||||
if (cpuMemorySizeGiB < 6 || context.Capabilities.MaximumGpuMemory == 0)
|
||||
{
|
||||
_maxCacheMemoryUsage = DefaultTextureSizeCapacity;
|
||||
return;
|
||||
}
|
||||
else if (cpuMemorySizeGiB == 6)
|
||||
{
|
||||
MaxTextureSizeCapacity = TextureSizeCapacity6GiB;
|
||||
}
|
||||
else if (cpuMemorySizeGiB == 8)
|
||||
{
|
||||
MaxTextureSizeCapacity = TextureSizeCapacity8GiB;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxTextureSizeCapacity = TextureSizeCapacity12GiB;
|
||||
}
|
||||
MaxTextureSizeCapacity =
|
||||
context.Capabilities.MaximumGpuMemory == 0 || cpuMemorySizeGiB < 6 && MaximumGpuMemoryGiB < 6
|
||||
? DefaultTextureSizeCapacity
|
||||
: cpuMemorySizeGiB < 12
|
||||
? TextureSizeCapacity
|
||||
: cpuMemorySize;
|
||||
|
||||
ulong cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 7353;
|
||||
private const uint CodeGenVersion = 7354;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
||||
@@ -231,6 +231,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64;
|
||||
|
||||
public bool QueryHostSupportsShaderNonUniformIndexing() => _context.Capabilities.SupportsShaderNonUniformIndexing;
|
||||
|
||||
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
|
||||
|
||||
public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets;
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
public bool IsHardwareAccelerated => false;
|
||||
|
||||
private readonly MemoryAllocator _allocator = new();
|
||||
private byte[] _lastSegmentMap = [];
|
||||
private int _lastSegmentMapMiRows;
|
||||
private int _lastSegmentMapMiCols;
|
||||
|
||||
public ISurface CreateSurface(int width, int height)
|
||||
{
|
||||
@@ -106,7 +109,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
|
||||
cm.AllocTileWorkerData(_allocator, tileCols, tileRows, maxThreads);
|
||||
cm.AllocContextBuffers(_allocator, output.Width, output.Height);
|
||||
|
||||
cm.InitContextBuffers();
|
||||
LoadSegmentMap(ref cm, ref pictureInfo);
|
||||
cm.SetupSegmentationDequant();
|
||||
cm.SetupScaleFactors();
|
||||
|
||||
@@ -153,6 +158,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
}
|
||||
}
|
||||
|
||||
SaveSegmentMap(ref cm);
|
||||
cm.GetMvs(mvsOut);
|
||||
|
||||
cm.FreeTileWorkerData(_allocator);
|
||||
@@ -161,6 +167,52 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LoadSegmentMap(ref Vp9Common cm, ref Vp9PictureInfo pictureInfo)
|
||||
{
|
||||
int mapLength = cm.MiRows * cm.MiCols;
|
||||
if (mapLength == 0 || cm.LastFrameSegMap.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool reset =
|
||||
pictureInfo.IsKeyFrame ||
|
||||
pictureInfo.IntraOnly ||
|
||||
_lastSegmentMapMiRows != cm.MiRows ||
|
||||
_lastSegmentMapMiCols != cm.MiCols;
|
||||
|
||||
if (_lastSegmentMap.Length != mapLength)
|
||||
{
|
||||
_lastSegmentMap = new byte[mapLength];
|
||||
}
|
||||
else if (reset)
|
||||
{
|
||||
Array.Clear(_lastSegmentMap);
|
||||
}
|
||||
|
||||
_lastSegmentMapMiRows = cm.MiRows;
|
||||
_lastSegmentMapMiCols = cm.MiCols;
|
||||
_lastSegmentMap.CopyTo(cm.LastFrameSegMap.AsSpan());
|
||||
}
|
||||
|
||||
private void SaveSegmentMap(ref Vp9Common cm)
|
||||
{
|
||||
int mapLength = cm.MiRows * cm.MiCols;
|
||||
if (!cm.Seg.Enabled || mapLength == 0 || cm.CurrentFrameSegMap.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastSegmentMap.Length != mapLength)
|
||||
{
|
||||
_lastSegmentMap = new byte[mapLength];
|
||||
}
|
||||
|
||||
cm.CurrentFrameSegMap.AsSpan()[..mapLength].CopyTo(_lastSegmentMap);
|
||||
_lastSegmentMapMiRows = cm.MiRows;
|
||||
_lastSegmentMapMiCols = cm.MiCols;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_allocator.Dispose();
|
||||
|
||||
@@ -1088,6 +1088,12 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
|
||||
|
||||
public void GetMvs(Span<Vp9MvRef> mvs)
|
||||
{
|
||||
if (FrameIsIntraOnly())
|
||||
{
|
||||
mvs.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mvs.Length > CurFrameMvs.Length)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Nvdec.Types.Vp9
|
||||
IsKeyFrame = Flags.HasFlag(FrameFlags.IsKeyFrame),
|
||||
IntraOnly = Flags.HasFlag(FrameFlags.IntraOnly),
|
||||
UsePrevInFindMvRefs =
|
||||
!Flags.HasFlag(FrameFlags.IsKeyFrame) &&
|
||||
!Flags.HasFlag(FrameFlags.ErrorResilientMode) &&
|
||||
!Flags.HasFlag(FrameFlags.FrameSizeChanged) &&
|
||||
!Flags.HasFlag(FrameFlags.IntraOnly) &&
|
||||
|
||||
@@ -184,6 +184,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
|
||||
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||
supportsShaderFloat64: true,
|
||||
supportsShaderNonUniformIndexing: false,
|
||||
supportsTextureGatherOffsets: true,
|
||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||
supportsVertexStoreAndAtomics: true,
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
public bool IsMainFunction { get; private set; }
|
||||
public bool MayHaveReturned { get; set; }
|
||||
public bool WasNonUniformAccessDeclared { get; set; }
|
||||
|
||||
public CodeGenContext(
|
||||
StructuredProgramInfo info,
|
||||
@@ -89,6 +90,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
GeneratorPool<Instruction> instPool,
|
||||
GeneratorPool<LiteralInteger> integerPool) : base(SpirvVersionPacked, instPool, integerPool)
|
||||
{
|
||||
WasNonUniformAccessDeclared = false;
|
||||
|
||||
Info = info;
|
||||
AttributeUsage = parameters.AttributeUsage;
|
||||
Definitions = parameters.Definitions;
|
||||
|
||||
@@ -587,6 +587,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
return OperationResult.Invalid;
|
||||
}
|
||||
|
||||
private static void MarkNonUniform(CodeGenContext context, SpvInstruction inst)
|
||||
{
|
||||
if (context.HostCapabilities.SupportsShaderNonUniformIndexing)
|
||||
{
|
||||
if (!context.WasNonUniformAccessDeclared)
|
||||
{
|
||||
context.AddExtension("SPV_EXT_descriptor_indexing");
|
||||
context.AddCapability(Capability.ShaderNonUniform);
|
||||
context.AddCapability(Capability.SampledImageArrayNonUniformIndexing);
|
||||
context.AddCapability(Capability.StorageImageArrayNonUniformIndexing);
|
||||
}
|
||||
|
||||
context.Decorate(inst, Decoration.NonUniform);
|
||||
context.WasNonUniformAccessDeclared = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static OperationResult GenerateImageAtomic(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||
@@ -613,6 +630,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
SpvInstruction textureIndex = Src(AggregateType.S32);
|
||||
|
||||
image = context.AccessChain(imagePointerType, image, textureIndex);
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
int coordsCount = texOp.Type.Dimensions;
|
||||
@@ -683,15 +701,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
|
||||
SpvInstruction image = declaration.Image;
|
||||
bool isIndexed = declaration.IsIndexed;
|
||||
|
||||
if (declaration.IsIndexed)
|
||||
if (isIndexed)
|
||||
{
|
||||
SpvInstruction textureIndex = Src(AggregateType.S32);
|
||||
|
||||
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
image = context.Load(declaration.ImageType, image);
|
||||
if (isIndexed)
|
||||
{
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
int coordsCount = texOp.Type.Dimensions;
|
||||
|
||||
@@ -740,15 +764,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
|
||||
SpvInstruction image = declaration.Image;
|
||||
bool isIndexed = declaration.IsIndexed;
|
||||
|
||||
if (declaration.IsIndexed)
|
||||
if (isIndexed)
|
||||
{
|
||||
SpvInstruction textureIndex = Src(AggregateType.S32);
|
||||
|
||||
image = context.AccessChain(declaration.ImagePointerType, image, textureIndex);
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
image = context.Load(declaration.ImageType, image);
|
||||
if (isIndexed)
|
||||
{
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
int coordsCount = texOp.Type.Dimensions;
|
||||
|
||||
@@ -1878,35 +1908,56 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex)
|
||||
{
|
||||
SpvInstruction image = declaration.Image;
|
||||
bool imageIndexed = declaration.IsIndexed;
|
||||
|
||||
if (declaration.IsIndexed)
|
||||
if (imageIndexed)
|
||||
{
|
||||
SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
|
||||
|
||||
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
if (texOp.IsSeparate)
|
||||
{
|
||||
image = context.Load(declaration.ImageType, image);
|
||||
if (imageIndexed)
|
||||
{
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
|
||||
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()];
|
||||
|
||||
SpvInstruction sampler = samplerDeclaration.Image;
|
||||
bool samplerIndexed = samplerDeclaration.IsIndexed;
|
||||
|
||||
if (samplerDeclaration.IsIndexed)
|
||||
if (samplerIndexed)
|
||||
{
|
||||
SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
|
||||
|
||||
sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex);
|
||||
MarkNonUniform(context, sampler);
|
||||
}
|
||||
|
||||
sampler = context.Load(samplerDeclaration.ImageType, sampler);
|
||||
if (samplerIndexed)
|
||||
{
|
||||
MarkNonUniform(context, sampler);
|
||||
}
|
||||
|
||||
image = context.SampledImage(declaration.SampledImageType, image, sampler);
|
||||
if (imageIndexed || samplerIndexed)
|
||||
{
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
image = context.Load(declaration.SampledImageType, image);
|
||||
if (imageIndexed)
|
||||
{
|
||||
MarkNonUniform(context, image);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
|
||||
@@ -336,6 +336,10 @@ namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool QueryHostSupportsShaderNonUniformIndexing()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU support for signed normalized buffer texture formats.
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public readonly bool SupportsShaderBallot;
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
public readonly bool SupportsShaderNonUniformIndexing;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsViewportMask;
|
||||
|
||||
@@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
bool supportsShaderBallot,
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsShaderNonUniformIndexing,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsViewportMask)
|
||||
{
|
||||
@@ -30,6 +32,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
SupportsShaderBallot = supportsShaderBallot;
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsViewportMask = supportsViewportMask;
|
||||
}
|
||||
|
||||
@@ -364,6 +364,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
GpuAccessor.QueryHostSupportsShaderBallot(),
|
||||
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
|
||||
GpuAccessor.QueryHostSupportsShaderFloat64(),
|
||||
GpuAccessor.QueryHostSupportsShaderNonUniformIndexing(),
|
||||
GpuAccessor.QueryHostSupportsTextureShadowLod(),
|
||||
GpuAccessor.QueryHostSupportsViewportMask());
|
||||
|
||||
|
||||
@@ -46,7 +46,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
|
||||
{
|
||||
AccessFlags access = BufferAccess;
|
||||
AccessFlags access = BufferAccess |
|
||||
AccessFlags.ShaderReadBit |
|
||||
AccessFlags.ShaderWriteBit |
|
||||
AccessFlags.ColorAttachmentReadBit |
|
||||
AccessFlags.ColorAttachmentWriteBit |
|
||||
AccessFlags.DepthStencilAttachmentReadBit |
|
||||
AccessFlags.DepthStencilAttachmentWriteBit;
|
||||
|
||||
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
|
||||
|
||||
if (gd.TransformFeedbackApi != null)
|
||||
|
||||
@@ -42,10 +42,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
|
||||
ResourceStages stages = descriptor.Stages;
|
||||
|
||||
if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
|
||||
if (descriptor.Type == ResourceType.StorageBuffer && gd.IsMoltenVk)
|
||||
{
|
||||
// There's a bug on MoltenVK where using the same buffer across different stages
|
||||
// There's a bug in MoltenVK where using the same buffer across different stages
|
||||
// causes invalid resource errors, allow the binding on all active stages as workaround.
|
||||
// https://github.com/KhronosGroup/MoltenVK/issues/1870
|
||||
stages = activeStages;
|
||||
}
|
||||
|
||||
|
||||
@@ -588,8 +588,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
dynamicStates[5] = DynamicState.StencilReference;
|
||||
dynamicStates[6] = DynamicState.BlendConstants;
|
||||
|
||||
if (supportsExtDynamicState)
|
||||
if (supportsExtDynamicState && (gd.SupportsMTL31 || !gd.IsMoltenVk))
|
||||
{
|
||||
// Requires Metal 3.1 and new MoltenVK, however extended dynamic states extension is not
|
||||
// available on older versions of MVK, so we can safely check only OS version.
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" />
|
||||
<PackageReference Include="shaderc.net" />
|
||||
<PackageReference Include="Silk.NET.Shaderc" ExcludeAssets="native" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using shaderc;
|
||||
using Silk.NET.Shaderc;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Result = shaderc.Result;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class Shader : IDisposable
|
||||
{
|
||||
// The shaderc.net dependency's Options constructor and dispose are not thread safe.
|
||||
// Take this lock when using them.
|
||||
private static readonly Lock _shaderOptionsLock = new();
|
||||
|
||||
private static readonly nint _ptrMainEntryPointName = Marshal.StringToHGlobalAnsi("main");
|
||||
|
||||
private readonly Vk _api;
|
||||
@@ -75,38 +70,33 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private unsafe static byte[] GlslToSpirv(string glsl, ShaderStage stage)
|
||||
{
|
||||
Options options;
|
||||
Shaderc api = Shaderc.GetApi();
|
||||
Compiler* compiler = api.CompilerInitialize();
|
||||
CompileOptions* options = api.CompileOptionsInitialize();
|
||||
|
||||
lock (_shaderOptionsLock)
|
||||
api.CompileOptionsSetSourceLanguage(options, SourceLanguage.Glsl);
|
||||
api.CompileOptionsSetTargetSpirv(options, SpirvVersion.Shaderc15);
|
||||
api.CompileOptionsSetTargetEnv(options, TargetEnv.Vulkan, Vk.Version12);
|
||||
|
||||
CompilationResult* scr = api.CompileIntoSpv(compiler, glsl, (nuint)glsl.Length, GetShaderCShaderStage(stage), "Ryu", "main", options);
|
||||
|
||||
CompilationStatus status = api.ResultGetCompilationStatus(scr);
|
||||
|
||||
if (status != CompilationStatus.Success)
|
||||
{
|
||||
options = new Options(false)
|
||||
{
|
||||
SourceLanguage = SourceLanguage.Glsl,
|
||||
TargetSpirVVersion = new SpirVVersion(1, 5),
|
||||
};
|
||||
}
|
||||
|
||||
options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2);
|
||||
Compiler compiler = new(options);
|
||||
Result scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage));
|
||||
|
||||
lock (_shaderOptionsLock)
|
||||
{
|
||||
options.Dispose();
|
||||
}
|
||||
|
||||
if (scr.Status != Status.Success)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}");
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {status} {api.ResultGetErrorMessageS(scr)}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Span<byte> spirvBytes = new((void*)scr.CodePointer, (int)scr.CodeLength);
|
||||
Span<byte> spirvBytes = new(api.ResultGetBytes(scr), (int)api.ResultGetLength(scr));
|
||||
|
||||
byte[] code = new byte[(scr.CodeLength + 3) & ~3];
|
||||
byte[] code = new byte[(spirvBytes.Length + 3) & ~3];
|
||||
|
||||
spirvBytes.CopyTo(code.AsSpan()[..(int)scr.CodeLength]);
|
||||
spirvBytes.CopyTo(code.AsSpan()[..spirvBytes.Length]);
|
||||
|
||||
api.CompilerRelease(compiler);
|
||||
api.CompileOptionsRelease(options);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user