mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-12 07:19:14 +00:00
Compare commits
109 Commits
Canary-1.3
...
ecd503cf20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd503cf20 | ||
|
|
36d9466427 | ||
|
|
6282db2cb0 | ||
|
|
6ea4ce3404 | ||
|
|
72c88b9b50 | ||
|
|
0baf3b84ed | ||
|
|
1552d31e01 | ||
|
|
5d0fc6d456 | ||
|
|
5993c78344 | ||
|
|
e1b01f2e70 | ||
|
|
e3bdda7afa | ||
|
|
0d5503c014 | ||
|
|
b26375cc4b | ||
|
|
85dad79581 | ||
|
|
2941951f4d | ||
|
|
925ae1652b | ||
|
|
af59454e19 | ||
|
|
beb7dfe7a6 | ||
|
|
85e62c3ad2 | ||
|
|
082a2a2051 | ||
|
|
2b2159f330 | ||
|
|
3620c76cc1 | ||
|
|
5a8f6fa46d | ||
|
|
813057acbf | ||
|
|
620eba5fcb | ||
|
|
829a5561ab | ||
|
|
0aec32f437 | ||
|
|
6619de59ab | ||
|
|
746dc2cd22 | ||
|
|
17ab09119e | ||
|
|
46bae1c40a | ||
|
|
5536bba1fa | ||
|
|
8c6b642129 | ||
|
|
76dd747811 | ||
|
|
1c073ebc63 | ||
|
|
cebe423df2 | ||
|
|
93f53b24b8 | ||
|
|
cb5c9ce585 | ||
|
|
167b41354b | ||
|
|
e52fa0b9d1 | ||
|
|
6ed92dd9b7 | ||
|
|
d9846faa5f | ||
|
|
928a189d99 | ||
|
|
de11115971 | ||
|
|
c093b34767 | ||
|
|
13036dcd5b | ||
|
|
51bec1f4a2 | ||
|
|
00cb9e42f8 | ||
|
|
5be56d0ccf | ||
|
|
a9bb932491 | ||
|
|
e1f215de46 | ||
|
|
b6eb78598c | ||
|
|
1392fcfbc5 | ||
|
|
9b82e8452f | ||
|
|
379ce9e7aa | ||
|
|
fc89c17037 | ||
|
|
222db1a736 | ||
|
|
89c6c490a3 | ||
|
|
4d3a98e71d | ||
|
|
18233cf7e6 | ||
|
|
c0cc54cc56 | ||
|
|
d9ab68b1e9 | ||
|
|
e7e0d4d877 | ||
|
|
a2fa346cfd | ||
|
|
6ae279300c | ||
|
|
82e392604d | ||
|
|
860112c910 | ||
|
|
0ecef83316 | ||
|
|
93256afd24 | ||
|
|
5d3f22ac57 | ||
|
|
5b63aabe8b | ||
|
|
9b6dfab66e | ||
|
|
9e1ee169d9 | ||
|
|
0958796a29 | ||
|
|
f257481cdb | ||
|
|
6533270499 | ||
|
|
c3c6f36fea | ||
|
|
df153efadf | ||
|
|
4b2362f18b | ||
|
|
c2fc1a8582 | ||
|
|
598d6076ee | ||
|
|
69e2ea2894 | ||
|
|
93fe2d36aa | ||
|
|
0d2f280303 | ||
|
|
24ac55f4d6 | ||
|
|
af5d9a90b7 | ||
|
|
d52415b535 | ||
|
|
4d56f4dcd3 | ||
|
|
57c91089f7 | ||
|
|
15c9d50815 | ||
|
|
4012fecc25 | ||
|
|
80df6e2336 | ||
|
|
9e2837d885 | ||
|
|
96028daff1 | ||
|
|
46fa8c1426 | ||
|
|
35aacdb289 | ||
|
|
7a2802d870 | ||
|
|
8548d35620 | ||
|
|
07ef8e9c9a | ||
|
|
5963b425d1 | ||
|
|
7f5a67434e | ||
|
|
3abfeebd58 | ||
|
|
8b438c69db | ||
|
|
1d86653c9d | ||
|
|
79f3ea5cfa | ||
|
|
ba656e560b | ||
|
|
5a6d476490 | ||
|
|
16c35344d8 | ||
|
|
9f2ab7aa8f |
@@ -3,13 +3,13 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
|
||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
@@ -22,7 +22,7 @@
|
||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageVersion Include="DynamicData" Version="9.4.1" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" />
|
||||
<PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" />
|
||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
@@ -41,7 +41,7 @@
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.126" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
||||
<PackageVersion Include="Gommon" Version="2.8.0.1" />
|
||||
@@ -56,6 +56,7 @@
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.2" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
854
assets/Locales/UserProfiles.json
Normal file
854
assets/Locales/UserProfiles.json
Normal file
@@ -0,0 +1,854 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "MenuBarOptions_OpenUserProfiles",
|
||||
"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 utent",
|
||||
"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": "WindowTitle",
|
||||
"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 utent",
|
||||
"ja_JP": "ユーザプロファイル",
|
||||
"ko_KR": "사용자 프로필",
|
||||
"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": "用户配置文件",
|
||||
"zh_TW": "使用者設定檔"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ManageSaves",
|
||||
"Translations": {
|
||||
"ar_SA": "عمليات الحفظ",
|
||||
"de_DE": "Speicherstände",
|
||||
"el_GR": "Αποθηκεύσεις",
|
||||
"en_US": "Saves",
|
||||
"es_ES": "Partidas",
|
||||
"fr_FR": "Sauvegardes",
|
||||
"he_IL": "שמירות",
|
||||
"it_IT": "Salvataggi",
|
||||
"ja_JP": "セーブデータ",
|
||||
"ko_KR": "저장",
|
||||
"no_NO": "Lagringer",
|
||||
"pl_PL": "Zapisy",
|
||||
"pt_BR": "Salvamentos",
|
||||
"ru_RU": "Сохранения",
|
||||
"sv_SE": "Sparningar",
|
||||
"th_TH": "บันทึก",
|
||||
"tr_TR": "Kayıtlar",
|
||||
"uk_UA": "Збереження",
|
||||
"zh_CN": "存档",
|
||||
"zh_TW": "存檔"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DeleteSaveNote",
|
||||
"Translations": {
|
||||
"ar_SA": "هل حذف بيانات حفظ المستخدم لهذه اللعبة؟",
|
||||
"de_DE": "Löschen Sie die gespeicherten Spielstände dieses Spiels?",
|
||||
"el_GR": "Διαγραφή των δεδομένων αποθήκευσης αυτού του παιχνιδιού;",
|
||||
"en_US": "Delete this game's save data?",
|
||||
"es_ES": "¿Eliminar los datos de guardado de este juego?",
|
||||
"fr_FR": "Supprimer les données de sauvegarde de ce jeu ?",
|
||||
"he_IL": "האם למחוק את נתוני השמירה של המשחק הזה?",
|
||||
"it_IT": "Eliminare i dati di salvataggio di questo gioco?",
|
||||
"ja_JP": "このゲームのセーブデータを削除しますか?",
|
||||
"ko_KR": "이 게임의 저장 데이터를 삭제하시겠습니까?",
|
||||
"no_NO": "Slette lagrede data for dette spillet?",
|
||||
"pl_PL": "Usunąć dane zapisu dla tej gry?",
|
||||
"pt_BR": "Excluir os dados salvos deste jogo?",
|
||||
"ru_RU": "Удалить данные сохранений для этой игры?",
|
||||
"sv_SE": "Ta bort sparad data för detta spel?",
|
||||
"th_TH": "ลบข้อมูลบันทึกของเกมนี้หรือไม่?",
|
||||
"tr_TR": "Bu oyun için kaydedilen veriyi silmek?",
|
||||
"uk_UA": "Видалити збереження даних для цієї гри?",
|
||||
"zh_CN": "删除此游戏的存档数据?",
|
||||
"zh_TW": "刪除此遊戲的存檔資料?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SaveManagerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "حفظات {0}",
|
||||
"de_DE": "{0}s Speicherstände",
|
||||
"el_GR": "Αποθηκεύσεις του {0}",
|
||||
"en_US": "{0}'s Saves",
|
||||
"es_ES": "Guardados de {0}",
|
||||
"fr_FR": "Sauvegardes de {0}",
|
||||
"he_IL": "שמירות של {0}",
|
||||
"it_IT": "Salvataggi di {0}",
|
||||
"ja_JP": "{0} のセーブデータ",
|
||||
"ko_KR": "{0} 의 저장",
|
||||
"no_NO": "Lagringer til {0}",
|
||||
"pl_PL": "Zapisy {0}",
|
||||
"pt_BR": "Salvamentos de {0}",
|
||||
"ru_RU": "Сохранения {0}",
|
||||
"sv_SE": "{0}s Sparningar",
|
||||
"th_TH": "ข้อมูลที่บันทึกไว้ของ {0}",
|
||||
"tr_TR": "{0}’nin Kayıtları",
|
||||
"uk_UA": "Збереження {0}",
|
||||
"zh_CN": "{0} 的存档",
|
||||
"zh_TW": "{0} 的存檔"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RecoverLostProfiles",
|
||||
"Translations": {
|
||||
"ar_SA": "الملفات الشخصية المفقودة",
|
||||
"de_DE": "Verlorene Profile",
|
||||
"el_GR": "Χαμένα προφίλ",
|
||||
"en_US": "Lost Profiles",
|
||||
"es_ES": "Perfiles Perdidos",
|
||||
"fr_FR": "Profils Perdus",
|
||||
"he_IL": "פרופילים אבודים",
|
||||
"it_IT": "Profili persi",
|
||||
"ja_JP": "失われたプロフィール",
|
||||
"ko_KR": "분실된 프로필",
|
||||
"no_NO": "Tapte profiler",
|
||||
"pl_PL": "Utracone profile",
|
||||
"pt_BR": "Perfis perdidos",
|
||||
"ru_RU": "Потерянные учёные записи",
|
||||
"sv_SE": "Förlorade profiler",
|
||||
"th_TH": "โปรไฟล์ที่สูญหาย",
|
||||
"tr_TR": "Kayıp profiller",
|
||||
"uk_UA": "Втрачені профілі",
|
||||
"zh_CN": "丢失的个人资料",
|
||||
"zh_TW": "遺失的個人資料"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RecoverLostProfiles_ToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "يستعيد الملفات الشخصية التي لم تُحذف يدويًا والتي تحتوي على حفظات.",
|
||||
"de_DE": "Stellt nicht manuell gelöschte Profile mit Speicherständen wieder.",
|
||||
"el_GR": "Ανακτά προφίλ που δεν διαγράφηκαν χειροκίνητα και έχουν αποθηκεύσεις.",
|
||||
"en_US": "Recovers non-manually-deleted profiles that have saves.",
|
||||
"es_ES": "Recupera perfiles no eliminados manualmente que tienen guardados.",
|
||||
"fr_FR": "Récupère les profils non supprimés manuellement ayant des sauvegardes.",
|
||||
"he_IL": "שחזור פרופילים שלא נמחקו ידנית ויש להם שמירות.",
|
||||
"it_IT": "Recupera profili non eliminati manualmente che hanno salvataggi.",
|
||||
"ja_JP": "手動で削除されていない、保存されたプロフィールを回復します。",
|
||||
"ko_KR": "수동으로 삭제되지 않은 저장된 프로필을 복구합니다.",
|
||||
"no_NO": "Gjenoppretter profiler som ikke er manuelt slettet og som har lagringer.",
|
||||
"pl_PL": "Odzyskuje profile, które nie zostały usunięte ręcznie, a które mają zapisy.",
|
||||
"pt_BR": "Recupera perfis não deletados manualmente que possuem saves.",
|
||||
"ru_RU": "Восстанавливает учётные записи, не удалённые вручную и имеющие сохранения.",
|
||||
"sv_SE": "Återställer profiler som inte har raderats manuellt och har sparade data.",
|
||||
"th_TH": "กู้คืนโปรไฟล์ที่ไม่ได้ลบด้วยตนเองและมีการบันทึก",
|
||||
"tr_TR": "Manuel olarak silinmemiş ve kayıtlara sahip profilleri kurtarır.",
|
||||
"uk_UA": "Відновлює учётні записи, які не були видалені вручну і мають збереження.",
|
||||
"zh_CN": "恢复未手动删除且有存档的个人资料。",
|
||||
"zh_TW": "恢復未手動刪除且有存檔的個人資料。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RecoverProfile",
|
||||
"Translations": {
|
||||
"ar_SA": "استعادة",
|
||||
"de_DE": "Wiederherstellen",
|
||||
"el_GR": "Ανάκτηση",
|
||||
"en_US": "Recover",
|
||||
"es_ES": "Recuperar",
|
||||
"fr_FR": "Récupérer",
|
||||
"he_IL": "שחזר",
|
||||
"it_IT": "Recupera",
|
||||
"ja_JP": "復旧",
|
||||
"ko_KR": "복구",
|
||||
"no_NO": "Gjenopprett",
|
||||
"pl_PL": "Odzyskaj",
|
||||
"pt_BR": "Recuperar",
|
||||
"ru_RU": "Восстановить",
|
||||
"sv_SE": "Återskapa",
|
||||
"th_TH": "กู้คืน",
|
||||
"tr_TR": "Kurtar",
|
||||
"uk_UA": "Відновити",
|
||||
"zh_CN": "恢复",
|
||||
"zh_TW": "復原"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RecoverProfile_EmptyList",
|
||||
"Translations": {
|
||||
"ar_SA": "لا توجد ملفات شخصية لاستردادها",
|
||||
"de_DE": "Keine Profile zum Wiederherstellen",
|
||||
"el_GR": "Δεν υπάρχουν προφίλ για ανάκτηση",
|
||||
"en_US": "No Profiles To Recover",
|
||||
"es_ES": "No hay perfiles a recuperar",
|
||||
"fr_FR": "Aucun profil à restaurer",
|
||||
"he_IL": "אין פרופילים לשחזור",
|
||||
"it_IT": "Nessun profilo da recuperare",
|
||||
"ja_JP": "復元するプロファイルはありません",
|
||||
"ko_KR": "복구할 프로필 없음",
|
||||
"no_NO": "Ingen profiler å gjenopprette",
|
||||
"pl_PL": "Brak profili do odzyskania",
|
||||
"pt_BR": "Nenhum perfil para recuperar",
|
||||
"ru_RU": "Нет учётных записей для восстановления",
|
||||
"sv_SE": "Inga profiler att återskapa",
|
||||
"th_TH": "ไม่มีโปรไฟล์ที่สามารถกู้คืนได้",
|
||||
"tr_TR": "Kurtarılacak profil bulunamadı",
|
||||
"uk_UA": "Немає профілів для відновлення",
|
||||
"zh_CN": "没有可以恢复的用户数据",
|
||||
"zh_TW": "無設定檔可復原"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ManageSaves_SortByName",
|
||||
"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": "ManageSaves_SortBySize",
|
||||
"Translations": {
|
||||
"ar_SA": "الحجم",
|
||||
"de_DE": "Größe",
|
||||
"el_GR": "Μέγεθος",
|
||||
"en_US": "Size",
|
||||
"es_ES": "Tamaño",
|
||||
"fr_FR": "Taille",
|
||||
"he_IL": "גודל",
|
||||
"it_IT": "Dimensione",
|
||||
"ja_JP": "サイズ",
|
||||
"ko_KR": "크기",
|
||||
"no_NO": "Størrelse",
|
||||
"pl_PL": "Rozmiar",
|
||||
"pt_BR": "Tamanho",
|
||||
"ru_RU": "Размер",
|
||||
"sv_SE": "Storlek",
|
||||
"th_TH": "ขนาด",
|
||||
"tr_TR": "Boyut",
|
||||
"uk_UA": "Розмір",
|
||||
"zh_CN": "大小",
|
||||
"zh_TW": "大小"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ManageSaves_SortOrderAscending",
|
||||
"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": "ManageSaves_SortOrderDescending",
|
||||
"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": "從大到小"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ManageSaves_Search",
|
||||
"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": "搜尋"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "IrreversibleActionNote",
|
||||
"Translations": {
|
||||
"ar_SA": "هذا الإجراء لا يمكن التراجع عنه.",
|
||||
"de_DE": "Diese Option kann nicht rückgängig gemacht werden.",
|
||||
"el_GR": "Αυτή η ενέργεια είναι μη αναστρέψιμη.",
|
||||
"en_US": "This action is not reversible.",
|
||||
"es_ES": "Esta acción no es reversible.",
|
||||
"fr_FR": "Cette action n'est pas réversible.",
|
||||
"he_IL": "הפעולה הזו בלתי הפיכה.",
|
||||
"it_IT": "Questa azione non è reversibile.",
|
||||
"ja_JP": "この操作は元に戻せません.",
|
||||
"ko_KR": "이 작업은 되돌릴 수 없습니다.",
|
||||
"no_NO": "Denne handlingen er ikke reverserbar.",
|
||||
"pl_PL": "Ta czynność nie jest odwracalna.",
|
||||
"pt_BR": "Esta ação não é reversível.",
|
||||
"ru_RU": "Данное действие является необратимым.",
|
||||
"sv_SE": "Denna åtgärd går inte att ångra.",
|
||||
"th_TH": "การดำเนินการนี้ไม่สามารถย้อนกลับได้",
|
||||
"tr_TR": "Bu eylem geri alınamaz.",
|
||||
"uk_UA": "Цю дію не можна скасувати.",
|
||||
"zh_CN": "删除后不可恢复。",
|
||||
"zh_TW": "此動作將無法復原。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ButtonClose",
|
||||
"Translations": {
|
||||
"ar_SA": "إغلاق",
|
||||
"de_DE": "Schließen",
|
||||
"el_GR": "Κλείσιμο",
|
||||
"en_US": "Close",
|
||||
"es_ES": "Cerrar",
|
||||
"fr_FR": "Fermer",
|
||||
"he_IL": "סגירה",
|
||||
"it_IT": "Chiudi",
|
||||
"ja_JP": "閉じる",
|
||||
"ko_KR": "닫기",
|
||||
"no_NO": "Lukk",
|
||||
"pl_PL": "Zamknij",
|
||||
"pt_BR": "Fechar",
|
||||
"ru_RU": "Закрыть",
|
||||
"sv_SE": "Stäng",
|
||||
"th_TH": "ปิด",
|
||||
"tr_TR": "Kapat",
|
||||
"uk_UA": "Закрити",
|
||||
"zh_CN": "关闭",
|
||||
"zh_TW": "關閉"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NameLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "الاسم:",
|
||||
"de_DE": null,
|
||||
"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": "ProfileNameSelectionWatermark",
|
||||
"Translations": {
|
||||
"ar_SA": "اختر اسم الملف الشخصي",
|
||||
"de_DE": "Wähle einen Profilnamen",
|
||||
"el_GR": "Επιλέξτε όνομα προφίλ",
|
||||
"en_US": "Choose a Profile Name",
|
||||
"es_ES": "Escoge un Nombre de Perfil",
|
||||
"fr_FR": "Choisir un Nom de Profil",
|
||||
"he_IL": "בחרו שם פרופיל",
|
||||
"it_IT": "Scegli un Nome Profilo",
|
||||
"ja_JP": "プロフィール名を選択",
|
||||
"ko_KR": "프로필 이름 선택",
|
||||
"no_NO": "Velg et Profilnavn",
|
||||
"pl_PL": "Wybierz nazwę profilu",
|
||||
"pt_BR": "Escolha um Nome de Perfil",
|
||||
"ru_RU": "Выберите имя профиля",
|
||||
"sv_SE": "Välj ett Profilnamn",
|
||||
"th_TH": "เลือก ชื่อโปรไฟล์",
|
||||
"tr_TR": "Profil Adı Seç",
|
||||
"uk_UA": "Оберіть ім'я профілю",
|
||||
"zh_CN": "选择个人资料名称",
|
||||
"zh_TW": "選擇個人資料名稱"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UserIdLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "معرف المستخدم:",
|
||||
"de_DE": "Benutzer-ID:",
|
||||
"el_GR": "Ταυτότητα Χρήστη:",
|
||||
"en_US": "User ID:",
|
||||
"es_ES": "ID de Usuario:",
|
||||
"fr_FR": "Identifiant Utilisateur :",
|
||||
"he_IL": "מזהה משתמש:",
|
||||
"it_IT": "ID utente:",
|
||||
"ja_JP": "ユーザID:",
|
||||
"ko_KR": "사용자 ID :",
|
||||
"no_NO": "Bruker ID:",
|
||||
"pl_PL": "ID Użytkownika:",
|
||||
"pt_BR": "ID de Usuário:",
|
||||
"ru_RU": "ID пользователя:",
|
||||
"sv_SE": "Användar-id:",
|
||||
"th_TH": "รหัสผู้ใช้:",
|
||||
"tr_TR": "Kullanıcı ID:",
|
||||
"uk_UA": "ID користувача:",
|
||||
"zh_CN": "用户 ID:",
|
||||
"zh_TW": "使用者 ID:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ProfileImage_Import",
|
||||
"Translations": {
|
||||
"ar_SA": "استيراد الصورة",
|
||||
"de_DE": "Bild importieren",
|
||||
"el_GR": "Εισαγωγή Εικόνας",
|
||||
"en_US": "Import Image",
|
||||
"es_ES": "Importar Imagen",
|
||||
"fr_FR": "Importer une image",
|
||||
"he_IL": "ייבוא תמונה",
|
||||
"it_IT": "Importa immagine",
|
||||
"ja_JP": "画像をインポート",
|
||||
"ko_KR": "이미지 가져오기",
|
||||
"no_NO": "Importer bilde",
|
||||
"pl_PL": "Importuj obraz",
|
||||
"pt_BR": "Importar Imagem",
|
||||
"ru_RU": "Импорт изображения",
|
||||
"sv_SE": "Importera bild",
|
||||
"th_TH": "นำเข้าภาพ",
|
||||
"tr_TR": "Resim İçeri Aktar",
|
||||
"uk_UA": "Імпорт зображення",
|
||||
"zh_CN": "导入图像",
|
||||
"zh_TW": "匯入圖像"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ProfileImage_SelectAvatar",
|
||||
"Translations": {
|
||||
"ar_SA": "حدد صورة الأفاتار من البرنامج الثابت",
|
||||
"de_DE": "Firmware-Avatar auswählen",
|
||||
"el_GR": "Επιλέξτε Avatar από Firmware",
|
||||
"en_US": "Select Firmware Avatar",
|
||||
"es_ES": "Seleccionar Avatar del Firmware",
|
||||
"fr_FR": "Choisir un Avatar du Firmware",
|
||||
"he_IL": "בחרו אוואטר קושחה",
|
||||
"it_IT": "Seleziona avatar dal firmware",
|
||||
"ja_JP": "ファームウェア内のアバターを選択",
|
||||
"ko_KR": "펌웨어 아바타 선택",
|
||||
"no_NO": "Velg firmware-avatar",
|
||||
"pl_PL": "Wybierz avatar z oprogramowania",
|
||||
"pt_BR": "Selecionar Avatar do Firmware",
|
||||
"ru_RU": "Выбрать аватар прошивки",
|
||||
"sv_SE": "Välj avatar från firmware",
|
||||
"th_TH": "เลือกอวาต้าจากระบบ",
|
||||
"tr_TR": "Yazılım Avatarı Seç",
|
||||
"uk_UA": "Виберіть аватар прошивки",
|
||||
"zh_CN": "选择固件头像",
|
||||
"zh_TW": "選取韌體大頭貼"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SupportedImageFormatDialogTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "اختر إما JPG أو JPEG أو PNG أو BMP",
|
||||
"de_DE": "Wählen Sie entweder ein JPG, JPEG, PNG oder BMP",
|
||||
"el_GR": "Επιλέξτε είτε JPG, JPEG, PNG ή BMP",
|
||||
"en_US": "Choose either a JPG, JPEG, PNG, or BMP",
|
||||
"es_ES": "Elige ya sea JPG, JPEG, PNG o BMP",
|
||||
"fr_FR": "Choisissez soit un JPG, JPEG, PNG ou BMP",
|
||||
"he_IL": "בחר את JPG, JPEG, PNG או BMP",
|
||||
"it_IT": "Scegli tra JPG, JPEG, PNG o BMP",
|
||||
"ja_JP": "JPG、JPEG、PNG、またはBMPのいずれかを選択してください",
|
||||
"ko_KR": "JPG, JPEG, PNG 또는 BMP 중에서 선택하세요",
|
||||
"no_NO": "Velg enten et JPG, JPEG, PNG eller BMP",
|
||||
"pl_PL": "Wybierz JPG, JPEG, PNG lub BMP",
|
||||
"pt_BR": "Escolha JPG, JPEG, PNG ou BMP",
|
||||
"ru_RU": "Выберите либо JPG, JPEG, PNG, или BMP",
|
||||
"sv_SE": "Välj antingen ett JPG, JPEG, PNG eller BMP",
|
||||
"th_TH": "เลือก JPG, JPEG, PNG หรือ BMP",
|
||||
"tr_TR": "JPG, JPEG, PNG veya BMP seçin",
|
||||
"uk_UA": "Виберіть або JPG, JPEG, PNG, або BMP",
|
||||
"zh_CN": "选择 JPG、JPEG、PNG 或 BMP",
|
||||
"zh_TW": "選擇 JPG、JPEG、PNG 或 BMP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SelectAvatarTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "تحديد أفاتار البرنامج الثابت",
|
||||
"de_DE": "Firmware-Avatar auswählen",
|
||||
"el_GR": "Επιλογή Avatar Firmware",
|
||||
"en_US": "Select Firmware Avatar",
|
||||
"es_ES": "Seleccionar Avatar del Firmware",
|
||||
"fr_FR": "Sélection d’un Avatar du Firmware",
|
||||
"he_IL": "בחירת אוואטר קושחה",
|
||||
"it_IT": "Selezione Avatar Firmware",
|
||||
"ja_JP": "ファームウェアアバター選択",
|
||||
"ko_KR": "펌웨어 아바타 선택",
|
||||
"no_NO": "Velg firmware-avatar",
|
||||
"pl_PL": "Wybór awatara oprogramowania",
|
||||
"pt_BR": "Selecionar Avatar do Firmware",
|
||||
"ru_RU": "Выбор аватара прошивки",
|
||||
"sv_SE": "Välj firmware-avatar",
|
||||
"th_TH": "การเลือกอวตารเฟิร์มแวร์",
|
||||
"tr_TR": "Firmware Avatar Seçimi",
|
||||
"uk_UA": "Вибір аватара прошивки",
|
||||
"zh_CN": "选择固件头像",
|
||||
"zh_TW": "選取韌體頭像"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ButtonChooseAvatar",
|
||||
"Translations": {
|
||||
"ar_SA": "اختر الأفاتار",
|
||||
"de_DE": "Wähle Avatar",
|
||||
"el_GR": "Επιλέξτε Avatar",
|
||||
"en_US": "Choose Avatar",
|
||||
"es_ES": "Elegir Avatar",
|
||||
"fr_FR": "Choisir un Avatar",
|
||||
"he_IL": "בחרו אוואטר",
|
||||
"it_IT": "Scegli Avatar",
|
||||
"ja_JP": "アバターを選択",
|
||||
"ko_KR": "아바타 선택",
|
||||
"no_NO": "Velg avatar",
|
||||
"pl_PL": "Wybierz awatar",
|
||||
"pt_BR": "Escolher Avatar",
|
||||
"ru_RU": "Выбрать аватар",
|
||||
"sv_SE": "Välj avatar",
|
||||
"th_TH": "เลือกอวาต้าของคุณ",
|
||||
"tr_TR": "Avatar Seç",
|
||||
"uk_UA": "Вибрати аватар",
|
||||
"zh_CN": "选择头像",
|
||||
"zh_TW": "選擇大頭貼"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DialogUserProfileUnsavedChangesMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "لقد قمت بإجراء تغييرات غير محفوظة على هذا الملف الشخصي.",
|
||||
"de_DE": "Sie haben nicht gespeicherte Änderungen an diesem Profil.",
|
||||
"el_GR": "Έχετε μη αποθηκευμένες αλλαγές σε αυτό το προφίλ.",
|
||||
"en_US": "You have unsaved changes to this profile.",
|
||||
"es_ES": "Tienes cambios no guardados en este perfil.",
|
||||
"fr_FR": "Vous avez des modifications non enregistrées sur ce profil.",
|
||||
"he_IL": "ביצעת שינויים לא שמורים בפרופיל זה.",
|
||||
"it_IT": "Hai modifiche non salvate su questo profilo.",
|
||||
"ja_JP": "このプロファイルには保存されていない変更があります.",
|
||||
"ko_KR": "이 프로필에는 저장되지 않은 변경 사항이 있습니다.",
|
||||
"no_NO": "Du har usparende endringer på denne profilen.",
|
||||
"pl_PL": "Masz niezapisane zmiany w tym profilu.",
|
||||
"pt_BR": "Você tem alterações não salvas neste perfil.",
|
||||
"ru_RU": "У вас есть несохраненные изменения в этом профиле.",
|
||||
"sv_SE": "Du har osparade ändringar i den här profilen.",
|
||||
"th_TH": "คุณมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกในโปรไฟล์นี้",
|
||||
"tr_TR": "Bu profilde kaydedilmemiş değişiklikleriniz var.",
|
||||
"uk_UA": "У вас є незбережені зміни в цьому профілі.",
|
||||
"zh_CN": "您对该账户有未保存的更改。",
|
||||
"zh_TW": "您對該使用者設定檔有未儲存的變更。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DialogUserProfileUnsavedChangesSubMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "هل تريد تجاهل التغييرات؟",
|
||||
"de_DE": "Verwerfen Sie die Änderungen?",
|
||||
"el_GR": "Θέλετε να απορρίψετε τις αλλαγές?",
|
||||
"en_US": "Discard changes?",
|
||||
"es_ES": "¿Descartar los cambios?",
|
||||
"fr_FR": "Annuler les modifications ?",
|
||||
"he_IL": "האם ברצונך להתעלם מהשינויים?",
|
||||
"it_IT": "Scartare le modifiche?",
|
||||
"ja_JP": "変更を破棄しますか?",
|
||||
"ko_KR": "변경 사항을 취소하시겠습니까?",
|
||||
"no_NO": "Vil du forkaste endringene?",
|
||||
"pl_PL": "Czy chcesz odrzucić zmiany?",
|
||||
"pt_BR": "Deseja descartar as alterações?",
|
||||
"ru_RU": "Отменить изменения?",
|
||||
"sv_SE": "Vill du förkasta ändringarna?",
|
||||
"th_TH": "คุณต้องการทิ้งการเปลี่ยนแปลงหรือไม่?",
|
||||
"tr_TR": "Değişiklikleri iptal et?",
|
||||
"uk_UA": "Бажаєте скасувати зміни?",
|
||||
"zh_CN": "确定要放弃更改吗?",
|
||||
"zh_TW": "您確定要放棄變更嗎?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DialogUserProfileUnsavedChangesTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "تحذير - التغييرات غير محفوظة",
|
||||
"de_DE": "WARNUNG - Nicht gespeicherte Änderungen",
|
||||
"el_GR": "ΠΡΟΣΟΧΗ - Μην Αποθηκευμένες Αλλαγές.",
|
||||
"en_US": "WARNING - Unsaved Changes",
|
||||
"es_ES": "ADVERTENCIA - Cambios Sin Guardar",
|
||||
"fr_FR": "AVERTISSEMENT - Modifications Non Enregistrées",
|
||||
"he_IL": "אזהרה - שינויים לא שמורים",
|
||||
"it_IT": "ATTENZIONE - Modifiche non salvate",
|
||||
"ja_JP": "警告 - 保存されていない変更",
|
||||
"ko_KR": "경고 - 저장되지 않은 변경 사항",
|
||||
"no_NO": "ADVARSEL - Ulagrede endringer",
|
||||
"pl_PL": "UWAGA - Niezapisane zmiany",
|
||||
"pt_BR": "ALERTA - Alterações não salvas",
|
||||
"ru_RU": "ВНИМАНИЕ - Несохраненные изменения",
|
||||
"sv_SE": "VARNING - Ej sparade ändringar",
|
||||
"th_TH": "คำเตือน - มีการเปลี่ยนแปลงที่ไม่ได้บันทึก",
|
||||
"tr_TR": "UYARI - Kaydedilmemiş Değişiklikler",
|
||||
"uk_UA": "УВАГА — Незбережені зміни",
|
||||
"zh_CN": "警告 - 有未保存的更改",
|
||||
"zh_TW": "警告 - 未儲存的變更"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DialogUserProfileDeletionConfirmMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "هل حذف الملف الشخصي المحدد؟",
|
||||
"de_DE": "Löschen Sie das ausgewählte Profil?",
|
||||
"el_GR": "Διαγραφή του επιλεγμένου προφίλ;",
|
||||
"en_US": "Delete the selected profile?",
|
||||
"es_ES": "¿Eliminar el perfil seleccionado?",
|
||||
"fr_FR": "Supprimer le profil sélectionné ?",
|
||||
"he_IL": "האם למחוק את הפרופיל שנבחר?",
|
||||
"it_IT": "Eliminare il profilo selezionato?",
|
||||
"ja_JP": "選択されたプロファイルを削除しますか?",
|
||||
"ko_KR": "선택한 프로필을 삭제하시겠습니까?",
|
||||
"no_NO": "Slette den valgte profilen?",
|
||||
"pl_PL": "Usunąć wybrany profil?",
|
||||
"pt_BR": "Excluir o perfil selecionado?",
|
||||
"ru_RU": "Удалить выбранный профиль?",
|
||||
"sv_SE": "Ta bort den valda profilen?",
|
||||
"th_TH": "ลบโปรไฟล์ที่เลือก?",
|
||||
"tr_TR": "Seçilen profili silmek?",
|
||||
"uk_UA": "Видалити вибраний профіль?",
|
||||
"zh_CN": "删除所选账户?",
|
||||
"zh_TW": "刪除所選設定檔?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "DialogUserProfileDeletionWarningMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "لن تكون هناك ملفات الشخصية أخرى لفتحها إذا تم حذف الملف الشخصي المحدد.",
|
||||
"de_DE": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.",
|
||||
"el_GR": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο.",
|
||||
"en_US": "There would be no other profiles to be opened if selected profile is deleted.",
|
||||
"es_ES": "Si eliminas el perfil seleccionado no quedará ningún otro perfil.",
|
||||
"fr_FR": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé.",
|
||||
"he_IL": "לא יהיו פרופילים אחרים שייפתחו אם הפרופיל שנבחר יימחק.",
|
||||
"it_IT": "Non ci sarebbero altri profili da aprire se il profilo selezionato venisse cancellato.",
|
||||
"ja_JP": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります.",
|
||||
"ko_KR": "선택한 프로필을 삭제하면 다른 프로필을 열 수 없음.",
|
||||
"no_NO": "Det vil ikke være noen profiler å åpnes hvis valgt profil blir slettet.",
|
||||
"pl_PL": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty.",
|
||||
"pt_BR": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado.",
|
||||
"ru_RU": "Если выбранный профиль будет удален, другие профили не будут открываться.",
|
||||
"sv_SE": "Det skulle inte finnas några andra profiler att öppnas om angiven profil tas bort.",
|
||||
"th_TH": "จะไม่มีโปรไฟล์อื่นให้เปิดหากโปรไฟล์ที่เลือกถูกลบ",
|
||||
"tr_TR": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak.",
|
||||
"uk_UA": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться.",
|
||||
"zh_CN": "删除后将没有可用的账户。",
|
||||
"zh_TW": "如果刪除選取的設定檔,將無法開啟其他設定檔。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ButtonDelete",
|
||||
"Translations": {
|
||||
"ar_SA": "حذف",
|
||||
"de_DE": "Löschen",
|
||||
"el_GR": "Διαγράφω",
|
||||
"en_US": "Delete",
|
||||
"es_ES": "Eliminar",
|
||||
"fr_FR": "Supprimer",
|
||||
"he_IL": "מחיקה",
|
||||
"it_IT": "Elimina",
|
||||
"ja_JP": "削除",
|
||||
"ko_KR": "삭제",
|
||||
"no_NO": "Slett",
|
||||
"pl_PL": "Usuń",
|
||||
"pt_BR": "Apagar",
|
||||
"ru_RU": "Удалить",
|
||||
"sv_SE": "Ta bort",
|
||||
"th_TH": "ลบ",
|
||||
"tr_TR": "Sil",
|
||||
"uk_UA": "Видалити",
|
||||
"zh_CN": "删除",
|
||||
"zh_TW": "刪除"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ButtonSave",
|
||||
"Translations": {
|
||||
"ar_SA": "حفظ",
|
||||
"de_DE": "Speichern",
|
||||
"el_GR": "Αποθήκευση",
|
||||
"en_US": "Save",
|
||||
"es_ES": "Guardar",
|
||||
"fr_FR": "Enregistrer",
|
||||
"he_IL": "שמור",
|
||||
"it_IT": "Salva",
|
||||
"ja_JP": "セーブ",
|
||||
"ko_KR": "저장",
|
||||
"no_NO": "Lagre",
|
||||
"pl_PL": "Zapisz",
|
||||
"pt_BR": "Salvar",
|
||||
"ru_RU": "Сохранить",
|
||||
"sv_SE": "Spara",
|
||||
"th_TH": "บันทึก",
|
||||
"tr_TR": "Kaydet",
|
||||
"uk_UA": "Зберегти",
|
||||
"zh_CN": "保存",
|
||||
"zh_TW": "儲存"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UserEditorTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "جارٍ تعديل {0}",
|
||||
"de_DE": "{0} wird bearbeitet",
|
||||
"el_GR": "Επεξεργασία {0}",
|
||||
"en_US": "Editing {0}",
|
||||
"es_ES": "Editando {0}",
|
||||
"fr_FR": "Modification de {0}",
|
||||
"he_IL": "עריכת {0}",
|
||||
"it_IT": "Modifica di {0}",
|
||||
"ja_JP": "{0} を編集中",
|
||||
"ko_KR": "{0} 편집 중",
|
||||
"no_NO": "Redigerer {0}",
|
||||
"pl_PL": "Edycja {0}",
|
||||
"pt_BR": "Editando {0}",
|
||||
"ru_RU": "Редактирование {0}",
|
||||
"sv_SE": "Redigerar {0}",
|
||||
"th_TH": "กำลังกำลังแก้ไข {0}",
|
||||
"tr_TR": "{0} düzenleniyor",
|
||||
"uk_UA": "Редагування {0}",
|
||||
"zh_CN": "正在编辑 {0}",
|
||||
"zh_TW": "正在編輯 {0}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UserEditorTitleNewUser",
|
||||
"Translations": {
|
||||
"ar_SA": "مستخدم جديد",
|
||||
"de_DE": "Neuer Nutzer",
|
||||
"el_GR": "Νέος Χρήστης",
|
||||
"en_US": "New User",
|
||||
"es_ES": "Nuevo Usuario",
|
||||
"fr_FR": "Nouvel Utilisateur",
|
||||
"he_IL": "משתמש חדש",
|
||||
"it_IT": "Nuovo utente",
|
||||
"ja_JP": "新しいユーザー",
|
||||
"ko_KR": "새 사용자",
|
||||
"no_NO": "Ny bruker",
|
||||
"pl_PL": "Nowy użytkownik",
|
||||
"pt_BR": "Novo usuário",
|
||||
"ru_RU": "Новый пользователь",
|
||||
"sv_SE": "Ny användare",
|
||||
"th_TH": "ผู้ใช้ใหม่",
|
||||
"tr_TR": "Yeni kullanıcı",
|
||||
"uk_UA": "Новий користувач",
|
||||
"zh_CN": "新用户",
|
||||
"zh_TW": "新使用者"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "EmptyNameError",
|
||||
"Translations": {
|
||||
"ar_SA": "الاسم مطلوب",
|
||||
"de_DE": "Name ist erforderlich",
|
||||
"el_GR": "Απαιτείται όνομα",
|
||||
"en_US": "Name is required",
|
||||
"es_ES": "El nombre es obligatorio",
|
||||
"fr_FR": "Le nom est requis",
|
||||
"he_IL": "נדרש שם",
|
||||
"it_IT": "Il nome è obbligatorio",
|
||||
"ja_JP": "名称が必要です",
|
||||
"ko_KR": "이름 필수 입력",
|
||||
"no_NO": "Navn er påkrevd",
|
||||
"pl_PL": "Nazwa jest wymagana",
|
||||
"pt_BR": "Nome é obrigatório",
|
||||
"ru_RU": "Необходимо ввести имя",
|
||||
"sv_SE": "Namn krävs",
|
||||
"th_TH": "จำเป็นต้องระบุชื่อ",
|
||||
"tr_TR": "İsim gerekli",
|
||||
"uk_UA": "Імʼя обовʼязкове",
|
||||
"zh_CN": "必须输入名称",
|
||||
"zh_TW": "名稱為必填"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2050,9 +2050,7 @@
|
||||
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
|
||||
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
|
||||
0100C9A00ECE6000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
||||
010057D00ECE4000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
|
||||
0100e0601c632000,"Nintendo 64™ – Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
|
||||
010037A0170D2000,"NINTENDO 64™ – Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
|
||||
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
|
||||
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
|
||||
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
|
||||
@@ -2640,7 +2638,6 @@
|
||||
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
|
||||
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
|
||||
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
|
||||
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
|
||||
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
|
||||
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
|
||||
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
|
||||
@@ -3310,7 +3307,6 @@
|
||||
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
|
||||
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
|
||||
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
|
||||
0100BFC01D976000,"Virtual Boy – Nintendo Classics",services,nothing,2026-02-17 11:26:59
|
||||
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
|
||||
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
|
||||
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
|
||||
|
||||
|
@@ -107,12 +107,12 @@ namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine(
|
||||
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="System.Management" />
|
||||
<PackageReference Include="Humanizer" />
|
||||
<PackageReference Include="Gommon" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -184,7 +184,6 @@ namespace Ryujinx.Common
|
||||
"01001b300b9be000", // Diablo III: Eternal Collection
|
||||
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
|
||||
"01008c8012920000", // Dying Light Platinum Edition
|
||||
"0100d11013e6a000", // Eschatos
|
||||
"01001cc01b2d4000", // Goat Simulator 3
|
||||
"01003620068ea000", // Hand of Fate 2
|
||||
"0100f7e00c70e000", // Hogwarts Legacy
|
||||
@@ -194,15 +193,9 @@ namespace Ryujinx.Common
|
||||
"0100d71004694000", // Minecraft
|
||||
"01007430037f6000", // Monopoly
|
||||
"0100853015e86000", // No Man's Sky
|
||||
"0100f85014ed0000", // No More Heroes
|
||||
"0100463014ed4000", // No More Heroes 2
|
||||
"0100e570094e8000", // Owlboy
|
||||
"01007bb017812000", // Portal
|
||||
"0100abd01785c000", // Portal 2
|
||||
"01009f100bc52000", // Psikyo Collection 1
|
||||
"01009d400c4a8000", // Psikyo Collection 2
|
||||
"01008e200c5c2000", // Muse Dash
|
||||
"01005ff002e2a000", // Rayman Legends
|
||||
"01007820196a6000", // Red Dead Redemption
|
||||
"0100e8300a67a000", // Risk
|
||||
"01002f7013224000", // Rune Factory 5
|
||||
|
||||
@@ -22,11 +22,10 @@ namespace Ryujinx.Common.Utilities
|
||||
}
|
||||
|
||||
// "dumpable" attribute of the calling process
|
||||
private const int PR_GET_DUMPABLE = 3;
|
||||
private const int PR_SET_DUMPABLE = 4;
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int prctl(int option, int arg2);
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int prctl(int option, int arg2);
|
||||
|
||||
public static void SetCoreDumpable(bool dumpable)
|
||||
{
|
||||
@@ -37,13 +36,5 @@ namespace Ryujinx.Common.Utilities
|
||||
Debug.Assert(result == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the below line to display dumpable status in the console:
|
||||
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
|
||||
public static bool IsCoreDumpable()
|
||||
{
|
||||
int result = prctl(PR_GET_DUMPABLE, 0);
|
||||
return result == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
|
||||
using Ryujinx.HLE.HOS.Services.Nv;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
||||
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
|
||||
@@ -67,8 +66,6 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
internal List<NfpDevice> NfpDevices { get; private set; }
|
||||
|
||||
internal List<NfcDevice> NfcDevices { get; private set; }
|
||||
|
||||
internal SmRegistry SmRegistry { get; private set; }
|
||||
|
||||
internal ServerBase SmServer { get; private set; }
|
||||
@@ -135,7 +132,6 @@ namespace Ryujinx.HLE.HOS
|
||||
PerformanceState = new PerformanceState();
|
||||
|
||||
NfpDevices = [];
|
||||
NfcDevices = [];
|
||||
|
||||
// Note: This is not really correct, but with HLE of services, the only memory
|
||||
// region used that is used is Application, so we can use the other ones for anything.
|
||||
@@ -376,15 +372,6 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
public void ScanSkylander(int nfcDeviceId, byte[] data)
|
||||
{
|
||||
if (NfcDevices[nfcDeviceId].State == NfcDeviceState.SearchingForTag)
|
||||
{
|
||||
NfcDevices[nfcDeviceId].State = NfcDeviceState.TagFound;
|
||||
NfcDevices[nfcDeviceId].Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SearchingForAmiibo(out int nfpDeviceId)
|
||||
{
|
||||
nfpDeviceId = default;
|
||||
@@ -402,53 +389,6 @@ namespace Ryujinx.HLE.HOS
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SearchingForSkylander(out int nfcDeviceId)
|
||||
{
|
||||
nfcDeviceId = default;
|
||||
|
||||
for (int i = 0; i < NfcDevices.Count; i++)
|
||||
{
|
||||
if (NfcDevices[i].State == NfcDeviceState.SearchingForTag)
|
||||
{
|
||||
nfcDeviceId = i;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasSkylander(out int nfcDeviceId)
|
||||
{
|
||||
nfcDeviceId = default;
|
||||
|
||||
for (int i = 0; i < NfcDevices.Count; i++)
|
||||
{
|
||||
if (NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
nfcDeviceId = i;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveSkylander()
|
||||
{
|
||||
for (int i = 0; i < NfcDevices.Count; i++)
|
||||
{
|
||||
if (NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
NfcDevices[i].State = NfcDeviceState.Initialized;
|
||||
NfcDevices[i].SignalDeactivate();
|
||||
Thread.Sleep(100); // NOTE: Simulate skylander scanning delay.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SignalDisplayResolutionChange()
|
||||
{
|
||||
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
||||
|
||||
@@ -56,7 +56,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
_activeCount = 0;
|
||||
|
||||
JoyHold = NpadJoyHoldType.Vertical;
|
||||
SixAxisActive = false;
|
||||
}
|
||||
|
||||
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
|
||||
@@ -581,24 +580,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
return needUpdateRight;
|
||||
}
|
||||
|
||||
public bool isAtRest(int playerNumber)
|
||||
{
|
||||
|
||||
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
|
||||
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
|
||||
|
||||
float acceleration = Math.Abs(storage.Acceleration.X)
|
||||
+ Math.Abs(storage.Acceleration.Y)
|
||||
+ Math.Abs(storage.Acceleration.Z);
|
||||
|
||||
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
|
||||
+ Math.Abs(storage.AngularVelocity.Y)
|
||||
+ Math.Abs(storage.AngularVelocity.Z);
|
||||
|
||||
// TODO: check against config deadzone and add sensitivity setting
|
||||
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
|
||||
}
|
||||
|
||||
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
|
||||
{
|
||||
|
||||
@@ -602,33 +602,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
|
||||
[CommandCmif(82)]
|
||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
|
||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
|
||||
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
|
||||
// 4 byte struct w/ 4-byte alignment
|
||||
|
||||
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
|
||||
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
|
||||
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
|
||||
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
|
||||
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
|
||||
|
||||
// 32bit sign extension padding -> if = 0, + offset, else - offset
|
||||
|
||||
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
|
||||
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
|
||||
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
|
||||
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
|
||||
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
|
||||
// We currently do not support stopping or starting SixAxisTracking.
|
||||
|
||||
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
|
||||
|
||||
bool isAtRest = true;
|
||||
|
||||
context.ResponseData.Write(isAtRest);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -643,7 +629,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
|
||||
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
|
||||
{
|
||||
[Service("nfc:mf:u")]
|
||||
class IUserManager : IpcService
|
||||
{
|
||||
public IUserManager(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(0)]
|
||||
// CreateUserInterface() -> object<nn::nfc::mf::IUser>
|
||||
public ResultCode CreateUserInterface(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IMifare());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
class IMifare : IpcService
|
||||
{
|
||||
private State _state;
|
||||
|
||||
private KEvent _availabilityChangeEvent;
|
||||
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
|
||||
public IMifare()
|
||||
{
|
||||
_state = State.NonInitialized;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
public ResultCode Initialize(ServiceCtx context)
|
||||
{
|
||||
_state = State.Initialized;
|
||||
|
||||
NfcDevice devicePlayer1 = new()
|
||||
{
|
||||
NpadIdType = NpadIdType.Player1,
|
||||
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
|
||||
State = NfcDeviceState.Initialized,
|
||||
};
|
||||
|
||||
context.Device.System.NfcDevices.Add(devicePlayer1);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
public ResultCode Finalize(ServiceCtx context)
|
||||
{
|
||||
if (_state == State.Initialized)
|
||||
{
|
||||
_cancelTokenSource?.Cancel();
|
||||
|
||||
// NOTE: All events are destroyed here.
|
||||
context.Device.System.NfcDevices.Clear();
|
||||
|
||||
_state = State.NonInitialized;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
public ResultCode GetListDevices(ServiceCtx context)
|
||||
{
|
||||
if (context.Request.RecvListBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
ulong outputPosition = context.Request.RecvListBuff[0].Position;
|
||||
ulong outputSize = context.Request.RecvListBuff[0].Size;
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfcDevices[i].Handle);
|
||||
}
|
||||
|
||||
context.ResponseData.Write(context.Device.System.NfcDevices.Count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
public ResultCode StartDetection(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].State = NfcDeviceState.SearchingForTag;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_cancelTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].SignalActivate();
|
||||
Thread.Sleep(125); // NOTE: Simulate skylander scanning delay.
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, _cancelTokenSource.Token);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
public ResultCode StopDetection(ServiceCtx context)
|
||||
{
|
||||
_cancelTokenSource?.Cancel();
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].State = NfcDeviceState.Initialized;
|
||||
Array.Clear(context.Device.System.NfcDevices[i].Data);
|
||||
context.Device.System.NfcDevices[i].SignalDeactivate();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
public ResultCode ReadMifare(ServiceCtx context)
|
||||
{
|
||||
if (context.Request.ReceiveBuff.Count == 0 || context.Request.SendBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
|
||||
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
byte[] readBlockParameter = new byte[inputSize];
|
||||
|
||||
context.Memory.Read(inputPosition, readBlockParameter);
|
||||
|
||||
var span = MemoryMarshal.Cast<byte, NfcMifareReadBlockParameter>(readBlockParameter);
|
||||
var list = new List<NfcMifareReadBlockParameter>(span.Length);
|
||||
|
||||
foreach (var item in span)
|
||||
list.Add(item);
|
||||
|
||||
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
|
||||
{
|
||||
return ResultCode.TagNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int p = 0; p < list.Count; p++)
|
||||
{
|
||||
NfcMifareReadBlockData blockData = new()
|
||||
{
|
||||
SectorNumber = list[p].SectorNumber,
|
||||
Reserved = new Array7<byte>(),
|
||||
};
|
||||
byte[] data = new byte[16];
|
||||
|
||||
switch (list[p].SectorKey.MifareCommand)
|
||||
{
|
||||
case NfcMifareCommand.NfcMifareCommand_Read:
|
||||
case NfcMifareCommand.NfcMifareCommand_AuthA:
|
||||
if (IsCurrentBlockKeyBlock(list[p].SectorNumber))
|
||||
{
|
||||
Array.Copy(context.Device.System.NfcDevices[i].Data, (16 * list[p].SectorNumber) + 6, data, 6, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(context.Device.System.NfcDevices[i].Data, 16 * list[p].SectorNumber, data, 0, 16);
|
||||
}
|
||||
data.CopyTo(blockData.Data.AsSpan());
|
||||
context.Memory.Write(outputPosition + ((uint)(p * Unsafe.SizeOf<NfcMifareReadBlockData>())), blockData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
public ResultCode WriteMifare(ServiceCtx context)
|
||||
{
|
||||
if (context.Request.SendBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
byte[] writeBlockParameter = new byte[inputSize];
|
||||
|
||||
context.Memory.Read(inputPosition, writeBlockParameter);
|
||||
|
||||
var span = MemoryMarshal.Cast<byte, NfcMifareWriteBlockParameter>(writeBlockParameter);
|
||||
var list = new List<NfcMifareWriteBlockParameter>(span.Length);
|
||||
|
||||
foreach (var item in span)
|
||||
list.Add(item);
|
||||
|
||||
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
|
||||
{
|
||||
return ResultCode.TagNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int p = 0; p < list.Count; p++)
|
||||
{
|
||||
switch (list[p].SectorKey.MifareCommand)
|
||||
{
|
||||
case NfcMifareCommand.NfcMifareCommand_Write:
|
||||
case NfcMifareCommand.NfcMifareCommand_AuthA:
|
||||
list[p].Data.AsSpan().CopyTo(context.Device.System.NfcDevices[i].Data.AsSpan(list[p].SectorNumber * 16, 16));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(7)]
|
||||
public ResultCode GetTagInfo(ServiceCtx context)
|
||||
{
|
||||
ResultCode resultCode = ResultCode.Success;
|
||||
|
||||
if (context.Request.RecvListBuff.Count == 0)
|
||||
{
|
||||
return ResultCode.WrongArgument;
|
||||
}
|
||||
|
||||
ulong outputPosition = context.Request.RecvListBuff[0].Position;
|
||||
|
||||
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
|
||||
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
if (context.Device.System.NfcDevices.Count == 0)
|
||||
{
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
|
||||
{
|
||||
resultCode = ResultCode.TagNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagMounted || context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
|
||||
{
|
||||
TagInfo tagInfo = new()
|
||||
{
|
||||
UuidLength = 4,
|
||||
Reserved1 = new Array21<byte>(),
|
||||
Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol
|
||||
TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type
|
||||
Reserved2 = new Array6<byte>(),
|
||||
};
|
||||
|
||||
byte[] uuid = new byte[4];
|
||||
|
||||
Array.Copy(context.Device.System.NfcDevices[i].Data, 0, uuid, 0, 4);
|
||||
|
||||
uuid.CopyTo(tagInfo.Uuid.AsSpan());
|
||||
|
||||
context.Memory.Write(outputPosition, tagInfo);
|
||||
|
||||
resultCode = ResultCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode = ResultCode.WrongDeviceState;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(8)]
|
||||
public ResultCode AttachActivateEvent(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(9)]
|
||||
public ResultCode AttachDeactivateEvent(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
context.Device.System.NfcDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(10)]
|
||||
public ResultCode GetState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((int)_state);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
public ResultCode GetDeviceState(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
if (context.Device.System.NfcDevices[i].State > NfcDeviceState.Finalized)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(context.Device.System.NfcDevices)} contains an invalid state for device {i}: {context.Device.System.NfcDevices[i].State}");
|
||||
}
|
||||
context.ResponseData.Write((uint)context.Device.System.NfcDevices[i].State);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)NfcDeviceState.Unavailable);
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(12)]
|
||||
public ResultCode GetNpadId(ServiceCtx context)
|
||||
{
|
||||
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
|
||||
|
||||
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
|
||||
{
|
||||
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
|
||||
{
|
||||
context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfcDevices[i].Handle));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.DeviceNotFound;
|
||||
}
|
||||
|
||||
[CommandCmif(13)]
|
||||
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
|
||||
{
|
||||
_availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private bool IsCurrentBlockKeyBlock(byte block)
|
||||
{
|
||||
return ((block + 1) % 4) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
class NfcDevice
|
||||
{
|
||||
public KEvent ActivateEvent;
|
||||
public KEvent DeactivateEvent;
|
||||
|
||||
public void SignalActivate() => ActivateEvent.ReadableEvent.Signal();
|
||||
public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
|
||||
|
||||
public NfcDeviceState State = NfcDeviceState.Unavailable;
|
||||
|
||||
public PlayerIndex Handle;
|
||||
public NpadIdType NpadIdType;
|
||||
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcMifareCommand : byte
|
||||
{
|
||||
NfcMifareCommand_Read = 0x30,
|
||||
NfcMifareCommand_AuthA = 0x60,
|
||||
NfcMifareCommand_AuthB = 0x61,
|
||||
NfcMifareCommand_Write = 0xA0,
|
||||
NfcMifareCommand_Transfer = 0xB0,
|
||||
NfcMifareCommand_Decrement = 0xC0,
|
||||
NfcMifareCommand_Increment = 0xC1,
|
||||
NfcMifareCommand_Store = 0xC2,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
|
||||
struct NfcMifareReadBlockData
|
||||
{
|
||||
public Array16<byte> Data;
|
||||
public byte SectorNumber;
|
||||
public Array7<byte> Reserved;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
|
||||
struct NfcMifareReadBlockParameter
|
||||
{
|
||||
public byte SectorNumber;
|
||||
public Array7<byte> Reserved;
|
||||
public NfcSectorKey SectorKey;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
|
||||
struct NfcMifareWriteBlockParameter
|
||||
{
|
||||
public Array16<byte> Data;
|
||||
public byte SectorNumber;
|
||||
public Array7<byte> Reserved;
|
||||
public NfcSectorKey SectorKey;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcProtocol : byte
|
||||
{
|
||||
NfcProtocol_None = 0b_0000_0000,
|
||||
NfcProtocol_TypeA = 0b_0000_0001, ///< ISO14443A
|
||||
NfcProtocol_TypeB = 0b_0000_0010, ///< ISO14443B
|
||||
NfcProtocol_TypeF = 0b_0000_0100, ///< Sony FeliCa
|
||||
NfcProtocol_All = 0xFF,
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
struct NfcSectorKey
|
||||
{
|
||||
public NfcMifareCommand MifareCommand;
|
||||
public byte Unknown;
|
||||
public Array6<byte> Reserved1;
|
||||
public Array6<byte> SectorKey;
|
||||
public Array2<byte> Reserved2;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcTagType : byte
|
||||
{
|
||||
NfcTagType_None = 0b_0000_0000,
|
||||
NfcTagType_Type1 = 0b_0000_0001, ///< ISO14443A RW. Topaz
|
||||
NfcTagType_Type2 = 0b_0000_0010, ///< ISO14443A RW. Ultralight, NTAGX, ST25TN
|
||||
NfcTagType_Type3 = 0b_0000_0100, ///< ISO14443A RW/RO. Sony FeliCa
|
||||
NfcTagType_Type4A = 0b_0000_1000, ///< ISO14443A RW/RO. DESFire
|
||||
NfcTagType_Type4B = 0b_0001_0000, ///< ISO14443B RW/RO. DESFire
|
||||
NfcTagType_Type5 = 0b_0010_0000, ///< ISO15693 RW/RO. SLI, SLIX, ST25TV
|
||||
NfcTagType_Mifare = 0b_0100_0000, ///< Mifare clasic. Skylanders
|
||||
NfcTagType_All = 0xFF,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum NfcDeviceState : byte
|
||||
{
|
||||
Initialized = 0,
|
||||
SearchingForTag = 1,
|
||||
TagFound = 2,
|
||||
TagRemoved = 3,
|
||||
TagMounted = 4,
|
||||
Unavailable = 5,
|
||||
Finalized = 6,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
enum State
|
||||
{
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x58)]
|
||||
struct TagInfo
|
||||
{
|
||||
public Array10<byte> Uuid;
|
||||
public byte UuidLength;
|
||||
public Array21<byte> Reserved1;
|
||||
public uint Protocol;
|
||||
public uint TagType;
|
||||
public Array6<byte> Reserved2;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
|
||||
{
|
||||
public enum ResultCode
|
||||
{
|
||||
ModuleId = 161,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, // 0x80A1
|
||||
WrongArgument = (65 << ErrorCodeShift) | ModuleId, // 0x82A1
|
||||
WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, // 0x92A1
|
||||
NfcDisabled = (80 << ErrorCodeShift) | ModuleId, // 0xA0A1
|
||||
TagNotFound = (97 << ErrorCodeShift) | ModuleId, // 0xC2A1
|
||||
MifareAccessError = (288 << ErrorCodeShift) | ModuleId, // 0x240a1
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,5 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
BrazilianPortuguese,
|
||||
Polish,
|
||||
Thai,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
"es-419",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
"pt-BR",
|
||||
"pl",
|
||||
"th"
|
||||
"pt-BR"
|
||||
];
|
||||
|
||||
internal long DesiredKeyboardLayout { get; private set; }
|
||||
|
||||
@@ -18,7 +18,5 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
TraditionalChinese,
|
||||
SimplifiedChinese,
|
||||
BrazilianPortuguese,
|
||||
Polish,
|
||||
Thai,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ns
|
||||
{
|
||||
public struct ApplicationControlProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
|
||||
/// </summary>
|
||||
public Array16<ApplicationTitle> TitleBlock;
|
||||
public Array16<ApplicationTitle> Title;
|
||||
public Array37<byte> Isbn;
|
||||
public StartupUserAccountValue StartupUserAccount;
|
||||
public UserAccountSwitchLockValue UserAccountSwitchLock;
|
||||
@@ -65,10 +58,7 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
public RepairFlagValue RepairFlag;
|
||||
public byte ProgramIndex;
|
||||
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
|
||||
public byte ApplicationErrorCodePrefix;
|
||||
public TitleCompressionValue TitleCompression;
|
||||
public byte AcdIndex;
|
||||
public byte ApparentPlatform;
|
||||
public Array4<byte> Reserved3214;
|
||||
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
|
||||
public ApplicationJitConfiguration JitConfiguration;
|
||||
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
|
||||
@@ -84,47 +74,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
|
||||
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
|
||||
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
|
||||
|
||||
private const int TitleCount = 32;
|
||||
private const int TitleEntrySize = 0x300;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
|
||||
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
|
||||
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
|
||||
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
|
||||
/// </summary>
|
||||
public readonly ApplicationTitle[] Title
|
||||
{
|
||||
get
|
||||
{
|
||||
var titles = new ApplicationTitle[TitleCount];
|
||||
|
||||
if (TitleCompression != TitleCompressionValue.Enable)
|
||||
{
|
||||
TitleBlock.AsSpan().CopyTo(titles);
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
|
||||
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
|
||||
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
|
||||
|
||||
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
|
||||
|
||||
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
|
||||
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
|
||||
}
|
||||
|
||||
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
|
||||
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationTitle
|
||||
{
|
||||
@@ -181,8 +130,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
TraditionalChinese = 13,
|
||||
SimplifiedChinese = 14,
|
||||
BrazilianPortuguese = 15,
|
||||
Polish = 16,
|
||||
Thai = 17,
|
||||
}
|
||||
|
||||
public enum Organization
|
||||
@@ -355,11 +302,5 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||
Deny = 0,
|
||||
Allow = 1,
|
||||
}
|
||||
|
||||
public enum TitleCompressionValue : byte
|
||||
{
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace Ryujinx.Ava.Input
|
||||
AvaKey.OemComma,
|
||||
AvaKey.OemPeriod,
|
||||
AvaKey.OemQuestion,
|
||||
AvaKey.OemPipe,
|
||||
AvaKey.OemBackslash,
|
||||
|
||||
// NOTE: invalid
|
||||
AvaKey.None
|
||||
|
||||
@@ -42,7 +42,6 @@ namespace Ryujinx.Ava
|
||||
public static bool PreviewerDetached { get; private set; }
|
||||
public static bool UseHardwareAcceleration { get; private set; }
|
||||
public static string BackendThreadingArg { get; private set; }
|
||||
public static bool CoreDumpArg { get; private set; }
|
||||
|
||||
private const uint MbIconwarning = 0x30;
|
||||
|
||||
@@ -82,8 +81,6 @@ namespace Ryujinx.Ava
|
||||
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
|
||||
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
|
||||
|
||||
CoreDumpArg = coreDumpArg;
|
||||
|
||||
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
|
||||
// This is undesirable and causes very odd behavior during development (the process stops responding,
|
||||
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>partial</TrimMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-arm64'">
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
|
||||
@@ -49,7 +54,7 @@
|
||||
<PackageReference Include="Svg.Controls.Avalonia" />
|
||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
||||
<PackageReference Include="DynamicData" />
|
||||
<PackageReference Include="FluentAvaloniaUI" />
|
||||
<PackageReference Include="FluentAvaloniaUI.NoAnim" />
|
||||
<PackageReference Include="CommandLineParser" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="DiscordRichPresence" />
|
||||
|
||||
@@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data.Name))
|
||||
{
|
||||
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||
{
|
||||
if (!controlTitle.NameString.IsEmpty())
|
||||
{
|
||||
@@ -1417,7 +1417,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data.Developer))
|
||||
{
|
||||
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||
{
|
||||
if (!controlTitle.PublisherString.IsEmpty())
|
||||
{
|
||||
|
||||
@@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Systems.Configuration.System
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
BrazilianPortuguese,
|
||||
Polish,
|
||||
Thai,
|
||||
}
|
||||
|
||||
public static class LanguageEnumHelper
|
||||
|
||||
@@ -16,41 +16,31 @@
|
||||
<Design.DataContext>
|
||||
<viewModels:ProfileSelectorDialogViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
|
||||
|
||||
<Border
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1">
|
||||
|
||||
<Border Padding="-3" BorderThickness="0">
|
||||
<ListBox
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectionChanged="ProfilesList_SelectionChanged">
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal" />
|
||||
Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Margin" Value="5 5 0 5" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
</Style>
|
||||
<Style Selector="Rectangle#SelectionIndicator">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate
|
||||
DataType="models:UserProfile">
|
||||
@@ -58,6 +48,7 @@
|
||||
PointerEntered="Grid_PointerEntered"
|
||||
PointerExited="Grid_OnPointerExited">
|
||||
<Border
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
@@ -69,37 +60,26 @@
|
||||
<Image
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0,0,0,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxWidth="90"
|
||||
Text="{Binding Name}"
|
||||
Height="30"
|
||||
MaxWidth="90"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxLines="2"
|
||||
Margin="5" />
|
||||
TextWrapping="Wrap"
|
||||
MaxLines="2" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate
|
||||
DataType="viewModels:BaseModel">
|
||||
<Panel
|
||||
Height="118"
|
||||
Width="96">
|
||||
<Panel.Styles>
|
||||
<Style Selector="Panel">
|
||||
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle],
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
|
||||
SecondaryButtonText = string.Empty,
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
Command="{Binding RunApplication}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuRunApplication}"
|
||||
Icon="{ext:Icon fa-solid fa-play}"/>
|
||||
Icon="{ext:Icon fa-solid fa-play}" />
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuToggleFavorite}"
|
||||
Header="{Binding FavoriteStatusText}"
|
||||
Icon="{ext:Icon fa-solid fa-star}" />
|
||||
<MenuItem
|
||||
Command="{Binding CreateApplicationShortcut}"
|
||||
@@ -30,15 +30,15 @@
|
||||
ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding EditGameConfiguration}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
|
||||
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
|
||||
Icon="{ext:Icon fa-solid fa-gear}"
|
||||
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
|
||||
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
|
||||
Icon="{ext:Icon fa-solid fa-gear}"
|
||||
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
|
||||
<MenuItem
|
||||
IsVisible="{Binding HasCompatibilityEntry}"
|
||||
Command="{Binding OpenApplicationCompatibility}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding HasCompatibilityEntry}"
|
||||
Header="{ext:Locale GameListContextMenuShowCompatEntry}"
|
||||
Icon="{ext:Icon fa-solid fa-database}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuShowCompatEntryToolTip}"/>
|
||||
@@ -104,8 +104,8 @@
|
||||
Command="{Binding TrimXci}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuTrimXCI}"
|
||||
IsEnabled="{Binding TrimXCIEnabled}"
|
||||
Icon="{ext:Icon fa-solid fa-scissors}"
|
||||
IsEnabled="{Binding TrimXCIEnabled}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" />
|
||||
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon fa-solid fa-memory}">
|
||||
<MenuItem
|
||||
@@ -151,9 +151,9 @@
|
||||
Header="{ext:Locale GameListContextMenuExtractDataRomFS}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
||||
<MenuItem
|
||||
IsVisible="{Binding HasDlc}"
|
||||
Command="{Binding ExtractApplicationAocRomFs}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding HasDlc}"
|
||||
Header="{ext:Locale GameListContextMenuExtractDataAocRomFS}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataAocRomFSToolTip}" />
|
||||
<MenuItem
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
NavigationDialogHost content = new(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle],
|
||||
PrimaryButtonText = string.Empty,
|
||||
SecondaryButtonText = string.Empty,
|
||||
CloseButtonText = string.Empty,
|
||||
@@ -156,7 +156,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
_ = Dispatcher.UIThread.InvokeAsync(async ()
|
||||
=> await ContentDialogHelper.CreateErrorDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]));
|
||||
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileDeletionWarningMessage]));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -165,8 +165,8 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
}
|
||||
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileDeletionConfirmMessage],
|
||||
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
string.Empty);
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace Ryujinx.Ava.UI.Models
|
||||
[ObservableProperty]
|
||||
public partial byte[] Image { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool FirmwareFound { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Name { get; set; } = string.Empty;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
@@ -54,9 +53,6 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Name = FormatTitle();
|
||||
|
||||
// Disable menu animations
|
||||
FAUISettings.SetAnimationsEnabledAtAppLevel(false);
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
|
||||
@@ -370,39 +370,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
|
||||
|
||||
public bool IsSkylanderRequested
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSkylander
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSkylanderActions
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowLoadProgress
|
||||
{
|
||||
get;
|
||||
@@ -1897,46 +1864,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task OpenSkylanderWindow()
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
|
||||
{
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"],
|
||||
},
|
||||
},
|
||||
});
|
||||
if (result.HasValue)
|
||||
{
|
||||
// Open reading stream from the first file.
|
||||
await using var stream = await result.Value.OpenReadAsync();
|
||||
using var streamReader = new BinaryReader(stream);
|
||||
// Reads all the content of file as a text.
|
||||
byte[] data = new byte[1024];
|
||||
var count = streamReader.Read(data, 0, 1024);
|
||||
if (count < 1024)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppHost.Device.System.ScanSkylander(deviceId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveSkylander()
|
||||
{
|
||||
AppHost.Device.System.RemoveSkylander();
|
||||
}
|
||||
|
||||
public void ReloadRenderDocApi()
|
||||
{
|
||||
@@ -2190,6 +2117,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
);
|
||||
|
||||
public string FavoriteStatusText => SelectedApplication?.Favorite == false ? LocaleManager.Instance[LocaleKeys.GameListContextMenuAddToFavorites] : LocaleManager.Instance[LocaleKeys.GameListContextMenuRemoveFromFavorites];
|
||||
|
||||
public static RelayCommand<MainWindowViewModel> CreateApplicationShortcut { get; } =
|
||||
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
|
||||
viewModel => ShortcutHelper.CreateAppShortcut(
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ProfileImageModel> Images { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool FirmwareFound { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Color BackgroundColor { get; set; } = Colors.White;
|
||||
|
||||
@@ -43,17 +46,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
};
|
||||
}
|
||||
|
||||
private int _selectedIndex = -1;
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get;
|
||||
get => _selectedIndex;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
SelectedImage = field == -1
|
||||
? null
|
||||
: Images[field].Data;
|
||||
|
||||
_selectedIndex = value;
|
||||
SelectedImage = value == -1 ? null : Images[value].Data;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SelectedImage));
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public partial class UserProfileImageSelectorViewModel : BaseModel
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool FirmwareFound { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
@@ -9,20 +9,35 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public UserProfileViewModel()
|
||||
{
|
||||
Profiles = [];
|
||||
LostProfiles = [];
|
||||
IsEmpty = !LostProfiles.Any();
|
||||
Profiles = new ObservableCollection<BaseModel>();
|
||||
LostProfiles = new ObservableCollection<UserProfile>();
|
||||
LostProfiles.CollectionChanged += LostProfilesChanged;
|
||||
}
|
||||
|
||||
public ObservableCollection<BaseModel> Profiles { get; set; }
|
||||
public ObservableCollection<BaseModel> Profiles { get; }
|
||||
|
||||
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||
public ObservableCollection<UserProfile> LostProfiles { get; }
|
||||
|
||||
public bool IsEmpty { get; set; }
|
||||
public bool IsEmpty => LostProfiles.Count == 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
LostProfiles.CollectionChanged -= LostProfilesChanged;
|
||||
}
|
||||
|
||||
private void LostProfilesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsEmpty));
|
||||
}
|
||||
|
||||
public void UpdateLostProfiles(ObservableCollection<UserProfile> newProfiles)
|
||||
{
|
||||
LostProfiles.Clear();
|
||||
|
||||
foreach (var profile in newProfiles)
|
||||
LostProfiles.Add(profile);
|
||||
|
||||
OnPropertyChanged(nameof(IsEmpty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private readonly AccountManager _accountManager;
|
||||
|
||||
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
|
||||
public string SaveManagerTitle => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UserProfiles_SaveManagerTitle, _accountManager.LastOpenedUser.Name);
|
||||
|
||||
public UserSaveManagerViewModel(AccountManager accountManager)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Views.Dialog
|
||||
{
|
||||
PrimaryButtonText = string.Empty,
|
||||
SecondaryButtonText = string.Empty,
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
||||
Content = new AboutView { ViewModel = viewModel }
|
||||
};
|
||||
|
||||
|
||||
@@ -47,13 +47,18 @@
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
|
||||
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
|
||||
<ColorPicker
|
||||
<ui:ColorPickerButton
|
||||
Margin="5"
|
||||
IsMoreButtonVisible="False"
|
||||
UseColorPalette="False"
|
||||
UseColorTriangle="False"
|
||||
UseColorWheel="False"
|
||||
ShowAcceptDismissButtons="False"
|
||||
IsAlphaEnabled="False"
|
||||
AttachedToVisualTree="ColorPicker_OnAttachedToVisualTree"
|
||||
ColorChanged="ColorPicker_OnColorChanged"
|
||||
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
|
||||
ColorChanged="ColorPickerButton_OnColorChanged"
|
||||
Color="{Binding LedColor, Mode=TwoWay}">
|
||||
</ColorPicker>
|
||||
</ui:ColorPickerButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
@@ -31,17 +30,19 @@ namespace Ryujinx.UI.Views.Input
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ColorPicker_OnColorChanged(object sender, ColorChangedEventArgs args)
|
||||
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
|
||||
{
|
||||
if (!args.NewColor.HasValue)
|
||||
return;
|
||||
if (!ViewModel.EnableLedChanging)
|
||||
return;
|
||||
if (ViewModel.TurnOffLed)
|
||||
return;
|
||||
|
||||
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.ToUInt32());
|
||||
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
|
||||
}
|
||||
|
||||
private void ColorPicker_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (!ViewModel.EnableLedChanging)
|
||||
return;
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
<MenuItem
|
||||
Command="{Binding ManageProfiles}"
|
||||
Padding="0"
|
||||
Header="{ext:Locale MenuBarOptionsManageUserProfiles}"
|
||||
Header="{ext:Locale UserProfiles_MenuBarOptions_OpenUserProfiles}"
|
||||
Icon="{ext:Icon fa-solid fa-user}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Classes="withCheckbox">
|
||||
@@ -184,22 +184,6 @@
|
||||
IsVisible="{Binding CanScanAmiiboBinaries}"
|
||||
InputGesture="Ctrl + B"
|
||||
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenSkylanderWindow}"
|
||||
AttachedToVisualTree="ScanSkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBarActionsScanSkylander}"
|
||||
Icon="{ext:Icon fa-solid fa-cube}"
|
||||
IsVisible="{Binding ShowSkylanderActions}"
|
||||
InputGesture="Ctrl + S"
|
||||
IsEnabled="{Binding IsSkylanderRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding RemoveSkylander}"
|
||||
AttachedToVisualTree="RemoveSkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBarActionsRemoveSkylander}"
|
||||
Icon="{ext:Icon fa-solid fa-cube}"
|
||||
IsVisible="{Binding ShowSkylanderActions}"
|
||||
InputGesture="Ctrl + D"
|
||||
IsEnabled="{Binding HasSkylander}" />
|
||||
<MenuItem
|
||||
Command="{Binding TakeScreenshot}"
|
||||
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
|
||||
@@ -193,20 +193,6 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
||||
}
|
||||
|
||||
private void ScanSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
ViewModel.IsSkylanderRequested = ViewModel.AppHost.Device.System.SearchingForSkylander(out _);
|
||||
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||
}
|
||||
|
||||
private void RemoveSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
ViewModel.HasSkylander = ViewModel.AppHost.Device.System.HasSkylander(out _);
|
||||
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||
}
|
||||
|
||||
private async Task InstallFileTypes()
|
||||
{
|
||||
ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install();
|
||||
|
||||
@@ -8,107 +8,102 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
Margin="0"
|
||||
MinWidth="500"
|
||||
Padding="0"
|
||||
mc:Ignorable="d"
|
||||
MinWidth="500"
|
||||
Focusable="True"
|
||||
x:DataType="models:TempProfile">
|
||||
<Grid Margin="0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Text="{ext:Locale UserProfilesName}" />
|
||||
<Grid Margin="0,10,0,0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="{ext:Locale UserProfiles_NameLabel}" />
|
||||
<TextBox
|
||||
Name="NameBox"
|
||||
Margin="0,0,0,5"
|
||||
Width="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxLength="{Binding MaxProfileNameLength}"
|
||||
Watermark="{ext:Locale ProfileNameSelectionWatermark}"
|
||||
Watermark="{ext:Locale UserProfiles_ProfileNameSelectionWatermark}"
|
||||
Text="{Binding Name}" />
|
||||
<TextBlock Name="IdText" Text="{ext:Locale UserProfilesUserId}" />
|
||||
<TextBlock Name="IdText" Text="{ext:Locale UserProfiles_UserIdLabel}" />
|
||||
<TextBox
|
||||
Name="IdLabel"
|
||||
Width="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding UserIdString}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<Grid Grid.Column="1">
|
||||
<Border
|
||||
Name="ImageBox"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1">
|
||||
BorderThickness="1"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right">
|
||||
<Panel>
|
||||
<ui:SymbolIcon
|
||||
FontSize="60"
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0"
|
||||
FontSize="70"
|
||||
Width="120"
|
||||
Height="120"
|
||||
Foreground="{DynamicResource AppListHoverBackgroundColor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Symbol="Camera" />
|
||||
<Image
|
||||
Name="ProfileImage"
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Width="120"
|
||||
Height="120"
|
||||
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
|
||||
<Border
|
||||
Margin="2"
|
||||
Height="27"
|
||||
Width="27"
|
||||
CornerRadius="17"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}">
|
||||
<Button
|
||||
Name="ProfileImageButton"
|
||||
MaxHeight="27"
|
||||
MaxWidth="27"
|
||||
MinWidth="27"
|
||||
MinHeight="27"
|
||||
CornerRadius="17"
|
||||
Padding="0">
|
||||
<ui:SymbolIcon Symbol="Edit" />
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="Bottom">
|
||||
<MenuItem
|
||||
Header="{ext:Locale UserProfiles_ProfileImage_Import}"
|
||||
Icon="{ext:Icon fa-solid fa-image}"
|
||||
Click="Import_OnClick" />
|
||||
<MenuItem
|
||||
Header="{ext:Locale UserProfiles_ProfileImage_SelectAvatar}"
|
||||
Icon="{ext:Icon fa-solid fa-floppy-disk}"
|
||||
Click="SelectFirmwareImage_OnClick" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Border>
|
||||
</Panel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Click="BackButton_Click">
|
||||
<Button MinWidth="50" Width="50" Click="BackButton_Click">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Name="DeleteButton"
|
||||
Click="DeleteButton_Click">
|
||||
<TextBlock Text="{ext:Locale UserProfilesDelete}" />
|
||||
<Button Name="DeleteButton" Click="DeleteButton_Click">
|
||||
<TextBlock Text="{ext:Locale UserProfiles_ButtonDelete}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="ChangePictureButton"
|
||||
Click="ChangePictureButton_Click">
|
||||
<TextBlock Text="{ext:Locale UserProfilesChangeProfileImage}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="AddPictureButton"
|
||||
Click="ChangePictureButton_Click">
|
||||
<TextBlock Text="{ext:Locale UserProfilesSetProfileImage}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
Click="SaveButton_Click">
|
||||
<TextBlock Text="{ext:Locale UserProfilesSave}" />
|
||||
<Button Name="SaveButton" Click="SaveButton_Click">
|
||||
<TextBlock Text="{ext:Locale UserProfiles_ButtonSave}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml.MarkupExtensions;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.VisualTree;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
@@ -8,6 +12,10 @@ using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
@@ -15,90 +23,75 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
public partial class UserEditorView : RyujinxControl<TempProfile>
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
private ContentManager _contentManager;
|
||||
private UserProfile _profile;
|
||||
private TempProfile _tempProfile;
|
||||
private bool _isNewUser;
|
||||
|
||||
public static uint MaxProfileNameLength => 0x20;
|
||||
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
|
||||
|
||||
public string UserEditorTitle => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UserProfiles_UserEditorTitle, _profile.Name);
|
||||
public UserEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
if (!Program.PreviewerDetached)
|
||||
return;
|
||||
|
||||
if (arg.NavigationMode == NavigationMode.New)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||
_isNewUser = isNewUser;
|
||||
_profile = profile;
|
||||
ViewModel = new TempProfile(_profile);
|
||||
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) =
|
||||
((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||
|
||||
_parent = parent;
|
||||
break;
|
||||
}
|
||||
_parent = parent;
|
||||
_profile = profile;
|
||||
_isNewUser = isNewUser;
|
||||
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
|
||||
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
|
||||
DataValidationErrors.ClearErrors(NameBox);
|
||||
DataValidationErrors.ClearErrors(ImageBox);
|
||||
ImageBox.Bind(Border.BorderBrushProperty, new DynamicResourceExtension("AppListHoverBackgroundColor"));
|
||||
|
||||
AddPictureButton.IsVisible = _isNewUser;
|
||||
ChangePictureButton.IsVisible = !_isNewUser;
|
||||
IdLabel.IsVisible = _profile != null;
|
||||
IdText.IsVisible = _profile != null;
|
||||
if (!_isNewUser && IsDeletable)
|
||||
{
|
||||
DeleteButton.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteButton.IsVisible = false;
|
||||
}
|
||||
ViewModel = new TempProfile(_profile);
|
||||
_tempProfile = ViewModel;
|
||||
|
||||
_contentManager = _parent.ContentManager;
|
||||
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
|
||||
}
|
||||
|
||||
((ContentDialog)_parent.Parent).Title =
|
||||
$"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - " +
|
||||
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserProfiles_UserEditorTitleNewUser] : UserEditorTitle)}";
|
||||
|
||||
bool hasProfile = _profile != null;
|
||||
IdLabel.IsVisible = hasProfile;
|
||||
IdText.IsVisible = hasProfile;
|
||||
|
||||
DeleteButton.IsVisible = !_isNewUser && IsDeletable;
|
||||
}
|
||||
|
||||
private async void BackButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isNewUser)
|
||||
bool hasUnsavedChanges =
|
||||
_isNewUser
|
||||
? (ViewModel.Name != string.Empty || ViewModel.Image != null)
|
||||
: (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image);
|
||||
|
||||
if (hasUnsavedChanges)
|
||||
{
|
||||
if (ViewModel.Name != string.Empty || ViewModel.Image != null)
|
||||
{
|
||||
if (await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool confirm = await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileUnsavedChangesTitle],
|
||||
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileUnsavedChangesMessage],
|
||||
LocaleManager.Instance[LocaleKeys.UserProfiles_DialogUserProfileUnsavedChangesSubMessage]);
|
||||
|
||||
if (confirm)
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image)
|
||||
{
|
||||
if (await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesSubMessage]))
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
_parent?.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,18 +103,28 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
private void SaveButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DataValidationErrors.ClearErrors(NameBox);
|
||||
DataValidationErrors.ClearErrors(ImageBox);
|
||||
ImageBox.Bind(Border.BorderBrushProperty, new DynamicResourceExtension("AppListHoverBackgroundColor"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ViewModel.Name))
|
||||
bool nameEmpty = string.IsNullOrWhiteSpace(ViewModel.Name);
|
||||
bool imageMissing = ViewModel.Image == null;
|
||||
|
||||
if (nameEmpty && imageMissing)
|
||||
{
|
||||
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfiles_EmptyNameError]));
|
||||
DataValidationErrors.SetError(ImageBox, new DataValidationException(""));
|
||||
ImageBox.BorderBrush = Brush.Parse("#ff99a4");
|
||||
return;
|
||||
}
|
||||
else if (nameEmpty)
|
||||
{
|
||||
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
|
||||
|
||||
DataValidationErrors.SetError(NameBox,new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfiles_EmptyNameError]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ViewModel.Image == null)
|
||||
else if (imageMissing)
|
||||
{
|
||||
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
|
||||
|
||||
DataValidationErrors.SetError(ImageBox, new DataValidationException(""));
|
||||
ImageBox.BorderBrush = Brush.Parse("#ff99a4");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,6 +133,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
_profile.Name = ViewModel.Name;
|
||||
_profile.Image = ViewModel.Image;
|
||||
_profile.UpdateState();
|
||||
|
||||
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
|
||||
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
|
||||
}
|
||||
@@ -145,17 +149,80 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
_parent?.GoBack();
|
||||
}
|
||||
|
||||
public void SelectProfileImage()
|
||||
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
|
||||
}
|
||||
|
||||
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_profile != null || _isNewUser)
|
||||
if (ViewModel.FirmwareFound)
|
||||
{
|
||||
SelectProfileImage();
|
||||
_parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _tempProfile));
|
||||
}
|
||||
}
|
||||
|
||||
private async void Import_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)this.GetVisualRoot()!;
|
||||
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfiles_SupportedImageFormatDialogTitle],
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"],
|
||||
AppleUniformTypeIdentifiers = ["public.jpeg", "public.png", "com.microsoft.bmp"],
|
||||
MimeTypes = ["image/jpeg", "image/png", "image/bmp"],
|
||||
},
|
||||
new("JPG")
|
||||
{
|
||||
Patterns = ["*.jpg"],
|
||||
AppleUniformTypeIdentifiers = ["public.jpeg"],
|
||||
MimeTypes = ["image/jpeg"],
|
||||
},
|
||||
new("JPEG")
|
||||
{
|
||||
Patterns = ["*.jpeg"],
|
||||
AppleUniformTypeIdentifiers = ["public.jpeg"],
|
||||
MimeTypes = ["image/jpeg"],
|
||||
},
|
||||
new("PNG")
|
||||
{
|
||||
Patterns = ["*.png"],
|
||||
AppleUniformTypeIdentifiers = ["public.png"],
|
||||
MimeTypes = ["image/png"],
|
||||
},
|
||||
new("BMP")
|
||||
{
|
||||
Patterns = ["*.bmp"],
|
||||
AppleUniformTypeIdentifiers = ["com.microsoft.bmp"],
|
||||
MimeTypes = ["image/bmp"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.Count == 0 || DataContext is not TempProfile temp)
|
||||
return;
|
||||
|
||||
temp.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
|
||||
|
||||
if (_profile != null)
|
||||
_profile.Image = temp.Image;
|
||||
}
|
||||
|
||||
private static byte[] ProcessProfileImage(byte[] buffer)
|
||||
{
|
||||
using SKBitmap bitmap = SKBitmap.Decode(buffer);
|
||||
SKBitmap resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
|
||||
|
||||
using MemoryStream streamJpg = new();
|
||||
|
||||
if (resizedBitmap != null)
|
||||
{
|
||||
using SKImage image = SKImage.FromBitmap(resizedBitmap);
|
||||
using SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
|
||||
dataJpeg.SaveTo(streamJpg);
|
||||
}
|
||||
|
||||
return streamJpg.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,81 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserFirmwareAvatarSelectorView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="578"
|
||||
mc:Ignorable="d"
|
||||
Width="528"
|
||||
d:DesignWidth="578"
|
||||
d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserFirmwareAvatarSelectorView"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel"
|
||||
Focusable="True">
|
||||
Focusable="True"
|
||||
x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserFirmwareAvatarSelectorViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto">
|
||||
<ListBox
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto">
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
BorderThickness="0"
|
||||
SelectedIndex="{Binding SelectedIndex}"
|
||||
Height="400"
|
||||
ItemsSource="{Binding Images}"
|
||||
Padding="2.5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
CornerRadius="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel
|
||||
Orientation="Horizontal"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Width" Value="85" />
|
||||
<Setter Property="MaxWidth" Value="85" />
|
||||
<Setter Property="MinWidth" Value="85" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator">
|
||||
<Setter Property="MinHeight" Value="70" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Panel
|
||||
Background="{Binding BackgroundColor}"
|
||||
Margin="5">
|
||||
<Image Source="{Binding Data, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
VerticalAlignment="Stretch">
|
||||
<ListBox
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
SelectedIndex="{Binding SelectedIndex}"
|
||||
Height="400"
|
||||
ItemsSource="{Binding Images}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel
|
||||
Orientation="Horizontal"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Width" Value="85" />
|
||||
<Setter Property="MaxWidth" Value="85" />
|
||||
<Setter Property="MinWidth" Value="85" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator">
|
||||
<Setter Property="MinHeight" Value="70" />
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Panel
|
||||
Background="{Binding BackgroundColor}"
|
||||
Margin="5">
|
||||
<Image Source="{Binding Data, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="3"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
HorizontalAlignment="Left">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Height="35"
|
||||
Height="37"
|
||||
Click="GoBack">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
@@ -76,23 +84,29 @@
|
||||
Grid.Row="3"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
HorizontalAlignment="Right">
|
||||
<ColorPicker
|
||||
<ui:ColorPickerButton
|
||||
FlyoutPlacement="Top"
|
||||
IsMoreButtonVisible="False"
|
||||
UseColorPalette="False"
|
||||
UseColorTriangle="False"
|
||||
UseColorWheel="False"
|
||||
ShowAcceptDismissButtons="False"
|
||||
IsAlphaEnabled="False"
|
||||
Color="{Binding BackgroundColor, Mode=TwoWay}"
|
||||
Name="ColorButton">
|
||||
<ColorPicker.Styles>
|
||||
<ui:ColorPickerButton.Styles>
|
||||
<Style Selector="Grid#Root > DockPanel > Grid">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
</ColorPicker.Styles>
|
||||
</ColorPicker>
|
||||
</ui:ColorPickerButton.Styles>
|
||||
</ui:ColorPickerButton>
|
||||
<Button
|
||||
Content="{ext:Locale AvatarChoose}"
|
||||
Height="35"
|
||||
Name="ChooseButton"
|
||||
Click="ChooseButton_OnClick" />
|
||||
Height="37"
|
||||
Click="ChooseButton_OnClick">
|
||||
<TextBlock Text="{ext:Locale UserProfiles_ButtonChooseAvatar}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SkiaSharp;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
@@ -14,44 +17,53 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
private TempProfile _profile;
|
||||
public ContentManager ContentManager { get; private set; }
|
||||
public UserFirmwareAvatarSelectorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new UserFirmwareAvatarSelectorViewModel();
|
||||
DataContext = ViewModel;
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
public UserFirmwareAvatarSelectorView(ContentManager contentManager)
|
||||
{
|
||||
ContentManager = contentManager;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public UserFirmwareAvatarSelectorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
ViewModel = new UserFirmwareAvatarSelectorViewModel();
|
||||
DataContext = ViewModel;
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) => NavigatedTo(e), RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
if (!Program.PreviewerDetached)
|
||||
return;
|
||||
|
||||
if (arg.NavigationMode != NavigationMode.New)
|
||||
return;
|
||||
|
||||
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
||||
ContentManager = _parent.ContentManager;
|
||||
|
||||
((ContentDialog)_parent.Parent).Title =
|
||||
$"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - " +
|
||||
$"{LocaleManager.Instance[LocaleKeys.UserProfiles_SelectAvatarTitle]}";
|
||||
|
||||
ViewModel.SelectedIndex = -1;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
if (arg.NavigationMode == NavigationMode.New)
|
||||
bool found = ContentManager.GetCurrentFirmwareVersion() != null;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
||||
ContentManager = _parent.ContentManager;
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ViewModel = new UserFirmwareAvatarSelectorViewModel();
|
||||
}
|
||||
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
}
|
||||
ViewModel.FirmwareFound = found;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public ContentManager ContentManager { get; private set; }
|
||||
|
||||
private void GoBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.GoBack();
|
||||
@@ -59,32 +71,31 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
|
||||
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.SelectedImage != null)
|
||||
if (ViewModel.SelectedImage == null)
|
||||
return;
|
||||
|
||||
using MemoryStream streamJpg = new();
|
||||
using SKBitmap bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
|
||||
using SKBitmap newBitmap = new(bitmap.Width, bitmap.Height);
|
||||
|
||||
using (SKCanvas canvas = new(newBitmap))
|
||||
{
|
||||
using MemoryStream streamJpg = new();
|
||||
using SKBitmap bitmap = SKBitmap.Decode(ViewModel.SelectedImage);
|
||||
using SKBitmap newBitmap = new(bitmap.Width, bitmap.Height);
|
||||
|
||||
using (SKCanvas canvas = new(newBitmap))
|
||||
{
|
||||
canvas.Clear(new SKColor(
|
||||
ViewModel.BackgroundColor.R,
|
||||
ViewModel.BackgroundColor.G,
|
||||
ViewModel.BackgroundColor.B,
|
||||
ViewModel.BackgroundColor.A));
|
||||
canvas.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
using (SKImage image = SKImage.FromBitmap(newBitmap))
|
||||
using (SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
|
||||
{
|
||||
dataJpeg.SaveTo(streamJpg);
|
||||
}
|
||||
|
||||
_profile.Image = streamJpg.ToArray();
|
||||
|
||||
_parent.GoBack();
|
||||
canvas.Clear(new SKColor(
|
||||
ViewModel.BackgroundColor.R,
|
||||
ViewModel.BackgroundColor.G,
|
||||
ViewModel.BackgroundColor.B,
|
||||
ViewModel.BackgroundColor.A));
|
||||
canvas.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
using (SKImage image = SKImage.FromBitmap(newBitmap))
|
||||
using (SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100))
|
||||
{
|
||||
dataJpeg.SaveTo(streamJpg);
|
||||
}
|
||||
|
||||
_profile.Image = streamJpg.ToArray();
|
||||
_parent.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:viewModles="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
Focusable="True"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView"
|
||||
x:DataType="viewModles:UserProfileImageSelectorViewModel"
|
||||
Width="500"
|
||||
d:DesignWidth="500">
|
||||
<Design.DataContext>
|
||||
<viewModles:UserProfileImageSelectorViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center" RowDefinitions="Auto,70,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Left"
|
||||
TextAlignment="Start"
|
||||
Text="{ext:Locale ProfileImageSelectionNote}" />
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
Click="GoBack">
|
||||
<ui:SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Name="Import"
|
||||
Click="Import_OnClick">
|
||||
<TextBlock Text="{ext:Locale ProfileImageSelectionImportImage}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="SelectFirmwareImage"
|
||||
IsEnabled="{Binding FirmwareFound}"
|
||||
Click="SelectFirmwareImage_OnClick">
|
||||
<TextBlock Text="{ext:Locale ProfileImageSelectionSelectAvatar}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,118 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.VisualTree;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
public partial class UserProfileImageSelectorView : RyujinxControl<UserProfileImageSelectorViewModel>
|
||||
{
|
||||
private ContentManager _contentManager;
|
||||
private NavigationDialogHost _parent;
|
||||
private TempProfile _profile;
|
||||
|
||||
public UserProfileImageSelectorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
|
||||
_contentManager = _parent.ContentManager;
|
||||
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}";
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
DataContext = ViewModel = new UserProfileImageSelectorViewModel();
|
||||
ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null;
|
||||
}
|
||||
|
||||
break;
|
||||
case NavigationMode.Back:
|
||||
if (_profile.Image != null)
|
||||
{
|
||||
_parent.GoBack();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Import_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IReadOnlyList<IStorageFile> result = await ((Window)this.GetVisualRoot()!).StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"],
|
||||
AppleUniformTypeIdentifiers = ["public.jpeg", "public.png", "com.microsoft.bmp"],
|
||||
MimeTypes = ["image/jpeg", "image/png", "image/bmp"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.Count > 0)
|
||||
{
|
||||
_profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
|
||||
_parent.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
private void GoBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent.GoBack();
|
||||
}
|
||||
|
||||
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.FirmwareFound)
|
||||
{
|
||||
_parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ProcessProfileImage(byte[] buffer)
|
||||
{
|
||||
using SKBitmap bitmap = SKBitmap.Decode(buffer);
|
||||
|
||||
SKBitmap resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
|
||||
|
||||
using MemoryStream streamJpg = new();
|
||||
|
||||
if (resizedBitmap != null)
|
||||
{
|
||||
using SKImage image = SKImage.FromBitmap(resizedBitmap);
|
||||
using SKData dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100);
|
||||
|
||||
dataJpeg.SaveTo(streamJpg);
|
||||
}
|
||||
|
||||
return streamJpg.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,64 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserRecovererView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
d:DesignWidth="550"
|
||||
d:DesignHeight="450"
|
||||
mc:Ignorable="d"
|
||||
Width="500"
|
||||
Height="400"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserRecovererView"
|
||||
x:DataType="viewModels:UserProfileViewModel"
|
||||
Focusable="True">
|
||||
Focusable="True"
|
||||
x:DataType="viewModels:UserProfileViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch" RowDefinitions="*,Auto">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
|
||||
<Border
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
Grid.Row="0">
|
||||
Grid.Row="0"
|
||||
Padding="2.5">
|
||||
<Panel>
|
||||
<ListBox
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ItemsSource="{Binding LostProfiles}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="10" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
CornerRadius="4">
|
||||
<Grid Margin="0" ColumnDefinitions="*,Auto">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="5"
|
||||
Text="{Binding UserId}"
|
||||
TextAlignment="Start"
|
||||
TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Click="Recover"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{ext:Locale Recover}"/>
|
||||
Margin="5"
|
||||
Command="{Binding Recover}"
|
||||
CommandParameter="{Binding}">
|
||||
<TextBlock Text="{ext:Locale UserProfiles_RecoverProfile}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
@@ -56,12 +67,12 @@
|
||||
<TextBlock
|
||||
IsVisible="{Binding IsEmpty}"
|
||||
TextAlignment="Center"
|
||||
Text="{ext:Locale UserProfilesRecoverEmptyList}"/>
|
||||
Text="{ext:Locale UserProfiles_RecoverProfile_EmptyList}"/>
|
||||
</Panel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
|
||||
@@ -27,11 +27,12 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
case NavigationMode.Back:
|
||||
NavigationDialogHost parent = (NavigationDialogHost)arg.Parameter;
|
||||
|
||||
_parent = parent;
|
||||
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}";
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfiles_RecoverLostProfiles]}";
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -42,10 +43,5 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
_parent?.GoBack();
|
||||
}
|
||||
|
||||
private void Recover(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_parent?.RecoverLostAccounts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
@@ -13,7 +14,6 @@
|
||||
d:DesignHeight="500"
|
||||
Height="450"
|
||||
Width="550"
|
||||
x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView"
|
||||
x:DataType="viewModels:UserSaveManagerViewModel"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
@@ -22,9 +22,9 @@
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,5"
|
||||
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*">
|
||||
<StackPanel
|
||||
Margin="0,0,0,10"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -33,9 +33,9 @@
|
||||
HorizontalContentAlignment="Left"
|
||||
MinWidth="100">
|
||||
<ComboBoxItem
|
||||
Content="{ext:Locale Name}" />
|
||||
Content="{ext:Locale UserProfiles_ManageSaves_SortByName}" />
|
||||
<ComboBoxItem
|
||||
Content="{ext:Locale Size}" />
|
||||
Content="{ext:Locale UserProfiles_ManageSaves_SortBySize}" />
|
||||
<ComboBox.Styles>
|
||||
<Style Selector="ContentControl#ContentPresenter">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
@@ -46,9 +46,9 @@
|
||||
HorizontalContentAlignment="Left"
|
||||
MinWidth="150">
|
||||
<ComboBoxItem
|
||||
Content="{ext:Locale OrderAscending}" />
|
||||
Content="{ext:Locale UserProfiles_ManageSaves_SortOrderAscending}" />
|
||||
<ComboBoxItem
|
||||
Content="{ext:Locale OrderDescending}" />
|
||||
Content="{ext:Locale UserProfiles_ManageSaves_SortOrderDescending}" />
|
||||
<ComboBox.Styles>
|
||||
<Style Selector="ContentControl#ContentPresenter">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
@@ -59,18 +59,18 @@
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="10,0, 0, 0" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Text="{ext:Locale Search}" VerticalAlignment="Center" />
|
||||
Margin="20,0,0,10" ColumnDefinitions="Auto,*">
|
||||
<TextBox
|
||||
Margin="10,0,0,0"
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Search}" />
|
||||
Text="{Binding Search}"
|
||||
Watermark="{ext:Locale UserProfiles_ManageSaves_Search}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,5"
|
||||
Padding="2.5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
CornerRadius="5"
|
||||
@@ -84,7 +84,7 @@
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="10" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
|
||||
@@ -168,7 +168,7 @@
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
}
|
||||
|
||||
DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager);
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}";
|
||||
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle]} - {ViewModel.SaveManagerTitle}";
|
||||
|
||||
Task.Run(LoadSaves);
|
||||
}
|
||||
@@ -127,8 +127,8 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
{
|
||||
if (button.DataContext is SaveModel saveModel)
|
||||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave],
|
||||
LocaleManager.Instance[LocaleKeys.IrreversibleActionNote],
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.UserProfiles_DeleteSaveNote],
|
||||
LocaleManager.Instance[LocaleKeys.UserProfiles_IrreversibleActionNote],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
string.Empty);
|
||||
|
||||
@@ -4,43 +4,39 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
MinWidth="500"
|
||||
Focusable="True"
|
||||
x:DataType="viewModels:UserProfileViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
|
||||
<Border
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1">
|
||||
<Border Padding="-3" BorderThickness="0">
|
||||
<ListBox
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="ProfilesList_SelectionChanged"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Profiles}">
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectionChanged="ProfilesList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Margin" Value="5 5 0 5" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
</Style>
|
||||
<Style Selector="Rectangle#SelectionIndicator">
|
||||
@@ -54,6 +50,7 @@
|
||||
PointerEntered="Grid_PointerEntered"
|
||||
PointerExited="Grid_OnPointerExited">
|
||||
<Border
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
@@ -65,18 +62,20 @@
|
||||
<Image
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0,0,0,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxWidth="90"
|
||||
Text="{Binding Name}"
|
||||
Height="30"
|
||||
MaxWidth="90"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxLines="2"
|
||||
Margin="5" />
|
||||
TextWrapping="Wrap"
|
||||
MaxLines="2" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border
|
||||
@@ -89,10 +88,10 @@
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding IsPointerOver}">
|
||||
<Button
|
||||
MaxHeight="24"
|
||||
MaxWidth="24"
|
||||
MinHeight="24"
|
||||
MinWidth="24"
|
||||
MaxHeight="24"
|
||||
MaxWidth="24"
|
||||
CornerRadius="12"
|
||||
Padding="0"
|
||||
Click="EditUser">
|
||||
@@ -104,8 +103,8 @@
|
||||
<DataTemplate
|
||||
DataType="viewModels:BaseModel">
|
||||
<Panel
|
||||
Height="118"
|
||||
Width="96">
|
||||
Height="146"
|
||||
Width="106">
|
||||
<Button
|
||||
MinWidth="50"
|
||||
MinHeight="50"
|
||||
@@ -119,11 +118,6 @@
|
||||
Click="AddUser">
|
||||
<ui:SymbolIcon Symbol="Add" />
|
||||
</Button>
|
||||
<Panel.Styles>
|
||||
<Style Selector="Panel">
|
||||
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}"/>
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
@@ -131,27 +125,29 @@
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button
|
||||
Click="ManageSaves">
|
||||
<TextBlock Text="{ext:Locale UserProfilesManageSaves}" />
|
||||
<TextBlock Text="{ext:Locale UserProfiles_ManageSaves}" />
|
||||
</Button>
|
||||
<Button
|
||||
Click="RecoverLostAccounts">
|
||||
<TextBlock Text="{ext:Locale UserProfilesRecoverLostAccounts}" />
|
||||
<TextBlock
|
||||
Text="{ext:Locale UserProfiles_RecoverLostProfiles}"
|
||||
ToolTip.Tip="{ext:Locale UserProfiles_RecoverLostProfiles_ToolTip}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
Margin="0,30,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale UserProfilesClose}" />
|
||||
<TextBlock Text="{ext:Locale UserProfiles_ButtonClose}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
|
||||
if (arg.NavigationMode == NavigationMode.Back)
|
||||
{
|
||||
((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle];
|
||||
((ContentDialog)_parent.Parent).Title = LocaleManager.Instance[LocaleKeys.UserProfiles_WindowTitle];
|
||||
}
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Avalonia.Platform.Storage;
|
||||
using Gommon;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -13,42 +11,29 @@ namespace Ryujinx.Ava.Utilities
|
||||
extension(IStorageProvider storageProvider)
|
||||
{
|
||||
public Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
|
||||
CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false)))
|
||||
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false))
|
||||
.Then(folders => folders.FindFirst());
|
||||
|
||||
|
||||
public Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
|
||||
CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false)))
|
||||
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false))
|
||||
.Then(files => files.FindFirst());
|
||||
|
||||
|
||||
public Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
|
||||
CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true)))
|
||||
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true))
|
||||
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
|
||||
|
||||
|
||||
public Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
|
||||
CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true)))
|
||||
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true))
|
||||
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
|
||||
}
|
||||
|
||||
private static async Task<T> CoreDumpable<T>(Func<Task<T>> picker)
|
||||
{
|
||||
OsUtils.SetCoreDumpable(true);
|
||||
try
|
||||
{
|
||||
return await picker();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!Program.CoreDumpArg)
|
||||
OsUtils.SetCoreDumpable(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
|
||||
{
|
||||
if (openOptions is null)
|
||||
return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
|
||||
|
||||
openOptions.AllowMultiple = allowMultiple;
|
||||
|
||||
return openOptions;
|
||||
}
|
||||
|
||||
@@ -58,6 +43,7 @@ namespace Ryujinx.Ava.Utilities
|
||||
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
|
||||
|
||||
openOptions.AllowMultiple = allowMultiple;
|
||||
|
||||
return openOptions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
@@ -10,7 +11,7 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
|
||||
{
|
||||
internal WindowsSystemInfo()
|
||||
{
|
||||
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameFromRegistry()} ; {LogicalCoreCount} logical";
|
||||
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow
|
||||
(RamTotal, RamAvailable) = GetMemoryStats();
|
||||
}
|
||||
|
||||
@@ -27,26 +28,25 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
private static string GetCpuNameFromRegistry()
|
||||
private static string GetCpuNameWMI()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
|
||||
ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor");
|
||||
|
||||
return key?.GetValue("ProcessorNameString")?.ToString()?.Trim();
|
||||
}
|
||||
catch (Exception ex)
|
||||
if (cpuObjs != null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Registry CPU name lookup failed: {ex.Message}");
|
||||
|
||||
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
|
||||
foreach (ManagementBaseObject cpuObj in cpuObjs)
|
||||
{
|
||||
return cpuObj["Name"].ToString().Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MemoryStatusEx()
|
||||
private struct MemoryStatusEx
|
||||
{
|
||||
public uint Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
|
||||
public uint Length;
|
||||
public uint MemoryLoad;
|
||||
public ulong TotalPhys;
|
||||
public ulong AvailPhys;
|
||||
@@ -55,10 +55,33 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
|
||||
public ulong TotalVirtual;
|
||||
public ulong AvailVirtual;
|
||||
public ulong AvailExtendedVirtual;
|
||||
|
||||
public MemoryStatusEx()
|
||||
{
|
||||
Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
|
||||
}
|
||||
}
|
||||
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer);
|
||||
|
||||
private static ManagementObjectCollection GetWMIObjects(string scope, string query)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ManagementObjectSearcher(scope, query).Get();
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user