mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-07-11 09:54:02 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b19e2ac65d | |||
| 6953128ff2 | |||
| b72990bf53 | |||
| 732e882524 | |||
| 68432d210b | |||
| e47417a161 | |||
| d665de1f4f | |||
| 680b5b719a | |||
| b236732713 | |||
| 97aed15d19 | |||
| 134773e95f | |||
| 5072904162 |
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "WindowTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "أميبو",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Amiibo",
|
||||
"es_ES": null,
|
||||
"fr_FR": null,
|
||||
"he_IL": "אמיבו",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": null,
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": null,
|
||||
"ru_RU": null,
|
||||
"sv_SE": null,
|
||||
"th_TH": "อมิโบ้",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": null,
|
||||
"zh_TW": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SeriesLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "سلسلة",
|
||||
"de_DE": "Serie",
|
||||
"el_GR": "Σειρά",
|
||||
"en_US": "Series",
|
||||
"es_ES": "Serie",
|
||||
"fr_FR": "Séries",
|
||||
"he_IL": "סדרה",
|
||||
"it_IT": "Serie",
|
||||
"ja_JP": "シリーズ",
|
||||
"ko_KR": "시리즈",
|
||||
"no_NO": "Serie",
|
||||
"pl_PL": "Seria",
|
||||
"pt_BR": "Série",
|
||||
"ru_RU": "Серия",
|
||||
"sv_SE": "Serie",
|
||||
"th_TH": "ซีรีส์",
|
||||
"tr_TR": "Seri",
|
||||
"uk_UA": "Серія",
|
||||
"zh_CN": "系列",
|
||||
"zh_TW": "系列"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ShowAllAmiiboLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "إظهار الكل Amiibo",
|
||||
"de_DE": "Alle Amiibo anzeigen",
|
||||
"el_GR": "Εμφάνιση Όλων των Amiibo",
|
||||
"en_US": "Show All Amiibo",
|
||||
"es_ES": "Mostrar Todas las Amiibo",
|
||||
"fr_FR": "Afficher Tous les Amiibo",
|
||||
"he_IL": "הצג הכל Amiibo",
|
||||
"it_IT": "Mostra tutti gli Amiibo",
|
||||
"ja_JP": "すべてのAmiiboを表示",
|
||||
"ko_KR": "모든 Amiibo 표시",
|
||||
"no_NO": "Vis alle Amiibo",
|
||||
"pl_PL": "Pokaż Wszystkie Amiibo",
|
||||
"pt_BR": "Mostrar Todos os Amiibo",
|
||||
"ru_RU": "Показывать все Amiibo",
|
||||
"sv_SE": "Visa alla Amiibo",
|
||||
"th_TH": "แสดง Amiibo ทั้งหมด",
|
||||
"tr_TR": "Tüm Amiibo'ları Göster",
|
||||
"uk_UA": "Показати всі Amiibo",
|
||||
"zh_CN": "显示所有 Amiibo",
|
||||
"zh_TW": "顯示所有 Amiibo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UseRandomUuidLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "استخدام UUID عشوائي",
|
||||
"de_DE": "Zufällige UUID verwenden",
|
||||
"el_GR": "Χρήση τυχαίου UUID",
|
||||
"en_US": "Use Random UUID",
|
||||
"es_ES": "Usar UUID aleatorio",
|
||||
"fr_FR": "Utiliser un UUID aléatoire",
|
||||
"he_IL": "השתמש ב-UUID אקראי",
|
||||
"it_IT": "Usa UUID casuale",
|
||||
"ja_JP": "ランダム UUID を使用",
|
||||
"ko_KR": "무작위 UUID 사용",
|
||||
"no_NO": "Bruk tilfeldig UUID",
|
||||
"pl_PL": "Użyj losowego UUID",
|
||||
"pt_BR": "Usar UUID aleatório",
|
||||
"ru_RU": "Использовать случайный UUID",
|
||||
"sv_SE": "Använd slumpmässig UUID",
|
||||
"th_TH": "ใช้ UUID แบบสุ่ม",
|
||||
"tr_TR": "Rastgele UUID kullan",
|
||||
"uk_UA": "Використовувати випадковий UUID",
|
||||
"zh_CN": "使用随机 UUID",
|
||||
"zh_TW": "使用隨機 UUID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "PauseEmulationWhileScanningAmiibo",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Emulation während des Scannens pausieren",
|
||||
"el_GR": "",
|
||||
"en_US": "Pause Emulation While Scanning",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "Приостанавливать эмуляцию во время сканирования",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UsageLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "الاستخدام:",
|
||||
"de_DE": "Nutzung:",
|
||||
"el_GR": "Χρήση:",
|
||||
"en_US": "Usage:",
|
||||
"es_ES": "Uso:",
|
||||
"fr_FR": "Utilisation :",
|
||||
"he_IL": "שימוש:",
|
||||
"it_IT": "Utilizzo:",
|
||||
"ja_JP": "使用法:",
|
||||
"ko_KR": "사용법:",
|
||||
"no_NO": "Forbruk:",
|
||||
"pl_PL": "Użycie:",
|
||||
"pt_BR": "Uso:",
|
||||
"ru_RU": "Применение:",
|
||||
"sv_SE": "Användning:",
|
||||
"th_TH": "การใช้งาน:",
|
||||
"tr_TR": "Kullanım:",
|
||||
"uk_UA": "Використання:",
|
||||
"zh_CN": "用法:",
|
||||
"zh_TW": "用途:"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "WritableLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "الاستخدام (يمكنه حفظ البيانات):",
|
||||
"de_DE": "Nutzung (Kann Daten speichern):",
|
||||
"el_GR": "Χρήση (Μπορεί να αποθηκεύσει δεδομένα):",
|
||||
"en_US": "Usage (Can Save Data):",
|
||||
"es_ES": "Uso (Puede guardar datos):",
|
||||
"fr_FR": "Utilisation (Peut enregistrer des données) :",
|
||||
"he_IL": "שימוש (יכול לשמור נתונים):",
|
||||
"it_IT": "Utilizzo (Può salvare dati):",
|
||||
"ja_JP": "使用法(データを保存できます):",
|
||||
"ko_KR": "사용법 (데이터 저장 가능):",
|
||||
"no_NO": "Bruk (Kan lagre data):",
|
||||
"pl_PL": "Użycie (Może zapisywać dane):",
|
||||
"pt_BR": "Uso (Pode salvar dados):",
|
||||
"ru_RU": "Применение (Может сохранять данные):",
|
||||
"sv_SE": "Användning (Kan spara data):",
|
||||
"th_TH": "การใช้งาน (สามารถบันทึกข้อมูลได้):",
|
||||
"tr_TR": "Kullanım (Veri kaydedebilir):",
|
||||
"uk_UA": "Використання (Може зберігати дані):",
|
||||
"zh_CN": "用法(可保存数据):",
|
||||
"zh_TW": "用途(可儲存資料):"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "UnknownLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "غير معروف",
|
||||
"de_DE": "Unbekannt",
|
||||
"el_GR": "Άγνωστο",
|
||||
"en_US": "Unknown",
|
||||
"es_ES": "Desconocido",
|
||||
"fr_FR": "Inconnu",
|
||||
"he_IL": "לא ידוע",
|
||||
"it_IT": "Sconosciuto",
|
||||
"ja_JP": "不明",
|
||||
"ko_KR": "알 수 없음",
|
||||
"no_NO": "Ukjent",
|
||||
"pl_PL": "Nieznane",
|
||||
"pt_BR": "Desconhecido",
|
||||
"ru_RU": "Неизвестно",
|
||||
"sv_SE": "Okänt",
|
||||
"th_TH": "ไม่รู้จัก",
|
||||
"tr_TR": "Bilinmeyen",
|
||||
"uk_UA": "Невідомо",
|
||||
"zh_CN": "未知",
|
||||
"zh_TW": "未知"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ScanButton",
|
||||
"Translations": {
|
||||
"ar_SA": "فحص Amiibo",
|
||||
"de_DE": "Amiibo einscannen",
|
||||
"el_GR": "Σαρώστε το Amiibo",
|
||||
"en_US": "Scan Amiibo",
|
||||
"es_ES": "Escanear Amiibo",
|
||||
"fr_FR": "Scanner l'Amiibo",
|
||||
"he_IL": "סרוק את ה-Amiibo",
|
||||
"it_IT": "Scansiona l'Amiibo",
|
||||
"ja_JP": "Amiiboをスキャン",
|
||||
"ko_KR": "Amiibo 스캔하기",
|
||||
"no_NO": "Skanne Amiibo",
|
||||
"pl_PL": "Zeskanuj Amiibo",
|
||||
"pt_BR": "Escanear Amiibo",
|
||||
"ru_RU": "Сканировать Amiibo",
|
||||
"sv_SE": "Skanna Amiibo",
|
||||
"th_TH": "สแกน Amiibo",
|
||||
"tr_TR": "Amiibo'yu tara",
|
||||
"uk_UA": "Сканувати Amiibo",
|
||||
"zh_CN": "扫描 Amiibo",
|
||||
"zh_TW": "掃描 Amiibo"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "Cancel",
|
||||
"Translations": {
|
||||
"ar_SA": "إلغاء",
|
||||
"de_DE": "Abbrechen",
|
||||
"el_GR": "Ακύρωση",
|
||||
"en_US": "Cancel",
|
||||
"es_ES": "Cancelar",
|
||||
"fr_FR": "Annuler",
|
||||
"he_IL": "ביטול",
|
||||
"it_IT": "Annulla",
|
||||
"ja_JP": "キャンセル",
|
||||
"ko_KR": "취소",
|
||||
"no_NO": "Avbryt",
|
||||
"pl_PL": "Anuluj",
|
||||
"pt_BR": "Cancelar",
|
||||
"ru_RU": "Отмена",
|
||||
"sv_SE": "Avbryt",
|
||||
"th_TH": "ยกเลิก",
|
||||
"tr_TR": "İptal",
|
||||
"uk_UA": "Скасувати",
|
||||
"zh_CN": "取消",
|
||||
"zh_TW": "取消"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "AllSupportedFormats",
|
||||
"Translations": {
|
||||
"ar_SA": "كل التنسيقات المدعومة",
|
||||
"de_DE": "Alle unterstützten Formate",
|
||||
"el_GR": "Όλες Οι Υποστηριζόμενες Μορφές",
|
||||
"en_US": "All Supported Formats",
|
||||
"es_ES": "Todos los Formatos Soportados",
|
||||
"fr_FR": "Tous les Formats Supportés",
|
||||
"he_IL": "כל הפורמטים הנתמכים",
|
||||
"it_IT": "Tutti i formati supportati",
|
||||
"ja_JP": "すべての対応フォーマット",
|
||||
"ko_KR": "지원되는 모든 형식",
|
||||
"no_NO": "Alle støttede formater",
|
||||
"pl_PL": "Wszystkie Obsługiwane Formaty",
|
||||
"pt_BR": "Todos os Formatos Suportados",
|
||||
"ru_RU": "Все поддерживаемые форматы",
|
||||
"sv_SE": "Alla format som stöds",
|
||||
"th_TH": "รูปแบบที่รองรับทั้งหมด",
|
||||
"tr_TR": "Tüm Desteklenen Formatlar",
|
||||
"uk_UA": "Усі підтримувані формати",
|
||||
"zh_CN": "所有支持的格式",
|
||||
"zh_TW": "所有支援的格式"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,30 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "SearchAmiiboWatermark",
|
||||
"Translations": {
|
||||
"ar_SA": "بحث Amiibo...",
|
||||
"de_DE": "Amiibo suchen...",
|
||||
"el_GR": "Αναζήτηση Amiibo...",
|
||||
"en_US": "Search Amiibo...",
|
||||
"es_ES": "Buscar Amiibo...",
|
||||
"fr_FR": "Rechercher Amiibo...",
|
||||
"he_IL": "חפש Amiibo...",
|
||||
"it_IT": "Cerca Amiibo...",
|
||||
"ja_JP": "Amiiboを検索...",
|
||||
"ko_KR": "Amiibo 검색...",
|
||||
"no_NO": "Søk Amiibo...",
|
||||
"pl_PL": "Wyszukaj Amiibo...",
|
||||
"pt_BR": "Buscar Amiibo...",
|
||||
"ru_RU": "Поиск Amiibo...",
|
||||
"sv_SE": "Sök Amiibo...",
|
||||
"th_TH": "ค้นหา Amiibo...",
|
||||
"tr_TR": "Amiibo Ara...",
|
||||
"uk_UA": "Пошук Amiibo...",
|
||||
"zh_CN": "搜索 Amiibo...",
|
||||
"zh_TW": "搜尋 Amiibo..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "SearchWatermark",
|
||||
"Translations": {
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"ID": "TrimStatusLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Trimmstatus",
|
||||
"el_GR": "",
|
||||
"en_US": "Trim Status",
|
||||
"es_ES": "Estado del recorte",
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "ScanAmiiboFromBinFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle eine BIN-Datei aus, um ein Amiibo zu scannen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a BIN file to scan an Amiibo from",
|
||||
"es_ES": "Selecciona un archivo BIN para escanear un Amiibo",
|
||||
"fr_FR": "Sélectionnez un fichier BIN pour scanner un Amiibo",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona un file BIN da cui scansionare un Amiibo",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione um arquivo BIN para escanear um Amiibo",
|
||||
"ru_RU": "Выберите BIN-файл для сканирования Amiibo",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть BIN-файл для сканування Amiibo",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "APITitle",
|
||||
"Translations": {
|
||||
"ar_SA": "أميبو API",
|
||||
"de_DE": "Amiibo-API",
|
||||
"el_GR": "API για Amiibo.",
|
||||
"en_US": "Amiibo API",
|
||||
"es_ES": "API Amiibo",
|
||||
"fr_FR": "API Amiibo",
|
||||
"he_IL": "ממשק תכנות אמיבו",
|
||||
"it_IT": "API Amiibo",
|
||||
"ja_JP": "",
|
||||
"ko_KR": null,
|
||||
"no_NO": "",
|
||||
"pl_PL": "API Amiibo",
|
||||
"pt_BR": "API Amiibo",
|
||||
"ru_RU": "API Amiibo",
|
||||
"sv_SE": "Amiibo-API",
|
||||
"th_TH": "อามิโบ้ API",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "API Amiibo",
|
||||
"zh_CN": null,
|
||||
"zh_TW": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "APIFailFetchMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "حدث خطأ أثناء جلب المعلومات من API.",
|
||||
"de_DE": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.",
|
||||
"el_GR": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.",
|
||||
"en_US": "An error occured while fetching information from the API.",
|
||||
"es_ES": "Ocurrió un error al recibir información de la API.",
|
||||
"fr_FR": "Une erreur est survenue lors de la récupération des informations de l'API.",
|
||||
"he_IL": "אירעה שגיאה בעת שליפת מידע מהממשק.",
|
||||
"it_IT": "Si è verificato un errore durante il recupero delle informazioni dall'API.",
|
||||
"ja_JP": "API からの情報取得中にエラーが発生しました.",
|
||||
"ko_KR": "API에서 정보를 가져오는 중에 오류가 발생했습니다.",
|
||||
"no_NO": "En feil oppstod under henting av informasjon fra API.",
|
||||
"pl_PL": "Wystąpił błąd podczas pobierania informacji z API.",
|
||||
"pt_BR": "Um erro ocorreu ao tentar obter informações da API.",
|
||||
"ru_RU": "Произошла ошибка при получении информации из API.",
|
||||
"sv_SE": "Ett fel inträffade vid hämtning av information från API.",
|
||||
"th_TH": "เกิดข้อผิดพลาดขณะเรียกข้อมูลจาก API",
|
||||
"tr_TR": "API'dan bilgi alırken bir hata oluştu.",
|
||||
"uk_UA": "Під час отримання інформації з API сталася помилка.",
|
||||
"zh_CN": "从 API 获取信息时出错。",
|
||||
"zh_TW": "從 API 取得資訊時出現錯誤。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "APIConnectErrorMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "غير قادر على الاتصال بخادم API أميبو. قد تكون الخدمة معطلة أو قد تحتاج إلى التحقق من اتصالك بالإنترنت.",
|
||||
"de_DE": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.",
|
||||
"el_GR": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.",
|
||||
"en_US": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.",
|
||||
"es_ES": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.",
|
||||
"fr_FR": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.",
|
||||
"he_IL": "לא ניתן להתחבר לממשק שרת האמיבו. ייתכן שהשירות מושבת או שתצטרך לוודא שהחיבור לאינטרנט שלך מקוון.",
|
||||
"it_IT": "Impossibile connettersi al server dell'API Amiibo. Il servizio potrebbe non essere disponibile o potresti non essere connesso a Internet.",
|
||||
"ja_JP": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.",
|
||||
"ko_KR": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 온라인 상태인지 확인이 필요합니다.",
|
||||
"no_NO": "Kan ikke koble til Amiibo API server. Tjenesten kan være nede, eller du må kanskje verifisere at din internettforbindelse er tilkoblet.",
|
||||
"pl_PL": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.",
|
||||
"pt_BR": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.",
|
||||
"ru_RU": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна или вам может потребоваться проверить ваше интернет-соединение.",
|
||||
"sv_SE": "Kunde inte ansluta till Amiibo API-server. Tjänsten kanske är nere eller så behöver du kontrollera att din internetanslutning fungerar.",
|
||||
"th_TH": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ Amiibo API บางบริการอาจหยุดทำงาน หรือไม่คุณต้องทำการตรวจสอบว่าอินเทอร์เน็ตของคุณอยู่ในสถานะเชื่อมต่ออยู่หรือไม่",
|
||||
"tr_TR": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.",
|
||||
"uk_UA": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.",
|
||||
"zh_CN": "无法连接到 Amiibo API 服务器,服务可能已关闭,或者没有连接互联网。",
|
||||
"zh_TW": "無法連接 Amiibo API 伺服器。服務可能已停機,或者您可能需要確認網際網路連線是否在線上。"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "RenameAmiiboTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Amiibo umbenennen",
|
||||
"el_GR": "",
|
||||
"en_US": "Rename Amiibo",
|
||||
"es_ES": "Renombrar Amiibo",
|
||||
"fr_FR": "Renommer l'Amiibo",
|
||||
"he_IL": "",
|
||||
"it_IT": "Rinomina Amiibo",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Renomear Amiibo",
|
||||
"ru_RU": "Переименовать Amiibo",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Перейменувати Amiibo",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ScanAmiiboTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "فحص Amiibo",
|
||||
"de_DE": "Amiibo einscannen",
|
||||
"el_GR": "Σαρώστε το Amiibo",
|
||||
"en_US": "Scan Amiibo",
|
||||
"es_ES": "Escanear Amiibo",
|
||||
"fr_FR": "Scanner l'Amiibo",
|
||||
"he_IL": "סרוק את ה-Amiibo",
|
||||
"it_IT": "Scansiona l'Amiibo",
|
||||
"ja_JP": "Amiiboをスキャン",
|
||||
"ko_KR": "Amiibo 스캔하기",
|
||||
"no_NO": "Skanne Amiibo",
|
||||
"pl_PL": "Zeskanuj Amiibo",
|
||||
"pt_BR": "Escanear Amiibo",
|
||||
"ru_RU": "Сканировать Amiibo",
|
||||
"sv_SE": "Skanna Amiibo",
|
||||
"th_TH": "สแกน Amiibo",
|
||||
"tr_TR": "Amiibo'yu tara",
|
||||
"uk_UA": "Сканувати Amiibo",
|
||||
"zh_CN": "扫描 Amiibo",
|
||||
"zh_TW": "掃描 Amiibo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ScanAmiiboMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Bitte scanne jetzt dein Amiibo.",
|
||||
"el_GR": "",
|
||||
"en_US": "Please scan your Amiibo now.",
|
||||
"es_ES": "Escanea tu Amiibo ahora.",
|
||||
"fr_FR": "Veuillez scannez votre Amiibo.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Scansiona ora il tuo Amiibo.",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "지금 Amiibo를 스캔하세요.",
|
||||
"no_NO": "Vennligst skann Amiiboene dine nå.",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Por favor, escaneie seu Amiibo agora.",
|
||||
"ru_RU": "Пожалуйста, сканируйте ваш Amiibo.",
|
||||
"sv_SE": "Skanna din Amiibo nu.",
|
||||
"th_TH": "กรุณาสแกน Amiibo ของคุณตอนนี้",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Будь ласка, проскануйте Ваш Amiibo.",
|
||||
"zh_CN": "请现在扫描你的 Amiibo",
|
||||
"zh_TW": "請掃描你的 Amiibo。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"ID": "UpdatesAddedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Updates hinzugefügt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "Updates Added: {0}",
|
||||
"es_ES": "Actualizaciones Añadidas: {0}",
|
||||
@@ -29,7 +29,7 @@
|
||||
"ID": "UpdatesRemovedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Updates entfernt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "Updates Removed: {0}",
|
||||
"es_ES": "Actualizaciones Eliminadas: {0}",
|
||||
@@ -54,7 +54,7 @@
|
||||
"ID": "DLCAddedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "DLC hinzugefügt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "DLC Added: {0}",
|
||||
"es_ES": "DLC Añadidos: {0}",
|
||||
@@ -79,7 +79,7 @@
|
||||
"ID": "DLCRemovedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "DLC entfernt: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "DLC Removed: {0}",
|
||||
"es_ES": "DLC Eliminados: {0}",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"ID": "LoadApplicationFromFileFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Wähle eine Switch-Anwendungsdatei zum Laden aus",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a Switch application file to load",
|
||||
"es_ES": "Selecciona un archivo de aplicación de Switch para cargar",
|
||||
@@ -29,7 +29,7 @@
|
||||
"ID": "LoadUnpackedApplicationFromFolderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Wähle einen Ordner mit einer entpackten Switch-Anwendung zum Laden",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a folder containing an unpacked Switch application to load",
|
||||
"es_ES": "Selecciona una carpeta que contenga una aplicación de Switch descomprimida para cargar",
|
||||
@@ -54,7 +54,7 @@
|
||||
"ID": "LoadUpdatesAndDLCFromFolderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Wähle einen oder mehrere Ordner zum Laden von Updates und DLC aus",
|
||||
"el_GR": "",
|
||||
"en_US": "Select one or more folders to bulk load updates and DLC from",
|
||||
"es_ES": "Selecciona una o más carpetas para cargar de forma masiva actualizaciones y DLC",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
"ID": "ScanSkylanderFilePickerTitle",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Wähle eine BIN-, SKY-, DMP- oder DUMP-Datei aus, um einen Skylander zu scannen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a BIN, SKY, DMP, or DUMP file to scan a Skylander from",
|
||||
"es_ES": "Selecciona un archivo BIN, SKY, DMP o DUMP para escanear un Skylander",
|
||||
"fr_FR": "Sélectionnez un fichier BIN, SKY, DMP ou DUMP pour scanner un Skylander",
|
||||
"he_IL": "",
|
||||
"it_IT": "Seleziona un file BIN, SKY, DMP o DUMP da cui scansionare uno Skylander",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "Selecione um arquivo BIN, SKY, DMP ou DUMP para escanear um Skylander",
|
||||
"ru_RU": "Выберите файл BIN, SKY, DMP или DUMP для сканирования Skylander",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Виберіть файл BIN, SKY, DMP або DUMP для сканування Skylander",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"ID": "PrimaryMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Entfernt ungenutzten Speicherplatz aus der XCI-Datei, um ihre Dateigröße zu verringern.",
|
||||
"el_GR": "",
|
||||
"en_US": "Removes unused space from the XCI to reduce its file size.",
|
||||
"es_ES": "Elimina el espacio no utilizado del XCI para reducir su tamaño.",
|
||||
@@ -29,7 +29,7 @@
|
||||
"ID": "SecondaryMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Datei: {0:n} MB • Spiel: {1:n} MB\n\nErsparnis: {2:n} MB",
|
||||
"el_GR": "",
|
||||
"en_US": "File: {0:n} MB • Game: {1:n} MB\n\nSavings: {2:n} MB",
|
||||
"es_ES": "Archivo: {0:n} MB • Juego: {1:n} MB\n\nAhorro: {2:n} MB",
|
||||
@@ -47,18 +47,18 @@
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Файл: {0:n} МБ • Гра: {1:n} МБ\n\nЕкономія: {2:n} МБ",
|
||||
"zh_CN": "文件: {0:n} MB • 游戏: {1:n} MB\n\n节省: {2:n} MB",
|
||||
"zh_TW": "檔案: {0:n} MB • 遊戲: {1:n} MB\n\n節省: {2:n} MB"
|
||||
"zh_TW": "檔案: {0:n} MB • 遊戲: {1:n} MB\n\n節省: {2:n} MB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "NoTrimNecessaryMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI muss nicht getrimmt werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI does not require trimming. Check logs for details.",
|
||||
"es_ES": "El XCI no necesita ser recortado. Verifica los logs para detalles.",
|
||||
"fr_FR": "Le XCI n’a pas besoin d’être réduit. Référez-vous aux journaux pour détails.",
|
||||
"fr_FR": "Le XCI n’a pas besoin d’être réduit. Référez-vous aux journaux pour détails.",
|
||||
"he_IL": "",
|
||||
"it_IT": "Non è necessario ridurre la dimensione del XCI. Controlla i log per dettagli.",
|
||||
"ja_JP": "",
|
||||
@@ -79,7 +79,7 @@
|
||||
"ID": "NoUntrimPossibleMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Das Trimmen der XCI kann nicht rückgängig gemacht werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI cannot be untrimmed. Check logs for details.",
|
||||
"es_ES": "El recorte del XCI no puede ser deshecho. Verifica los registros para detalles.",
|
||||
@@ -104,7 +104,7 @@
|
||||
"ID": "ReadOnlyFileCannotFixMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI ist schreibgeschützt und konnte nicht schreibbar gemacht werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI is read-only and could not be made writable. Check logs for details.",
|
||||
"es_ES": "XCI es solo lectura y no se puede escribir. Verifica los registros para detalles.",
|
||||
@@ -120,7 +120,7 @@
|
||||
"sv_SE": "XCI-filen är skrivskyddad och kunde inte göras skrivbar. Kontrollera loggen för mer information",
|
||||
"th_TH": "XCI เป็นอ่านอย่างเดียวและไม่สามารถเขียนได้ ตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "XCI тільки для читання і не можна записати. Перевірте логи для деталей.",
|
||||
"uk_UA": "XCI тільки для читання і не можна записати. Перевірте логи для деталей.",
|
||||
"zh_CN": "XCI 只读,无法写入。查看日志以获取详情。",
|
||||
"zh_TW": "XCI 檔案唯讀,無法寫入。檢查日誌以取得詳情。"
|
||||
}
|
||||
@@ -129,11 +129,11 @@
|
||||
"ID": "SizeChangedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI-Größe hat sich seit dem letzten Scan geändert. Stellen Sie sicher, dass die Datei nicht beschrieben wird, und versuchen Sie es erneut.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI size changed since last scan. Ensure the file is not being written to and try again.",
|
||||
"es_ES": "El tamaño de XCI ha cambiado desde que fue escaneado. Verifica que no se esté escribiendo al archivo y vuelve a intentarlo.",
|
||||
"fr_FR": "La taille de XCI a changé depuis son analyse. Vérifiez que le fichier n’est pas en cours d’écriture, puis réessayez.",
|
||||
"fr_FR": "La taille de XCI a changé depuis son analyse. Vérifiez que le fichier n’est pas en cours d’écriture, puis réessayez.",
|
||||
"he_IL": "",
|
||||
"it_IT": "La dimensione di XCI è cambiata da quando è stato scansionato. Controlla che il file non sia scritto e riprova.",
|
||||
"ja_JP": "",
|
||||
@@ -154,7 +154,7 @@
|
||||
"ID": "FreeSpaceCheckFailedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI enthält Daten im freien Speicherbereich. Es ist unsicher zu trimmen.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI has data in the free space area. It is not safe to trim.",
|
||||
"es_ES": "XCI tiene datos en el área de espacio libre. No es seguro recortarlo.",
|
||||
@@ -166,7 +166,7 @@
|
||||
"no_NO": "XCI har data i den ledige plassen. Det er ikke trygt å trimme den.",
|
||||
"pl_PL": "XCI zawiera dane w obszarze wolnego miejsca. Nie jest bezpieczne go przycinać.",
|
||||
"pt_BR": "XCI tem dados na área de espaço livre. Não é seguro reduzi-lo.",
|
||||
"ru_RU": "XCI содержит данные в свободной области. Его обрезка небезопасна.",
|
||||
"ru_RU": "XCI содержит данные в свободной области. Его обрезка небезопасна.",
|
||||
"sv_SE": "XCI har data i det lediga utrymmet. Det är inte säkert att optimera.",
|
||||
"th_TH": "XCI มีข้อมูลในพื้นที่ว่าง จึงไม่ปลอดภัยที่จะทำการตัดแต่ง",
|
||||
"tr_TR": "",
|
||||
@@ -179,7 +179,7 @@
|
||||
"ID": "InvalidDataMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI enthält ungültige Daten. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI contains invalid data. Check logs for details.",
|
||||
"es_ES": "XCI contiene datos inválidos. Lee el registro para detalles.",
|
||||
@@ -204,7 +204,7 @@
|
||||
"ID": "WriteErrorMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI konnte nicht zum Schreiben geöffnet werden. Prüfen Sie die Logs für Details.",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI could not be opened for writing. Check logs for details.",
|
||||
"es_ES": "XCI no se puede abrir para escribir. Lee el registro para detalles.",
|
||||
@@ -216,11 +216,11 @@
|
||||
"no_NO": "XCI kunne ikke åpnes for skriving. Sjekk loggene for detaljer.",
|
||||
"pl_PL": "Nie można otworzyć XCI do zapisu. Sprawdź dzienniki, aby uzyskać szczegóły.",
|
||||
"pt_BR": "XCI não pôde ser aberto para gravação. Verifique os logs para detalhes.",
|
||||
"ru_RU": "Не удалось открыть XCI для записи. Проверьте журналы для подробностей.",
|
||||
"ru_RU": "Не удалось открыть XCI для записи. Проверьте журналы для подробностей.",
|
||||
"sv_SE": "XCI kunde inte öppnas för skrivning. Kontrollera loggen för detaljer.",
|
||||
"th_TH": "ไม่สามารถเปิด XCI เพื่อเขียนข้อมูลได้ โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "Не вдалося відкрити XCI для запису. Перевірте журнали для деталей.",
|
||||
"uk_UA": "Не вдалося відкрити XCI для запису. Перевірте журнали для деталей.",
|
||||
"zh_CN": "XCI 不能写入。查看日志以获得更多细节。",
|
||||
"zh_TW": "XCI 無法開啟以進行寫入。請檢查日誌以取得更多資訊。"
|
||||
}
|
||||
@@ -229,7 +229,7 @@
|
||||
"ID": "TrimFailedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Trimmen der XCI fehlgeschlagen.",
|
||||
"el_GR": "",
|
||||
"en_US": "Failed to trim XCI.",
|
||||
"es_ES": "El recorte del XCI falló.",
|
||||
@@ -254,7 +254,7 @@
|
||||
"ID": "TrimCancelledMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Der Vorgang wurde abgebrochen.",
|
||||
"el_GR": "",
|
||||
"en_US": "The operation was cancelled.",
|
||||
"es_ES": "La operación fue cancelada.",
|
||||
@@ -279,7 +279,7 @@
|
||||
"ID": "NoOperationPerformedMessage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Es wurde keine Aktion durchgeführt.",
|
||||
"el_GR": "",
|
||||
"en_US": "No operation was performed.",
|
||||
"es_ES": "No se realizó ninguna operación.",
|
||||
@@ -301,4 +301,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"ID": "NoUnpackedApplicationFoundInFolder",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Bitte wähle einen entpackten Anwendungsordner mit gültigem ExeFS oder NSO/NRO aus.",
|
||||
"el_GR": "",
|
||||
"en_US": "Please select an unpacked application folder with a valid ExeFS or NSO/NRO.",
|
||||
"es_ES": "",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"ID": "TrimXCIButton",
|
||||
"Translations": {
|
||||
"ar_SA": "تقليم XCI",
|
||||
"ar_SA": "تقليم XCI",
|
||||
"de_DE": "Zuschneiden der XCI",
|
||||
"el_GR": "Κοπή XCI",
|
||||
"en_US": "Trim XCI",
|
||||
@@ -19,11 +19,11 @@
|
||||
"ru_RU": "Обрезать XCI",
|
||||
"sv_SE": "Optimera XCI",
|
||||
"th_TH": "ลดขนาด XCI",
|
||||
"tr_TR": "XCI'yi Kırp",
|
||||
"tr_TR": "XCI'yi Kırp",
|
||||
"uk_UA": "Нарізка XCI",
|
||||
"zh_CN": "精简 XCI",
|
||||
"zh_TW": "修剪 XCI"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
{
|
||||
"ID": "XCITrimmerButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"ar_SA": "",
|
||||
"de_DE": "XCI-Trimmer",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI Trimmer",
|
||||
@@ -378,26 +378,26 @@
|
||||
{
|
||||
"ID": "ScanAmiiboFromBinButton",
|
||||
"Translations": {
|
||||
"ar_SA": "مسح Amiibo (.BIN)",
|
||||
"de_DE": "Amiibo scannen (.BIN)",
|
||||
"el_GR": "Σάρωση Amiibo (.BIN)",
|
||||
"en_US": "Scan Amiibo (.BIN)",
|
||||
"es_ES": "Escanear un Amiibo (.BIN)",
|
||||
"fr_FR": "Scanner un Amiibo (.BIN)",
|
||||
"he_IL": "סרוק Amiibo (.BIN)",
|
||||
"it_IT": "Scansiona un Amiibo (.BIN)",
|
||||
"ja_JP": "Amiibo をスキャン (.BIN)",
|
||||
"ko_KR": "Amiibo 스캔 (.BIN)",
|
||||
"no_NO": "Skann en Amiibo (.BIN)",
|
||||
"pl_PL": "Skanuj Amiibo (.BIN)",
|
||||
"pt_BR": "Escaneie um Amiibo (.BIN)",
|
||||
"ru_RU": "Сканировать Amiibo (.BIN)",
|
||||
"sv_SE": "Skanna en Amiibo (.BIN)",
|
||||
"th_TH": "สแกนอามีโบ (.BIN)",
|
||||
"tr_TR": "Amiibo Tara (.BIN)",
|
||||
"uk_UA": "Сканувати Amiibo (.BIN)",
|
||||
"zh_CN": "扫描 Amiibo (.BIN)",
|
||||
"zh_TW": "掃瞄 Amiibo (.BIN)"
|
||||
"ar_SA": "مسح Amiibo (BIN)",
|
||||
"de_DE": "Amiibo scannen (BIN)",
|
||||
"el_GR": "Σάρωση Amiibo (BIN)",
|
||||
"en_US": "Scan Amiibo (BIN)",
|
||||
"es_ES": "Escanear un Amiibo (BIN)",
|
||||
"fr_FR": "Scanner un Amiibo (BIN)",
|
||||
"he_IL": "סרוק Amiibo (BIN)",
|
||||
"it_IT": "Scansiona un Amiibo (BIN)",
|
||||
"ja_JP": "Amiibo をスキャン (BIN)",
|
||||
"ko_KR": "Amiibo 스캔 (BIN)",
|
||||
"no_NO": "Skann en Amiibo (BIN)",
|
||||
"pl_PL": "Skanuj Amiibo (BIN)",
|
||||
"pt_BR": "Escaneie um Amiibo (BIN)",
|
||||
"ru_RU": "Сканировать Amiibo (BIN)",
|
||||
"sv_SE": "Skanna en Amiibo (BIN)",
|
||||
"th_TH": "สแกนอามีโบ (BIN)",
|
||||
"tr_TR": "Amiibo Tara (BIN)",
|
||||
"uk_UA": "Сканувати Amiibo (BIN)",
|
||||
"zh_CN": "扫描 Amiibo (BIN)",
|
||||
"zh_TW": "掃瞄 Amiibo (BIN)"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"ID": "OpenScreenshotsFolderButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "Screenshots-Ordner öffnen",
|
||||
"de_DE": "Bildschirmfoto-Ordner öffnen",
|
||||
"el_GR": "",
|
||||
"en_US": "Open Screenshots Folder",
|
||||
"es_ES": "Abrir Carpeta de Capturas de Pantalla",
|
||||
@@ -179,7 +179,7 @@
|
||||
"ID": "AssociateFileTypesButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Dateitypen zuordnen",
|
||||
"el_GR": "",
|
||||
"en_US": "Associate File Types",
|
||||
"es_ES": "",
|
||||
@@ -204,7 +204,7 @@
|
||||
"ID": "RemoveFileTypeAssociationsButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Dateityp-Zuordnungen entfernen",
|
||||
"el_GR": "",
|
||||
"en_US": "Remove File Type Associations",
|
||||
"es_ES": "",
|
||||
@@ -229,7 +229,7 @@
|
||||
"ID": "FileTypeAssociationsToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Ordnet unterstützte Dateitypen (NSP, XCI, NCA, NRO, NSO) Ryujinx zu, sodass sie beim Doppelklick im Dateimanager automatisch geöffnet werden. Das Entfernen der Dateityp-Zuordnungen deaktiviert dieses Verhalten.",
|
||||
"el_GR": "",
|
||||
"en_US": "Associates supported file types (NSP, XCI, NCA, NRO, NSO) with Ryujinx so they open automatically when double-clicked in your file manager. Removing file type associations stops this behavior.",
|
||||
"es_ES": "",
|
||||
|
||||
+168
-868
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@
|
||||
"ID": "StartGamesWithoutUICheckboxLabelToolTip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Blendet die Benutzeroberfläche von Ryujinx automatisch aus, sobald ein Spiel gestartet wird. Während des Spiels kann die Oberfläche mit F4 eingeblendet werden.",
|
||||
"el_GR": "",
|
||||
"en_US": "Automatically hides Ryujinx's UI whenever a game launches. While in-game, press F4 to show the UI.",
|
||||
"es_ES": "",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Locales": [
|
||||
{
|
||||
{
|
||||
"ID": "TrimmingXCILabel",
|
||||
"Translations": {
|
||||
"ar_SA": "جاري تقليم: {0}",
|
||||
@@ -19,7 +19,7 @@
|
||||
"ru_RU": "Обрезка: {0}",
|
||||
"sv_SE": "Trimmar: {0}",
|
||||
"th_TH": "กำลังตัด: {0}",
|
||||
"tr_TR": "{0} Kısaltılıyor:",
|
||||
"tr_TR": "{0} Kısaltılıyor:",
|
||||
"uk_UA": "Обрізка: {0}",
|
||||
"zh_CN": "正在修剪: {0}",
|
||||
"zh_TW": "正在修剪: {0}"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"ID": "StatusCountLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI ausgewählt: {0}/{1}",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI Selected: {0}/{1}",
|
||||
"es_ES": "XCI Seleccionados: {0}/{1}",
|
||||
@@ -29,7 +29,7 @@
|
||||
"ID": "StatusCountWithFilterLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI ausgewählt: {0}/{1} (Sichtbar: {2})",
|
||||
"el_GR": "",
|
||||
"en_US": "XCI Selected: {0}/{1} (Displayed: {2})",
|
||||
"es_ES": "XCI Seleccionados: {0}/{1} (Mostrados: {2})",
|
||||
@@ -48,13 +48,13 @@
|
||||
"uk_UA": "Вибрано XCI: {0}/{1} (Відображається: {2})",
|
||||
"zh_CN": "XCI 已选: {0}/{1}(显示: {2})",
|
||||
"zh_TW": "XCI 已選擇: {0}/{1}(顯示: {2})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "StatusTrimmingLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Trimmen: {0}/{1}...",
|
||||
"el_GR": "",
|
||||
"en_US": "Trimming: {0}/{1}...",
|
||||
"es_ES": "Recortando: {0}/{1}...",
|
||||
@@ -79,7 +79,7 @@
|
||||
"ID": "StatusUntrimmingLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Trimmung von {0} wieder aufheben...",
|
||||
"el_GR": "",
|
||||
"en_US": "Untrimming {0}...",
|
||||
"es_ES": "Deshaciendo el recorte {0}...",
|
||||
@@ -103,7 +103,7 @@
|
||||
{
|
||||
"ID": "SelectAllButton",
|
||||
"Translations": {
|
||||
"ar_SA": "اختر الكل",
|
||||
"ar_SA": "اختر الكل",
|
||||
"de_DE": "Alles auswählen",
|
||||
"el_GR": "Επιλογή όλων",
|
||||
"en_US": "Select All",
|
||||
@@ -154,7 +154,7 @@
|
||||
"ID": "TrimmedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Getrimmt",
|
||||
"el_GR": "",
|
||||
"en_US": "Trimmed",
|
||||
"es_ES": "Recortado",
|
||||
@@ -179,7 +179,7 @@
|
||||
"ID": "UntrimmedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Ungetrimmt",
|
||||
"el_GR": "",
|
||||
"en_US": "Untrimmed",
|
||||
"es_ES": "Sin Recortar",
|
||||
@@ -204,7 +204,7 @@
|
||||
"ID": "PartialLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Teilweise",
|
||||
"el_GR": "",
|
||||
"en_US": "Partial",
|
||||
"es_ES": "Parcial",
|
||||
@@ -229,7 +229,7 @@
|
||||
"ID": "FailedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Fehlgeschlagen",
|
||||
"el_GR": "",
|
||||
"en_US": "Failed",
|
||||
"es_ES": "Fallido",
|
||||
@@ -304,7 +304,7 @@
|
||||
"ID": "SavedLabel",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Gespeichert:",
|
||||
"el_GR": "",
|
||||
"en_US": "Saved:",
|
||||
"es_ES": "Ahorrado:",
|
||||
@@ -366,7 +366,7 @@
|
||||
"no_NO": null,
|
||||
"pl_PL": null,
|
||||
"pt_BR": null,
|
||||
"ru_RU": "{0} МБ",
|
||||
"ru_RU": "{0} МБ",
|
||||
"sv_SE": null,
|
||||
"th_TH": null,
|
||||
"tr_TR": null,
|
||||
@@ -379,7 +379,7 @@
|
||||
"ID": "TrimButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Trimmen",
|
||||
"el_GR": "",
|
||||
"en_US": "Trim",
|
||||
"es_ES": "Recortar",
|
||||
@@ -404,7 +404,7 @@
|
||||
"ID": "UntrimButton",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Trimmen aufheben",
|
||||
"el_GR": "",
|
||||
"en_US": "Untrim",
|
||||
"es_ES": "Desrecortar",
|
||||
|
||||
@@ -2667,7 +2667,8 @@
|
||||
0100C52011460000,"Sky: Children of the Light",cpu;online-broken,nothing,2023-02-23 10:57:10
|
||||
010041C01014E000,"Skybolt Zack",,playable,2021-04-12 18:28:00
|
||||
0100A0A00D1AA000,"SKYHILL",,playable,2021-03-05 15:19:11
|
||||
0100CCC0002E6000,"Skylanders Imaginators",crash;services,boots,2020-05-30 18:49:18
|
||||
010048D000BFC000,"Skylanders Imaginators",,playable,2026-07-02 22:43:30
|
||||
0100CCC0002E6000,"Skylanders Imaginators",,playable,2026-07-02 22:43:30
|
||||
010021A00ABEE000,"SKYPEACE",,playable,2020-05-29 14:14:30
|
||||
0100EA400BF44000,"SkyScrappers",,playable,2020-05-28 22:11:25
|
||||
0100F3C00C400000,"SkyTime",slow,ingame,2020-05-30 09:24:51
|
||||
|
||||
|
@@ -6,7 +6,7 @@ namespace ARMeilleure.Common
|
||||
public readonly struct AddressTableLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the index of the <see cref="Level"/> in the guest address.
|
||||
/// Gets the index of the <see cref="Index"/> in the guest address.
|
||||
/// </summary>
|
||||
public int Index { get; }
|
||||
|
||||
|
||||
@@ -19,46 +19,22 @@ namespace ARMeilleure.Common
|
||||
new( 7, 8),
|
||||
new( 1, 6)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels64BitSparseTiny =
|
||||
|
||||
private static readonly AddressTableLevel[] _monoSparse64Bit =
|
||||
[
|
||||
new( 11, 28),
|
||||
new( 2, 9)
|
||||
new( 2, 37)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels32BitSparseTiny =
|
||||
private static readonly AddressTableLevel[] _monoSparse32Bit =
|
||||
[
|
||||
new( 10, 22),
|
||||
new( 1, 9)
|
||||
new( 1, 31)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels64BitSparseGiant =
|
||||
[
|
||||
new( 38, 1),
|
||||
new( 2, 36)
|
||||
];
|
||||
|
||||
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
|
||||
[
|
||||
new( 31, 1),
|
||||
new( 1, 30)
|
||||
];
|
||||
|
||||
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
|
||||
//low power will never run worse than non-sparse, but for most systems it won't be necessary
|
||||
//high power is always used, but I've left low power in here for future reference
|
||||
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
|
||||
|
||||
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse)
|
||||
{
|
||||
if (sparse)
|
||||
{
|
||||
if (lowPower)
|
||||
{
|
||||
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
|
||||
}
|
||||
else
|
||||
{
|
||||
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
|
||||
}
|
||||
return for64Bits ? _monoSparse64Bit : _monoSparse32Bit;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
|
||||
using System.Linq;
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else if (table.Sparse)
|
||||
{
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
|
||||
// Deliberately attempts to avoid branches.
|
||||
|
||||
Operand tableBase = !context.HasPtc ?
|
||||
@@ -247,18 +247,15 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
hostAddress = tableBase;
|
||||
|
||||
for (int i = 0; i < table.Levels.Length; i++)
|
||||
{
|
||||
AddressTableLevel level = table.Levels[i];
|
||||
int clearBits = 64 - (level.Index + level.Length);
|
||||
AddressTableLevel level = table.Levels.Last();
|
||||
int clearBits = 64 - (level.Index + level.Length);
|
||||
|
||||
Operand index = context.ShiftLeft(
|
||||
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
|
||||
Const(3)
|
||||
);
|
||||
Operand index = context.ShiftLeft(
|
||||
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
|
||||
Const(3)
|
||||
);
|
||||
|
||||
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
|
||||
}
|
||||
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -129,8 +129,10 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
|
||||
int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
int regionEnd = endOffs % (int)CacheSize == 0
|
||||
? (((int)CacheSize) + _pageMask) & ~_pageMask
|
||||
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
@@ -138,7 +140,9 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
|
||||
int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
int regionEnd = endOffs % (int)CacheSize == 0
|
||||
? (((int)CacheSize) + _pageMask) & ~_pageMask
|
||||
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
|
||||
|
||||
GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7016; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7018; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace ARMeilleure.Translation
|
||||
context.BranchIfTrue(lblFallback, masked);
|
||||
|
||||
Operand index = default;
|
||||
Operand page = Const((long)_functionTable.Base);
|
||||
Operand page = Const(_functionTable.Base);
|
||||
|
||||
for (int i = 0; i < _functionTable.Levels.Length; i++)
|
||||
{
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
sw.Write($"{jsonString}\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Ryujinx.Common
|
||||
//Dragon Quest Franchise
|
||||
"010008900705c000", // Dragon Quest Builders
|
||||
"010042000a986000", // Dragon Quest Builders 2
|
||||
"010021701c45e000", // Dragon Quest VII Reimagined Demo
|
||||
|
||||
//Fire Emblem Franchise
|
||||
"0100a6301214e000", // Fire Emblem Engage
|
||||
@@ -33,7 +34,6 @@ namespace Ryujinx.Common
|
||||
"01007e3006dda000", // Kirby Star Allies
|
||||
"01003fb00c5a8000", // Super Kirby Clash
|
||||
|
||||
|
||||
//The Zelda Franchise
|
||||
"01000b900d8b0000", // Cadence of Hyrule
|
||||
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
|
||||
@@ -43,8 +43,7 @@ namespace Ryujinx.Common
|
||||
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
||||
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
||||
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
||||
|
||||
|
||||
|
||||
//Luigi Franchise
|
||||
"010048701995e000", // Luigi's Mansion 2 HD
|
||||
"0100dca0064a6000", // Luigi's Mansion 3
|
||||
@@ -75,16 +74,13 @@ namespace Ryujinx.Common
|
||||
"010049900f546002", // Super Mario 3D All-Stars | Super Mario Sunshine
|
||||
"010049900f546003", // Super Mario 3D All-Stars | Super Mario Galaxy
|
||||
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
||||
"010049900F546001", // Super Mario 64
|
||||
"0100ea80032ea000", // Super Mario Bros. U Deluxe
|
||||
"010015100b514000", // Super Mario Bros. Wonder
|
||||
"010049900F546003", // Super Mario Galaxy
|
||||
"01009b90006dc000", // Super Mario Maker 2
|
||||
"0100000000010000", // SUPER MARIO ODYSSEY
|
||||
"010036b0034e4000", // Super Mario Party
|
||||
"0100965017338000", // Super Mario Party Jamboree
|
||||
"0100bc0018138000", // Super Mario RPG
|
||||
"010049900F546002", // Super Mario Sunshine
|
||||
"0100a3900c3e2000", // Paper Mario: The Origami King
|
||||
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
||||
"01009cC022b96000", // Super Mario Galaxy
|
||||
@@ -149,6 +145,7 @@ namespace Ryujinx.Common
|
||||
"010067b017588000", // Endless Ocean Luminous
|
||||
"01003da010e8a000", // Miitopia
|
||||
"01006b5012b32000", // Part Time UFO
|
||||
"0100107027310000", // Rhythm Heave Groove: Starter Demo
|
||||
"0100704000B3A000", // Snipperclips
|
||||
"01006a800016e000", // Super Smash Bros. Ultimate
|
||||
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
|
||||
@@ -176,7 +173,7 @@ namespace Ryujinx.Common
|
||||
"01004ad014bf0000", // Sonic Frontiers
|
||||
"01009aa000faa000", // Sonic Mania
|
||||
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
|
||||
"01005ea01c0fc001", // ^
|
||||
"01005ea01c0fc001", // ^ TODO: (Needs to be differentiated like 3D All Stars)
|
||||
"01006e001823c000", // Sonic Racing: CrossWorlds
|
||||
"010064b0242be000", // Sonic Racing: CrossWorlds - Demo
|
||||
|
||||
@@ -199,6 +196,7 @@ namespace Ryujinx.Common
|
||||
"0100744001588000", // Cars 3: Driven to Win
|
||||
"0100b41013c82000", // Cruis'n Blast
|
||||
"010085900337e000", // Death Squared
|
||||
"0100a0d022a68000", // DELTARUNE
|
||||
"01001b300b9be000", // Diablo III: Eternal Collection
|
||||
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
|
||||
"01008c8012920000", // Dying Light Platinum Edition
|
||||
@@ -211,6 +209,8 @@ namespace Ryujinx.Common
|
||||
"010013c00e930000", // Hollow Knight: Silksong
|
||||
"010085500130a000", // Lego City: Undercover
|
||||
"010073c01af34000", // LEGO Horizon Adventures
|
||||
"0100653026dde000", // MARVEL Cosmic Invasion Demo
|
||||
"0100419014456000", // Mary Skelter Finale
|
||||
"0100d71004694000", // Minecraft
|
||||
"01007430037f6000", // Monopoly
|
||||
"0100853015e86000", // No Man's Sky
|
||||
@@ -231,6 +231,8 @@ namespace Ryujinx.Common
|
||||
"01008d100d43e000", // Saints Row IV
|
||||
"0100de600beee000", // Saints Row: The Third - The Full Package
|
||||
"01001180021fa000", // Shovel Knight: Specter of Torment
|
||||
"0100ccc0002e6000", // Skylanders Imaginators (US)
|
||||
"010048d000bfc000", // Skylanders Imaginators (EU)
|
||||
"010079f00671c000", // Sparkle 2: Evo
|
||||
"010077b00e046000", // Spyro: Reignited Trilogy
|
||||
"0100e1D01eb2e000", // Squeakross: Home Squeak Home
|
||||
|
||||
@@ -81,12 +81,13 @@ namespace ARMeilleure.Common
|
||||
|
||||
private bool _disposed;
|
||||
private TEntry** _table;
|
||||
private TEntry* _sparseTable;
|
||||
private readonly List<AddressTablePage> _pages;
|
||||
private TEntry _fill;
|
||||
|
||||
private readonly MemoryBlock _sparseFill;
|
||||
private readonly SparseMemoryBlock _fillBottomLevel;
|
||||
private readonly TEntry* _fillBottomLevelPtr;
|
||||
private MemoryBlock _sparseFill;
|
||||
private SparseMemoryBlock _fillBottomLevel;
|
||||
private TEntry* _fillBottomLevelPtr;
|
||||
|
||||
private readonly List<TableSparseBlock> _sparseReserved;
|
||||
private readonly ReaderWriterLockSlim _sparseLock;
|
||||
@@ -122,16 +123,23 @@ namespace ARMeilleure.Common
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
lock (_pages)
|
||||
if (Sparse)
|
||||
{
|
||||
return (nint)GetRootPage();
|
||||
return (nint)_sparseTable;
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_pages)
|
||||
{
|
||||
return (nint)GetRootPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
||||
/// <see cref="Level"/>.
|
||||
/// <see cref="AddressTableLevel"/>.
|
||||
/// </summary>
|
||||
/// <param name="levels">Levels for the address table</param>
|
||||
/// <param name="sparse">True if the bottom page should be sparsely mapped</param>
|
||||
@@ -156,18 +164,8 @@ namespace ARMeilleure.Common
|
||||
if (sparse)
|
||||
{
|
||||
// If the address table is sparse, allocate a fill block
|
||||
|
||||
_sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul
|
||||
|
||||
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
|
||||
|
||||
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
||||
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
||||
|
||||
_sparseReserved = [];
|
||||
_sparseLock = new ReaderWriterLockSlim();
|
||||
|
||||
_sparseBlockSize = bottomLevelSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,13 +207,24 @@ namespace ARMeilleure.Common
|
||||
public void SignalCodeRange(ulong address, ulong size)
|
||||
{
|
||||
AddressTableLevel bottom = Levels.Last();
|
||||
ulong bottomLevelEntries = 1ul << bottom.Length;
|
||||
|
||||
ulong entryIndex = address >> bottom.Index;
|
||||
|
||||
ulong entries = size >> bottom.Index;
|
||||
entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries);
|
||||
|
||||
if (Sparse)
|
||||
{
|
||||
ulong bottomLevelSize = (ulong)BitUtils.Pow2RoundUp((int)entries) * (ulong)sizeof(TEntry);
|
||||
|
||||
_sparseFill = new MemoryBlock(bottomLevelSize, MemoryAllocationFlags.Mirrorable);
|
||||
|
||||
_sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry));
|
||||
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
||||
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
||||
|
||||
_sparseBlockSize = bottomLevelSize;
|
||||
|
||||
_sparseTable = (TEntry*)Allocate((int)entries, Fill, leaf: true);
|
||||
|
||||
_sparseTable -= Levels.Last().GetValue(address);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -234,15 +243,26 @@ namespace ARMeilleure.Common
|
||||
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
|
||||
}
|
||||
|
||||
lock (_pages)
|
||||
if (Sparse)
|
||||
{
|
||||
TEntry* page = GetPage(address);
|
||||
long index = Levels.Last().GetValue(address);
|
||||
|
||||
EnsureMapped((nint)(_sparseTable + index));
|
||||
|
||||
return ref _sparseTable[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_pages)
|
||||
{
|
||||
TEntry* page = GetPage(address);
|
||||
|
||||
long index = Levels[^1].GetValue(address);
|
||||
long index = Levels.Last().GetValue(address);
|
||||
|
||||
EnsureMapped((nint)(page + index));
|
||||
EnsureMapped((nint)(page + index));
|
||||
|
||||
return ref page[index];
|
||||
return ref page[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -189,7 +190,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
}
|
||||
else if (inlineLookup)
|
||||
{
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
|
||||
|
||||
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||
|
||||
@@ -203,18 +204,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
// Index into the table.
|
||||
asm.Mov(rn, tableBase);
|
||||
|
||||
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||
{
|
||||
AddressTableLevel level = funcTable.Levels[i];
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
AddressTableLevel level = funcTable.Levels.Last();
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
}
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
|
||||
if (tempGuestAddress != -1)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -355,7 +356,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
}
|
||||
else if (inlineLookup)
|
||||
{
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
|
||||
|
||||
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||
|
||||
@@ -369,18 +370,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
// Index into the table.
|
||||
asm.Mov(rn, tableBase);
|
||||
|
||||
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||
{
|
||||
AddressTableLevel level = funcTable.Levels[i];
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
AddressTableLevel level = funcTable.Levels.Last();
|
||||
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||
asm.Lsl(indexReg, indexReg, Const(3));
|
||||
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
// Index into the page.
|
||||
asm.Add(rn, rn, indexReg);
|
||||
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
}
|
||||
// Load the page address.
|
||||
asm.LdrRiUn(rn, rn, 0);
|
||||
|
||||
if (tempGuestAddress != -1)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
public bool IsHardwareAccelerated => false;
|
||||
|
||||
private readonly MemoryAllocator _allocator = new();
|
||||
private byte[] _lastSegmentMap = [];
|
||||
private int _lastSegmentMapMiRows;
|
||||
private int _lastSegmentMapMiCols;
|
||||
|
||||
public ISurface CreateSurface(int width, int height)
|
||||
{
|
||||
@@ -106,7 +109,9 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
|
||||
cm.AllocTileWorkerData(_allocator, tileCols, tileRows, maxThreads);
|
||||
cm.AllocContextBuffers(_allocator, output.Width, output.Height);
|
||||
|
||||
cm.InitContextBuffers();
|
||||
LoadSegmentMap(ref cm, ref pictureInfo);
|
||||
cm.SetupSegmentationDequant();
|
||||
cm.SetupScaleFactors();
|
||||
|
||||
@@ -153,6 +158,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
}
|
||||
}
|
||||
|
||||
SaveSegmentMap(ref cm);
|
||||
cm.GetMvs(mvsOut);
|
||||
|
||||
cm.FreeTileWorkerData(_allocator);
|
||||
@@ -161,6 +167,52 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LoadSegmentMap(ref Vp9Common cm, ref Vp9PictureInfo pictureInfo)
|
||||
{
|
||||
int mapLength = cm.MiRows * cm.MiCols;
|
||||
if (mapLength == 0 || cm.LastFrameSegMap.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool reset =
|
||||
pictureInfo.IsKeyFrame ||
|
||||
pictureInfo.IntraOnly ||
|
||||
_lastSegmentMapMiRows != cm.MiRows ||
|
||||
_lastSegmentMapMiCols != cm.MiCols;
|
||||
|
||||
if (_lastSegmentMap.Length != mapLength)
|
||||
{
|
||||
_lastSegmentMap = new byte[mapLength];
|
||||
}
|
||||
else if (reset)
|
||||
{
|
||||
Array.Clear(_lastSegmentMap);
|
||||
}
|
||||
|
||||
_lastSegmentMapMiRows = cm.MiRows;
|
||||
_lastSegmentMapMiCols = cm.MiCols;
|
||||
_lastSegmentMap.CopyTo(cm.LastFrameSegMap.AsSpan());
|
||||
}
|
||||
|
||||
private void SaveSegmentMap(ref Vp9Common cm)
|
||||
{
|
||||
int mapLength = cm.MiRows * cm.MiCols;
|
||||
if (!cm.Seg.Enabled || mapLength == 0 || cm.CurrentFrameSegMap.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastSegmentMap.Length != mapLength)
|
||||
{
|
||||
_lastSegmentMap = new byte[mapLength];
|
||||
}
|
||||
|
||||
cm.CurrentFrameSegMap.AsSpan()[..mapLength].CopyTo(_lastSegmentMap);
|
||||
_lastSegmentMapMiRows = cm.MiRows;
|
||||
_lastSegmentMapMiCols = cm.MiCols;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_allocator.Dispose();
|
||||
|
||||
@@ -1088,6 +1088,12 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
|
||||
|
||||
public void GetMvs(Span<Vp9MvRef> mvs)
|
||||
{
|
||||
if (FrameIsIntraOnly())
|
||||
{
|
||||
mvs.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mvs.Length > CurFrameMvs.Length)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Nvdec.Types.Vp9
|
||||
IsKeyFrame = Flags.HasFlag(FrameFlags.IsKeyFrame),
|
||||
IntraOnly = Flags.HasFlag(FrameFlags.IntraOnly),
|
||||
UsePrevInFindMvRefs =
|
||||
!Flags.HasFlag(FrameFlags.IsKeyFrame) &&
|
||||
!Flags.HasFlag(FrameFlags.ErrorResilientMode) &&
|
||||
!Flags.HasFlag(FrameFlags.FrameSizeChanged) &&
|
||||
!Flags.HasFlag(FrameFlags.IntraOnly) &&
|
||||
|
||||
@@ -11,7 +11,11 @@ namespace Ryujinx.HLE.HOS.Services.Notification
|
||||
|
||||
private readonly KEvent _getNotificationSendingNotifierEvent;
|
||||
private int _getNotificationSendingNotifierEventHandle;
|
||||
public INotificationSystemEventAccessor(ServiceCtx context) { }
|
||||
|
||||
public INotificationSystemEventAccessor(ServiceCtx context)
|
||||
{
|
||||
_getNotificationSendingNotifierEvent = new KEvent(context.Device.System.KernelContext);
|
||||
}
|
||||
|
||||
[CommandCmif(0)] // 9.0.0+
|
||||
// GetNotificationSendingNotifier() -> nn::notification::server::INotificationSystemEventAccessor
|
||||
|
||||
@@ -103,8 +103,12 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
|
||||
loadedCount++;
|
||||
}
|
||||
|
||||
// The first return value is a boolean "fonts_are_loaded" flag (u8), not a count.
|
||||
// Some titles (e.g. newer SDK builds) validate it strictly against 1, so writing
|
||||
// the font count here (e.g. 6) makes them reject the result and spin re-opening pl:u.
|
||||
context.ResponseData.Write(1);
|
||||
context.ResponseData.Write(loadedCount);
|
||||
context.ResponseData.Write((int)SharedFontType.Count);
|
||||
// context.ResponseData.Write((int)SharedFontType.Count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,9 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
public Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent)
|
||||
{
|
||||
bool isAutoClearEvent = autoClearEvent != 0;
|
||||
uint requestId = Register(module, fgmPriority, isAutoClearEvent);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent });
|
||||
|
||||
Register(module, fgmPriority, isAutoClearEvent);
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Initialized module={module} requestId={requestId} autoClearEvent={isAutoClearEvent}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
@@ -27,41 +26,48 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
[CmifCommand(1)]
|
||||
public Result FinalizeOld(Module module)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module });
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
_sessionList.Remove(GetSessionByModule(module));
|
||||
Session session = GetSessionByModule(module);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
_sessionList.Remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Finalized module={module}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(2)]
|
||||
public Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax)
|
||||
public Result SetAndWaitOld(Module module, uint clockRateMin, uint timeout)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, clockRateMin, clockRateMax });
|
||||
uint actualClockRate;
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
GetSessionByModule(module)?.SetAndWait(clockRateMin, clockRateMax);
|
||||
Session session = GetSessionByModule(module) ?? RegisterSession(module, false);
|
||||
ApplyClockRequest(session, clockRateMin, timeout);
|
||||
actualClockRate = GetEffectiveClockRate(module);
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"SetAndWait module={module} requested={clockRateMin} actual={actualClockRate} timeout={timeout}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(3)]
|
||||
public Result GetOld(out uint clockRateActual, Module module)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module });
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
Session session = GetSessionByModule(module);
|
||||
|
||||
clockRateActual = session == null ? 0 : session.ClockRateMin;
|
||||
clockRateActual = GetEffectiveClockRate(module);
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"Get module={module} actual={clockRateActual}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@@ -70,51 +76,76 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
{
|
||||
bool isAutoClearEvent = autoClearEvent != 0;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent });
|
||||
|
||||
requestId = Register(module, fgmPriority, isAutoClearEvent);
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Initialized module={module} requestId={requestId} autoClearEvent={isAutoClearEvent}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(5)]
|
||||
public Result Finalize(uint requestId)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId });
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
_sessionList.Remove(GetSessionById(requestId));
|
||||
Session session = GetSessionById(requestId);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
_sessionList.Remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceMm, $"Finalized requestId={requestId}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(6)]
|
||||
public Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax)
|
||||
public Result SetAndWait(uint requestId, uint clockRateMin, uint timeout)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId, clockRateMin, clockRateMax });
|
||||
uint actualClockRate = 0;
|
||||
Module? module = null;
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
GetSessionById(requestId)?.SetAndWait(clockRateMin, clockRateMax);
|
||||
Session session = GetSessionById(requestId);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
module = session.Module;
|
||||
ApplyClockRequest(session, clockRateMin, timeout);
|
||||
actualClockRate = GetEffectiveClockRate(session.Module);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"SetAndWait requestId={requestId} module={module?.ToString() ?? "<missing>"} requested={clockRateMin} actual={actualClockRate} timeout={timeout}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(7)]
|
||||
public Result Get(out uint clockRateActual, uint requestId)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId });
|
||||
Module? module = null;
|
||||
|
||||
lock (_sessionList)
|
||||
{
|
||||
Session session = GetSessionById(requestId);
|
||||
|
||||
clockRateActual = session == null ? 0 : session.ClockRateMin;
|
||||
if (session == null)
|
||||
{
|
||||
clockRateActual = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
module = session.Module;
|
||||
clockRateActual = GetEffectiveClockRate(session.Module);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Trace?.PrintMsg(LogClass.ServiceMm, $"Get requestId={requestId} module={module?.ToString() ?? "<missing>"} actual={clockRateActual}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
@@ -149,12 +180,51 @@ namespace Ryujinx.Horizon.MmNv.Ipc
|
||||
lock (_sessionList)
|
||||
{
|
||||
// Nintendo ignores the fgm priority as the other services were deprecated.
|
||||
Session session = new(_uniqueId++, module, isAutoClearEvent);
|
||||
|
||||
_sessionList.Add(session);
|
||||
Session session = RegisterSession(module, isAutoClearEvent);
|
||||
|
||||
return session.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private Session RegisterSession(Module module, bool isAutoClearEvent)
|
||||
{
|
||||
Session session = new(_uniqueId++, module, isAutoClearEvent);
|
||||
|
||||
_sessionList.Add(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
private void ApplyClockRequest(Session session, uint requestedClockRate, uint timeout)
|
||||
{
|
||||
if (IsClockRateSentinel(requestedClockRate))
|
||||
{
|
||||
session.ClearRequest(timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
session.SetAndWait(requestedClockRate, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private uint GetEffectiveClockRate(Module module)
|
||||
{
|
||||
uint clockRate = 0;
|
||||
|
||||
foreach (Session session in _sessionList)
|
||||
{
|
||||
if (session.Module == module && session.HasActiveRequest && session.RequestedClockRate > clockRate)
|
||||
{
|
||||
clockRate = session.RequestedClockRate;
|
||||
}
|
||||
}
|
||||
|
||||
return clockRate;
|
||||
}
|
||||
|
||||
private static bool IsClockRateSentinel(uint clockRate)
|
||||
{
|
||||
return clockRate == 0 || unchecked((int)clockRate) < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace Ryujinx.Horizon.Sdk.MmNv
|
||||
{
|
||||
Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent);
|
||||
Result FinalizeOld(Module module);
|
||||
Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax);
|
||||
Result SetAndWaitOld(Module module, uint clockRateMin, uint timeout);
|
||||
Result GetOld(out uint clockRateActual, Module module);
|
||||
Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent);
|
||||
Result Finalize(uint requestId);
|
||||
Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax);
|
||||
Result SetAndWait(uint requestId, uint clockRateMin, uint timeout);
|
||||
Result Get(out uint clockRateActual, uint requestId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,32 @@ namespace Ryujinx.Horizon.Sdk.MmNv
|
||||
public Module Module { get; }
|
||||
public uint Id { get; }
|
||||
public bool IsAutoClearEvent { get; }
|
||||
public uint ClockRateMin { get; private set; }
|
||||
public int ClockRateMax { get; private set; }
|
||||
public uint RequestedClockRate { get; private set; }
|
||||
public uint LastTimeout { get; private set; }
|
||||
public bool HasActiveRequest { get; private set; }
|
||||
|
||||
public Session(uint id, Module module, bool isAutoClearEvent)
|
||||
{
|
||||
Module = module;
|
||||
Id = id;
|
||||
IsAutoClearEvent = isAutoClearEvent;
|
||||
ClockRateMin = 0;
|
||||
ClockRateMax = -1;
|
||||
RequestedClockRate = 0;
|
||||
LastTimeout = uint.MaxValue;
|
||||
HasActiveRequest = false;
|
||||
}
|
||||
|
||||
public void SetAndWait(uint clockRateMin, int clockRateMax)
|
||||
public void SetAndWait(uint clockRate, uint timeout)
|
||||
{
|
||||
ClockRateMin = clockRateMin;
|
||||
ClockRateMax = clockRateMax;
|
||||
RequestedClockRate = clockRate;
|
||||
LastTimeout = timeout;
|
||||
HasActiveRequest = true;
|
||||
}
|
||||
|
||||
public void ClearRequest(uint timeout)
|
||||
{
|
||||
RequestedClockRate = 0;
|
||||
LastTimeout = timeout;
|
||||
HasActiveRequest = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Ryujinx.Input.SDL3
|
||||
}
|
||||
|
||||
int serialNumber = 0;
|
||||
string? serial = SDL_GetGamepadSerial(gamepadHandle);
|
||||
string serial = SDL_GetGamepadSerial(gamepadHandle);
|
||||
if (serial is not null)
|
||||
{
|
||||
int.TryParse(serial, out serialNumber);
|
||||
|
||||
@@ -701,16 +701,17 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
|
||||
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
|
||||
|
||||
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
|
||||
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
|
||||
|
||||
leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
|
||||
leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
|
||||
rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
|
||||
rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
|
||||
|
||||
if (!controllerConfig.Rumble.UseHDRumble || gamepad.HDRumble(leftVibrationValue, rightVibrationValue) == false)
|
||||
if (!controllerConfig.Rumble.UseHDRumble || !gamepad.HDRumble(leftVibrationValue, rightVibrationValue))
|
||||
{
|
||||
float low = Math.Min(1f, (float)(rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15));
|
||||
float high = Math.Min(1f, (float)(leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85));
|
||||
gamepad.Rumble(low, high, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
@@ -767,8 +768,9 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
foreach (AssignedInputDevice assignedController in assignedControllers)
|
||||
{
|
||||
foreach (string gamepadId in gamepadDriver.GamepadsIds)
|
||||
for (int index = 0; index < gamepadDriver.GamepadsIds.Length; index++)
|
||||
{
|
||||
string gamepadId = gamepadDriver.GamepadsIds[index];
|
||||
if (string.Equals(gamepadId, assignedController.Id, StringComparison.Ordinal))
|
||||
{
|
||||
yield return assignedController;
|
||||
@@ -782,14 +784,14 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
if (config is StandardControllerInputConfig)
|
||||
{
|
||||
foreach (string gamepadId in gamepadDriver.GamepadsIds)
|
||||
for (int index = 0; index < gamepadDriver.GamepadsIds.Length; index++)
|
||||
{
|
||||
string gamepadId = gamepadDriver.GamepadsIds[index];
|
||||
if (string.Equals(gamepadId, config.Id, StringComparison.Ordinal))
|
||||
{
|
||||
yield return new AssignedInputDevice
|
||||
{
|
||||
Type = AssignedInputDeviceType.Controller,
|
||||
Id = gamepadId,
|
||||
Type = AssignedInputDeviceType.Controller, Id = gamepadId,
|
||||
};
|
||||
yield break;
|
||||
}
|
||||
@@ -1092,7 +1094,7 @@ namespace Ryujinx.Input.HLE
|
||||
: _assignedControllerConfigs.FirstOrDefault(),
|
||||
_controllerDriver
|
||||
),
|
||||
_ => ((IGamepad?)null, (InputConfig?)null, (IGamepadDriver?)null)
|
||||
_ => ((IGamepad)null, (InputConfig)null, (IGamepadDriver)null)
|
||||
};
|
||||
|
||||
_controllerGamepad = _gamepad;
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
{
|
||||
"Locales": {
|
||||
"ar_SA": [],
|
||||
"de_DE": [],
|
||||
"de_DE": [
|
||||
"Ryubing ist mein zweiter Vorname.",
|
||||
"Hier schlechten Witz einfügen.",
|
||||
"ES IST RYUBING-ZEIT!",
|
||||
"Fantastisch!",
|
||||
"Jetzt mit 100% mehr Humor!",
|
||||
"Jetzt geht es erst richtig los...",
|
||||
"War wohl nur der Wind...",
|
||||
"Kann Spuren von Nüssen enthalten!",
|
||||
"Aus 100% biologischem Anbau!",
|
||||
"Schönen Tag noch! : )",
|
||||
"Der Weltraum... unendliche Weiten!",
|
||||
"Falls du es noch nicht bemerkt hast: Ich liebe es schlechte Witze zu machen : )",
|
||||
"In C# programmiert!",
|
||||
"Forgejo hat Gitlab entthront!",
|
||||
"Irgendwelche Ideen, was hier stehen könnte?",
|
||||
"Guten Morgen!",
|
||||
"Guten Tag!",
|
||||
"Guten Abend!",
|
||||
"Ich hoffe, du hast einen tollen Tag!",
|
||||
"ICH... BIN RYUBING!",
|
||||
"Nein, Lossless Scaling wird NICHT unterstützt.",
|
||||
"Trans-Rechte sind Menschenrechte!",
|
||||
":3",
|
||||
"Bitte einen Controller anschließen!",
|
||||
"Never gonna give you up!",
|
||||
"Ganon beobachtet dich!",
|
||||
"Unzählige Stunden voller Spaß!",
|
||||
"Yahaha! Du hast mich endeckt!",
|
||||
"Finde uns auf BlueSky"
|
||||
],
|
||||
"el_GR": [],
|
||||
"en_US": [
|
||||
"Ryubing is my middle name.",
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Collections.Generic;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
@@ -11,47 +10,48 @@ namespace Ryujinx.Common
|
||||
{
|
||||
public static void PrintSplash()
|
||||
{
|
||||
string splash = GetSplash();
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, " ___ __ _ ");
|
||||
Logger.Notice.Print(LogClass.Application, @" / _ \ __ __ __ __ / / (_) ___ ___ _");
|
||||
Logger.Notice.Print(LogClass.Application, @" / , _/ / // // // / / _ \ / / / _ \ / _ `/");
|
||||
Logger.Notice.Print(LogClass.Application, @"/_/|_| \_, / \_,_/ /_.__//_/ /_//_/ \_, / ");
|
||||
Logger.Notice.Print(LogClass.Application, " /___/ /___/ ");
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
Logger.Notice.Print(LogClass.Application, GetSplash());
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
|
||||
if (splash is null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to fetch Splash Text! Splash JSON is invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!splash.IsNullOrEmpty())
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
Logger.Notice.Print(LogClass.Application, splash);
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
}
|
||||
}
|
||||
|
||||
private static string s_finalSplash = "";
|
||||
private static string _finalSplash;
|
||||
|
||||
public static string GetSplash()
|
||||
{
|
||||
if (string.IsNullOrEmpty(s_finalSplash))
|
||||
if (_finalSplash is null)
|
||||
{
|
||||
s_finalSplash = GetLangJson();
|
||||
if (string.IsNullOrEmpty(s_finalSplash))
|
||||
try
|
||||
{
|
||||
s_finalSplash = "Splash Text";
|
||||
string data;
|
||||
data = EmbeddedResources.ReadAllText("Ryujinx/Assets/Splashes.json");
|
||||
SplashLocales splashJson = JsonSerializer.Deserialize<SplashLocales>(data);
|
||||
_finalSplash = splashJson.Locales[ConfigurationState.Instance.UI.LanguageCode.Value].GetRandomElement() ?? "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $"{s_finalSplash}";
|
||||
}
|
||||
|
||||
private static SplashLocales s_splashJson;
|
||||
|
||||
private static string GetLangJson()
|
||||
{
|
||||
try
|
||||
{
|
||||
string data;
|
||||
data = EmbeddedResources.ReadAllText("Ryujinx/Assets/Splashes.json");
|
||||
s_splashJson = JsonSerializer.Deserialize<SplashLocales>(data);
|
||||
return s_splashJson.Locales[ConfigurationState.Instance.UI.LanguageCode.Value].GetRandomElement();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return _finalSplash;
|
||||
}
|
||||
|
||||
private struct SplashLocales
|
||||
|
||||
@@ -26,9 +26,9 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
internal class TrimmerWindow : Ryujinx.Common.Logging.XCIFileTrimmerLog
|
||||
{
|
||||
private readonly XCITrimmerViewModel _viewModel;
|
||||
private readonly XciTrimmerViewModel _viewModel;
|
||||
|
||||
public TrimmerWindow(XCITrimmerViewModel viewModel)
|
||||
public TrimmerWindow(XciTrimmerViewModel viewModel)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
}
|
||||
|
||||
@@ -502,15 +502,20 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
public bool DebuggerSuspendOnStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show toggles for dirty hacks in the UI.
|
||||
/// Show toggles for dirty hacks in the UI.
|
||||
/// </summary>
|
||||
public bool ShowDirtyHacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The packed values of the enabled dirty hacks.
|
||||
/// The packed values of the enabled dirty hacks.
|
||||
/// </summary>
|
||||
public ulong[] DirtyHacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pause emulation while scanning Amiibo
|
||||
/// </summary>
|
||||
public bool PauseEmulationWhileScanningAmiibo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a configuration file from disk
|
||||
/// </summary>
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
System.SkipUserProfilesManager.Value = cff.SkipUserProfiles;
|
||||
System.UseHypervisor.Value = cff.UseHypervisor;
|
||||
System.GCLowLatency.Value = cff.GCLowLatency;
|
||||
|
||||
|
||||
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
|
||||
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
|
||||
UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
|
||||
@@ -151,6 +151,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
|
||||
UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
|
||||
UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
|
||||
UI.PauseEmulationWhileScanningAmiibo.Value = cff.PauseEmulationWhileScanningAmiibo;
|
||||
|
||||
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||
|
||||
@@ -196,6 +196,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> ShowDynamicInputSwapWarning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pause emulation while scanning Amiibo
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> PauseEmulationWhileScanningAmiibo { get; private set; }
|
||||
|
||||
public UISection()
|
||||
{
|
||||
GuiColumns = new Columns();
|
||||
@@ -217,6 +222,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
ShowConsole.Event += static (_, e) => ConsoleHelper.SetConsoleWindowState(e.NewValue);
|
||||
ShowDynamicInputSwapWarning = new ReactiveObject<bool>();
|
||||
ShowDynamicInputSwapWarning.Value = true;
|
||||
PauseEmulationWhileScanningAmiibo = new ReactiveObject<bool>();
|
||||
PauseEmulationWhileScanningAmiibo.Value = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
|
||||
ShowDirtyHacks = Hacks.ShowDirtyHacks,
|
||||
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
|
||||
PauseEmulationWhileScanningAmiibo = UI.PauseEmulationWhileScanningAmiibo,
|
||||
};
|
||||
|
||||
return configurationFile;
|
||||
@@ -262,6 +263,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
UI.WindowStartup.WindowPositionX.Value = 0;
|
||||
UI.WindowStartup.WindowPositionY.Value = 0;
|
||||
UI.WindowStartup.WindowMaximized.Value = false;
|
||||
UI.PauseEmulationWhileScanningAmiibo.Value = true;
|
||||
Hid.EnableKeyboard.Value = false;
|
||||
Hid.EnableMouse.Value = false;
|
||||
Hid.DisableInputWhenOutOfFocus.Value = false;
|
||||
|
||||
@@ -9,7 +9,9 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Horizon;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Ava.Systems
|
||||
{
|
||||
@@ -135,20 +137,48 @@ namespace Ryujinx.Ava.Systems
|
||||
|
||||
if (!formattedValue.Handled)
|
||||
return;
|
||||
|
||||
|
||||
_discordPresencePlaying.Details = TruncateToByteLength(
|
||||
formattedValue.Reset
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: formattedValue.FormattedString
|
||||
);
|
||||
|
||||
try // New format that attempts to deserialize json, and if it fails (using old method)...
|
||||
{
|
||||
Dictionary<string, string> outDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(formattedValue.FormattedString);
|
||||
|
||||
_discordPresencePlaying.Details = TruncateToByteLength(
|
||||
outDictionary["Details"].IsNullOrEmpty()
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: outDictionary["Details"]
|
||||
);
|
||||
|
||||
_discordPresencePlaying.State = TruncateToByteLength(
|
||||
outDictionary["State"].IsNullOrEmpty()
|
||||
? $"Total play time: {ValueFormatUtils.FormatTimeSpan(_currentApp.TimePlayed)}"
|
||||
: outDictionary["State"]
|
||||
);
|
||||
|
||||
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
|
||||
}
|
||||
catch // Utilize original code
|
||||
{
|
||||
_discordPresencePlaying.Details = TruncateToByteLength(
|
||||
formattedValue.Reset
|
||||
? $"Playing {_currentApp.Title}"
|
||||
: formattedValue.FormattedString
|
||||
);
|
||||
}
|
||||
|
||||
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details) && _discordClient.CurrentPresence.State.Equals(_discordPresencePlaying.State))
|
||||
return; //don't trigger an update if the set presence Details are identical to current
|
||||
|
||||
_discordClient.SetPresence(_discordPresencePlaying);
|
||||
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
|
||||
}
|
||||
|
||||
public static string PrepareMultilineRpcString(string line1 = "", string line2 = "")
|
||||
{
|
||||
Dictionary<string, string> rpcdict = new() { { "Details", line1 }, {"State", line2} };
|
||||
return JsonSerializer.Serialize(rpcdict);
|
||||
}
|
||||
|
||||
private static string TruncateToByteLength(string input)
|
||||
{
|
||||
if (Encoding.UTF8.GetByteCount(input) <= ApplicationByteLimit)
|
||||
|
||||
@@ -1105,7 +1105,7 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
|
||||
return $"At the main menu";
|
||||
|
||||
static string LocFinal(string? location) => location switch
|
||||
static string LocFinal(string location) => location switch
|
||||
{
|
||||
"0" => "Somewhere in Miitopia",
|
||||
"1" => "Wandering around Greenhorne",
|
||||
@@ -1151,8 +1151,10 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
{
|
||||
return "At the main menu";
|
||||
}
|
||||
|
||||
static string MarioOrLuigiGamemode(string? gamemode) => gamemode switch
|
||||
|
||||
return "";
|
||||
|
||||
static string MarioOrLuigiGamemode(string gamemode) => gamemode switch
|
||||
{
|
||||
"0" => "mario",
|
||||
"1" => "luigi",
|
||||
@@ -1161,14 +1163,14 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
_ => gamemode
|
||||
};
|
||||
|
||||
static string OtherGameMode(string? gamemode) => gamemode switch
|
||||
{
|
||||
"2" => "Boost Rush",
|
||||
"3" => "Challenges",
|
||||
"4" => "Coin Battle",
|
||||
"5" => "Coin Battle Editor",
|
||||
_ => ""
|
||||
};
|
||||
// static string OtherGameMode(string gamemode) => gamemode switch
|
||||
// {
|
||||
// "2" => "Boost Rush",
|
||||
// "3" => "Challenges",
|
||||
// "4" => "Coin Battle",
|
||||
// "5" => "Coin Battle Editor",
|
||||
// _ => ""
|
||||
// };
|
||||
|
||||
static string SpecialMapNames(int? course) => course switch
|
||||
{
|
||||
@@ -1196,9 +1198,6 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
// Glacier seals = 16, water leaf = 15
|
||||
// desert ice = 14, acorn squid = 13
|
||||
// all other course numbers are to be considered a hazard
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
StringLengthMax = 25
|
||||
};
|
||||
(UserResult result, string userInput) =
|
||||
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
|
||||
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_RenameAmiiboTitle], args);
|
||||
if (result == UserResult.Ok)
|
||||
{
|
||||
inputText = userInput;
|
||||
@@ -212,11 +212,11 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
{
|
||||
dialogCloseEvent.Set();
|
||||
await ContentDialogHelper.CreateInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_ScanAmiiboMessage],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.CabinetTitle]
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_ScanAmiiboTitle]
|
||||
);
|
||||
});
|
||||
dialogCloseEvent.WaitOne();
|
||||
|
||||
@@ -74,10 +74,14 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
content._host = contentDialog;
|
||||
contentDialog.Title = title;
|
||||
contentDialog.PrimaryButtonText = args.SubmitText;
|
||||
|
||||
contentDialog.PrimaryButtonText = string.IsNullOrWhiteSpace(args.SubmitText)
|
||||
? LocaleManager.Instance[LocaleKeys.InputDialogOk]
|
||||
: args.SubmitText;
|
||||
|
||||
contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length);
|
||||
contentDialog.SecondaryButtonText = string.Empty;
|
||||
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel];
|
||||
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.Common_Buttons_Cancel];
|
||||
contentDialog.Content = content;
|
||||
|
||||
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
try
|
||||
{
|
||||
string labelsJson = File.ReadAllText(labelsPath);
|
||||
Dictionary<string, string>? labels = JsonSerializer.Deserialize<Dictionary<string, string>>(labelsJson, _serializerOptions);
|
||||
Dictionary<string, string> labels = JsonSerializer.Deserialize<Dictionary<string, string>>(labelsJson, _serializerOptions);
|
||||
|
||||
if (labels != null)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,10 @@ using Avalonia.Collections;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Path = System.IO.Path;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Common.Models.Amiibo;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
@@ -20,31 +22,39 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public partial class AmiiboWindowViewModel : BaseModel, IDisposable
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static bool _cachedUseRandomUuid;
|
||||
|
||||
private const string DefaultJson = "{ \"amiibo\": [] }";
|
||||
private const float AmiiboImageSize = 350f;
|
||||
|
||||
private readonly string _amiiboJsonPath;
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AmiiboWindow _owner;
|
||||
|
||||
private List<AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
public enum AmiiboSortField
|
||||
{
|
||||
Name,
|
||||
|
||||
}
|
||||
|
||||
private int _amiiboSelectedIndex;
|
||||
private int _seriesSelectedIndex;
|
||||
private bool _showAllAmiibo;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static bool _cachedUseRandomUuid;
|
||||
public bool IsSortedByName => _sortField == AmiiboSortField.Name;
|
||||
private const string DefaultJson = "{ \"amiibo\": [] }";
|
||||
private const float AmiiboImageSize = 350f;
|
||||
public string TitleId { get; set; }
|
||||
public string LastScannedAmiiboId { get; set; }
|
||||
public string SortingFieldName => LocaleManager.Instance[LocaleKeys.Common_Sort_NameLabel];
|
||||
private readonly string _amiiboJsonPath;
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AmiiboWindow _owner;
|
||||
private List<AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
private CancellationTokenSource _imageCts = new();
|
||||
private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AmiiboWindowViewModel(AmiiboWindow owner, string lastScannedAmiiboId, string titleId)
|
||||
@@ -73,9 +83,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public AmiiboWindowViewModel() { }
|
||||
|
||||
public string TitleId { get; set; }
|
||||
public string LastScannedAmiiboId { get; set; }
|
||||
|
||||
public UserResult Response { get; private set; }
|
||||
|
||||
public bool UseRandomUuid
|
||||
@@ -151,6 +158,42 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool PauseEmulationWhileScanningAmiibo
|
||||
{
|
||||
get => ConfigurationState.Instance.UI.PauseEmulationWhileScanningAmiibo.Value;
|
||||
set
|
||||
{
|
||||
ConfigurationState.Instance.UI.PauseEmulationWhileScanningAmiibo.Value = value;
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _searchText = string.Empty;
|
||||
private bool _sortingAscending = true;
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set
|
||||
{
|
||||
_searchText = value;
|
||||
FilterAmiibo();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SortingAscending
|
||||
{
|
||||
get => _sortingAscending;
|
||||
set
|
||||
{
|
||||
_sortingAscending = value;
|
||||
FilterAmiibo();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Bitmap AmiiboImage { get; set; }
|
||||
|
||||
@@ -166,14 +209,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
_owner.ScannedAmiibo = AmiiboList[AmiiboSelectedIndex];
|
||||
_owner.IsScanned = true;
|
||||
_owner.Close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_owner.IsScanned = false;
|
||||
_owner.Close();
|
||||
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -315,42 +358,46 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private void ParseAmiiboData()
|
||||
{
|
||||
_amiiboSeries.Clear();
|
||||
_amiibos.Clear();
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
foreach (AmiiboApi amiibo in _amiiboList)
|
||||
{
|
||||
if (!_amiiboSeries.Contains(_amiiboList[i].AmiiboSeries))
|
||||
if (!_amiiboSeries.Contains(amiibo.AmiiboSeries))
|
||||
{
|
||||
if (!ShowAllAmiibo)
|
||||
if (_showAllAmiibo)
|
||||
{
|
||||
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
if (game.GameId.Contains(TitleId))
|
||||
{
|
||||
AmiiboSeries.Add(_amiiboList[i].AmiiboSeries);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_amiiboSeries.Add(amiibo.AmiiboSeries);
|
||||
}
|
||||
else
|
||||
{
|
||||
AmiiboSeries.Add(_amiiboList[i].AmiiboSeries);
|
||||
bool hasCompatible = amiibo.GamesSwitch.Any(game =>
|
||||
game != null && game.GameId.Contains(TitleId));
|
||||
|
||||
if (hasCompatible)
|
||||
{
|
||||
_amiiboSeries.Add(amiibo.AmiiboSeries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LastScannedAmiiboId != string.Empty)
|
||||
if (_showAllAmiibo)
|
||||
{
|
||||
SeriesSelectedIndex = -1;
|
||||
}
|
||||
else if (LastScannedAmiiboId != string.Empty)
|
||||
{
|
||||
SelectLastScannedAmiibo();
|
||||
}
|
||||
else
|
||||
else if (_amiiboSeries.Count > 0)
|
||||
{
|
||||
SeriesSelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
SeriesSelectedIndex = -1;
|
||||
}
|
||||
|
||||
FilterAmiibo();
|
||||
}
|
||||
|
||||
private void SelectLastScannedAmiibo()
|
||||
@@ -360,104 +407,109 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||
}
|
||||
|
||||
|
||||
private void FilterAmiibo()
|
||||
{
|
||||
_amiibos.Clear();
|
||||
|
||||
List<AmiiboApi> amiiboSortedList;
|
||||
IEnumerable<AmiiboApi> query = _amiiboList.AsEnumerable();
|
||||
|
||||
// If no series selected, just display all available amiibos
|
||||
if (_seriesSelectedIndex < 0)
|
||||
if (_seriesSelectedIndex >= 0 && _seriesSelectedIndex < _amiiboSeries.Count)
|
||||
{
|
||||
amiiboSortedList = _amiiboList
|
||||
.OrderBy(amiibo => amiibo.AmiiboSeries)
|
||||
.ThenBy(x => x.Name)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
amiiboSortedList = _amiiboList
|
||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||
string selectedSeries = _amiiboSeries[_seriesSelectedIndex];
|
||||
query = query.Where(amiibo => amiibo.AmiiboSeries == selectedSeries);
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < amiiboSortedList.Count; i++)
|
||||
if (!string.IsNullOrWhiteSpace(_searchText))
|
||||
{
|
||||
if (!_amiibos.Contains(amiiboSortedList[i]))
|
||||
query = query.Where(amiibo =>
|
||||
amiibo.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
query = query.Where(amiibo =>
|
||||
amiibo.GamesSwitch.Any(game => game != null && game.GameId.Contains(TitleId)));
|
||||
}
|
||||
|
||||
query = _sortingAscending
|
||||
? query.OrderBy(amiibo => amiibo.Name)
|
||||
: query.OrderByDescending(amiibo => amiibo.Name);
|
||||
|
||||
_amiibos.AddRange(query);
|
||||
|
||||
int restoredIndex = -1;
|
||||
for (int i = 0; i < _amiibos.Count; i++)
|
||||
{
|
||||
if (_amiibos[i].GetId() == LastScannedAmiiboId)
|
||||
{
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
if (game.GameId.Contains(TitleId))
|
||||
{
|
||||
_amiibos.Add(amiiboSortedList[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_amiibos.Add(amiiboSortedList[i]);
|
||||
}
|
||||
restoredIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AmiiboSelectedIndex = 0;
|
||||
AmiiboSelectedIndex = restoredIndex != -1
|
||||
? restoredIndex
|
||||
: (_amiibos.Count > 0 ? 0 : -1);
|
||||
|
||||
SetAmiiboDetails();
|
||||
}
|
||||
|
||||
private AmiiboSortField _sortField = AmiiboSortField.Name;
|
||||
|
||||
public AmiiboSortField SortingField
|
||||
{
|
||||
get => _sortField;
|
||||
set
|
||||
{
|
||||
_sortField = value;
|
||||
FilterAmiibo();
|
||||
OnPropertyChanged(nameof(SortingFieldName));
|
||||
OnPropertyChanged(nameof(IsSortedByName));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAmiiboDetails()
|
||||
{
|
||||
ResetAmiiboPreview();
|
||||
|
||||
Usage = string.Empty;
|
||||
|
||||
if (_amiiboSelectedIndex < 0 || _amiibos.Count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
|
||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||
string imageUrl = selected.Image;
|
||||
|
||||
StringBuilder usageStringBuilder = new();
|
||||
bool writable = false;
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
foreach (AmiiboApiGamesSwitch game in selected.GamesSwitch)
|
||||
{
|
||||
if (_amiiboList[i].Equals(selected))
|
||||
if (game != null && game.GameId.Contains(TitleId))
|
||||
{
|
||||
bool writable = false;
|
||||
|
||||
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
foreach (AmiiboApiUsage usageItem in game.AmiiboUsage)
|
||||
{
|
||||
if (item.GameId.Contains(TitleId))
|
||||
{
|
||||
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
{
|
||||
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
|
||||
|
||||
writable = usageItem.Write;
|
||||
}
|
||||
}
|
||||
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
|
||||
if (usageItem.Write)
|
||||
writable = true;
|
||||
}
|
||||
|
||||
if (usageStringBuilder.Length == 0)
|
||||
{
|
||||
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
|
||||
}
|
||||
|
||||
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : string.Empty)} : {usageStringBuilder}";
|
||||
}
|
||||
}
|
||||
|
||||
string usageLabel = writable
|
||||
? LocaleManager.Instance[LocaleKeys.Amiibo_WritableLabel]
|
||||
: LocaleManager.Instance[LocaleKeys.Amiibo_UsageLabel];
|
||||
|
||||
if (usageStringBuilder.Length == 0)
|
||||
{
|
||||
usageStringBuilder.Append(Environment.NewLine + Environment.NewLine + LocaleManager.Instance[LocaleKeys.Amiibo_UnknownLabel]);
|
||||
}
|
||||
else
|
||||
{
|
||||
usageStringBuilder.Replace(Environment.NewLine + "-", Environment.NewLine + Environment.NewLine + "-");
|
||||
}
|
||||
|
||||
Usage = usageLabel + usageStringBuilder.ToString();
|
||||
_ = UpdateAmiiboPreview(imageUrl);
|
||||
}
|
||||
|
||||
@@ -510,8 +562,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}");
|
||||
}
|
||||
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APITitle],
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APIFailFetchMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
|
||||
@@ -526,26 +578,33 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private async Task UpdateAmiiboPreview(string imageUrl)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(imageUrl);
|
||||
_imageCts.Cancel();
|
||||
_imageCts = new CancellationTokenSource();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
try
|
||||
{
|
||||
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
using MemoryStream memoryStream = new(amiiboPreviewBytes);
|
||||
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_imageCts.Token);
|
||||
|
||||
Bitmap bitmap = new(memoryStream);
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(imageUrl, linkedCts.Token);
|
||||
|
||||
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
|
||||
AmiiboImageSize / bitmap.Size.Height);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync(linkedCts.Token);
|
||||
using MemoryStream ms = new(bytes);
|
||||
|
||||
int resizeHeight = (int)(bitmap.Size.Height * ratio);
|
||||
int resizeWidth = (int)(bitmap.Size.Width * ratio);
|
||||
Bitmap bitmap = new(ms);
|
||||
|
||||
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
|
||||
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width, AmiiboImageSize / bitmap.Size.Height);
|
||||
int newWidth = (int)(bitmap.Size.Width * ratio);
|
||||
int newHeight = (int)(bitmap.Size.Height * ratio);
|
||||
|
||||
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(newWidth, newHeight));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to load amiibo preview: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,8 +619,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private static async Task ShowInfoDialog()
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APITitle],
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APIConnectErrorMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
|
||||
|
||||
@@ -160,6 +160,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
[ObservableProperty] public partial bool UpdateAvailable { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string SkylanderButtonHeader { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty] public partial bool SkylanderButtonEnabled { get; set; }
|
||||
|
||||
public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () =>
|
||||
{
|
||||
if (Updater.CanUpdate(true))
|
||||
@@ -204,6 +208,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
internal AppHost AppHost { get; set; }
|
||||
|
||||
public RelayCommand ToggleSkylanderCommand { get; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
Applications = [];
|
||||
@@ -233,6 +239,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Volume = ConfigurationState.Instance.System.AudioVolume;
|
||||
CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
|
||||
}
|
||||
|
||||
ToggleSkylanderCommand = new RelayCommand(ExecuteToggleSkylander);
|
||||
}
|
||||
|
||||
~MainWindowViewModel()
|
||||
@@ -376,6 +384,50 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
|
||||
|
||||
[RelayCommand]
|
||||
private void UpdateSkylanderButton()
|
||||
{
|
||||
SkylanderButtonHeader = HasSkylander
|
||||
? LocaleManager.Instance[LocaleKeys.MenuBar_Actions_RemoveSkylanderButton]
|
||||
: LocaleManager.Instance[LocaleKeys.MenuBar_Actions_ScanSkylanderButton];
|
||||
|
||||
SkylanderButtonEnabled = HasSkylander || IsSkylanderRequested;
|
||||
|
||||
OnPropertyChanged(nameof(SkylanderButtonHeader));
|
||||
OnPropertyChanged(nameof(SkylanderButtonEnabled));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(SkylanderButtonHeader));
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
private void UpdateSkylanderState()
|
||||
{
|
||||
if (AppHost?.Device?.System == null) return;
|
||||
|
||||
IsSkylanderRequested = AppHost.Device.System.SearchingForSkylander(out _);
|
||||
HasSkylander = AppHost.Device.System.HasSkylander(out _);
|
||||
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
|
||||
private async void ExecuteToggleSkylander()
|
||||
{
|
||||
if (!IsGameRunning) return;
|
||||
|
||||
if (HasSkylander)
|
||||
{
|
||||
await RemoveSkylander();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ScanSkylander();
|
||||
}
|
||||
|
||||
UpdateSkylanderState();
|
||||
}
|
||||
|
||||
public bool IsSkylanderRequested
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
@@ -384,6 +436,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,17 +448,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSkylanderActions
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1260,7 +1303,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
break;
|
||||
case ShaderCacheLoadingState shaderCacheState:
|
||||
CacheLoadStatus = $"{current} / {total}";
|
||||
Splash = $"\"{SplashTextHelper.GetSplash()}\"";
|
||||
|
||||
string splash = SplashTextHelper.GetSplash();
|
||||
|
||||
if (!splash.IsNullOrEmpty())
|
||||
{
|
||||
Splash = $"\"{splash}\"";
|
||||
}
|
||||
switch (shaderCacheState)
|
||||
{
|
||||
case ShaderCacheLoadingState.Start:
|
||||
@@ -1490,7 +1539,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallFromFileFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.xci", "*.zip"],
|
||||
AppleUniformTypeIdentifiers = ["com.ryujinx.xci", "public.zip-archive"],
|
||||
@@ -1674,7 +1723,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_FileMenu_LoadApplicationFromFileFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.nsp", "*.xci", "*.nca", "*.nro", "*.nso"],
|
||||
AppleUniformTypeIdentifiers =
|
||||
@@ -1761,7 +1810,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<string>?> PickFolders(LocaleKeys titleKey)
|
||||
private async Task<IReadOnlyList<string>> PickFolders(LocaleKeys titleKey)
|
||||
{
|
||||
return (await StorageProvider.OpenMultiFolderPickerAsync(new FolderPickerOpenOptions
|
||||
{
|
||||
@@ -2004,40 +2053,58 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenBinFile()
|
||||
public async Task ScanAmiiboFromBin()
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
|
||||
{
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.bin"],
|
||||
}
|
||||
}
|
||||
});
|
||||
bool shouldPause = ConfigurationState.Instance.UI.PauseEmulationWhileScanningAmiibo.Value && IsGameRunning;
|
||||
|
||||
if (result.HasValue)
|
||||
if (shouldPause && AppHost?.Device?.System != null)
|
||||
{
|
||||
AppHost?.Pause();
|
||||
}
|
||||
try
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
|
||||
{
|
||||
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_ScanAmiiboFromBinFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.bin"],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (result.HasValue)
|
||||
{
|
||||
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (shouldPause && AppHost?.Device?.System != null)
|
||||
{
|
||||
AppHost?.Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task OpenSkylanderWindow()
|
||||
|
||||
[RelayCommand]
|
||||
public async Task ScanSkylander()
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
|
||||
{
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_Skylanders_ScanSkylanderFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"],
|
||||
},
|
||||
@@ -2060,12 +2127,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
AppHost.Device.System.ScanSkylander(deviceId, data);
|
||||
}
|
||||
}
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task RemoveSkylander()
|
||||
{
|
||||
AppHost.Device.System.RemoveSkylander();
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
|
||||
public void ReloadRenderDocApi()
|
||||
@@ -2155,7 +2225,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Window.Position.X + (int)(Window.Bounds.Width / 2),
|
||||
Window.Position.Y + (int)(Window.Bounds.Height / 2));
|
||||
|
||||
Avalonia.Platform.Screen? screen =
|
||||
Avalonia.Platform.Screen screen =
|
||||
Window.Screens.ScreenFromVisual(Window) ??
|
||||
Window.Screens.ScreenFromPoint(windowCenter) ??
|
||||
Window.Screens.Primary;
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
AllowMultiple = true,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.nsp"],
|
||||
AppleUniformTypeIdentifiers = ["com.ryujinx.nsp"],
|
||||
|
||||
@@ -16,7 +16,7 @@ using static Ryujinx.Common.Utilities.XCIFileTrimmer;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class XCITrimmerViewModel : BaseModel
|
||||
public class XciTrimmerViewModel : BaseModel
|
||||
{
|
||||
private const long BytesPerMb = 1024 * 1024;
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private int _processingTotal;
|
||||
|
||||
|
||||
public XCITrimmerViewModel(MainWindowViewModel mainWindowViewModel)
|
||||
public XciTrimmerViewModel(MainWindowViewModel mainWindowViewModel)
|
||||
{
|
||||
_logger = new XCITrimmerLog.TrimmerWindow(this);
|
||||
_mainWindowViewModel = mainWindowViewModel;
|
||||
@@ -299,9 +299,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private class CompareXCITrimmerFiles : IComparer<XCITrimmerFileModel>
|
||||
{
|
||||
private readonly XCITrimmerViewModel _viewModel;
|
||||
private readonly XciTrimmerViewModel _viewModel;
|
||||
|
||||
public CompareXCITrimmerFiles(XCITrimmerViewModel ViewModel)
|
||||
public CompareXCITrimmerFiles(XciTrimmerViewModel ViewModel)
|
||||
{
|
||||
_viewModel = ViewModel;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Dialog.XCITrimmerView"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Dialog.XciTrimmerView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -11,7 +11,7 @@
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Common.Models"
|
||||
Width="700"
|
||||
Height="600"
|
||||
x:DataType="viewModels:XCITrimmerViewModel"
|
||||
x:DataType="viewModels:XciTrimmerViewModel"
|
||||
Focusable="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Margin="25,10,25,0" RowDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
@@ -121,7 +121,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding $parent[UserControl].DataContext.Processing}"
|
||||
IsVisible="{Binding !$parent[UserControl].((viewModels:XciTrimmerViewModel)DataContext).Processing}"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{Binding PercentageProgress}" />
|
||||
@@ -131,7 +131,7 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
MaxLines="1"
|
||||
IsVisible="{Binding !$parent[UserControl].DataContext.Processing}"
|
||||
IsVisible="{Binding !$parent[UserControl].((viewModels:XciTrimmerViewModel)DataContext).Processing}"
|
||||
Text="{Binding ., Converter={x:Static helpers:XCITrimmerFileSpaceSavingsConverter.Instance}}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -173,7 +173,7 @@
|
||||
<Binding Path="!Cancel" />
|
||||
</MultiBinding>
|
||||
</Button.IsVisible>
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
<Button Name="CloseButton" MinWidth="90" Click="Close" IsVisible="{Binding !Processing}">
|
||||
<TextBlock Text="{ext:Locale SettingsButtonClose}" />
|
||||
@@ -182,4 +182,4 @@
|
||||
</Grid>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@@ -11,16 +11,16 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Dialog
|
||||
{
|
||||
public partial class XCITrimmerView : RyujinxControl<XCITrimmerViewModel>
|
||||
public partial class XciTrimmerView : RyujinxControl<XciTrimmerViewModel>
|
||||
{
|
||||
public XCITrimmerView()
|
||||
public XciTrimmerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ToggleSelect(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is XCITrimmerViewModel vm)
|
||||
if (DataContext is XciTrimmerViewModel vm)
|
||||
vm.ToggleSelect();
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace Ryujinx.Ava.UI.Views.Dialog
|
||||
PrimaryButtonText = string.Empty,
|
||||
SecondaryButtonText = string.Empty,
|
||||
CloseButtonText = string.Empty,
|
||||
Content = new XCITrimmerView
|
||||
Content = new XciTrimmerView
|
||||
{
|
||||
ViewModel = new XCITrimmerViewModel(RyujinxApp.MainWindow.ViewModel)
|
||||
ViewModel = new XciTrimmerViewModel(RyujinxApp.MainWindow.ViewModel)
|
||||
},
|
||||
Title = LocaleManager.Instance[LocaleKeys.MenuBar_Actions_XCITrimmerButton]
|
||||
};
|
||||
@@ -58,7 +58,7 @@ namespace Ryujinx.Ava.UI.Views.Dialog
|
||||
|
||||
private void Close(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((ContentDialog)Parent).Hide();
|
||||
((ContentDialog)Parent!).Hide();
|
||||
}
|
||||
|
||||
private void Cancel(Object sender, RoutedEventArgs e)
|
||||
@@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Views.Dialog
|
||||
public void Sort_Checked(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is RadioButton { Tag: string sortField })
|
||||
ViewModel.SortingField = Enum.Parse<XCITrimmerViewModel.SortField>(sortField);
|
||||
ViewModel.SortingField = Enum.Parse<XciTrimmerViewModel.SortField>(sortField);
|
||||
}
|
||||
|
||||
public void Order_Checked(object sender, RoutedEventArgs args)
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
InputGesture="Ctrl + A"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBinFile}"
|
||||
Command="{Binding ScanAmiiboFromBin}"
|
||||
AttachedToVisualTree="ScanBinAmiiboMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBar_Actions_ScanAmiiboFromBinButton}"
|
||||
Icon="{ext:Icon fa-solid fa-chess-rook}"
|
||||
@@ -146,21 +146,12 @@
|
||||
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
||||
<Separator IsVisible="{Binding CanScanAmiiboBinaries}"/>
|
||||
<MenuItem
|
||||
Command="{Binding OpenSkylanderWindow}"
|
||||
AttachedToVisualTree="ScanSkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBar_Actions_ScanSkylanderButton}"
|
||||
Command="{Binding ToggleSkylanderCommand}"
|
||||
AttachedToVisualTree="SkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{Binding SkylanderButtonHeader}"
|
||||
Icon="{ext:Icon fa-solid fa-dragon}"
|
||||
IsVisible="{Binding ShowSkylanderActions}"
|
||||
InputGesture="Ctrl + S"
|
||||
IsEnabled="{Binding IsSkylanderRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding RemoveSkylander}"
|
||||
AttachedToVisualTree="RemoveSkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBar_Actions_RemoveSkylanderButton}"
|
||||
Icon="{ext:Icon fa-solid fa-dragon}"
|
||||
IsVisible="{Binding ShowSkylanderActions}"
|
||||
InputGesture="Ctrl + D"
|
||||
IsEnabled="{Binding HasSkylander}" />
|
||||
IsEnabled="{Binding SkylanderButtonEnabled}" />
|
||||
<MenuItem
|
||||
Command="{Binding SimulateWakeUpMessage}"
|
||||
Header="{ext:Locale MenuBar_Actions_SimulateWakeUpMessageButton}"
|
||||
@@ -215,7 +206,7 @@
|
||||
<Separator />
|
||||
<MenuItem Header="{ext:Locale MenuBar_Actions_ToolsLabel}" Icon="{ext:Icon fa-solid fa-toolbox}">
|
||||
<MenuItem
|
||||
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" />
|
||||
Name="MiiEditorMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" />
|
||||
<MenuItem Name="XCITrimmerMenuItem" Header="{ext:Locale MenuBar_Actions_XCITrimmerButton}" Icon="{ext:Icon fa-solid fa-scissors}" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
|
||||
@@ -36,14 +36,14 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
|
||||
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
||||
MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet);
|
||||
MiiEditorMenuItem.Command = Commands.Create(OpenMiiEditor);
|
||||
CloseRyujinxMenuItem.Command = Commands.Create(() => Window?.Close());
|
||||
OpenSettingsMenuItem.Command = Commands.Create(OpenSettings);
|
||||
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
|
||||
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
|
||||
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
|
||||
RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation());
|
||||
XCITrimmerMenuItem.Command = Commands.Create(XCITrimmerView.Show);
|
||||
XCITrimmerMenuItem.Command = Commands.Create(XciTrimmerView.Show);
|
||||
AboutWindowMenuItem.Command = Commands.Create(AboutView.Show);
|
||||
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityListWindow.Show());
|
||||
LdnGameListMenuItem.Command = Commands.Create(() => LdnGamesListWindow.Show());
|
||||
@@ -150,11 +150,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ViewModel.LoadConfigurableHotKeys();
|
||||
}
|
||||
|
||||
public AppletMetadata MiiApplet => new(ViewModel.ContentManager, LocaleManager.Instance[LocaleKeys.MenuBar_Actions_MiiEditorButton], 0x0100000000001009);
|
||||
public AppletMetadata MiiEditor => new(ViewModel.ContentManager, LocaleManager.Instance[LocaleKeys.MenuBar_Actions_MiiEditorButton], 0x0100000000001009);
|
||||
|
||||
public async Task OpenMiiApplet()
|
||||
public async Task OpenMiiEditor()
|
||||
{
|
||||
if (!MiiApplet.CanStart(out ApplicationData appData, out BlitStruct<ApplicationControlProperty> nacpData))
|
||||
if (!MiiEditor.CanStart(out ApplicationData appData, out BlitStruct<ApplicationControlProperty> nacpData))
|
||||
return;
|
||||
|
||||
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
||||
@@ -172,18 +172,13 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
||||
}
|
||||
|
||||
private void ScanSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
private void SkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
{
|
||||
ViewModel.IsSkylanderRequested = ViewModel.AppHost.Device.System.SearchingForSkylander(out _);
|
||||
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||
}
|
||||
|
||||
private void RemoveSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
ViewModel.HasSkylander = ViewModel.AppHost.Device.System.HasSkylander(out _);
|
||||
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeWindowSize(string resolution)
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindow? mainWindow = RyujinxApp.MainWindow;
|
||||
MainWindow mainWindow = RyujinxApp.MainWindow;
|
||||
if (mainWindow?.ViewModel?.AppHost?.NpadManager is not { } npadManager)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"],
|
||||
AppleUniformTypeIdentifiers = ["public.jpeg", "public.png", "com.microsoft.bmp"],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||
xmlns:pt="using:Projektanker.Icons.Avalonia"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="400"
|
||||
d:DesignHeight="350"
|
||||
@@ -14,82 +15,163 @@
|
||||
x:DataType="viewModels:AmiiboWindowViewModel"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="800"
|
||||
Width="950"
|
||||
MinHeight="650"
|
||||
Height="650"
|
||||
Height="700"
|
||||
SizeToContent="Manual"
|
||||
MinWidth="600"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:AmiiboWindowViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
|
||||
<Grid Name="FlushControls" ColumnDefinitions="Auto,Auto" Grid.Row="1" Margin="-15">
|
||||
<controls:RyujinxLogo
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Margin="8, 8, 7, 0"
|
||||
ToolTip.Tip="{ext:WindowTitle Amiibo}" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0, 0, 0, 20">
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock VerticalAlignment="Center" Text="{ext:Locale AmiiboSeriesLabel}" />
|
||||
<ComboBox Margin="0, 8, 0, 0" SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" />
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*,Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
|
||||
<!-- UI FlushControls -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" Name="FlushControls">
|
||||
<controls:RyujinxLogo Grid.Column="0" Margin="15,0,7,0" ToolTip.Tip="{ext:WindowTitle Amiibo_WindowTitle}" />
|
||||
<TextBox Grid.Column="1"
|
||||
Margin="0,5,0,7"
|
||||
Width="250"
|
||||
Watermark="{ext:Locale Common_Search_SearchAmiiboWatermark}"
|
||||
Text="{Binding SearchText}" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<ComboBox Margin="10,5,0,5" SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="140" />
|
||||
</StackPanel>
|
||||
<DropDownButton Grid.Column="3" Margin="10,5,0,5" Content="{Binding SortingFieldName}">
|
||||
<DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Margin="0" HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton GroupName="AmiiboSort" IsChecked="{Binding IsSortedByName}" Content="{ext:Locale Common_Sort_NameLabel}" Tag="Name" Checked="Sort_Checked" />
|
||||
</StackPanel>
|
||||
<Border Width="60" Height="2" Margin="5" HorizontalAlignment="Stretch" BorderBrush="White" BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding SortingAscending}" Content="{ext:Locale Common_Sort_OrderAscending}" Tag="Ascending" Checked="Order_Checked" />
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding !SortingAscending}" Content="{ext:Locale Common_Sort_OrderDescending}" Tag="Descending" Checked="Order_Checked" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
Margin="10,5,0,5"
|
||||
MinWidth="32"
|
||||
MinHeight="32"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Click="SettingsButton_Click">
|
||||
<pt:Icon Value="fa-solid fa-gear" />
|
||||
<FlyoutBase.AttachedFlyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel>
|
||||
<CheckBox
|
||||
IsChecked="{Binding UseRandomUuid}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_UseRandomUuidLabel}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsChecked="{Binding PauseEmulationWhileScanningAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_PauseEmulationWhileScanningAmiibo}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</FlyoutBase.AttachedFlyout>
|
||||
</Button>
|
||||
<CheckBox Grid.Column="5" Margin="10,5,0,5" IsChecked="{Binding ShowAllAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_ShowAllAmiiboLabel}" />
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
<Grid Name="NormalControls" ColumnDefinitions="*,*" Grid.Row="1" HorizontalAlignment="Stretch" >
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock VerticalAlignment="Center" Text="{ext:Locale AmiiboSeriesLabel}" />
|
||||
<ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" />
|
||||
|
||||
<!-- UI NormalControls -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" Name="NormalControls">
|
||||
<TextBox Grid.Column="0"
|
||||
Margin="15,5,0,7"
|
||||
Width="250"
|
||||
Watermark="{ext:Locale Common_Search_SearchAmiiboWatermark}"
|
||||
Text="{Binding SearchText}" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<ComboBox Margin="10,5,0,5" SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="140" />
|
||||
</StackPanel>
|
||||
<DropDownButton Grid.Column="2" Margin="10,5,0,5" Content="{Binding SortingFieldName}">
|
||||
<DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Margin="0" HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton GroupName="AmiiboSort" IsChecked="{Binding IsSortedByName}" Content="{ext:Locale Common_Sort_NameLabel}" Tag="Name" Checked="Sort_Checked" />
|
||||
</StackPanel>
|
||||
<Border Width="60" Height="2" Margin="5" HorizontalAlignment="Stretch" BorderBrush="White" BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding SortingAscending}" Content="{ext:Locale Common_Sort_OrderAscending}" Tag="Ascending" Checked="Order_Checked" />
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding !SortingAscending}" Content="{ext:Locale Common_Sort_OrderDescending}" Tag="Descending" Checked="Order_Checked" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
Margin="10,5,0,5"
|
||||
MinWidth="32"
|
||||
MinHeight="32"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Click="SettingsButton_Click">
|
||||
<pt:Icon Value="fa-solid fa-gear" />
|
||||
<FlyoutBase.AttachedFlyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel>
|
||||
<CheckBox
|
||||
IsChecked="{Binding UseRandomUuid}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_UseRandomUuidLabel}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsChecked="{Binding PauseEmulationWhileScanningAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_PauseEmulationWhileScanningAmiibo}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</FlyoutBase.AttachedFlyout>
|
||||
</Button>
|
||||
<CheckBox Grid.Column="4" Margin="10,5,0,5" IsChecked="{Binding ShowAllAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_ShowAllAmiiboLabel}" />
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
<DockPanel Grid.Row="2">
|
||||
|
||||
<!-- Amiibo list, images, and usage -->
|
||||
<StackPanel Grid.Row="2" Margin="0,20,0,20" Orientation="Horizontal" HorizontalAlignment="Center" Spacing="50">
|
||||
<ListBox
|
||||
DockPanel.Dock="Left"
|
||||
Width="300"
|
||||
Margin="20"
|
||||
Padding="5"
|
||||
SelectionMode="Single"
|
||||
ItemsSource="{Binding AmiiboList}"
|
||||
SelectedIndex="{Binding AmiiboSelectedIndex}">
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="10, 0, 0 ,0" Text="{Binding Name}" />
|
||||
<TextBlock Margin="10,0,0,0" Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel Margin="20">
|
||||
<Image Source="{Binding AmiiboImage}" Height="350" Width="150" HorizontalAlignment="Center" />
|
||||
<ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
|
||||
Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch">
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center"
|
||||
TextAlignment="Center" />
|
||||
<StackPanel Width="300">
|
||||
<Image Source="{Binding AmiiboImage}" Height="400" Width="200" />
|
||||
<ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch">
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center" TextAlignment="Center" />
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<Grid Grid.Row="3" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<CheckBox Margin="10" Grid.Column="0" VerticalContentAlignment="Center"
|
||||
IsChecked="{Binding ShowAllAmiibo}"
|
||||
Content="{ext:Locale AmiiboOptionsShowAllLabel}" />
|
||||
<CheckBox HotKey="H"
|
||||
Margin="10" VerticalContentAlignment="Center" Grid.Column="1"
|
||||
IsChecked="{Binding UseRandomUuid}"
|
||||
Content="{ext:Locale AmiiboOptionsUsRandomTagLabel}" />
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="3" Margin="0,0,10,10" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<Button Grid.Column="3"
|
||||
IsEnabled="{Binding EnableScanning}"
|
||||
Width="80"
|
||||
Name="ScanButton"
|
||||
HotKey="Return"
|
||||
Content="{ext:Locale AmiiboScanButtonLabel}"
|
||||
Content="{ext:Locale Amiibo_ScanButton}"
|
||||
Click="ScanButton_Click" />
|
||||
<Button Grid.Column="4"
|
||||
Margin="10,0"
|
||||
Margin="10,0,0,0"
|
||||
Width="80"
|
||||
Name="CancelButton"
|
||||
HotKey="Escape"
|
||||
Content="{ext:Locale InputDialogCancel}"
|
||||
Content="{ext:Locale Common_Buttons_Cancel}"
|
||||
Click="CancelButton_Click" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</window:StyleableAppWindow>
|
||||
</window:StyleableAppWindow>
|
||||
@@ -1,8 +1,12 @@
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Common.Models.Amiibo;
|
||||
using Ryujinx.Ava.Systems;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using Avalonia.Controls.Primitives;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
@@ -20,7 +24,50 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
FlushControls.IsVisible = !ConfigurationState.Instance.ShowOldUI;
|
||||
NormalControls.IsVisible = ConfigurationState.Instance.ShowOldUI;
|
||||
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo_WindowTitle);
|
||||
|
||||
if (ViewModel.PauseEmulationWhileScanningAmiibo && RyujinxApp.MainWindow?.ViewModel?.AppHost != null)
|
||||
{
|
||||
RyujinxApp.MainWindow.ViewModel.AppHost.Pause();
|
||||
}
|
||||
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
}
|
||||
|
||||
private void SettingsButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FlyoutBase.ShowAttachedFlyout((Control)sender!);
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(ViewModel.PauseEmulationWhileScanningAmiibo))
|
||||
return;
|
||||
|
||||
AppHost host = RyujinxApp.MainWindow?.ViewModel?.AppHost;
|
||||
if (host == null)
|
||||
return;
|
||||
|
||||
if (ViewModel.PauseEmulationWhileScanningAmiibo)
|
||||
host.Pause();
|
||||
else
|
||||
host.Resume();
|
||||
}
|
||||
|
||||
private void AlwaysResumeOnClose()
|
||||
{
|
||||
if (RyujinxApp.MainWindow?.ViewModel?.AppHost != null)
|
||||
{
|
||||
RyujinxApp.MainWindow.ViewModel.AppHost.Resume();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
ViewModel.PropertyChanged -= ViewModel_PropertyChanged;
|
||||
AlwaysResumeOnClose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
public AmiiboWindow()
|
||||
@@ -31,7 +78,26 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo_WindowTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Sort_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is RadioButton { Tag: string tag } && ViewModel != null)
|
||||
{
|
||||
if (Enum.TryParse<AmiiboWindowViewModel.AmiiboSortField>(tag, out var sortField))
|
||||
{
|
||||
ViewModel.SortingField = sortField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Order_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is RadioButton { Tag: string tag } && ViewModel != null)
|
||||
{
|
||||
ViewModel.SortingAscending = tag == "Ascending";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +105,18 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
public AmiiboApi ScannedAmiibo { get; set; }
|
||||
public AmiiboWindowViewModel ViewModel;
|
||||
|
||||
private void ScanButton_Click(object sender, RoutedEventArgs e) => ViewModel.Scan();
|
||||
private void ScanButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AlwaysResumeOnClose();
|
||||
ViewModel.Scan();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e) => ViewModel.Cancel();
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AlwaysResumeOnClose();
|
||||
ViewModel.Cancel();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
Margin="5"
|
||||
HotKey="Escape"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
|
||||
@@ -40,10 +40,11 @@
|
||||
<KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" />
|
||||
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
|
||||
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
|
||||
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
|
||||
<KeyBinding Gesture="Ctrl+B" Command="{Binding ScanAmiiboFromBin}" />
|
||||
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
|
||||
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
|
||||
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
|
||||
<KeyBinding Gesture="Ctrl+S" Command="{Binding ToggleSkylanderCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+M" Command="{Binding SimulateWakeUpMessage}" />
|
||||
<KeyBinding Gesture="Cmd+O" Command="{Binding LoadApplicationFromFile}" />
|
||||
<KeyBinding Gesture="Ctrl+O" Command="{Binding LoadApplicationFromFile}" />
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
Command="{Binding OkButton}" />
|
||||
<Button
|
||||
HotKey="Escape"
|
||||
Content="{ext:Locale SettingsButtonCancel}"
|
||||
Content="{ext:Locale Common_Buttons_Cancel}"
|
||||
Command="{Binding CancelButton}" />
|
||||
<Button
|
||||
Content="{ext:Locale SettingsButtonApply}"
|
||||
|
||||
Reference in New Issue
Block a user