mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-30 01:29:48 -06:00
Compare commits
25 Commits
1.3.1
...
Canary-1.3
Author | SHA1 | Date | |
---|---|---|---|
1c411082db | |||
be7285f7fc | |||
6f59a14ec6 | |||
d85ec0eff5 | |||
02ad827a94 | |||
6e7220eb28 | |||
7a3a21b0c0 | |||
92440afcd7 | |||
df3b5b4bd8 | |||
11cc80f7fc | |||
f6c1e97110 | |||
e18e27fbc5 | |||
f1eb911d25 | |||
e1c0b3acab | |||
1d4928e859 | |||
28b8dc14c7 | |||
21971a2be7 | |||
321bdecbc2 | |||
6904d6a461 | |||
d8e3ab3974 | |||
9b429afbb4 | |||
b71a4cb745 | |||
844b1d8b9a | |||
c4ea0e0df2 | |||
0ae536a757 |
19
BuildAndPushLibraries.sh
Normal file
19
BuildAndPushLibraries.sh
Normal file
@ -0,0 +1,19 @@
|
||||
function pub {
|
||||
dotnet publish -c release
|
||||
}
|
||||
|
||||
function package {
|
||||
cd src/$1
|
||||
pub
|
||||
mv bin/Release/$1.1.0.0.nupkg ../../pkgs/$1.1.0.0.nupkg
|
||||
cd ../../
|
||||
}
|
||||
|
||||
rm -rf pkgs
|
||||
mkdir pkgs
|
||||
|
||||
package ARMeilleure
|
||||
package Ryujinx.Common
|
||||
package Ryujinx.Memory
|
||||
|
||||
dotnet nuget push pkgs/*.nupkg --source RyubingPkgs
|
@ -23,7 +23,6 @@
|
||||
<PackageVersion Include="DynamicData" Version="9.0.4" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
|
||||
@ -41,6 +40,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.20.0-alpha.103" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
|
@ -1947,6 +1947,31 @@
|
||||
"zh_TW": "LDN 上在線的玩家數量: {0}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "GameListLabelTotalTimePlayed",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Gesamte Spielzeit: {0}",
|
||||
"el_GR": "Συνολικός χρόνος παιχνιδιού: {0}",
|
||||
"en_US": "Total Play Time: {0}",
|
||||
"es_ES": "Tiempo total de juego: {0}",
|
||||
"fr_FR": "Temps de jeu total: {0}",
|
||||
"he_IL": "",
|
||||
"it_IT": "Tempo totale di gioco: {0}",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "Całkowity czas gry: {0}",
|
||||
"pt_BR": "Tempo total de jogo: {0}",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "Toplam Oyun Süresi: {0}",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "总游戏时间: {0}",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "GameListContextMenuOpenUserSaveDirectory",
|
||||
"Translations": {
|
||||
@ -5393,7 +5418,7 @@
|
||||
"th_TH": "ข้ามหน้าต่างโต้ตอบ 'จัดการโปรไฟล์ผู้ใช้งาน'",
|
||||
"tr_TR": "'Kullanıcı Profillerini Yönet' iletişim kutusunu atla",
|
||||
"uk_UA": "Пропустити діалог 'Керувати профілями користувачів'",
|
||||
"zh_CN": "跳过对话框“管理用户账户”",
|
||||
"zh_CN": "跳过 “管理用户账户” 对话框",
|
||||
"zh_TW": "略過對話框「管理使用者設定檔」"
|
||||
}
|
||||
},
|
||||
@ -7022,6 +7047,31 @@
|
||||
"zh_TW": "輸入裝置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsWaitingConnectDevice",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Configuration found:\n\nName:\t{0}\nGUID:\t{1}\n\n Waiting for controller connection...",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "발견된 구성 :\n\n이름 : \t{0}\n가이드 : \t{1}\n\n 컨트롤러 연결 대기 중...",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "发现配置:\n\n名称:\t{0}\nGUID:\t{1}\n\n 正在等待控制器连接...",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsRefresh",
|
||||
"Translations": {
|
||||
@ -7322,6 +7372,81 @@
|
||||
"zh_TW": "新增"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsModifiedNotification",
|
||||
"Translations": {
|
||||
"ar_SA": "(تم التعديل!)",
|
||||
"de_DE": "(modifiziert!)",
|
||||
"el_GR": "(τροποποιημένο!)",
|
||||
"en_US": "(Modified!)",
|
||||
"es_ES": "(modificado!)",
|
||||
"fr_FR": "(modifié!)",
|
||||
"he_IL": "(שונה!)",
|
||||
"it_IT": "(modificato!)",
|
||||
"ja_JP": "(変更済み!)",
|
||||
"ko_KR": "(수정됨!)",
|
||||
"no_NO": "(modifisert!)",
|
||||
"pl_PL": "(zmodyfikowane!)",
|
||||
"pt_BR": "(modificado!)",
|
||||
"ru_RU": "(изменено!)",
|
||||
"sv_SE": "(ändrad!)",
|
||||
"th_TH": "(แก้ไขแล้ว!)",
|
||||
"tr_TR": "(değiştirildi!)",
|
||||
"uk_UA": "(модифіковано!)",
|
||||
"zh_CN": "(已修改!)",
|
||||
"zh_TW": "(已修改!)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsDisableDeviceForSaving",
|
||||
"Translations": {
|
||||
"ar_SA": "تم إعداد التحكم.\n\nفي انتظار اتصال وحدة التحكم...",
|
||||
"de_DE": "Steuerung konfiguriert.\n\nWarten auf die Verbindung des Controllers...",
|
||||
"el_GR": "Η διαχείριση έχει ρυθμιστεί.\n\nΑναμένεται σύνδεση του χειριστηρίου...",
|
||||
"en_US": "Control configured.\n\nWaiting for controller connection...",
|
||||
"es_ES": "Control configurado.\n\nEsperando la conexión del controlador...",
|
||||
"fr_FR": "Contrôle configuré.\n\nEn attente de la connexion du contrôleur...",
|
||||
"he_IL": "השליטה הוגדרה.\n\nממתין לחיבור הבקר...",
|
||||
"it_IT": "Controllo configurato.\n\nIn attesa della connessione del controller...",
|
||||
"ja_JP": "コントロールが設定されました。\n\nコントローラーの接続を待っています...",
|
||||
"ko_KR": "제어가 설정되었습니다.\n\n컨트롤러 연결 대기 중...",
|
||||
"no_NO": "Kontroll konfigurert.\n\nVenter på tilkobling av kontroller...",
|
||||
"pl_PL": "Sterowanie skonfigurowane.\n\nOczekiwanie na połączenie kontrolera...",
|
||||
"pt_BR": "Controle configurado.\n\nAguardando conexão do controle...",
|
||||
"ru_RU": "Управление настроено.\n\nОжидается подключение контроллера...",
|
||||
"sv_SE": "Kontroll konfigurerad.\n\nVäntar på anslutning av kontrollen...",
|
||||
"th_TH": "การควบคุมได้รับการตั้งค่าแล้ว\n\nกำลังรอการเชื่อมต่อคอนโทรลเลอร์...",
|
||||
"tr_TR": "Kontrol yapılandırıldı.\n\nKontrolcü bağlantısı bekleniyor...",
|
||||
"uk_UA": "Керування налаштовано.\n\nОчікується підключення контролера...",
|
||||
"zh_CN": "已配置控制器。\n\n正在等待控制器连接...",
|
||||
"zh_TW": "控制已設定。\n\n等待控制器連接..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsUnlink",
|
||||
"Translations": {
|
||||
"ar_SA": "إلغاء الربط",
|
||||
"de_DE": "Entkoppeln",
|
||||
"el_GR": "Αποσύνδεση",
|
||||
"en_US": "Unlink",
|
||||
"es_ES": "Desvincular",
|
||||
"fr_FR": "Dissocier",
|
||||
"he_IL": "ניתוק קישור",
|
||||
"it_IT": "Scollega",
|
||||
"ja_JP": "リンク解除",
|
||||
"ko_KR": "연결 해제",
|
||||
"no_NO": "Frakoble",
|
||||
"pl_PL": "Odłącz",
|
||||
"pt_BR": "Desvincular",
|
||||
"ru_RU": "Отвязать",
|
||||
"sv_SE": "Koppla från",
|
||||
"th_TH": "ยกเลิกการเชื่อมโยง",
|
||||
"tr_TR": "Bağlantıyı Kes",
|
||||
"uk_UA": "Відв'язати",
|
||||
"zh_CN": "取消绑定",
|
||||
"zh_TW": "解除綁定"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsRemove",
|
||||
"Translations": {
|
||||
@ -11972,6 +12097,31 @@
|
||||
"zh_TW": "儲存設定檔"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsCancelCurrentChangesToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "إلغاء التغييرات الحالية",
|
||||
"de_DE": "Aktuelle Änderungen abbrechen",
|
||||
"el_GR": "Ακύρωση τρεχουσών αλλαγών",
|
||||
"en_US": "Cancel current changes",
|
||||
"es_ES": "Cancelar los cambios actuales",
|
||||
"fr_FR": "Annuler les modifications en cours",
|
||||
"he_IL": "ביטול השינויים הנוכחיים",
|
||||
"it_IT": "Annulla le modifiche correnti",
|
||||
"ja_JP": "現在の変更をキャンセル",
|
||||
"ko_KR": "현재 변경 취소",
|
||||
"no_NO": "Avbryt gjeldende endringer",
|
||||
"pl_PL": "Anuluj bieżące zmiany",
|
||||
"pt_BR": "Cancelar alterações atuais",
|
||||
"ru_RU": "Отменить текущие изменения",
|
||||
"sv_SE": "Avbryt aktuella ändringar",
|
||||
"th_TH": "ยกเลิกการเปลี่ยนแปลงปัจจุบัน",
|
||||
"tr_TR": "Geçerli değişiklikleri iptal et",
|
||||
"uk_UA": "Скасувати поточні зміни",
|
||||
"zh_CN": "取消当前更改",
|
||||
"zh_TW": "取消當前變更"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "MenuBarFileToolsTakeScreenshot",
|
||||
"Translations": {
|
||||
@ -15378,23 +15528,23 @@
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.",
|
||||
"en_US": "Ryujinx is an emulator for the Nintendo Switch™ 1.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitLab or Discord.",
|
||||
"es_ES": "",
|
||||
"fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™.\nObtenez le dernières nouvelles sur le Discord.\nLes développeurs qui veulent contribuer peuvent en savoir plus sur notre GitHub ou Discord.",
|
||||
"fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™ 1.\nObtenez le dernières nouvelles sur le Discord.\nLes développeurs qui veulent contribuer peuvent en savoir plus sur notre GitLab ou Discord.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Ryujinx è un emulatore della console Nintendo Switch™.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitHub.",
|
||||
"it_IT": "Ryujinx è un emulatore della console Nintendo Switch™ 1.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitLab.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "Ryujinx는 Nintendo Switch™용 에뮬레이터입니다.\n모든 최신 소식을 Discord에서 확인하세요.\n기여에 관심이 있는 개발자는 GitHub 또는 Discord에서 자세한 내용을 확인할 수 있습니다.",
|
||||
"no_NO": "Ryujinx er en emulator for Nintendo SwitchTM.\nVennligst støtt oss på Patreon.\nFå alle de siste nyhetene på vår Twitter eller Discord.\nUtviklere som er interessert i å bidra kan finne ut mer på GitHub eller Discord.",
|
||||
"ko_KR": "Ryujinx는 Nintendo Switch™ 1용 에뮬레이터입니다.\n모든 최신 소식을 Discord에서 확인하세요.\n기여에 관심이 있는 개발자는 GitLab 또는 Discord에서 자세한 내용을 확인할 수 있습니다.",
|
||||
"no_NO": "Ryujinx er en emulator for Nintendo Switch™ 1\nVennligst støtt oss på Patreon.\nFå alle de siste nyhetene på vår Twitter eller Discord.\nUtviklere som er interessert i å bidra kan finne ut mer på GitLab eller Discord.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Ryujinx é um emulador de Nintendo Switch™.\nReceba todas as últimas notícias em nosso Discord.\nDesenvolvedores interessados em contribuir podem descobrir mais em nosso GitHub ou Discord.",
|
||||
"ru_RU": "Ryujinx - это эмулятор для Nintendo Switch™.\nПолучайте все последние новости разработки в нашем Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или Discord.",
|
||||
"sv_SE": "Ryujinx är en emulator för Nintendo Switch™.\nFå de senaste nyheterna via vår Discord.\nUtvecklare som är intresserade att bidra kan hitta mer info på vår GitHub eller Discord.",
|
||||
"pt_BR": "Ryujinx é um emulador de Nintendo Switch™ 1.\nReceba todas as últimas notícias em nosso Discord.\nDesenvolvedores interessados em contribuir podem descobrir mais em nosso GitLab ou Discord.",
|
||||
"ru_RU": "Ryujinx - это эмулятор для Nintendo Switch™ 1.\nПолучайте все последние новости разработки в нашем Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitLab или Discord.",
|
||||
"sv_SE": "Ryujinx är en emulator för Nintendo Switch™ 1.\nFå de senaste nyheterna via vår Discord.\nUtvecklare som är intresserade att bidra kan hitta mer info på vår GitLab eller Discord.",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Ryujinx — це емулятор для Nintendo Switch™.\nОстанні новини можна отримати в нашому Discord.\nРозробники, що бажають долучитись до розробки та зробити свій внесок, можуть отримати більше інформації на нашому GitHub або в Discord.",
|
||||
"zh_CN": "Ryujinx 是一个 Nintendo Switch™ 模拟器。\n有兴趣做出贡献的开发者可以在我们的 GitHub 或 Discord 上了解更多信息。\n",
|
||||
"zh_TW": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n關注我們的 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。"
|
||||
"uk_UA": "Ryujinx — це емулятор для Nintendo Switch™ 1.\nОстанні новини можна отримати в нашому Discord.\nРозробники, що бажають долучитись до розробки та зробити свій внесок, можуть отримати більше інформації на нашому GitLab або в Discord.",
|
||||
"zh_CN": "Ryujinx 是一个 Nintendo Switch™ 1 模拟器。\n有兴趣做出贡献的开发者可以在我们的 GitLab 或 Discord 上了解更多信息。\n",
|
||||
"zh_TW": "Ryujinx 是一款 Nintendo Switch™ 1 模擬器。\n關注我們的 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitLab 或 Discord 上了解更多資訊。"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -16818,7 +16968,7 @@
|
||||
"th_TH": "ตัวเลือกนี้จะข้ามหน้าต่าง 'จัดการโปรไฟล์ผู้ใช้งาน' ระหว่างเล่นเกม โดยใช้โปรไฟล์ที่เลือกไว้ล่วงหน้า\n\nการสลับโปรไฟล์สามารถพบได้ใน 'ตั้งค่า' - 'จัดการโปรไฟล์ผู้ใช้งาน' เลือกโปรไฟล์ที่คุณต้องการก่อนโหลดเกม",
|
||||
"tr_TR": "Bu seçenek, oyun sırasında 'Kullanıcı Profillerini Yönet' iletişim kutusunu atlar ve önceden seçilmiş bir profil kullanır.\n\nProfil değiştirme 'Seçenekler' - 'Kullanıcı Profillerini Yönet' bölümünde bulunur. Oyunu yüklemeden önce istediğiniz profili seçin.",
|
||||
"uk_UA": "Ця опція пропускає діалогове вікно 'Керувати профілями користувачів' під час гри, використовуючи попередньо вибраний профіль.\n\nПеремикання профілів можна знайти в 'Налаштування' - 'Керувати профілями користувачів'. Виберіть потрібний профіль перед завантаженням гри.",
|
||||
"zh_CN": "此选项跳过游戏过程中的“管理用户账户”对话框,使用预选的配置。\n\n可以在“设置” - “管理用户账户”中找到配置文件切换。 在加载游戏之前选择所需的配置文件。",
|
||||
"zh_CN": "此选项将跳过在游戏过程中出现的 “管理用户账户” 对话框,使用预选的账户。\n\n可以在“设置” - “管理用户账户”中找到账户切换。 在加载游戏之前选择所需的账户。",
|
||||
"zh_TW": "這個選項跳過遊戲過程中的「管理使用者設定檔」對話框,使用預先選取的設定。\n\n可以在「設定」-「管理使用者設定檔」中找到設定檔切換。 在載入遊戲前選擇您需要的設定檔。"
|
||||
}
|
||||
},
|
||||
@ -18243,7 +18393,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_CN": "{0} FPS ({1}毫秒)",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@ -23125,26 +23275,26 @@
|
||||
{
|
||||
"ID": "AboutChangelogButton",
|
||||
"Translations": {
|
||||
"ar_SA": "عرض سجل التغييرات على غيت هاب",
|
||||
"de_DE": "Changelog in GitHub öffnen",
|
||||
"el_GR": "Προβολή αρχείου αλλαγών στο GitHub",
|
||||
"en_US": "View Changelog on GitHub",
|
||||
"es_ES": "Ver registro de cambios en GitHub",
|
||||
"fr_FR": "Voir le Changelog sur GitHub",
|
||||
"he_IL": "צפה במידע אודות שינויים בגיטהב",
|
||||
"it_IT": "Visualizza changelog su GitHub",
|
||||
"ja_JP": "GitHub で更新履歴を表示",
|
||||
"ko_KR": "GitHub에서 변경 내역 보기",
|
||||
"no_NO": "Vis endringslogg på GitHub",
|
||||
"pl_PL": "Zobacz listę zmian na GitHubie",
|
||||
"pt_BR": "Ver Mudanças no GitHub",
|
||||
"ru_RU": "Показать список изменений на GitHub",
|
||||
"sv_SE": "Visa ändringslogg på GitHub",
|
||||
"th_TH": "ดูประวัติการเปลี่ยนแปลงบน GitHub",
|
||||
"tr_TR": "GitHub'da Değişiklikleri Görüntüle",
|
||||
"uk_UA": "Переглянути журнал змін на GitHub",
|
||||
"zh_CN": "在 Github 上查看更新日志",
|
||||
"zh_TW": "在 GitHub 上檢視更新日誌"
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "View Changelog",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "ด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "查看更新日志",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -24359,7 +24509,7 @@
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "불가능",
|
||||
"ko_KR": "불가",
|
||||
"no_NO": "Ingenting",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Nada",
|
||||
|
@ -601,6 +601,7 @@
|
||||
010060200A4BE000,"Brawlout",ldn-untested;online,playable,2021-06-04 17:35:35
|
||||
0100C1B00E1CA000,"Brawlout Demo",demo,playable,2021-02-13 22:46:53
|
||||
010022C016DC8000,"Breakout: Recharged",slow,ingame,2022-11-06 15:32:57
|
||||
010048A021C40000,"Breakout Beyond",,playable,2025-04-26 19:11:35
|
||||
01000AA013A5E000,"Breathedge",UE4;nvdec,playable,2021-05-06 15:44:28
|
||||
01003D50100F4000,"Breathing Fear",,playable,2020-07-14 15:12:29
|
||||
010026800BB06000,"Brick Breaker",nvdec;online,playable,2020-12-15 18:26:23
|
||||
@ -2028,6 +2029,7 @@
|
||||
0100628004BCE000,"Nights of Azure 2: Bride of the New Moon",crash;nvdec;regression,menus,2022-11-24 16:00:39
|
||||
010042300C4F6000,"Nightshade/百花百狼",nvdec,playable,2020-05-10 19:43:31
|
||||
0100AA0008736000,"Nihilumbra",,playable,2020-05-10 16:00:12
|
||||
01009FA01FF6C000,"Nikoderiko: The Magical World",gpu,ingame,2025-04-26 19:13:31
|
||||
0100D03003F0E000,"Nine Parchments",ldn-untested,playable,2022-08-07 12:32:08
|
||||
0100E2F014F46000,"NINJA GAIDEN Σ",nvdec,playable,2022-11-13 16:27:02
|
||||
0100696014F4A000,"NINJA GAIDEN Σ2",nvdec,playable,2024-07-31 21:53:48
|
||||
|
|
24
nuget.config
24
nuget.config
@ -1,7 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
||||
<add key="RyubingPkgs" value="https://git.ryujinx.app/api/v4/projects/1/packages/nuget/index.json" />
|
||||
</packageSources>
|
||||
|
||||
<!-- Define mappings by adding package patterns beneath the target source. -->
|
||||
<!-- Ryujinx.LibHac packages will be restored from LibHacAlpha,
|
||||
everything else from nuget.org. -->
|
||||
<packageSourceMapping>
|
||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
<packageSource key="LibHacAlpha">
|
||||
<package pattern="Ryujinx.LibHac" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7007; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7008; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@ -566,9 +566,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
if (AreCarriersEmpty() || ContainsBlacklistedFunctions())
|
||||
{
|
||||
_infosStream.SetLength(0);
|
||||
_relocsStream.SetLength(0);
|
||||
_unwindInfosStream.SetLength(0);
|
||||
ResetCarriersIfNeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -875,7 +873,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
Debug.Assert(Profiler.IsAddressInStaticCodeRange(address));
|
||||
|
||||
TranslatedFunction func = translator.Translate(address, executionMode, highCq);
|
||||
TranslatedFunction func = translator.Translate(address, executionMode, highCq, pptcTranslation: true);
|
||||
|
||||
if (func == null)
|
||||
{
|
||||
|
@ -219,7 +219,7 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
|
||||
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false, bool pptcTranslation = false)
|
||||
{
|
||||
ArmEmitterContext context = new(
|
||||
Memory,
|
||||
@ -246,7 +246,7 @@ namespace ARMeilleure.Translation
|
||||
context.Branch(context.GetLabel(address));
|
||||
}
|
||||
|
||||
ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter);
|
||||
ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter, pptcTranslation);
|
||||
|
||||
if (cfg == null)
|
||||
{
|
||||
@ -326,7 +326,8 @@ namespace ARMeilleure.Translation
|
||||
ArmEmitterContext context,
|
||||
Block[] blocks,
|
||||
out Range range,
|
||||
out Counter<uint> counter)
|
||||
out Counter<uint> counter,
|
||||
bool pptcTranslation)
|
||||
{
|
||||
counter = null;
|
||||
|
||||
@ -411,7 +412,10 @@ namespace ARMeilleure.Translation
|
||||
if (opCode.Instruction.Emitter != null)
|
||||
{
|
||||
opCode.Instruction.Emitter(context);
|
||||
if (opCode.Instruction.Name == InstName.Und && blkIndex == 0)
|
||||
// if we're pre-compiling PPTC functions, and we hit an Undefined instruction as the first
|
||||
// instruction in the block, mark the function as blacklisted
|
||||
// this way, we don't pre-compile Exlaunch hooks, which allows ExeFS mods to run with PPTC
|
||||
if (pptcTranslation && opCode.Instruction.Name == InstName.Und && blkIndex == 0)
|
||||
{
|
||||
range = new Range(rangeStart, rangeEnd);
|
||||
return null;
|
||||
|
@ -21,6 +21,11 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller's Type
|
||||
/// </summary>
|
||||
|
@ -9,6 +9,7 @@ namespace Ryujinx.Common.Helper
|
||||
public static readonly Regex Numeric = NumericRegex();
|
||||
|
||||
public static readonly Regex AmdGcn = AmdGcnRegex();
|
||||
public static readonly Regex AmdRdna3 = AmdRdna3Regex();
|
||||
public static readonly Regex NvidiaConsumerClass = NvidiaConsumerClassRegex();
|
||||
|
||||
public static readonly Regex DomainLp1Ns = DomainLp1NsRegex();
|
||||
@ -46,6 +47,9 @@ namespace Ryujinx.Common.Helper
|
||||
"Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
|
||||
internal static partial Regex AmdGcnRegex();
|
||||
|
||||
[GeneratedRegex("Radeon ([7-8](\\d{2}\\d?)[MS]|PRO [VW]7(\\d{2}\\d?)|RX 7\\d{3}([MS]?| XTX?| GRE)?)")]
|
||||
public static partial Regex AmdRdna3Regex();
|
||||
|
||||
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
|
||||
internal static partial Regex NvidiaConsumerClassRegex();
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -331,7 +331,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
{
|
||||
context.GetOp<InstVotevtg>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
|
||||
// This instruction is proprietary and will not be implemented. Commenting it out to avoid false reports.
|
||||
//context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
|
||||
}
|
||||
|
||||
public static void Vset(EmitterContext context)
|
||||
|
@ -9,6 +9,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
{
|
||||
public static void RunPass(TransformContext context)
|
||||
{
|
||||
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
|
||||
{
|
||||
XmadOptimizer.RunPass(context.Blocks[blkIndex]);
|
||||
}
|
||||
|
||||
RunOptimizationPasses(context.Blocks, context.ResourceManager);
|
||||
|
||||
// TODO: Some of those are not optimizations and shouldn't be here.
|
||||
@ -355,7 +360,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
operation.TurnIntoCopy(attrMulLhs);
|
||||
}
|
||||
|
||||
private static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
|
||||
public static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
|
||||
{
|
||||
// Remove a node from the nodes list, and also remove itself
|
||||
// from all the use lists on the operands that this node uses.
|
||||
|
@ -0,0 +1,342 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
{
|
||||
static class XmadOptimizer
|
||||
{
|
||||
public static void RunPass(BasicBlock block)
|
||||
{
|
||||
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||
{
|
||||
if (!(node.Value is Operation operation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryMatchXmadPattern(operation, out Operand x, out Operand y, out Operand addend))
|
||||
{
|
||||
LinkedListNode<INode> nextNode;
|
||||
|
||||
if (addend != null)
|
||||
{
|
||||
Operand temp = OperandHelper.Local();
|
||||
|
||||
nextNode = block.Operations.AddAfter(node, new Operation(Instruction.Multiply, temp, x, y));
|
||||
nextNode = block.Operations.AddAfter(nextNode, new Operation(Instruction.Add, operation.Dest, temp, addend));
|
||||
}
|
||||
else
|
||||
{
|
||||
nextNode = block.Operations.AddAfter(node, new Operation(Instruction.Multiply, operation.Dest, x, y));
|
||||
}
|
||||
|
||||
Optimizer.RemoveNode(block, node);
|
||||
node = nextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryMatchXmadPattern(Operation operation, out Operand x, out Operand y, out Operand addend)
|
||||
{
|
||||
return TryMatchXmad32x32Pattern(operation, out x, out y, out addend) ||
|
||||
TryMatchXmad32x16Pattern(operation, out x, out y, out addend);
|
||||
}
|
||||
|
||||
private static bool TryMatchXmad32x32Pattern(Operation operation, out Operand x, out Operand y, out Operand addend)
|
||||
{
|
||||
x = null;
|
||||
y = null;
|
||||
addend = null;
|
||||
|
||||
if (operation.Inst != Instruction.Add)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand src1 = operation.GetSource(0);
|
||||
Operand src2 = operation.GetSource(1);
|
||||
|
||||
if (!(src2.AsgOp is Operation addOp) || addOp.Inst != Instruction.Add)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand lowTimesLowResult = GetCopySource(addOp.GetSource(0));
|
||||
|
||||
if (!(lowTimesLowResult.AsgOp is Operation lowTimesLowOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryMatchLowTimesLow(lowTimesLowOp, out x, out y, out addend))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand lowTimesHighResult = GetCopySource(GetShifted16Source(addOp.GetSource(1), Instruction.ShiftLeft));
|
||||
|
||||
if (!(lowTimesHighResult.AsgOp is Operation lowTimesHighOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryMatchLowTimesHigh(lowTimesHighOp, x, y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(src1.AsgOp is Operation highTimesHighOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryMatchHighTimesHigh(highTimesHighOp, x, lowTimesHighResult))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryMatchXmad32x16Pattern(Operation operation, out Operand x, out Operand y, out Operand addend)
|
||||
{
|
||||
x = null;
|
||||
y = null;
|
||||
addend = null;
|
||||
|
||||
if (operation.Inst != Instruction.Add)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand src1 = operation.GetSource(0);
|
||||
Operand src2 = operation.GetSource(1);
|
||||
|
||||
Operand lowTimesLowResult = GetCopySource(src2);
|
||||
|
||||
if (!(lowTimesLowResult.AsgOp is Operation lowTimesLowOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryMatchLowTimesLow(lowTimesLowOp, out x, out y, out addend))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand highTimesLowResult = src1;
|
||||
|
||||
if (!(highTimesLowResult.AsgOp is Operation highTimesLowOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryMatchHighTimesLow(highTimesLowOp, x, y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return y.Type == OperandType.Constant && (ushort)y.Value == y.Value;
|
||||
}
|
||||
|
||||
private static bool TryMatchLowTimesLow(Operation operation, out Operand x, out Operand y, out Operand addend)
|
||||
{
|
||||
// x = x & 0xFFFF
|
||||
// y = y & 0xFFFF
|
||||
// lowTimesLow = x * y
|
||||
|
||||
x = null;
|
||||
y = null;
|
||||
addend = null;
|
||||
|
||||
if (operation.Inst == Instruction.Add)
|
||||
{
|
||||
if (!(operation.GetSource(0).AsgOp is Operation mulOp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addend = operation.GetSource(1);
|
||||
operation = mulOp;
|
||||
}
|
||||
|
||||
if (operation.Inst != Instruction.Multiply)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand src1 = GetMasked16Source(operation.GetSource(0));
|
||||
Operand src2 = GetMasked16Source(operation.GetSource(1));
|
||||
|
||||
if (src1 == null || src2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
x = src1;
|
||||
y = src2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryMatchLowTimesHigh(Operation operation, Operand x, Operand y)
|
||||
{
|
||||
// xLow = x & 0xFFFF
|
||||
// yHigh = y >> 16
|
||||
// lowTimesHigh = xLow * yHigh
|
||||
// result = (lowTimesHigh & 0xFFFF) | (y << 16)
|
||||
|
||||
if (operation.Inst != Instruction.BitwiseOr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand mulResult = GetMasked16Source(operation.GetSource(0));
|
||||
|
||||
if (mulResult == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mulResult = GetCopySource(mulResult);
|
||||
|
||||
if (!(mulResult.AsgOp is Operation mulOp) || mulOp.Inst != Instruction.Multiply)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetMasked16Source(mulOp.GetSource(0)) != x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetShifted16Source(mulOp.GetSource(1), Instruction.ShiftRightU32) != y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetShifted16Source(operation.GetSource(1), Instruction.ShiftLeft) != y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryMatchHighTimesLow(Operation operation, Operand x, Operand y)
|
||||
{
|
||||
// xHigh = x >> 16
|
||||
// yLow = y & 0xFFFF
|
||||
// highTimesLow = xHigh * yLow
|
||||
// result = highTimesLow << 16
|
||||
|
||||
if (operation.Inst != Instruction.ShiftLeft || !IsConst(operation.GetSource(1), 16))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand mulResult = operation.GetSource(0);
|
||||
|
||||
if (!(mulResult.AsgOp is Operation mulOp) || mulOp.Inst != Instruction.Multiply)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetShifted16Source(mulOp.GetSource(0), Instruction.ShiftRightU32) != x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand src2 = GetMasked16Source(mulOp.GetSource(1));
|
||||
|
||||
if (src2.Type != y.Type || src2.Value != y.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryMatchHighTimesHigh(Operation operation, Operand x, Operand lowTimesHighResult)
|
||||
{
|
||||
// xHigh = x >> 16
|
||||
// lowTimesHighResultHigh = lowTimesHighResult >> 16
|
||||
// highTimesHigh = xHigh * lowTimesHighResultHigh
|
||||
// result = highTimesHigh << 16
|
||||
|
||||
if (operation.Inst != Instruction.ShiftLeft || !IsConst(operation.GetSource(1), 16))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand mulResult = operation.GetSource(0);
|
||||
|
||||
if (!(mulResult.AsgOp is Operation mulOp) || mulOp.Inst != Instruction.Multiply)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetShifted16Source(mulOp.GetSource(0), Instruction.ShiftRightU32) != x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetCopySource(GetShifted16Source(mulOp.GetSource(1), Instruction.ShiftRightU32)) != lowTimesHighResult)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Operand GetMasked16Source(Operand value)
|
||||
{
|
||||
if (!(value.AsgOp is Operation maskOp))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (maskOp.Inst != Instruction.BitwiseAnd || !IsConst(maskOp.GetSource(1), ushort.MaxValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return maskOp.GetSource(0);
|
||||
}
|
||||
|
||||
private static Operand GetShifted16Source(Operand value, Instruction shiftInst)
|
||||
{
|
||||
if (!(value.AsgOp is Operation shiftOp))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shiftOp.Inst != shiftInst || !IsConst(shiftOp.GetSource(1), 16))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return shiftOp.GetSource(0);
|
||||
}
|
||||
|
||||
private static Operand GetCopySource(Operand value)
|
||||
{
|
||||
while (value.AsgOp is Operation operation && IsCopy(operation))
|
||||
{
|
||||
value = operation.GetSource(0);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static bool IsCopy(Operation operation)
|
||||
{
|
||||
return operation.Inst == Instruction.Copy || (operation.Inst == Instruction.Add && IsConst(operation.GetSource(1), 0));
|
||||
}
|
||||
|
||||
private static bool IsConst(Operand operand, int value)
|
||||
{
|
||||
return operand.Type == OperandType.Constant && operand.Value == value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1527,24 +1527,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects)
|
||||
{
|
||||
if (_feedbackLoop != aspects)
|
||||
// AMD RDNA 3 GPUs only
|
||||
if (Gd.IsAmdRdna3)
|
||||
{
|
||||
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
if (_feedbackLoop != aspects)
|
||||
{
|
||||
DynamicState.SetFeedbackLoop(aspects);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.FeedbackLoopAspects = aspects;
|
||||
}
|
||||
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
DynamicState.SetFeedbackLoop(aspects);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.FeedbackLoopAspects = aspects;
|
||||
}
|
||||
|
||||
_feedbackLoop = aspects;
|
||||
_feedbackLoop = aspects;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool UpdateFeedbackLoop()
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@ -27,6 +28,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public uint ViewportsCount;
|
||||
public Array16<Viewport> Viewports;
|
||||
|
||||
[Flags]
|
||||
private enum DirtyFlags
|
||||
{
|
||||
None = 0,
|
||||
@ -190,14 +192,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
|
||||
ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
|
||||
|
||||
if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
|
||||
{
|
||||
aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
|
||||
{
|
||||
aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
|
||||
api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
|
||||
api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsAmdWindows { get; private set; }
|
||||
internal bool IsIntelWindows { get; private set; }
|
||||
internal bool IsAmdGcn { get; private set; }
|
||||
internal bool IsAmdRdna3 { get; private set; }
|
||||
internal bool IsNvidiaPreTuring { get; private set; }
|
||||
internal bool IsIntelArc { get; private set; }
|
||||
internal bool IsQualcommProprietary { get; private set; }
|
||||
@ -377,6 +378,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
|
||||
|
||||
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && Patterns.AmdGcn.IsMatch(GpuRenderer);
|
||||
|
||||
IsAmdRdna3 = Vendor == Vendor.Amd && (Patterns.AmdRdna3.IsMatch(GpuRenderer)
|
||||
// ROG Ally (X) Device IDs
|
||||
|| properties.DeviceID is 0x15BF or 0x15C8);
|
||||
|
||||
if (Vendor == Vendor.Nvidia)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
|
||||
{
|
||||
public Result Create(ref SharedRef<IFileSystem> outEncryptedFileSystem,
|
||||
ref SharedRef<IFileSystem> baseFileSystem, IEncryptedFileSystemCreator.KeyId idIndex,
|
||||
ref readonly SharedRef<IFileSystem> baseFileSystem, IEncryptedFileSystemCreator.KeyId idIndex,
|
||||
in EncryptionSeed encryptionSeed)
|
||||
{
|
||||
if (idIndex < IEncryptedFileSystemCreator.KeyId.Save || idIndex > IEncryptedFileSystemCreator.KeyId.CustomStorage)
|
||||
@ -18,7 +18,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
}
|
||||
|
||||
// TODO: Reenable when AesXtsFileSystem is fixed.
|
||||
outEncryptedFileSystem = SharedRef<IFileSystem>.CreateMove(ref baseFileSystem);
|
||||
outEncryptedFileSystem = SharedRef<IFileSystem>.CreateMove(ref baseFileSystem.Ref);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
@ -791,7 +791,7 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
string buildId = p switch
|
||||
{
|
||||
NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()).TrimEnd('0'),
|
||||
NsoExecutable nso => Convert.ToHexString(nso.BuildId).TrimEnd('0'),
|
||||
NroExecutable nro => Convert.ToHexString(nro.Header.BuildId).TrimEnd('0'),
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
@ -753,17 +753,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
public ResultCode OpenCloudBackupWorkStorageFileSystem(ServiceCtx context)
|
||||
{
|
||||
CloudBackupWorkStorageId storageId = (CloudBackupWorkStorageId)context.RequestData.ReadInt32();
|
||||
using SharedRef<IFileSystem> fileSystem = new();
|
||||
|
||||
Result result = _baseFileSystemProxy.Get.OpenCloudBackupWorkStorageFileSystem(ref fileSystem.Ref, storageId);
|
||||
if (result.IsFailure())
|
||||
{
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref));
|
||||
|
||||
return ResultCode.Success;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFs, new { storageId });
|
||||
throw new NotImplementedException(); // reimplementing behavior from LibHac 0.19.0
|
||||
}
|
||||
|
||||
[CommandCmif(130)]
|
||||
@ -1028,7 +1020,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
ref readonly FspPath path = ref FileSystemProxyHelper.GetFspPath(context);
|
||||
|
||||
Result result = _baseFileSystemProxy.Get.GetRightsIdByPath(out RightsId rightsId, in path);
|
||||
Result result = _baseFileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out _, in path, ContentAttributes.None);
|
||||
if (result.IsFailure())
|
||||
{
|
||||
return (ResultCode)result.Value;
|
||||
@ -1044,7 +1036,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
ref readonly FspPath path = ref FileSystemProxyHelper.GetFspPath(context);
|
||||
|
||||
Result result = _baseFileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in path);
|
||||
Result result = _baseFileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in path, ContentAttributes.None);
|
||||
if (result.IsFailure())
|
||||
{
|
||||
return (ResultCode)result.Value;
|
||||
@ -1240,8 +1232,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
BisPartitionId partitionId = (BisPartitionId)context.RequestData.ReadInt32();
|
||||
ref readonly FspPath path = ref FileSystemProxyHelper.GetFspPath(context);
|
||||
|
||||
return (ResultCode)_baseFileSystemProxy.Get.SetBisRootForHost(partitionId, in path).Value;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFs, new { partitionId, path });
|
||||
|
||||
throw new NotImplementedException(); // reimplementing behavior from LibHac 0.19.0
|
||||
}
|
||||
|
||||
[CommandCmif(1001)]
|
||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
NetworkError ConnectPrivate(ConnectPrivateRequest request);
|
||||
ResultCode Reject(DisconnectReason disconnectReason, uint nodeId);
|
||||
NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter);
|
||||
void SetGameVersion(byte[] versionString);
|
||||
void SetGameVersion(ReadOnlySpan<byte> versionString);
|
||||
void SetStationAcceptPolicy(AcceptPolicy acceptPolicy);
|
||||
void SetAdvertiseData(byte[] data);
|
||||
bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData);
|
||||
|
@ -62,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
|
||||
foreach (ulong localCommunicationId in controlProperty.LocalCommunicationId.ItemsRo)
|
||||
foreach (ulong localCommunicationId in controlProperty.LocalCommunicationId)
|
||||
{
|
||||
if (localCommunicationId == localCommunicationIdChecked)
|
||||
{
|
||||
@ -1114,7 +1114,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
|
||||
// TODO: Call nn::arp::GetApplicationLaunchProperty here when implemented.
|
||||
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion.Items.ToArray());
|
||||
NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
|
||||
|
||||
resultCode = ResultCode.Success;
|
||||
|
||||
|
@ -61,7 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
public void SetAdvertiseData(byte[] data) { }
|
||||
|
||||
public void SetGameVersion(byte[] versionString) { }
|
||||
public void SetGameVersion(ReadOnlySpan<byte> versionString) { }
|
||||
|
||||
public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy) { }
|
||||
|
||||
|
@ -85,7 +85,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
_lanDiscovery.SetAdvertiseData(data);
|
||||
}
|
||||
|
||||
public void SetGameVersion(byte[] versionString)
|
||||
public void SetGameVersion(ReadOnlySpan<byte> versionString)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetGameVersion");
|
||||
|
@ -346,9 +346,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGameVersion(byte[] versionString)
|
||||
public void SetGameVersion(ReadOnlySpan<byte> versionString)
|
||||
{
|
||||
_gameVersion = versionString;
|
||||
_gameVersion = versionString.ToArray();
|
||||
|
||||
if (_gameVersion.Length < 0x10)
|
||||
{
|
||||
|
@ -55,7 +55,13 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
_titleId = titleId;
|
||||
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
|
||||
_ratingAge = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
|
||||
_ratingAge = new int[context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.Length];
|
||||
|
||||
for (int i = 0; i < _ratingAge.Length; i++)
|
||||
{
|
||||
_ratingAge[i] = Convert.ToInt32(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge[i]);
|
||||
}
|
||||
|
||||
_parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag;
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
// Check if input title ids are in the whitelist.
|
||||
foreach (ulong titleId in titleIds)
|
||||
{
|
||||
if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
|
||||
if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.AsReadOnlySpan().Contains(titleId))
|
||||
{
|
||||
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
||||
}
|
||||
|
@ -91,7 +91,13 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programName))
|
||||
{
|
||||
programName = Array.Find(nacpData.Value.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString();
|
||||
foreach (ApplicationControlProperty.ApplicationTitle appTitle in nacpData.Value.Title)
|
||||
{
|
||||
if (appTitle.Name[0] != 0)
|
||||
continue;
|
||||
|
||||
programName = appTitle.NameString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using System;
|
||||
@ -158,7 +159,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool LoadNxo(string path)
|
||||
{
|
||||
BlitStruct<ApplicationControlProperty> nacpData = new(1);
|
||||
@ -195,9 +196,19 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if ("Switch Verification" ==
|
||||
nacpData.Value.Title[(int)TitleLanguage.AmericanEnglish].NameString.ToString())
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programName))
|
||||
{
|
||||
programName = Array.Find(nacpData.Value.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString();
|
||||
foreach (ApplicationControlProperty.ApplicationTitle nacpTitles in nacpData.Value.Title)
|
||||
{
|
||||
if (nacpTitles.Name[0] != 0)
|
||||
continue;
|
||||
|
||||
programName = nacpTitles.NameString.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (nacpData.Value.PresenceGroupId != 0)
|
||||
|
@ -258,7 +258,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
{
|
||||
buildIds[i] = (executables[i] switch
|
||||
{
|
||||
NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()),
|
||||
NsoExecutable nso => Convert.ToHexString(nso.BuildId),
|
||||
NroExecutable nro => Convert.ToHexString(nro.Header.BuildId),
|
||||
_ => string.Empty
|
||||
}).ToUpper();
|
||||
|
@ -59,7 +59,13 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
Name = Array.Find(ApplicationControlProperties.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString();
|
||||
foreach (ApplicationControlProperty.ApplicationTitle appTitle in ApplicationControlProperties.Title)
|
||||
{
|
||||
if (appTitle.Name[0] != 0)
|
||||
continue;
|
||||
|
||||
Name = appTitle.NameString.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
DisplayVersion = ApplicationControlProperties.DisplayVersionString.ToString();
|
||||
|
@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LibHac" />
|
||||
<PackageReference Include="Ryujinx.LibHac" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="SkiaSharp" />
|
||||
|
@ -14,6 +14,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Concentus" />
|
||||
<PackageReference Include="LibHac" />
|
||||
<PackageReference Include="Ryujinx.LibHac" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -49,7 +49,6 @@
|
||||
<TextBlock
|
||||
Classes="globalConfigMarker"/>
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
<Style Selector="DropDownButton">
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
BIN
src/Ryujinx/Assets/UIImages/Logo_GitLab_Dark.png
Normal file
BIN
src/Ryujinx/Assets/UIImages/Logo_GitLab_Dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
src/Ryujinx/Assets/UIImages/Logo_GitLab_Light.png
Normal file
BIN
src/Ryujinx/Assets/UIImages/Logo_GitLab_Light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
@ -489,7 +489,7 @@ namespace Ryujinx.Headless
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||
|
||||
|
@ -170,7 +170,8 @@
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Amiibo.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Dark.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Light.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Dark.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Light.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||
</ItemGroup>
|
||||
|
@ -221,7 +221,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
NsoReader reader = new();
|
||||
reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure();
|
||||
|
||||
return Convert.ToHexString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", string.Empty).ToUpper()[..16];
|
||||
return Convert.ToHexString(reader.Header.ModuleId).Replace("-", string.Empty).ToUpper()[..16];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,26 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
|
||||
public readonly IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents;
|
||||
|
||||
private Gommon.Optional<TimeSpan> _totalTimePlayed;
|
||||
|
||||
public Gommon.Optional<TimeSpan> TotalTimePlayed
|
||||
{
|
||||
get => _totalTimePlayed;
|
||||
private set
|
||||
{
|
||||
_totalTimePlayed = value;
|
||||
_totalTimePlayedChanged.Call(value);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<Gommon.Optional<TimeSpan>> TotalTimePlayedRecalculated
|
||||
{
|
||||
add => _totalTimePlayedChanged.Add(value);
|
||||
remove => _totalTimePlayedChanged.Remove(value);
|
||||
}
|
||||
|
||||
private readonly Event<Gommon.Optional<TimeSpan>> _totalTimePlayedChanged = new();
|
||||
|
||||
private readonly byte[] _nspIcon;
|
||||
private readonly byte[] _xciIcon;
|
||||
private readonly byte[] _ncaIcon;
|
||||
@ -421,6 +441,10 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
||||
|
||||
GetApplicationInformation(ref controlHolder.Value, ref application);
|
||||
|
||||
if ("Switch Verification" == controlHolder.Value
|
||||
.Title[(int)TitleLanguage.AmericanEnglish].NameString.ToString())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -821,6 +845,22 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
}
|
||||
}
|
||||
|
||||
public Task RefreshTotalTimePlayedAsync()
|
||||
{
|
||||
TotalTimePlayed = Gommon.Optional<TimeSpan>.None;
|
||||
|
||||
TimeSpan temporary = TimeSpan.Zero;
|
||||
|
||||
foreach (var installedApplication in Applications.Items)
|
||||
{
|
||||
temporary += LoadAndSaveMetaData(installedApplication.IdString).TimePlayed;
|
||||
}
|
||||
|
||||
TotalTimePlayed = temporary;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task RefreshLdn()
|
||||
{
|
||||
if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu)
|
||||
@ -1363,7 +1403,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
{
|
||||
_ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||
|
||||
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
|
||||
if (controlData.Title.Length > (int)desiredTitleLanguage)
|
||||
{
|
||||
data.Name = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
|
||||
data.Developer = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
|
||||
@ -1376,7 +1416,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data.Name))
|
||||
{
|
||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title.ItemsRo)
|
||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||
{
|
||||
if (!controlTitle.NameString.IsEmpty())
|
||||
{
|
||||
@ -1389,7 +1429,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data.Developer))
|
||||
{
|
||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title.ItemsRo)
|
||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||
{
|
||||
if (!controlTitle.PublisherString.IsEmpty())
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
|
||||
|
||||
return new Array(receivedData.Where(game =>
|
||||
communicationId.Items.Contains(game.TitleId.ToULong())
|
||||
communicationId.AsReadOnlySpan().Contains(game.TitleId.ToULong())
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -157,8 +157,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Multiplayer.LdnServer.Value = cff.LdnServer;
|
||||
|
||||
{
|
||||
Hacks.ShowDirtyHacks.Value = cff.ShowDirtyHacks;
|
||||
|
||||
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks: Hacks.ShowDirtyHacks.Value; // Get from global config only
|
||||
|
||||
DirtyHacks hacks = new (cff.DirtyHacks ?? []);
|
||||
|
||||
Hacks.Xc2MenuSoftlockFix.Value = hacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix);
|
||||
|
@ -276,6 +276,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
Id = "0",
|
||||
Name = "Keyboard",
|
||||
PlayerIndex = PlayerIndex.Player1,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
|
@ -22,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
public float StrongRumble { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public ControllerType ControllerType { get; set; }
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
@ -111,6 +113,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
if (config != null)
|
||||
{
|
||||
Id = config.Id;
|
||||
Name = config.Name;
|
||||
ControllerType = config.ControllerType;
|
||||
PlayerIndex = config.PlayerIndex;
|
||||
|
||||
@ -201,6 +204,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
StandardControllerInputConfig config = new()
|
||||
{
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
Backend = InputBackendType.GamepadSDL2,
|
||||
PlayerIndex = PlayerIndex,
|
||||
ControllerType = ControllerType,
|
||||
|
@ -2,12 +2,14 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models.Input
|
||||
{
|
||||
public partial class KeyboardInputConfig : BaseModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ControllerType ControllerType { get; set; }
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
@ -53,6 +55,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
if (config != null)
|
||||
{
|
||||
Id = config.Id;
|
||||
Name = config.Name;
|
||||
ControllerType = config.ControllerType;
|
||||
PlayerIndex = config.PlayerIndex;
|
||||
|
||||
@ -100,6 +103,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
StandardKeyboardInputConfig config = new()
|
||||
{
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
PlayerIndex = PlayerIndex,
|
||||
ControllerType = ControllerType,
|
||||
|
@ -24,8 +24,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Version = RyujinxApp.FullAppName + "\n" + Program.Version;
|
||||
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
|
||||
|
||||
GitLabLogo = LoadBitmap("resm:Ryujinx.Assets.UIImages.Logo_GitLab.png?assembly=Ryujinx");
|
||||
|
||||
RyujinxApp.ThemeChanged += Ryujinx_ThemeChanged;
|
||||
}
|
||||
|
||||
@ -43,6 +41,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
string themeName = isDarkTheme ? "Dark" : "Light";
|
||||
|
||||
DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName));
|
||||
GitLabLogo = LoadBitmap(LogoPathFormat.Format("GitLab", themeName));
|
||||
}
|
||||
|
||||
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
|
||||
|
@ -91,18 +91,21 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
|
||||
public async void ShowMotionConfig()
|
||||
{
|
||||
{
|
||||
await MotionInputView.Show(this);
|
||||
ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
public async void ShowRumbleConfig()
|
||||
{
|
||||
{
|
||||
await RumbleInputView.Show(this);
|
||||
ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
public async void ShowLedConfig()
|
||||
{
|
||||
await LedInputView.Show(this);
|
||||
ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
public void OnParentModelChanged()
|
||||
|
@ -51,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
private int _device;
|
||||
private object _configViewModel;
|
||||
[ObservableProperty] private string _profileName;
|
||||
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged()
|
||||
[ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged()
|
||||
private bool _isLoaded;
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
@ -88,13 +90,40 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
public bool IsKeyboard => !IsController;
|
||||
public bool IsRight { get; set; }
|
||||
public bool IsLeft { get; set; }
|
||||
|
||||
public string RevertDeviceId { get; set; }
|
||||
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
|
||||
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
||||
|
||||
public bool IsModified { get; set; }
|
||||
public bool _isChangeTrackingActive;
|
||||
|
||||
public bool _isModified;
|
||||
|
||||
public bool IsModified
|
||||
{
|
||||
get => _isModified;
|
||||
set
|
||||
{
|
||||
_isModified = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action NotifyChangesEvent;
|
||||
|
||||
public string _profileChoose;
|
||||
public string ProfileChoose
|
||||
{
|
||||
get => _profileChoose;
|
||||
set
|
||||
{
|
||||
// When you select a profile, the settings from the profile will be applied.
|
||||
// To save the settings, you still need to click the apply button
|
||||
_profileChoose = value;
|
||||
LoadProfile();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object ConfigViewModel
|
||||
{
|
||||
get => _configViewModel;
|
||||
@ -120,14 +149,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
set
|
||||
{
|
||||
if (IsModified)
|
||||
{
|
||||
|
||||
{
|
||||
_playerIdChoose = value;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
IsModified = false;
|
||||
_playerId = value;
|
||||
_isChangeTrackingActive = false;
|
||||
|
||||
if (!Enum.IsDefined<PlayerIndex>(_playerId))
|
||||
{
|
||||
@ -135,13 +164,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
}
|
||||
_isLoaded = false;
|
||||
|
||||
LoadConfiguration();
|
||||
LoadDevice();
|
||||
LoadProfiles();
|
||||
|
||||
RevertDeviceId = Devices[Device].Id;
|
||||
_isLoaded = true;
|
||||
|
||||
_isChangeTrackingActive = true;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
@ -151,6 +180,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
get => _controller;
|
||||
set
|
||||
{
|
||||
MarkAsChanged();
|
||||
|
||||
_controller = value;
|
||||
|
||||
if (_controller == -1)
|
||||
@ -185,11 +216,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
IsLeft = false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
LoadInputDriver();
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
|
||||
OnPropertyChanged();
|
||||
NotifyChanges();
|
||||
}
|
||||
@ -229,6 +260,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
get => _device;
|
||||
set
|
||||
{
|
||||
MarkAsChanged();
|
||||
|
||||
_device = value < 0 ? 0 : value;
|
||||
|
||||
if (_device >= Devices.Count)
|
||||
@ -248,11 +281,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
FindPairedDeviceInConfigFile();
|
||||
OnPropertyChanged();
|
||||
NotifyChanges();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public InputConfig Config { get; set; }
|
||||
|
||||
public InputViewModel(UserControl owner) : this()
|
||||
@ -274,6 +309,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
PlayerId = PlayerIndex.Player1;
|
||||
}
|
||||
|
||||
_isChangeTrackingActive = true;
|
||||
}
|
||||
|
||||
public InputViewModel()
|
||||
@ -311,8 +348,51 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void FindPairedDeviceInConfigFile()
|
||||
{
|
||||
// This function allows you to output a message about the device configuration found in the file
|
||||
// NOTE: if the configuration is found, we display the message "Waiting for controller connection",
|
||||
// but only if the id gamepad belongs to the selected player
|
||||
|
||||
NotificationIsVisible = Config != null && Devices.FirstOrDefault(d => d.Id == Config.Id).Id != Config.Id && Config.PlayerIndex == PlayerId;
|
||||
if (NotificationIsVisible)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Config.Name))
|
||||
{
|
||||
NotificationText = $"{LocaleManager.Instance[LocaleKeys.ControllerSettingsWaitingConnectDevice].Format("No information", Config.Id)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
NotificationText = $"{LocaleManager.Instance[LocaleKeys.ControllerSettingsWaitingConnectDevice].Format(Config.Name, Config.Id)}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void MarkAsChanged()
|
||||
{
|
||||
//If tracking is active, then allow changing the modifier
|
||||
if (!IsModified && _isChangeTrackingActive)
|
||||
{
|
||||
RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnlinkDevice()
|
||||
{
|
||||
// "Disabled" mode is available after unbinding the device
|
||||
// NOTE: the IsModified flag to be able to apply the settings.
|
||||
NotificationIsVisible = false;
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
|
||||
public void LoadDevice()
|
||||
{
|
||||
if (Config == null || Config.Backend == InputBackendType.Invalid)
|
||||
@ -380,12 +460,29 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
private void HandleOnGamepadDisconnected(string id)
|
||||
{
|
||||
Dispatcher.UIThread.Post(LoadDevices);
|
||||
_isChangeTrackingActive = false; // Disable configuration change tracking
|
||||
|
||||
LoadDevices();
|
||||
|
||||
IsModified = true;
|
||||
RevertChanges();
|
||||
FindPairedDeviceInConfigFile();
|
||||
|
||||
_isChangeTrackingActive = true; // Enable configuration change tracking
|
||||
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
{
|
||||
Dispatcher.UIThread.Post(LoadDevices);
|
||||
_isChangeTrackingActive = false; // Disable configuration change tracking
|
||||
|
||||
LoadDevices();
|
||||
|
||||
IsModified = true;
|
||||
RevertChanges();
|
||||
|
||||
_isChangeTrackingActive = true;// Enable configuration change tracking
|
||||
|
||||
}
|
||||
|
||||
private string GetCurrentGamepadId()
|
||||
@ -558,12 +655,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
if (activeDevice.Type == DeviceType.Keyboard)
|
||||
{
|
||||
string id = activeDevice.Id;
|
||||
string name = activeDevice.Name;
|
||||
|
||||
config = new StandardKeyboardInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
Id = id,
|
||||
Name = name,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
@ -613,12 +712,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
|
||||
|
||||
string id = activeDevice.Id.Split(" ")[0];
|
||||
string name = activeDevice.Name;
|
||||
|
||||
config = new StandardControllerInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.GamepadSDL2,
|
||||
Id = id,
|
||||
Name = name,
|
||||
ControllerType = ControllerType.ProController,
|
||||
DeadzoneLeft = 0.1f,
|
||||
DeadzoneRight = 0.1f,
|
||||
@ -688,6 +789,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
return config;
|
||||
}
|
||||
|
||||
public void LoadProfileButton()
|
||||
{
|
||||
LoadProfile();
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
public async void LoadProfile()
|
||||
{
|
||||
if (Device == 0)
|
||||
@ -739,9 +846,11 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
_isLoaded = false;
|
||||
|
||||
config.Id = Config.Id; // Set current device id instead of changing device(independent profiles)
|
||||
|
||||
LoadConfiguration(config);
|
||||
|
||||
LoadDevice();
|
||||
//LoadDevice(); This line of code hard-links profiles to controllers, the commented line allows profiles to be applied to all controllers
|
||||
|
||||
_isLoaded = true;
|
||||
|
||||
@ -751,54 +860,58 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
public async void SaveProfile()
|
||||
{
|
||||
if (Device == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Device == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]);
|
||||
|
||||
if (ConfigViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
||||
|
||||
if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]);
|
||||
if (validFileName)
|
||||
{
|
||||
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
||||
InputConfig config = null;
|
||||
|
||||
if (validFileName)
|
||||
{
|
||||
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
|
||||
if (IsKeyboard)
|
||||
{
|
||||
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
|
||||
}
|
||||
else if (IsController)
|
||||
{
|
||||
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
}
|
||||
|
||||
InputConfig config = null;
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
if (IsKeyboard)
|
||||
{
|
||||
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
|
||||
}
|
||||
else if (IsController)
|
||||
{
|
||||
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
}
|
||||
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
|
||||
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
|
||||
LoadProfiles();
|
||||
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
|
||||
}
|
||||
}
|
||||
ProfileChoose = ProfileName; // Show new profile
|
||||
}
|
||||
else
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async void RemoveProfile()
|
||||
@ -825,14 +938,36 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
|
||||
LoadProfiles();
|
||||
|
||||
ProfileChoose = ProfilesList[0].ToString(); // Show default profile
|
||||
}
|
||||
}
|
||||
|
||||
public void RevertChanges()
|
||||
{
|
||||
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
|
||||
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
|
||||
|
||||
LoadDevice();
|
||||
LoadConfiguration();
|
||||
|
||||
OnPropertyChanged();
|
||||
IsModified = false;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
|
||||
if (!IsModified)
|
||||
{
|
||||
return; //If the input settings were not touched, then do nothing
|
||||
}
|
||||
|
||||
IsModified = false;
|
||||
|
||||
List<InputConfig> newConfig = [];
|
||||
RevertDeviceId = Devices[Device].Id; // Remember selected device after saving
|
||||
|
||||
List <InputConfig> newConfig = [];
|
||||
|
||||
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||
|
||||
@ -862,6 +997,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
config.PlayerIndex = _playerId;
|
||||
config.Name = device.Name;
|
||||
|
||||
int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
|
||||
if (i == -1)
|
||||
|
@ -25,6 +25,7 @@ using Ryujinx.Ava.UI.Renderer;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Ava.Systems.AppLibrary;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Helper;
|
||||
@ -112,6 +113,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
await Updater.BeginUpdateAsync(true);
|
||||
});
|
||||
|
||||
private bool _showTotalTimePlayed;
|
||||
private bool _showLoadProgress;
|
||||
private bool _isGameRunning;
|
||||
private bool _isAmiiboRequested;
|
||||
@ -197,6 +199,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
#if DEBUG
|
||||
topLevel.AttachDevTools(new KeyGesture(Avalonia.Input.Key.F12, KeyModifiers.Control));
|
||||
#endif
|
||||
|
||||
Window.ApplicationLibrary.TotalTimePlayedRecalculated += TotalTimePlayed_Recalculated;
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@ -299,6 +303,24 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
OnPropertyChanged(nameof(ShowFirmwareStatus));
|
||||
}
|
||||
}
|
||||
|
||||
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
|
||||
{
|
||||
ShowTotalTimePlayed = ts.HasValue;
|
||||
|
||||
if (ts.HasValue)
|
||||
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, ValueFormatUtils.FormatTimeSpan(ts.Value));
|
||||
}
|
||||
|
||||
public bool ShowTotalTimePlayed
|
||||
{
|
||||
get => _showTotalTimePlayed && EnableNonGameRunningControls;
|
||||
set
|
||||
{
|
||||
_showTotalTimePlayed = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationData ListSelectedApplication
|
||||
{
|
||||
|
@ -113,6 +113,7 @@
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
CornerRadius="15"
|
||||
Tag="https://src.ryujinx.app"
|
||||
ToolTip.Tip="{ext:Locale AboutGitLabUrlTooltipMessage}">
|
||||
<Image Source="{Binding GitLabLogo}" />
|
||||
</Button>
|
||||
|
@ -6,7 +6,6 @@ using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Helper;
|
||||
using System.Threading.Tasks;
|
||||
using Button = Avalonia.Controls.Button;
|
||||
@ -18,9 +17,6 @@ namespace Ryujinx.Ava.UI.Views.Dialog
|
||||
public AboutView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
GitRepoButton.Tag =
|
||||
$"https://git.ryujinx.app/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}";
|
||||
}
|
||||
|
||||
public static async Task Show()
|
||||
|
@ -64,8 +64,9 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
};
|
||||
|
||||
if (!float.IsNaN(_changeSlider) && _changeSlider != (float)check.Value)
|
||||
{
|
||||
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true;
|
||||
{
|
||||
FlagInputConfigChanged();
|
||||
|
||||
_changeSlider = (float)check.Value;
|
||||
}
|
||||
}
|
||||
@ -75,7 +76,8 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
if (sender is CheckBox { IsPointerOver: true })
|
||||
{
|
||||
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true;
|
||||
FlagInputConfigChanged();
|
||||
|
||||
_currentAssigner?.Cancel();
|
||||
_currentAssigner = null;
|
||||
}
|
||||
@ -102,7 +104,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
this.Focus(NavigationMethod.Pointer);
|
||||
|
||||
PointerPressed += MouseClick;
|
||||
|
||||
|
||||
ControllerInputViewModel viewModel = (DataContext as ControllerInputViewModel);
|
||||
|
||||
IKeyboard keyboard =
|
||||
@ -115,7 +117,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
if (e.ButtonValue.HasValue)
|
||||
{
|
||||
Button buttonValue = e.ButtonValue.Value;
|
||||
viewModel.ParentModel.IsModified = true;
|
||||
FlagInputConfigChanged();
|
||||
|
||||
switch (button.Name)
|
||||
{
|
||||
@ -209,6 +211,11 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
}
|
||||
}
|
||||
|
||||
private void FlagInputConfigChanged()
|
||||
{
|
||||
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true;
|
||||
}
|
||||
|
||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
|
||||
@ -232,7 +239,6 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
gamepad?.ClearLed();
|
||||
}
|
||||
|
||||
_currentAssigner?.Cancel();
|
||||
_currentAssigner = null;
|
||||
}
|
||||
|
@ -41,13 +41,20 @@
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center" ColumnDefinitions="Auto,*">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center" ColumnDefinitions="Auto,*,Auto">
|
||||
<StackPanel
|
||||
Orientation="Vertical"
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale ControllerSettingsPlayer}" />
|
||||
Width="90">
|
||||
<TextBlock
|
||||
Text="{ext:Locale ControllerSettingsPlayer}" />
|
||||
<TextBlock
|
||||
Classes="pending"
|
||||
Text ="{ext:Locale ControllerSettingsModifiedNotification}"
|
||||
IsVisible="{Binding IsModified}"/>
|
||||
</StackPanel>
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Name="PlayerIndexBox"
|
||||
@ -62,6 +69,18 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsCancelCurrentChangesToolTip}"
|
||||
Command="{Binding RevertChanges}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Undo"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<!-- Profile Selection -->
|
||||
<Grid
|
||||
@ -81,7 +100,8 @@
|
||||
Name="ProfileBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectedIndex="0"
|
||||
SelectedItem="{Binding ProfileChoose, Mode=TwoWay}"
|
||||
SelectionChanged="ComboBox_SelectionChanged"
|
||||
ItemsSource="{Binding ProfilesList}"
|
||||
Text="{Binding ProfileName, Mode=TwoWay}" />
|
||||
<Button
|
||||
@ -90,7 +110,7 @@
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsLoadProfileToolTip}"
|
||||
Command="{Binding LoadProfile}">
|
||||
Command="{Binding LoadProfileButton}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="View"
|
||||
FontSize="15"
|
||||
@ -148,7 +168,7 @@
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding LoadDevices}">
|
||||
Command="{Binding LoadDevice}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Refresh"
|
||||
FontSize="15"
|
||||
@ -181,15 +201,37 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="viewModels:ControllerInputViewModel">
|
||||
<views:ControllerInputView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
|
||||
<views:KeyboardInputView />
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
<ContentControl IsVisible="{Binding NotificationIsVisible}">
|
||||
<ContentControl.Content>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="5,20,0,0"
|
||||
Text="{Binding NotificationText}" />
|
||||
<Button
|
||||
MinWidth="0"
|
||||
Width="90"
|
||||
Height="27"
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding UnlinkDevice}">
|
||||
<TextBlock
|
||||
Text="{ext:Locale ControllerSettingsUnlink}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</ContentControl.Content>
|
||||
</ContentControl>
|
||||
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="viewModels:ControllerInputViewModel">
|
||||
<views:ControllerInputView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
|
||||
<views:KeyboardInputView />
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
@ -62,14 +63,23 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ViewModel.PlayerId = ViewModel.PlayerIdChoose;
|
||||
|
||||
ViewModel.IsModified = false;
|
||||
}
|
||||
ViewModel.PlayerId = ViewModel.PlayerIdChoose;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is FAComboBox faComboBox)
|
||||
{
|
||||
faComboBox.IsDropDownOpen = false;
|
||||
ViewModel.IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ViewModel.Dispose();
|
||||
|
@ -9,6 +9,8 @@ using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Button = Ryujinx.Input.Button;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
@ -186,11 +188,63 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
|
||||
|
||||
bool shouldRemoveBinding = e.GetCurrentPoint(this).Properties.IsRightButtonPressed;
|
||||
|
||||
if (shouldRemoveBinding)
|
||||
{
|
||||
DeleteBind();
|
||||
}
|
||||
|
||||
_currentAssigner?.Cancel(shouldUnbind);
|
||||
|
||||
PointerPressed -= MouseClick;
|
||||
}
|
||||
|
||||
private void DeleteBind()
|
||||
{
|
||||
|
||||
if (_currentAssigner != null)
|
||||
{
|
||||
Dictionary<string, Action> buttonActions = new Dictionary<string, Action>
|
||||
{
|
||||
{ "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound },
|
||||
{ "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound },
|
||||
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound },
|
||||
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound },
|
||||
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound },
|
||||
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound },
|
||||
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound },
|
||||
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound },
|
||||
{ "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound },
|
||||
{ "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound },
|
||||
{ "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound },
|
||||
{ "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound },
|
||||
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound },
|
||||
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound },
|
||||
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound },
|
||||
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound },
|
||||
{ "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound },
|
||||
{ "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound },
|
||||
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound },
|
||||
{ "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound },
|
||||
{ "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound },
|
||||
{ "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound },
|
||||
{ "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound },
|
||||
{ "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound },
|
||||
{ "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound },
|
||||
{ "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound },
|
||||
{ "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound },
|
||||
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound }
|
||||
};
|
||||
|
||||
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
||||
{
|
||||
action();
|
||||
ViewModel.ParentModel.IsModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
@ -29,7 +29,7 @@
|
||||
Margin="5"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}">
|
||||
<Grid Margin="0" ColumnDefinitions="Auto,Auto,Auto,*">
|
||||
<Grid Margin="0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,*">
|
||||
<Button
|
||||
Width="25"
|
||||
Height="25"
|
||||
@ -68,6 +68,14 @@
|
||||
IsVisible="{Binding StatusBarVisible}"
|
||||
Maximum="{Binding StatusBarProgressMaximum}"
|
||||
Value="{Binding StatusBarProgressValue}" />
|
||||
<controls:MiniVerticalSeparator Grid.Column="4" IsVisible="{Binding ShowTotalTimePlayed}" />
|
||||
<TextBlock
|
||||
Grid.Column="5"
|
||||
Margin="5,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowTotalTimePlayed}"
|
||||
Text="{ext:Locale GameListLabelTotalTimePlayed}">
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
|
@ -10,6 +10,8 @@ using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Button = Ryujinx.Input.Button;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
@ -45,6 +47,51 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
|
||||
bool shouldRemoveBinding = e.GetCurrentPoint(this).Properties.IsRightButtonPressed;
|
||||
|
||||
if (shouldRemoveBinding)
|
||||
{
|
||||
DeleteBind();
|
||||
}
|
||||
|
||||
_currentAssigner?.Cancel(shouldUnbind);
|
||||
|
||||
PointerPressed -= MouseClick;
|
||||
}
|
||||
|
||||
private void DeleteBind()
|
||||
{
|
||||
if (DataContext is not SettingsViewModel viewModel)
|
||||
return;
|
||||
|
||||
if (_currentAssigner != null)
|
||||
{
|
||||
Dictionary<string, Action> buttonActions = new Dictionary<string, Action>
|
||||
{
|
||||
{ "ToggleVSyncMode", () => viewModel.KeyboardHotkey.ToggleVSyncMode = Key.Unbound },
|
||||
{ "Screenshot", () => viewModel.KeyboardHotkey.Screenshot = Key.Unbound },
|
||||
{ "ShowUI", () => viewModel.KeyboardHotkey.ShowUI = Key.Unbound },
|
||||
{ "Pause", () => viewModel.KeyboardHotkey.Pause = Key.Unbound },
|
||||
{ "ToggleMute", () => viewModel.KeyboardHotkey.ToggleMute = Key.Unbound },
|
||||
{ "ResScaleUp", () => viewModel.KeyboardHotkey.ResScaleUp = Key.Unbound },
|
||||
{ "ResScaleDown", () => viewModel.KeyboardHotkey.ResScaleDown = Key.Unbound },
|
||||
{ "VolumeUp", () => viewModel.KeyboardHotkey.VolumeUp = Key.Unbound },
|
||||
{ "VolumeDown", () => viewModel.KeyboardHotkey.VolumeDown = Key.Unbound },
|
||||
{ "CustomVSyncIntervalIncrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalIncrement = Key.Unbound },
|
||||
{ "CustomVSyncIntervalDecrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = Key.Unbound },
|
||||
{ "TurboMode", () => viewModel.KeyboardHotkey.TurboMode = Key.Unbound }
|
||||
};
|
||||
|
||||
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleButton button)
|
||||
@ -62,6 +109,8 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
|
||||
this.Focus(NavigationMethod.Pointer);
|
||||
|
||||
PointerPressed += MouseClick;
|
||||
|
||||
IKeyboard keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad("0");
|
||||
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
|
||||
|
||||
|
@ -717,6 +717,8 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
ShowNewContentAddedDialog(dlcLoaded, dlcRemoved, updatesLoaded, updatesRemoved);
|
||||
}
|
||||
|
||||
Executor.ExecuteBackgroundAsync(ApplicationLibrary.RefreshTotalTimePlayedAsync);
|
||||
|
||||
_isLoading = false;
|
||||
})
|
||||
|
Reference in New Issue
Block a user