mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-03-11 17:11:06 +00:00
Compare commits
8 Commits
removal/im
...
Canary-1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60992e2545 | ||
|
|
adae465b08 | ||
|
|
6602693477 | ||
|
|
b76e223cc3 | ||
|
|
2ea9e945ab | ||
|
|
a84cc62508 | ||
|
|
343de5171c | ||
|
|
5d8451b41a |
@@ -4939,7 +4939,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Multiplikator för turboläge:",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -4964,7 +4964,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Målvärdet för multiplikatorn i turboläget. \n\nLämna den på 200 om du är osäker.",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -4989,7 +4989,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Turboläget är en emulatorfunktion som effektivt ökar eller sänker hastigheten när ett spel inte är känsligt för bildfrekvens.\nDu kan växla denna funktion i spelet med en snabbtangent, konfigurerbar i Ryujinx inställningar för snabbtangenter.\n\nLämna den på 200 om du är osäker.",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -5322,6 +5322,31 @@
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsTabSystemIgnoreMissingServices",
|
||||
"Translations": {
|
||||
"ar_SA": "تجاهل الخدمات المفقودة",
|
||||
"de_DE": "Ignoriere fehlende Dienste",
|
||||
"el_GR": "Αγνόηση υπηρεσιών που λείπουν",
|
||||
"en_US": "Ignore Missing Services",
|
||||
"es_ES": "Ignorar servicios no implementados",
|
||||
"fr_FR": "Ignorer les services manquants",
|
||||
"he_IL": "התעלם משירותים חסרים",
|
||||
"it_IT": "Ignora servizi mancanti",
|
||||
"ja_JP": "未実装サービスを無視する",
|
||||
"ko_KR": "누락된 서비스 무시",
|
||||
"no_NO": "Ignorer manglende tjenester",
|
||||
"pl_PL": "Ignoruj Brakujące Usługi",
|
||||
"pt_BR": "Ignorar Serviços Ausentes",
|
||||
"ru_RU": "Игнорировать отсутствующие службы",
|
||||
"sv_SE": "Ignorera saknade tjänster",
|
||||
"th_TH": "เมินเฉยบริการที่หายไป",
|
||||
"tr_TR": "Eksik Servisleri Görmezden Gel",
|
||||
"uk_UA": "Ігнорувати відсутні служби",
|
||||
"zh_CN": "忽略缺失的服务",
|
||||
"zh_TW": "忽略缺少的模擬器功能"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsTabSystemIgnoreControllerApplet",
|
||||
"Translations": {
|
||||
@@ -5347,6 +5372,31 @@
|
||||
"zh_TW": "忽略控制器小程式"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsTabSystemSkipUserProfilesManager",
|
||||
"Translations": {
|
||||
"ar_SA": "تخطي مربع حوار 'إدارة الملفات الشخصية للمستخدم'",
|
||||
"de_DE": "Überspringen des Dialogs 'Benutzerprofile verwalten'",
|
||||
"el_GR": "Παράκαμψη διαλόγου 'Διαχείριση Προφίλ _Χρηστών'",
|
||||
"en_US": "Skip dialog 'Manage User Profiles'",
|
||||
"es_ES": "Omitir el diálogo 'Gestionar perfiles de usuario'",
|
||||
"fr_FR": "Ignorer le dialogue 'Gérer les profils d'utilisateurs'",
|
||||
"he_IL": "דילוג על הדיאלוג 'נהל פרופילי משתמש'",
|
||||
"it_IT": "Salta la finestra di dialogo 'Gestisci i profili utente'",
|
||||
"ja_JP": "「ユーザプロファイルを管理」ダイアログをスキップ",
|
||||
"ko_KR": "'사용자 프로필 관리' 대화 상자 건너뛰기",
|
||||
"no_NO": "Hopp over dialogen 'Administrere Brukerprofiler'",
|
||||
"pl_PL": "Pomiń okno dialogowe 'Zarządzaj profilami użytkowników'",
|
||||
"pt_BR": "Ignorar a caixa de diálogo 'Gerenciar Perfis de Usuário'",
|
||||
"ru_RU": "Пропустить диалог 'Менеджер учётных записей'",
|
||||
"sv_SE": "Hoppa över dialogen 'Hantera användarprofiler'",
|
||||
"th_TH": "ข้ามหน้าต่างโต้ตอบ 'จัดการโปรไฟล์ผู้ใช้งาน'",
|
||||
"tr_TR": "'Kullanıcı Profillerini Yönet' iletişim kutusunu atla",
|
||||
"uk_UA": "Пропустити діалог 'Керувати профілями користувачів'",
|
||||
"zh_CN": "跳过对话框“管理用户账户”",
|
||||
"zh_TW": "略過對話框「管理使用者設定檔」"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SettingsTabGraphics",
|
||||
"Translations": {
|
||||
@@ -9989,7 +10039,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Enter (блок цифр)",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Enter (numerisk)",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Enter (цифровий блок)",
|
||||
@@ -11039,7 +11089,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Тачпад",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Pekplatta",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Сенсорна панель",
|
||||
@@ -16697,6 +16747,31 @@
|
||||
"zh_TW": "利用另一種 MemoryMode 配置來模仿 Switch 開發模式。\n\n這僅對高解析度紋理套件或 4K 解析度模組有用。不會提高效能。\n\n如果不確定,請設定為 4GiB。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "IgnoreMissingServicesTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.",
|
||||
"de_DE": "Durch diese Option werden nicht implementierte Dienste der Switch-Firmware ignoriert. Dies kann dabei helfen, Abstürze beim Starten bestimmter Spiele zu umgehen.\n\nIm Zweifelsfall AUS lassen.",
|
||||
"el_GR": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν",
|
||||
"en_US": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
|
||||
"es_ES": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.",
|
||||
"fr_FR": "Ignore les services Horizon OS non-intégrés. Cela peut aider à contourner les plantages lors du démarrage de certains jeux.\n\nLaissez désactivé en cas d'incertitude.",
|
||||
"he_IL": "מתעלם מפעולות שלא קיבלו מימוש במערכת ההפעלה Horizon OS. זה עלול לעזור לעקוף קריסות של היישום במשחקים מסויימים.\n\nמוטב להשאיר כבוי אם לא בטוחים.",
|
||||
"it_IT": "Ignora i servizi non implementati del sistema operativo Horizon. Può aiutare ad aggirare gli arresti anomali che si verificano avviando alcuni giochi.\n\nNel dubbio, lascia l'opzione disattivata.",
|
||||
"ja_JP": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.",
|
||||
"ko_KR": "구현되지 않은 Horizon OS 서비스는 무시됩니다. 특정 게임을 부팅할 때, 발생하는 충돌을 우회하는 데 도움이 될 수 있습니다.\n\n모르면 끔으로 두세요.",
|
||||
"no_NO": "Ignorerer ikke implementerte Horisont OS-tjenester. Dette kan hjelpe med å omgå krasj ved oppstart av enkelte spill.\n\nLa AV hvis du er usikker.",
|
||||
"pl_PL": "Ignoruje niezaimplementowane usługi Horizon OS. Może to pomóc w ominięciu awarii podczas uruchamiania niektórych gier.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.",
|
||||
"pt_BR": "Ignora serviços não implementados do Horizon OS. Isso pode ajudar a contornar travamentos ao inicializar certos jogos.\n\nDeixe OFF se não tiver certeza.",
|
||||
"ru_RU": "Игнорирует нереализованные сервисы Horizon в новых прошивках. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.",
|
||||
"sv_SE": "Ignorerar Horizon OS-tjänster som inte har implementerats. Detta kan avhjälpa krascher när vissa spel startar upp.\n\nLämna AV om du är osäker.",
|
||||
"th_TH": "ละเว้นบริการ Horizon OS ที่ยังไม่ได้ใช้งาน วิธีนี้อาจช่วยในการหลีกเลี่ยงข้อผิดพลาดเมื่อบูตเกมบางเกม\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ",
|
||||
"tr_TR": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.",
|
||||
"uk_UA": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим якщо не впевнені.",
|
||||
"zh_CN": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。",
|
||||
"zh_TW": "忽略未實現的 Horizon OS 服務。這可能有助於在啟動某些遊戲時避免崩潰。\n\n如果不確定,請保持關閉狀態。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "IgnoreControllerAppletTooltip",
|
||||
"Translations": {
|
||||
@@ -16722,6 +16797,31 @@
|
||||
"zh_TW": "在模擬應用程式時如果遊戲手柄中斷連線則不會顯示控制器小程式。\n\n如果不確定,請保持關閉狀態。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SkipUserProfilesTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Diese Option überspringt den Dialog 'Benutzerprofile verwalten' während des Spiels und verwendet ein voreingestelltes Profil.\n\nDie Profilumschaltung finden Sie unter 'Einstellungen' - 'Benutzerprofile verwalten'. Wählen Sie das gewünschte Profil aus, bevor Sie das Spiel laden.",
|
||||
"el_GR": "Αυτή η επιλογή παρακάμπτει το παράθυρο διαλόγου 'Διαχειριστής Προφίλ Χρήστη' κατά τη διάρκεια του παιχνιδιού, χρησιμοποιώντας ένα προεπιλεγμένο προφίλ.\n\nΗ εναλλαγή προφίλ βρίσκεται στις 'Ρυθμίσεις' - 'Διαχειριστής Προφίλ Χρήστη'. Επιλέξτε το επιθυμητό προφίλ πριν φορτώσετε το παιχνίδι.",
|
||||
"en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.",
|
||||
"es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.",
|
||||
"fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de charger la partie.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Questa opzione salta la finestra di dialogo 'Gestisci i profili utente' durante il gioco, utilizzando un profilo pre-selezionato.\n\nIl cambio del profilo si trova in 'Impostazioni' - 'Gestisci i profili utente'. Seleziona il profilo desiderato prima di caricare il gioco.",
|
||||
"ja_JP": "このオプションは、ゲームプレイ中に「ユーザプロファイルを管理」ダイアログをスキップし、事前に選択されたプロファイルを使用します。\n\nプロファイルの切り替えは、「設定」-「ユーザプロファイルを管理」で見つけることができます。ゲームのロード前に目的のプロファイルをを選択してください。",
|
||||
"ko_KR": "이 옵션은 게임 플레이 중 '사용자 프로필 관리' 대화 상자를 건너뛰고, 미리 선택된 프로필을 사용합니다.\n\n프로필 전환은 '설정' - '사용자 프로필 관리'에서 찾을 수 있습니다. 게임 로드 전에 원하는 프로필을 선택하세요.",
|
||||
"no_NO": "Dette alternativet hopper over dialogen 'Administrere Brukerprofiler' under spilling, og bruker en forhåndsvalgt profil.\n\nProfilbytte finnes i 'Innstillinger' - 'Administrer Brukerprofiler'. Velg ønsket profil før du laster spillet.",
|
||||
"pl_PL": "Ta opcja pomija okno dialogowe 'Zarządzaj profilami użytkowników' podczas gry, używając wcześniej wybrany profil.\n\nPrzełączanie profili można znaleźć w 'Ustawienia' - 'Zarządzaj Profilami Użytkowników'. Wybierz żądany profil przed załadowaniem gry.",
|
||||
"pt_BR": "Esta opção ignora a caixa de diálogo 'Gerenciar Perfis de Usuário' durante o jogo, usando um perfil pré-selecionado.\n\nO gerenciamento de perfis pode ser encontrado em 'Configurações' - 'Gerenciar Perfis de Usuários'. Selecione o perfil desejado antes de carregar o jogo.",
|
||||
"ru_RU": "Эта опция пропускает диалоговое окно 'Менеджер учётных записей' во время игры, используя предварительно выбранный профиль.\n\nПереключение профилей можно найти в 'Параметры' - 'Менеджер учётных записей'. Выберите желаемый профиль перед загрузкой игры.",
|
||||
"sv_SE": "Det här alternativet hoppar över dialogrutan 'Hantera användarprofiler' under spelet och använder en förvald profil.\n\nProfilväxling finns i 'Inställningar' - 'Hantera användarprofiler'. Välj önskad profil innan du laddar spelet.",
|
||||
"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_TW": "這個選項跳過遊戲過程中的「管理使用者設定檔」對話框,使用預先選取的設定。\n\n可以在「設定」-「管理使用者設定檔」中找到設定檔切換。 在載入遊戲前選擇您需要的設定檔。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "GraphicsBackendThreadingTooltip",
|
||||
"Translations": {
|
||||
@@ -18139,7 +18239,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "{0} bilder/s ({1}ms)",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -18164,7 +18264,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "{0} bilder/s ({1}ms), Turbo ({2}%)",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -23914,7 +24014,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Turboläge:",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -23939,7 +24039,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Snabbtangenten för turboläge.\nKonfigurera beteendet för turboläge i Ryujinx CPU-inställningar.\n\nLämna Obunden om du är osäker.",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -23964,7 +24064,7 @@
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"sv_SE": "Endast när du trycker ner",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
@@ -15,7 +15,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
Console.WriteLine("Running Locale Validation Task...");
|
||||
|
||||
string path = projectPath + "src/Ryujinx/Assets/locales.json";
|
||||
string path = projectPath + "assets/locales.json";
|
||||
string data;
|
||||
|
||||
using (StreamReader sr = new(path))
|
||||
|
||||
@@ -265,25 +265,13 @@ namespace Ryujinx.HLE.HOS
|
||||
HorizonFsClient fsClient = new(this);
|
||||
|
||||
ServiceTable = new ServiceTable();
|
||||
|
||||
IEnumerable<ServiceEntry> services = ServiceTable.GetServices(new HorizonOptions(
|
||||
#if DEBUG
|
||||
Device.Configuration.IgnoreMissingServices,
|
||||
IEnumerable<ServiceEntry> services = ServiceTable.GetServices(new HorizonOptions
|
||||
(Device.Configuration.IgnoreMissingServices,
|
||||
LibHacHorizonManager.BcatClient,
|
||||
fsClient,
|
||||
AccountManager,
|
||||
Device.AudioDeviceDriver,
|
||||
TickSource
|
||||
#else
|
||||
LibHacHorizonManager.BcatClient,
|
||||
fsClient,
|
||||
AccountManager,
|
||||
Device.AudioDeviceDriver,
|
||||
TickSource
|
||||
#endif
|
||||
));
|
||||
|
||||
|
||||
TickSource));
|
||||
|
||||
foreach (ServiceEntry service in services)
|
||||
{
|
||||
|
||||
@@ -135,8 +135,7 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
int commandId = (int)context.RequestData.ReadInt64();
|
||||
|
||||
bool serviceExists = service.CmifCommands.TryGetValue(commandId, out MethodInfo processRequest);
|
||||
|
||||
#if DEBUG
|
||||
|
||||
if (context.Device.Configuration.IgnoreMissingServices || serviceExists)
|
||||
{
|
||||
ResultCode result = ResultCode.Success;
|
||||
@@ -179,39 +178,6 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
|
||||
throw new ServiceNotImplementedException(service, context, dbgMessage);
|
||||
}
|
||||
#else
|
||||
if (serviceExists)
|
||||
{
|
||||
context.ResponseData.BaseStream.Seek(_isDomain ? 0x20 : 0x10, SeekOrigin.Begin);
|
||||
|
||||
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
|
||||
|
||||
ResultCode result = (ResultCode)processRequest.Invoke(service, [context]);
|
||||
|
||||
if (_isDomain)
|
||||
{
|
||||
foreach (int id in context.Response.ObjectIds)
|
||||
{
|
||||
context.ResponseData.Write(id);
|
||||
}
|
||||
|
||||
context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
context.ResponseData.Write(context.Response.ObjectIds.Count);
|
||||
}
|
||||
|
||||
context.ResponseData.BaseStream.Seek(_isDomain ? 0x10 : 0, SeekOrigin.Begin);
|
||||
|
||||
context.ResponseData.Write(IpcMagic.Sfco);
|
||||
context.ResponseData.Write((long)result);
|
||||
}
|
||||
else
|
||||
{
|
||||
string dbgMessage = $"{service.GetType().FullName}: {commandId}";
|
||||
|
||||
throw new ServiceNotImplementedException(service, context, dbgMessage);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void CallTipcMethod(ServiceCtx context)
|
||||
@@ -220,7 +186,6 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
|
||||
bool serviceExists = TipcCommands.TryGetValue(commandId, out MethodInfo processRequest);
|
||||
|
||||
#if DEBUG
|
||||
if (context.Device.Configuration.IgnoreMissingServices || serviceExists)
|
||||
{
|
||||
ResultCode result = ResultCode.Success;
|
||||
@@ -253,26 +218,6 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
|
||||
throw new ServiceNotImplementedException(this, context, dbgMessage);
|
||||
}
|
||||
#else
|
||||
if (serviceExists)
|
||||
{
|
||||
context.ResponseData.BaseStream.Seek(0x4, SeekOrigin.Begin);
|
||||
|
||||
Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}");
|
||||
|
||||
ResultCode result = (ResultCode)processRequest.Invoke(this, [context]);
|
||||
|
||||
context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
context.ResponseData.Write((uint)result);
|
||||
}
|
||||
else
|
||||
{
|
||||
string dbgMessage = $"{GetType().FullName}: {commandId}";
|
||||
|
||||
throw new ServiceNotImplementedException(this, context, dbgMessage);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected void MakeObject(ServiceCtx context, IpcService obj)
|
||||
|
||||
@@ -102,13 +102,11 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
if (context.Device.Configuration.IgnoreMissingServices)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Service, $"Missing service {name} ignored");
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
throw new NotImplementedException(name);
|
||||
}
|
||||
|
||||
@@ -142,14 +142,12 @@ namespace Ryujinx.HLE
|
||||
/// </summary>
|
||||
public MemoryManagerMode MemoryManagerMode { internal get; set; }
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Control the initial state of the ignore missing services setting.
|
||||
/// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception.
|
||||
/// </summary>
|
||||
/// TODO: Update this again.
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
#endif
|
||||
public bool IgnoreMissingServices { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Aspect Ratio applied to the renderer window by the SurfaceFlinger service.
|
||||
@@ -215,9 +213,7 @@ namespace Ryujinx.HLE
|
||||
long systemTimeOffset,
|
||||
string timeZone,
|
||||
MemoryManagerMode memoryManagerMode,
|
||||
#if DEBUG
|
||||
bool ignoreMissingServices,
|
||||
#endif
|
||||
AspectRatio aspectRatio,
|
||||
float audioVolume,
|
||||
bool useHypervisor,
|
||||
@@ -243,9 +239,7 @@ namespace Ryujinx.HLE
|
||||
SystemTimeOffset = systemTimeOffset;
|
||||
TimeZone = timeZone;
|
||||
MemoryManagerMode = memoryManagerMode;
|
||||
#if DEBUG
|
||||
IgnoreMissingServices = ignoreMissingServices;
|
||||
#endif
|
||||
AspectRatio = aspectRatio;
|
||||
AudioVolume = audioVolume;
|
||||
UseHypervisor = useHypervisor;
|
||||
|
||||
@@ -4,7 +4,6 @@ using Ryujinx.Audio.Backends.CompatLayer;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
@@ -94,11 +93,6 @@ namespace Ryujinx.HLE
|
||||
|
||||
UpdateVSyncInterval();
|
||||
#pragma warning restore IDE0055
|
||||
|
||||
#if DEBUG
|
||||
if (Configuration.IgnoreMissingServices)
|
||||
Logger.Notice.Print(LogClass.Emulation, "Ignore Missing Services is enabled.", nameof(Switch));
|
||||
#endif
|
||||
|
||||
Shared = this;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ namespace Ryujinx.Horizon
|
||||
{
|
||||
public readonly struct HorizonOptions
|
||||
{
|
||||
#if DEBUG
|
||||
public bool IgnoreMissingServices { get; }
|
||||
#endif
|
||||
public bool ThrowOnInvalidCommandIds { get; }
|
||||
|
||||
public HorizonClient BcatClient { get; }
|
||||
@@ -20,18 +18,14 @@ namespace Ryujinx.Horizon
|
||||
public ITickSource TickSource { get; }
|
||||
|
||||
public HorizonOptions(
|
||||
#if DEBUG
|
||||
bool ignoreMissingServices,
|
||||
#endif
|
||||
HorizonClient bcatClient,
|
||||
IFsClient fsClient,
|
||||
IEmulatorAccountManager accountManager,
|
||||
IHardwareDeviceDriver audioDeviceDriver,
|
||||
ITickSource tickSource)
|
||||
{
|
||||
#if DEBUG
|
||||
IgnoreMissingServices = ignoreMissingServices;
|
||||
#endif
|
||||
ThrowOnInvalidCommandIds = true;
|
||||
BcatClient = bcatClient;
|
||||
FsClient = fsClient;
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||
|
||||
if (!entries.TryGetValue((int)commandId, out CommandHandler commandHandler))
|
||||
{
|
||||
#if DEBUG
|
||||
if (HorizonStatic.Options.IgnoreMissingServices)
|
||||
{
|
||||
// If ignore missing services is enabled, just pretend that everything is fine.
|
||||
@@ -50,10 +49,8 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||
Logger.Warning?.Print(LogClass.Service, $"Missing service {objectName} (command ID: {commandId}) ignored");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (HorizonStatic.Options.ThrowOnInvalidCommandIds)
|
||||
}
|
||||
else if (HorizonStatic.Options.ThrowOnInvalidCommandIds)
|
||||
{
|
||||
throw new NotImplementedException($"{objectName} command ID: {commandId} is not implemented");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -36,6 +37,7 @@ namespace Ryujinx.Input.SDL2
|
||||
SDL2Driver.Instance.Initialize();
|
||||
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
||||
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
|
||||
SDL2Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
|
||||
|
||||
// Add already connected gamepads
|
||||
int numJoysticks = SDL_NumJoysticks();
|
||||
@@ -86,19 +88,32 @@ namespace Ryujinx.Input.SDL2
|
||||
|
||||
private void HandleJoyStickDisconnected(int joystickInstanceId)
|
||||
{
|
||||
bool joyConPairDisconnected = false;
|
||||
|
||||
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_gamepadsIds.Remove(id);
|
||||
if (!SDL2JoyConPair.IsCombinable(_gamepadsIds))
|
||||
{
|
||||
_gamepadsIds.Remove(SDL2JoyConPair.Id);
|
||||
joyConPairDisconnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
OnGamepadDisconnected?.Invoke(id);
|
||||
if (joyConPairDisconnected)
|
||||
{
|
||||
OnGamepadDisconnected?.Invoke(SDL2JoyConPair.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
|
||||
{
|
||||
bool joyConPairConnected = false;
|
||||
|
||||
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
||||
{
|
||||
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
||||
@@ -123,12 +138,30 @@ namespace Ryujinx.Input.SDL2
|
||||
_gamepadsIds.Insert(joystickDeviceId, id);
|
||||
else
|
||||
_gamepadsIds.Add(id);
|
||||
|
||||
if (SDL2JoyConPair.IsCombinable(_gamepadsIds))
|
||||
{
|
||||
_gamepadsIds.Remove(SDL2JoyConPair.Id);
|
||||
_gamepadsIds.Add(SDL2JoyConPair.Id);
|
||||
joyConPairConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
OnGamepadConnected?.Invoke(id);
|
||||
if (joyConPairConnected)
|
||||
{
|
||||
OnGamepadConnected?.Invoke(SDL2JoyConPair.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Hid,
|
||||
$"{SDL_GameControllerNameForIndex(joystickDeviceId)} power level: {powerLevel}");
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
@@ -160,6 +193,14 @@ namespace Ryujinx.Input.SDL2
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
if (id == SDL2JoyConPair.Id)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return SDL2JoyConPair.GetGamepad(_gamepadsIds);
|
||||
}
|
||||
}
|
||||
|
||||
int joystickIndex = GetJoystickIndexByGamepadId(id);
|
||||
|
||||
if (joystickIndex == -1)
|
||||
@@ -173,6 +214,11 @@ namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SDL_GameControllerName(gamepadHandle).StartsWith(SDL2JoyCon.Prefix))
|
||||
{
|
||||
return new SDL2JoyCon(gamepadHandle, id);
|
||||
}
|
||||
|
||||
return new SDL2Gamepad(gamepadHandle, id);
|
||||
}
|
||||
|
||||
413
src/Ryujinx.Input.SDL2/SDL2JoyCon.cs
Normal file
413
src/Ryujinx.Input.SDL2/SDL2JoyCon.cs
Normal file
@@ -0,0 +1,413 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
internal class SDL2JoyCon : IGamepad
|
||||
{
|
||||
private bool HasConfiguration => _configuration != null;
|
||||
|
||||
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
|
||||
{
|
||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
|
||||
}
|
||||
|
||||
private StandardControllerInputConfig _configuration;
|
||||
|
||||
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _leftButtonsDriverMapping = new()
|
||||
{
|
||||
{ GamepadButtonInputId.LeftStick , SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK },
|
||||
{GamepadButtonInputId.DpadUp ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y},
|
||||
{GamepadButtonInputId.DpadDown ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A},
|
||||
{GamepadButtonInputId.DpadLeft ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B},
|
||||
{GamepadButtonInputId.DpadRight ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X},
|
||||
{GamepadButtonInputId.Minus ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START},
|
||||
{GamepadButtonInputId.LeftShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2},
|
||||
{GamepadButtonInputId.LeftTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4},
|
||||
{GamepadButtonInputId.SingleRightTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{GamepadButtonInputId.SingleLeftTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
};
|
||||
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _rightButtonsDriverMapping = new()
|
||||
{
|
||||
{GamepadButtonInputId.RightStick,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK},
|
||||
{GamepadButtonInputId.A,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B},
|
||||
{GamepadButtonInputId.B,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y},
|
||||
{GamepadButtonInputId.X,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A},
|
||||
{GamepadButtonInputId.Y,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X},
|
||||
{GamepadButtonInputId.Plus,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START},
|
||||
{GamepadButtonInputId.RightShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1},
|
||||
{GamepadButtonInputId.RightTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3},
|
||||
{GamepadButtonInputId.SingleRightTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{GamepadButtonInputId.SingleLeftTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER}
|
||||
};
|
||||
|
||||
private readonly Dictionary<GamepadButtonInputId, SDL_GameControllerButton> _buttonsDriverMapping;
|
||||
private readonly Lock _userMappingLock = new();
|
||||
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
|
||||
private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
|
||||
{
|
||||
StickInputId.Unbound, StickInputId.Left, StickInputId.Right,
|
||||
};
|
||||
|
||||
public GamepadFeaturesFlag Features { get; }
|
||||
|
||||
private nint _gamepadHandle;
|
||||
|
||||
private enum JoyConType
|
||||
{
|
||||
Left, Right
|
||||
}
|
||||
|
||||
public const string Prefix = "Nintendo Switch Joy-Con";
|
||||
public const string LeftName = "Nintendo Switch Joy-Con (L)";
|
||||
public const string RightName = "Nintendo Switch Joy-Con (R)";
|
||||
|
||||
private readonly JoyConType _joyConType;
|
||||
|
||||
public SDL2JoyCon(nint gamepadHandle, string driverId)
|
||||
{
|
||||
_gamepadHandle = gamepadHandle;
|
||||
_buttonsUserMapping = new List<ButtonMappingEntry>(10);
|
||||
|
||||
Name = SDL_GameControllerName(_gamepadHandle);
|
||||
Id = driverId;
|
||||
Features = GetFeaturesFlag();
|
||||
|
||||
// Enable motion tracking
|
||||
if (Features.HasFlag(GamepadFeaturesFlag.Motion))
|
||||
{
|
||||
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL,
|
||||
SDL_bool.SDL_TRUE) != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Hid,
|
||||
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
|
||||
}
|
||||
|
||||
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO,
|
||||
SDL_bool.SDL_TRUE) != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Hid,
|
||||
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
|
||||
}
|
||||
}
|
||||
|
||||
switch (Name)
|
||||
{
|
||||
case LeftName:
|
||||
{
|
||||
_buttonsDriverMapping = _leftButtonsDriverMapping;
|
||||
_joyConType = JoyConType.Left;
|
||||
break;
|
||||
}
|
||||
case RightName:
|
||||
{
|
||||
_buttonsDriverMapping = _rightButtonsDriverMapping;
|
||||
_joyConType = JoyConType.Right;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GamepadFeaturesFlag GetFeaturesFlag()
|
||||
{
|
||||
GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
|
||||
|
||||
if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE &&
|
||||
SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE)
|
||||
{
|
||||
result |= GamepadFeaturesFlag.Motion;
|
||||
}
|
||||
|
||||
int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100);
|
||||
|
||||
if (error == 0)
|
||||
{
|
||||
result |= GamepadFeaturesFlag.Rumble;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _gamepadHandle != nint.Zero)
|
||||
{
|
||||
SDL_GameControllerClose(_gamepadHandle);
|
||||
|
||||
_gamepadHandle = nint.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
if (!Features.HasFlag(GamepadFeaturesFlag.Rumble))
|
||||
return;
|
||||
|
||||
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||
|
||||
if (durationMs == uint.MaxValue)
|
||||
{
|
||||
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) !=
|
||||
0)
|
||||
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||
}
|
||||
else if (durationMs > SDL_HAPTIC_INFINITY)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
|
||||
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 GetMotionData(MotionInputId inputId)
|
||||
{
|
||||
SDL_SensorType sensorType = inputId switch
|
||||
{
|
||||
MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
|
||||
MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
|
||||
_ => SDL_SensorType.SDL_SENSOR_INVALID
|
||||
};
|
||||
|
||||
if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
|
||||
return Vector3.Zero;
|
||||
|
||||
const int ElementCount = 3;
|
||||
|
||||
unsafe
|
||||
{
|
||||
float* values = stackalloc float[ElementCount];
|
||||
|
||||
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount);
|
||||
|
||||
if (result != 0)
|
||||
return Vector3.Zero;
|
||||
|
||||
Vector3 value = _joyConType switch
|
||||
{
|
||||
JoyConType.Left => new Vector3(-values[2], values[1], values[0]),
|
||||
JoyConType.Right => new Vector3(values[2], values[1], -values[0])
|
||||
};
|
||||
|
||||
return inputId switch
|
||||
{
|
||||
MotionInputId.Gyroscope => RadToDegree(value),
|
||||
MotionInputId.Accelerometer => GsToMs2(value),
|
||||
_ => value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
|
||||
|
||||
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
_configuration = (StandardControllerInputConfig)configuration;
|
||||
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
// First update sticks
|
||||
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
|
||||
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
|
||||
|
||||
|
||||
switch (_joyConType)
|
||||
{
|
||||
case JoyConType.Left:
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
|
||||
break;
|
||||
case JoyConType.Right:
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
SetTriggerThreshold(_configuration.TriggerThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLed(uint packedRgb)
|
||||
{
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetStateSnapshot()
|
||||
{
|
||||
return IGamepad.GetStateSnapshot(this);
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
GamepadStateSnapshot rawState = GetStateSnapshot();
|
||||
GamepadStateSnapshot result = default;
|
||||
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
if (_buttonsUserMapping.Count == 0)
|
||||
return rawState;
|
||||
|
||||
|
||||
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
{
|
||||
if (!entry.IsValid)
|
||||
continue;
|
||||
|
||||
// Do not touch state of button already pressed
|
||||
if (!result.IsPressed(entry.To))
|
||||
{
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
}
|
||||
}
|
||||
|
||||
(float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
|
||||
(float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
|
||||
|
||||
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
|
||||
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static float ConvertRawStickValue(short value)
|
||||
{
|
||||
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
|
||||
|
||||
return value * ConvertRate;
|
||||
}
|
||||
|
||||
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId>
|
||||
GetLogicalJoyStickConfig(StickInputId inputId)
|
||||
{
|
||||
switch (inputId)
|
||||
{
|
||||
case StickInputId.Left:
|
||||
if (_configuration.RightJoyconStick.Joystick ==
|
||||
Common.Configuration.Hid.Controller.StickInputId.Left)
|
||||
return _configuration.RightJoyconStick;
|
||||
else
|
||||
return _configuration.LeftJoyconStick;
|
||||
case StickInputId.Right:
|
||||
if (_configuration.LeftJoyconStick.Joystick ==
|
||||
Common.Configuration.Hid.Controller.StickInputId.Right)
|
||||
return _configuration.LeftJoyconStick;
|
||||
else
|
||||
return _configuration.RightJoyconStick;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public (float, float) GetStick(StickInputId inputId)
|
||||
{
|
||||
if (inputId == StickInputId.Unbound)
|
||||
return (0.0f, 0.0f);
|
||||
|
||||
if (inputId == StickInputId.Left && _joyConType == JoyConType.Right || inputId == StickInputId.Right && _joyConType == JoyConType.Left)
|
||||
{
|
||||
return (0.0f, 0.0f);
|
||||
}
|
||||
|
||||
(short stickX, short stickY) = GetStickXY();
|
||||
|
||||
float resultX = ConvertRawStickValue(stickX);
|
||||
float resultY = -ConvertRawStickValue(stickY);
|
||||
|
||||
if (HasConfiguration)
|
||||
{
|
||||
var joyconStickConfig = GetLogicalJoyStickConfig(inputId);
|
||||
|
||||
if (joyconStickConfig != null)
|
||||
{
|
||||
if (joyconStickConfig.InvertStickX)
|
||||
resultX = -resultX;
|
||||
|
||||
if (joyconStickConfig.InvertStickY)
|
||||
resultY = -resultY;
|
||||
|
||||
if (joyconStickConfig.Rotate90CW)
|
||||
{
|
||||
float temp = resultX;
|
||||
resultX = resultY;
|
||||
resultY = -temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputId switch
|
||||
{
|
||||
StickInputId.Left when _joyConType == JoyConType.Left => (resultY, -resultX),
|
||||
StickInputId.Right when _joyConType == JoyConType.Right => (-resultY, resultX),
|
||||
_ => (0.0f, 0.0f)
|
||||
};
|
||||
}
|
||||
|
||||
private (short, short) GetStickXY()
|
||||
{
|
||||
return (
|
||||
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX),
|
||||
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY));
|
||||
}
|
||||
|
||||
public bool IsPressed(GamepadButtonInputId inputId)
|
||||
{
|
||||
if (!_buttonsDriverMapping.TryGetValue(inputId, out var button))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return SDL_GameControllerGetButton(_gamepadHandle, button) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs
Normal file
146
src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
internal class SDL2JoyConPair(IGamepad left, IGamepad right) : IGamepad
|
||||
{
|
||||
private StandardControllerInputConfig _configuration;
|
||||
|
||||
private readonly StickInputId[] _stickUserMapping =
|
||||
[
|
||||
StickInputId.Unbound,
|
||||
StickInputId.Left,
|
||||
StickInputId.Right
|
||||
];
|
||||
|
||||
public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) |
|
||||
(right?.Features ?? GamepadFeaturesFlag.None);
|
||||
|
||||
public const string Id = "JoyConPair";
|
||||
string IGamepad.Id => Id;
|
||||
|
||||
public string Name => "* Nintendo Switch Joy-Con (L/R)";
|
||||
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
left?.Dispose();
|
||||
right?.Dispose();
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
return GetStateSnapshot();
|
||||
}
|
||||
|
||||
public Vector3 GetMotionData(MotionInputId inputId)
|
||||
{
|
||||
return inputId switch
|
||||
{
|
||||
MotionInputId.Accelerometer or
|
||||
MotionInputId.Gyroscope => left.GetMotionData(inputId),
|
||||
MotionInputId.SecondAccelerometer => right.GetMotionData(MotionInputId.Accelerometer),
|
||||
MotionInputId.SecondGyroscope => right.GetMotionData(MotionInputId.Gyroscope),
|
||||
_ => Vector3.Zero
|
||||
};
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetStateSnapshot()
|
||||
{
|
||||
return IGamepad.GetStateSnapshot(this);
|
||||
}
|
||||
|
||||
public (float, float) GetStick(StickInputId inputId)
|
||||
{
|
||||
return inputId switch
|
||||
{
|
||||
StickInputId.Left => left.GetStick(StickInputId.Left),
|
||||
StickInputId.Right => right.GetStick(StickInputId.Right),
|
||||
_ => (0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
public bool IsPressed(GamepadButtonInputId inputId)
|
||||
{
|
||||
return left.IsPressed(inputId) || right.IsPressed(inputId);
|
||||
}
|
||||
|
||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
if (lowFrequency != 0)
|
||||
{
|
||||
right.Rumble(lowFrequency, lowFrequency, durationMs);
|
||||
}
|
||||
|
||||
if (highFrequency != 0)
|
||||
{
|
||||
left.Rumble(highFrequency, highFrequency, durationMs);
|
||||
}
|
||||
|
||||
if (lowFrequency == 0 && highFrequency == 0)
|
||||
{
|
||||
left.Rumble(0, 0, durationMs);
|
||||
right.Rumble(0, 0, durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
left.SetConfiguration(configuration);
|
||||
right.SetConfiguration(configuration);
|
||||
}
|
||||
|
||||
public void SetLed(uint packedRgb)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
left.SetTriggerThreshold(triggerThreshold);
|
||||
right.SetTriggerThreshold(triggerThreshold);
|
||||
}
|
||||
|
||||
public static bool IsCombinable(List<string> gamepadsIds)
|
||||
{
|
||||
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
||||
return leftIndex >= 0 && rightIndex >= 0;
|
||||
}
|
||||
|
||||
private static (int leftIndex, int rightIndex) DetectJoyConPair(List<string> gamepadsIds)
|
||||
{
|
||||
var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id)
|
||||
.Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList();
|
||||
int leftIndex = gamepadNames.IndexOf(SDL2JoyCon.LeftName);
|
||||
int rightIndex = gamepadNames.IndexOf(SDL2JoyCon.RightName);
|
||||
|
||||
return (leftIndex, rightIndex);
|
||||
}
|
||||
|
||||
public static IGamepad GetGamepad(List<string> gamepadsIds)
|
||||
{
|
||||
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
||||
if (leftIndex == -1 || rightIndex == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex);
|
||||
nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex);
|
||||
|
||||
if (leftGamepadHandle == nint.Zero || rightGamepadHandle == nint.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return new SDL2JoyConPair(new SDL2JoyCon(leftGamepadHandle, gamepadsIds[leftIndex]),
|
||||
new SDL2JoyCon(rightGamepadHandle, gamepadsIds[rightIndex]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,10 +269,12 @@ namespace Ryujinx.Input.HLE
|
||||
if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
|
||||
{
|
||||
_leftMotionInput = new MotionInput();
|
||||
_rightMotionInput = new MotionInput();
|
||||
}
|
||||
else
|
||||
{
|
||||
_leftMotionInput = null;
|
||||
_rightMotionInput = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +303,20 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
|
||||
{
|
||||
_rightMotionInput = _leftMotionInput;
|
||||
if (gamepad.Id == "JoyConPair")
|
||||
{
|
||||
Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.SecondAccelerometer);
|
||||
Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.SecondGyroscope);
|
||||
|
||||
rightAccelerometer = new Vector3(rightAccelerometer.X, -rightAccelerometer.Z, rightAccelerometer.Y);
|
||||
rightGyroscope = new Vector3(rightGyroscope.X, -rightGyroscope.Z, rightGyroscope.Y);
|
||||
|
||||
_rightMotionInput.Update(rightAccelerometer, rightGyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rightMotionInput = _leftMotionInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,6 +351,7 @@ namespace Ryujinx.Input.HLE
|
||||
// Reset states
|
||||
State = default;
|
||||
_leftMotionInput = null;
|
||||
_rightMotionInput = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,5 +21,17 @@ namespace Ryujinx.Input
|
||||
/// </summary>
|
||||
/// <remarks>Values are in degrees</remarks>
|
||||
Gyroscope,
|
||||
|
||||
/// <summary>
|
||||
/// Second accelerometer.
|
||||
/// </summary>
|
||||
/// <remarks>Values are in m/s^2</remarks>
|
||||
SecondAccelerometer,
|
||||
|
||||
/// <summary>
|
||||
/// Second gyroscope.
|
||||
/// </summary>
|
||||
/// <remarks>Values are in degrees</remarks>
|
||||
SecondGyroscope
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,13 @@ namespace Ryujinx.SDL2.Common
|
||||
private uint _refereceCount;
|
||||
private Thread _worker;
|
||||
|
||||
private const uint SDL_JOYBATTERYUPDATED = 1543;
|
||||
|
||||
public event Action<int, int> OnJoyStickConnected;
|
||||
public event Action<int> OnJoystickDisconnected;
|
||||
|
||||
public event Action<int, SDL_JoystickPowerLevel> OnJoyBatteryUpdated;
|
||||
|
||||
|
||||
private ConcurrentDictionary<uint, Action<SDL_Event>> _registeredWindowHandlers;
|
||||
|
||||
@@ -143,6 +148,10 @@ namespace Ryujinx.SDL2.Common
|
||||
|
||||
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
|
||||
}
|
||||
else if ((uint)evnt.type == SDL_JOYBATTERYUPDATED)
|
||||
{
|
||||
OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code);
|
||||
}
|
||||
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP)
|
||||
{
|
||||
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
Dictionary<LocaleKeys, string> localeStrings = new();
|
||||
|
||||
_localeData ??= EmbeddedResources.ReadAllText("Ryujinx/Assets/locales.json")
|
||||
_localeData ??= EmbeddedResources.ReadAllText("Ryujinx/Assets/Locale.json")
|
||||
.Into(it => JsonHelper.Deserialize(it, LocalesJsonContext.Default.LocalesJson));
|
||||
|
||||
foreach (LocalesEntry locale in _localeData.Value.Locales)
|
||||
|
||||
@@ -312,37 +312,34 @@ namespace Ryujinx.Headless
|
||||
|
||||
return new OpenGLRenderer();
|
||||
}
|
||||
|
||||
|
||||
private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) =>
|
||||
new(
|
||||
|
||||
new HleConfiguration(
|
||||
options.DramSize,
|
||||
options.SystemLanguage,
|
||||
options.SystemRegion,
|
||||
options.VSyncMode,
|
||||
!options.DisableDockedMode,
|
||||
!options.DisablePTC,
|
||||
ITickSource.RealityTickScalar,
|
||||
options.EnableInternetAccess,
|
||||
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
|
||||
options.FsGlobalAccessLogMode,
|
||||
options.SystemTimeOffset,
|
||||
options.SystemTimeZone,
|
||||
options.MemoryManagerMode,
|
||||
#if DEBUG
|
||||
options.IgnoreMissingServices,
|
||||
#endif
|
||||
options.AspectRatio,
|
||||
options.AudioVolume,
|
||||
options.UseHypervisor ?? true,
|
||||
options.MultiplayerLanInterfaceId,
|
||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
|
||||
false,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
options.CustomVSyncInterval
|
||||
)
|
||||
options.DramSize,
|
||||
options.SystemLanguage,
|
||||
options.SystemRegion,
|
||||
options.VSyncMode,
|
||||
!options.DisableDockedMode,
|
||||
!options.DisablePTC,
|
||||
ITickSource.RealityTickScalar,
|
||||
options.EnableInternetAccess,
|
||||
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
|
||||
options.FsGlobalAccessLogMode,
|
||||
options.SystemTimeOffset,
|
||||
options.SystemTimeZone,
|
||||
options.MemoryManagerMode,
|
||||
options.IgnoreMissingServices,
|
||||
options.AspectRatio,
|
||||
options.AudioVolume,
|
||||
options.UseHypervisor ?? true,
|
||||
options.MultiplayerLanInterfaceId,
|
||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
|
||||
false,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
options.CustomVSyncInterval
|
||||
)
|
||||
.Configure(
|
||||
_virtualFileSystem,
|
||||
_libHacHorizonManager,
|
||||
|
||||
@@ -145,9 +145,15 @@ namespace Ryujinx.Headless
|
||||
if (NeedsOverride(nameof(DramSize)))
|
||||
DramSize = configurationState.System.DramSize;
|
||||
|
||||
if (NeedsOverride(nameof(IgnoreMissingServices)))
|
||||
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
|
||||
|
||||
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
||||
IgnoreControllerApplet = configurationState.System.IgnoreControllerApplet;
|
||||
|
||||
|
||||
if (NeedsOverride(nameof(SkipUserProfilesManager)))
|
||||
SkipUserProfilesManager = configurationState.System.SkipUserProfilesManager;
|
||||
|
||||
return;
|
||||
|
||||
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
|
||||
@@ -405,14 +411,15 @@ namespace Ryujinx.Headless
|
||||
[Option("dram-size", Required = false, Default = MemoryConfiguration.MemoryConfiguration4GiB, HelpText = "Set the RAM amount on the emulated system.")]
|
||||
public MemoryConfiguration DramSize { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
[Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")]
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
#endif
|
||||
|
||||
[Option("ignore-controller-applet", Required = false, Default = false, HelpText = "Enable ignoring the controller applet when your game loses connection to your controller.")]
|
||||
public bool IgnoreControllerApplet { get; set; }
|
||||
|
||||
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
|
||||
public bool SkipUserProfilesManager { get; set; }
|
||||
|
||||
// Values
|
||||
|
||||
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
||||
|
||||
@@ -154,7 +154,9 @@
|
||||
<EmbeddedResource Include="..\..\docs\compatibility.csv" LogicalName="RyujinxGameCompatibilityList">
|
||||
<Link>Assets\RyujinxGameCompatibility.csv</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Assets\locales.json" />
|
||||
<EmbeddedResource Include="..\..\assets\locales.json">
|
||||
<Link>Assets\Locale.json</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
|
||||
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
|
||||
@@ -173,6 +175,6 @@
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Assets\locales.json" />
|
||||
<AdditionalFiles Include="..\..\assets\locales.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -195,6 +195,7 @@ namespace Ryujinx.Ava.Systems
|
||||
_defaultCursorWin = CreateArrowCursor();
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
||||
@@ -487,6 +488,14 @@ namespace Ryujinx.Ava.Systems
|
||||
Exit();
|
||||
}
|
||||
|
||||
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
|
||||
{
|
||||
if (Device != null)
|
||||
{
|
||||
Device.Configuration.IgnoreMissingServices = args.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args)
|
||||
{
|
||||
if (Device != null)
|
||||
@@ -600,7 +609,9 @@ namespace Ryujinx.Ava.Systems
|
||||
{
|
||||
if (Device.Processes != null)
|
||||
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||
|
||||
|
||||
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 68;
|
||||
public const int CurrentVersion = 69;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the configuration file format
|
||||
@@ -187,6 +187,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// </summary>
|
||||
public bool IgnoreApplet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skip user profiles manager dialog during gameplay(the used profile in the configuration will be selected)
|
||||
/// </summary>
|
||||
public bool SkipUserProfiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables save window size, position and state on close.
|
||||
/// </summary>
|
||||
@@ -299,6 +304,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// </summary>
|
||||
public MemoryConfiguration DramSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable or disable ignoring missing services
|
||||
/// </summary>
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to toggle columns in the GUI
|
||||
/// </summary>
|
||||
|
||||
@@ -101,7 +101,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
System.AudioVolume.Value = cff.AudioVolume;
|
||||
System.MemoryManagerMode.Value = cff.MemoryManagerMode;
|
||||
System.DramSize.Value = cff.DramSize;
|
||||
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
|
||||
System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
|
||||
System.SkipUserProfilesManager.Value = cff.SkipUserProfiles;
|
||||
System.UseHypervisor.Value = cff.UseHypervisor;
|
||||
|
||||
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
|
||||
@@ -458,7 +460,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
TurboMode = Key.Unbound,
|
||||
TurboModeWhileHeld = false
|
||||
};
|
||||
})
|
||||
}),
|
||||
(69, static cff => cff.SkipUserProfiles = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Gommon;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Ava.Systems.Configuration.System;
|
||||
using Ryujinx.Ava.Systems.Configuration.UI;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
@@ -385,12 +384,22 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// Defines the amount of RAM available on the emulated system, and how it is distributed
|
||||
/// </summary>
|
||||
public ReactiveObject<MemoryConfiguration> DramSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable or disable ignoring missing services
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> IgnoreMissingServices { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ignore Controller Applet
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> IgnoreControllerApplet { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skip User Profiles Manager
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> SkipUserProfilesManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uses Hypervisor over JIT if available
|
||||
/// </summary>
|
||||
@@ -437,8 +446,12 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
MemoryManagerMode.LogChangesToValue(nameof(MemoryManagerMode));
|
||||
DramSize = new ReactiveObject<MemoryConfiguration>();
|
||||
DramSize.LogChangesToValue(nameof(DramSize));
|
||||
IgnoreMissingServices = new ReactiveObject<bool>();
|
||||
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
|
||||
IgnoreControllerApplet = new ReactiveObject<bool>();
|
||||
IgnoreControllerApplet.LogChangesToValue(nameof(IgnoreControllerApplet));
|
||||
SkipUserProfilesManager = new ReactiveObject<bool>();
|
||||
SkipUserProfilesManager.LogChangesToValue(nameof(SkipUserProfilesManager));
|
||||
AudioVolume = new ReactiveObject<float>();
|
||||
AudioVolume.LogChangesToValue(nameof(AudioVolume));
|
||||
UseHypervisor = new ReactiveObject<bool>();
|
||||
@@ -861,9 +874,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
: System.SystemTimeOffset,
|
||||
System.TimeZone,
|
||||
System.MemoryManagerMode,
|
||||
#if DEBUG
|
||||
CommandLineState.IgnoreMissingServices,
|
||||
#endif
|
||||
System.IgnoreMissingServices,
|
||||
Graphics.AspectRatio,
|
||||
System.AudioVolume,
|
||||
System.UseHypervisor,
|
||||
@@ -871,8 +882,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Multiplayer.Mode,
|
||||
Multiplayer.DisableP2p,
|
||||
Multiplayer.LdnPassphrase,
|
||||
Instance.Multiplayer.GetLdnServer(),
|
||||
Instance.Graphics.CustomVSyncInterval,
|
||||
Instance.Hacks.ShowDirtyHacks ? Instance.Hacks.EnabledHacks : null);
|
||||
Multiplayer.GetLdnServer(),
|
||||
Graphics.CustomVSyncInterval,
|
||||
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
AudioVolume = System.AudioVolume,
|
||||
MemoryManagerMode = System.MemoryManagerMode,
|
||||
DramSize = System.DramSize,
|
||||
IgnoreMissingServices = System.IgnoreMissingServices,
|
||||
IgnoreApplet = System.IgnoreControllerApplet,
|
||||
SkipUserProfiles = System.SkipUserProfilesManager,
|
||||
UseHypervisor = System.UseHypervisor,
|
||||
GuiColumns = new GuiColumns
|
||||
{
|
||||
@@ -203,7 +205,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
System.AudioVolume.Value = 1;
|
||||
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
|
||||
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
|
||||
System.IgnoreMissingServices.Value = false;
|
||||
System.IgnoreControllerApplet.Value = false;
|
||||
System.SkipUserProfilesManager.Value = false;
|
||||
System.UseHypervisor.Value = true;
|
||||
Multiplayer.LanInterfaceId.Value = "0";
|
||||
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
@@ -78,6 +79,13 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
public static async Task<(UserId Id, bool Result)> ShowInputDialog(ProfileSelectorDialogViewModel viewModel)
|
||||
{
|
||||
|
||||
if (ConfigurationState.Instance.System.SkipUserProfilesManager)
|
||||
{
|
||||
UserId defaultId = viewModel.SelectedUserId;
|
||||
return (defaultId, true);
|
||||
}
|
||||
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool ShowConfirmExit { get; set; }
|
||||
public bool IgnoreApplet { get; set; }
|
||||
public bool SkipUserProfiles { get; set; }
|
||||
public bool RememberWindowState { get; set; }
|
||||
public bool ShowOldUI { get; set; }
|
||||
public int HideCursor { get; set; }
|
||||
@@ -228,6 +229,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool EnableInternetAccess { get; set; }
|
||||
public bool EnableFsIntegrityChecks { get; set; }
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
public MemoryConfiguration DramSize { get; set; }
|
||||
public bool EnableShaderCache { get; set; }
|
||||
public bool EnableTextureRecompression { get; set; }
|
||||
@@ -603,7 +605,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
VSyncMode = config.Graphics.VSyncMode;
|
||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||
DramSize = config.System.DramSize;
|
||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||
IgnoreApplet = config.System.IgnoreControllerApplet;
|
||||
SkipUserProfiles = config.System.SkipUserProfilesManager;
|
||||
|
||||
// CPU
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
@@ -705,7 +709,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.DramSize.Value = DramSize;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
config.System.IgnoreControllerApplet.Value = IgnoreApplet;
|
||||
config.System.SkipUserProfilesManager.Value = SkipUserProfiles;
|
||||
|
||||
// CPU
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
private static IEnumerable<MenuItem> GenerateLanguageMenuItems()
|
||||
{
|
||||
const string LocalePath = "Ryujinx/Assets/locales.json";
|
||||
const string LocalePath = "Ryujinx/Assets/Locale.json";
|
||||
|
||||
string languageJson = EmbeddedResources.ReadAllText(LocalePath);
|
||||
|
||||
|
||||
@@ -313,6 +313,11 @@
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox
|
||||
IsChecked="{Binding IgnoreMissingServices}"
|
||||
ToolTip.Tip="{ext:Locale IgnoreMissingServicesTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreMissingServices}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsChecked="{Binding IgnoreApplet}"
|
||||
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
|
||||
@@ -323,6 +328,11 @@
|
||||
ToolTip.Tip="{ext:Locale SettingsTabSystemEnableCustomVSyncIntervalTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemEnableCustomVSyncInterval}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsChecked="{Binding SkipUserProfiles}"
|
||||
ToolTip.Tip="{ext:Locale SkipUserProfilesTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemSkipUserProfilesManager}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -26,9 +26,6 @@ namespace Ryujinx.Ava.Utilities
|
||||
public static bool StartFullscreenArg { get; private set; }
|
||||
public static bool HideAvailableUpdates { get; private set; }
|
||||
|
||||
#if DEBUG
|
||||
public static bool IgnoreMissingServices { get; private set; }
|
||||
#endif
|
||||
|
||||
public static void ParseArguments(string[] args)
|
||||
{
|
||||
@@ -188,11 +185,6 @@ namespace Ryujinx.Ava.Utilities
|
||||
case "--hide-updates":
|
||||
HideAvailableUpdates = true;
|
||||
break;
|
||||
#if DEBUG
|
||||
case "--ignore-missing-services":
|
||||
IgnoreMissingServices = true;
|
||||
break;
|
||||
#endif
|
||||
case "--software-gui":
|
||||
OverrideHardwareAcceleration = false;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user