Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
2fa6413ed8 | |||
4523a73f75 | |||
f4c47f3c9a | |||
7d9a5feccb | |||
14ae4e276f | |||
3af42d6c7e | |||
bccf5e8b5a |
@ -109,21 +109,21 @@
|
||||
"SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese",
|
||||
"SettingsTabSystemSystemTimeZone": "System TimeZone:",
|
||||
"SettingsTabSystemSystemTime": "System Time:",
|
||||
"SettingsTabSystemEnableVsync": "Enable VSync",
|
||||
"SettingsTabSystemEnablePptc": "Enable PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "Enable FS Integrity Checks",
|
||||
"SettingsTabSystemEnableVsync": "VSync",
|
||||
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks",
|
||||
"SettingsTabSystemAudioBackend": "Audio Backend:",
|
||||
"SettingsTabSystemAudioBackendDummy": "Dummy",
|
||||
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
|
||||
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||
"SettingsTabSystemHacks": "Hacks",
|
||||
"SettingsTabSystemHacksNote": " - These many cause instabilities",
|
||||
"SettingsTabSystemExpandDramSize": "Expand DRAM size to 6GB",
|
||||
"SettingsTabSystemHacksNote": " - These may cause instabilities",
|
||||
"SettingsTabSystemExpandDramSize": "Expand DRAM Size to 6GB",
|
||||
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
|
||||
"SettingsTabGraphics": "Graphics",
|
||||
"SettingsTabGraphicsEnhancements": "Enhancements",
|
||||
"SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache",
|
||||
"SettingsTabGraphicsEnableShaderCache": "Shader Cache",
|
||||
"SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:",
|
||||
"SettingsTabGraphicsAnisotropicFilteringAuto": "Auto",
|
||||
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
|
||||
@ -164,7 +164,7 @@
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "All",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs",
|
||||
"SettingsTabInput": "Input",
|
||||
"SettingsTabInputEnableDockedMode": "Enable Docked Mode",
|
||||
"SettingsTabInputEnableDockedMode": "Docked Mode",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
|
||||
"SettingsButtonSave": "Save",
|
||||
"SettingsButtonClose": "Close",
|
||||
@ -418,52 +418,52 @@
|
||||
"OrderDescending": "Descending",
|
||||
"SettingsTabGraphicsFeatures": "Features",
|
||||
"ErrorWindowTitle": "Error Window",
|
||||
"ToggleDiscordTooltip": "Enables or disables Discord Rich Presence",
|
||||
"ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity",
|
||||
"AddGameDirBoxTooltip": "Enter a game directory to add to the list",
|
||||
"AddGameDirTooltip": "Add a game directory to the list",
|
||||
"RemoveGameDirTooltip": "Remove selected game directory",
|
||||
"CustomThemeCheckTooltip": "Enable or disable custom themes in the GUI",
|
||||
"CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus",
|
||||
"CustomThemePathTooltip": "Path to custom GUI theme",
|
||||
"CustomThemeBrowseTooltip": "Browse for a custom GUI theme",
|
||||
"DockModeToggleTooltip": "Enable or disable Docked Mode",
|
||||
"DirectKeyboardTooltip": "Enable or disable \"direct keyboard access (HID) support\" (Provides games access to your keyboard as a text entry device)",
|
||||
"DirectMouseTooltip": "Enable or disable \"direct mouse access (HID) support\" (Provides games access to your mouse as a pointing device)",
|
||||
"DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.",
|
||||
"DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.",
|
||||
"DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.",
|
||||
"RegionTooltip": "Change System Region",
|
||||
"LanguageTooltip": "Change System Language",
|
||||
"TimezoneTooltip": "Change System TimeZone",
|
||||
"TimeTooltip": "Change System Time",
|
||||
"VSyncToggleTooltip": "Enables or disables Vertical Sync",
|
||||
"PptcToggleTooltip": "Enables or disables PPTC",
|
||||
"FsIntegrityToggleTooltip": "Enables integrity checks on Game content files",
|
||||
"AudioBackendTooltip": "Change Audio Backend",
|
||||
"MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.",
|
||||
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
|
||||
"PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.",
|
||||
"FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.",
|
||||
"AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
|
||||
"MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.",
|
||||
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
|
||||
"MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
|
||||
"MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
|
||||
"DRamTooltip": "Expands the amount of memory on the emulated system from 4GB to 6GB",
|
||||
"IgnoreMissingServicesTooltip": "Enable or disable ignoring missing services option",
|
||||
"GraphicsBackendThreadingTooltip": "Enable Graphics Backend Multithreading",
|
||||
"GalThreadingTooltip": "Executes graphics backend commands on a second thread. Allows runtime multithreading of shader compilation, reduces stuttering, and improves performance on drivers without multithreading support of their own. Slightly varying peak performance on drivers with multithreading. Ryujinx may need to be restarted to correctly disable driver built-in multithreading, or you may need to do it manually to get the best performance.",
|
||||
"ShaderCacheToggleTooltip": "Enables or disables Shader Cache",
|
||||
"DRamTooltip": "Increases the amount of memory on the emulated system from 4GB to 6GB.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
|
||||
"IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
|
||||
"GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
|
||||
"GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
|
||||
"ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.",
|
||||
"ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets",
|
||||
"ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.",
|
||||
"AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)",
|
||||
"AspectRatioTooltip": "Aspect Ratio applied to the renderer window.",
|
||||
"ShaderDumpPathTooltip": "Graphics Shaders Dump Path",
|
||||
"FileLogTooltip": "Enables or disables logging to a file on disk",
|
||||
"StubLogTooltip": "Enables printing stub log messages",
|
||||
"InfoLogTooltip": "Enables printing info log messages",
|
||||
"WarnLogTooltip": "Enables printing warning log messages",
|
||||
"ErrorLogTooltip": "Enables printing error log messages",
|
||||
"TraceLogTooltip": "Enables printing trace log messages",
|
||||
"GuestLogTooltip": "Enables printing guest log messages",
|
||||
"FileAccessLogTooltip": "Enables printing file access log messages",
|
||||
"FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.",
|
||||
"StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.",
|
||||
"InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.",
|
||||
"WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.",
|
||||
"ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.",
|
||||
"TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.",
|
||||
"GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.",
|
||||
"FileAccessLogTooltip": "Prints file access log messages in the console.",
|
||||
"FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3",
|
||||
"DeveloperOptionTooltip": "Use with care",
|
||||
"OpenGlLogLevel": "Requires appropriate log levels enabled",
|
||||
"DebugLogTooltip": "Enables printing debug log messages",
|
||||
"LoadApplicationFileTooltip": "Open a file chooser to choose a Switch compatible file to load",
|
||||
"LoadApplicationFolderTooltip": "Open a file chooser to choose a Switch compatible, unpacked application to load",
|
||||
"DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.",
|
||||
"LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load",
|
||||
"LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load",
|
||||
"OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder",
|
||||
"OpenRyujinxLogsTooltip": "Opens the folder where logs are written to",
|
||||
"ExitTooltip": "Exit Ryujinx",
|
||||
@ -478,8 +478,8 @@
|
||||
"AboutRyujinxContributorsButtonHeader": "See All Contributors",
|
||||
"SettingsTabSystemAudioVolume": "Volume: ",
|
||||
"AudioVolumeTooltip": "Change Audio Volume",
|
||||
"SettingsTabSystemEnableInternetAccess": "Enable Guest Internet Access",
|
||||
"EnableInternetAccessTooltip": "Enables guest Internet access. If enabled, the application will behave as if the emulated Switch console was connected to the Internet. Note that in some cases, applications may still access the Internet even with this option disabled",
|
||||
"SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode",
|
||||
"EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.",
|
||||
"GameListContextMenuManageCheatToolTip": "Manage Cheats",
|
||||
"GameListContextMenuManageCheat": "Manage Cheats",
|
||||
"ControllerSettingsStickRange": "Range:",
|
||||
@ -494,10 +494,10 @@
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
|
||||
"UpdaterDisabledWarningTitle": "Updater Disabled!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
|
||||
"ControllerSettingsRotate90": "Rotate 90° Clockwise",
|
||||
"IconSize": "Icon Size",
|
||||
"IconSizeTooltip": "Change the size of game icon",
|
||||
"IconSizeTooltip": "Change the size of game icons",
|
||||
"MenuBarOptionsShowConsole": "Show Console",
|
||||
"ShaderCachePurgeError": "Error purging shader cache at {0}: {1}",
|
||||
"UserErrorNoKeys": "Keys not found",
|
||||
@ -553,6 +553,20 @@
|
||||
"SettingsTabHotkeysToggleMuteHotkey": "Mute:",
|
||||
"ControllerMotionTitle": "Motion Control Settings",
|
||||
"ControllerRumbleTitle": "Rumble Settings",
|
||||
"SettingsSelectThemeFileDialogTitle" : "Select Theme File",
|
||||
"SettingsXamlThemeFile" : "Xaml Theme File"
|
||||
"SettingsSelectThemeFileDialogTitle": "Select Theme File",
|
||||
"SettingsXamlThemeFile": "Xaml Theme File",
|
||||
"AvatarWindowTitle": "Manage Accounts - Avatar",
|
||||
"Amiibo": "Amiibo",
|
||||
"Unknown": "Unknown",
|
||||
"Usage": "Usage",
|
||||
"Writable": "Writable",
|
||||
"SelectDlcDialogTitle": "Select DLC files",
|
||||
"SelectUpdateDialogTitle": "Select update files",
|
||||
"UserProfileWindowTitle": "Manage User Profiles",
|
||||
"CheatWindowTitle": "Manage Game Cheats",
|
||||
"DlcWindowTitle": "Manage Game DLC",
|
||||
"UpdateWindowTitle": "Manage Game Updates",
|
||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||
"DlcWindowHeading": "DLC Available for {0} [{1}]",
|
||||
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]"
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"MenuBarFileOpenApplet": "Abrir applet",
|
||||
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo",
|
||||
"SettingsTabInputDirectMouseAccess": "Acceso directo al ratón",
|
||||
@ -109,9 +109,9 @@
|
||||
"SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional",
|
||||
"SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:",
|
||||
"SettingsTabSystemSystemTime": "Hora del sistema:",
|
||||
"SettingsTabSystemEnableVsync": "Habilitar sincronización vertical",
|
||||
"SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "Habilitar comprobaciones de integridad FS",
|
||||
"SettingsTabSystemEnableVsync": "Sincronización vertical",
|
||||
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos",
|
||||
"SettingsTabSystemAudioBackend": "Motor de audio:",
|
||||
"SettingsTabSystemAudioBackendDummy": "Dummy",
|
||||
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
|
||||
@ -120,10 +120,10 @@
|
||||
"SettingsTabSystemHacks": "Hacks",
|
||||
"SettingsTabSystemHacksNote": " - Pueden causar inestabilidad",
|
||||
"SettingsTabSystemExpandDramSize": "Expandir DRAM a 6GB",
|
||||
"SettingsTabSystemIgnoreMissingServices": "Ignorar falta de servicios",
|
||||
"SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados",
|
||||
"SettingsTabGraphics": "Gráficos",
|
||||
"SettingsTabGraphicsEnhancements": "Mejoras",
|
||||
"SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombreadores",
|
||||
"SettingsTabGraphicsEnableShaderCache": "Caché de sombreadores",
|
||||
"SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:",
|
||||
"SettingsTabGraphicsAnisotropicFilteringAuto": "Auto",
|
||||
"SettingsTabGraphicsAnisotropicFiltering2x": "x2",
|
||||
@ -152,6 +152,7 @@
|
||||
"SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info",
|
||||
"SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia",
|
||||
"SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error",
|
||||
"SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro",
|
||||
"SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest",
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:",
|
||||
@ -163,7 +164,7 @@
|
||||
"SettingsTabLoggingOpenglLogLevelAll": "Todo",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug",
|
||||
"SettingsTabInput": "Entrada",
|
||||
"SettingsTabInputEnableDockedMode": "Habilitar modo acoplado (dock/TV)",
|
||||
"SettingsTabInputEnableDockedMode": "Modo dock/TV",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
|
||||
"SettingsButtonSave": "Guardar",
|
||||
"SettingsButtonClose": "Cerrar",
|
||||
@ -312,8 +313,8 @@
|
||||
"DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.",
|
||||
"DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!",
|
||||
"DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!",
|
||||
"DialogUpdaterFailedToGetVersionMessage": "Ha ocurrido un error al intentar obtener información de versión desde AppVeyor.",
|
||||
"DialogUpdaterConvertFailedAppveyorMessage": "No se pudo convertir la versión de Ryujinx recibida de AppVeyor.",
|
||||
"DialogUpdaterFailedToGetVersionMessage": "Ha ocurrido un error al intentar obtener información de versión desde GitHub Release. Esto puede ocurrir cuando una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.",
|
||||
"DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.",
|
||||
"DialogUpdaterDownloadingMessage": "Descargando actualización...",
|
||||
"DialogUpdaterExtractionMessage": "Extrayendo actualización...",
|
||||
"DialogUpdaterRenamingMessage": "Renombrando actualización...",
|
||||
@ -417,49 +418,50 @@
|
||||
"OrderDescending": "Descendente",
|
||||
"SettingsTabGraphicsFeatures": "Funcionalidades",
|
||||
"ErrorWindowTitle": "Ventana de error",
|
||||
"ToggleDiscordTooltip": "Activa o desactiva la presencia enriquecida de Discord",
|
||||
"ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando",
|
||||
"AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal",
|
||||
"AddGameDirTooltip": "Agrega un directorio de juegos a la lista",
|
||||
"RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista",
|
||||
"CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz",
|
||||
"CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz",
|
||||
"CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz",
|
||||
"DockModeToggleTooltip": "Activa o desactiva el modo TV de la Nintendo Switch",
|
||||
"DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.",
|
||||
"DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)",
|
||||
"DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)",
|
||||
"RegionTooltip": "Cambia la región del sistema",
|
||||
"LanguageTooltip": "Cambia el idioma del sistema",
|
||||
"TimezoneTooltip": "Cambia la zona horaria del sistema",
|
||||
"TimeTooltip": "Cambia la hora del sistema",
|
||||
"VSyncToggleTooltip": "Activa o desactiva la sincronización vertical",
|
||||
"PptcToggleTooltip": "Activa o desactiva la caché de PPTC",
|
||||
"FsIntegrityToggleTooltip": "Activa comprobaciones de integridad de los archivos de un juego",
|
||||
"AudioBackendTooltip": "Cambia el motor de audio",
|
||||
"MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.",
|
||||
"VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.",
|
||||
"PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.",
|
||||
"FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.",
|
||||
"AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.",
|
||||
"MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.",
|
||||
"MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.",
|
||||
"MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.",
|
||||
"MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.",
|
||||
"DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GB a 6GB. Utilizar solo con mods 4K.",
|
||||
"IgnoreMissingServicesTooltip": "Activa o desactiva un hack para ignorar servicios no implementados",
|
||||
"GraphicsBackendThreadingTooltip": "Activa el multihilado del motor gráfico",
|
||||
"GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Permite multihilar la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos sin necesidad de que utilicen su propio multihilado. Rendimiento máximo ligeramente variable en controladores gráficos que soporten multihilado. Ryujinx puede necesitar reiniciarse para desactivar correctamente el multihilado de tu controlador de gráficos, o puede que tengas que desactivarlo manualmente para lograr el mejor rendimiento posible.",
|
||||
"ShaderCacheToggleTooltip": "Activa o desactiva la caché de sombreadores",
|
||||
"DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GB a 6GB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.",
|
||||
"IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.",
|
||||
"GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.",
|
||||
"GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.",
|
||||
"ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.",
|
||||
"ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado",
|
||||
"ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.",
|
||||
"AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)",
|
||||
"AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.",
|
||||
"ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos",
|
||||
"FileLogTooltip": "Activa o desactiva el guardado de registros en archivos en disco",
|
||||
"StubLogTooltip": "Activa mensajes de Stub en la consola",
|
||||
"InfoLogTooltip": "Activa mensajes de Info en la consola",
|
||||
"WarnLogTooltip": "Activa mensajes de Advertencia en la consola",
|
||||
"ErrorLogTooltip": "Activa mensajes de Error en la consola",
|
||||
"GuestLogTooltip": "Activa mensajes de Guest en la consola",
|
||||
"FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.",
|
||||
"StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.",
|
||||
"InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.",
|
||||
"WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.",
|
||||
"ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.",
|
||||
"TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.",
|
||||
"GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.",
|
||||
"FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola",
|
||||
"FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3",
|
||||
"DeveloperOptionTooltip": "Usar con cuidado",
|
||||
"OpenGlLogLevel": "Requiere activar los niveles de registro apropiados",
|
||||
"DebugLogTooltip": "Activa mensajes de debug en la consola",
|
||||
"DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.",
|
||||
"LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar",
|
||||
"LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar",
|
||||
"OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx",
|
||||
@ -473,22 +475,98 @@
|
||||
"GridSize": "Tamaño de cuadrícula",
|
||||
"GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula",
|
||||
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño",
|
||||
"AboutRyujinxContributorsButtonHeader" : "Ver todos los contribuidores",
|
||||
"AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores",
|
||||
"SettingsTabSystemAudioVolume": "Volumen: ",
|
||||
"AudioVolumeTooltip": "Ajusta el nivel de volumen",
|
||||
"SettingsTabSystemEnableInternetAccess": "Habilitar acceso a Internet del guest",
|
||||
"EnableInternetAccessTooltip": "Activa el acceso a Internet del guest. Cuando esté activo, la aplicación actuará como si la Nintendo Switch emulada estuviese conectada a Internet. Ten en cuenta que algunas aplicaciones pueden intentar acceder a Internet incluso con esta opción desactivada.",
|
||||
"GameListContextMenuManageCheatToolTip" : "Activa o desactiva los cheats",
|
||||
"GameListContextMenuManageCheat" : "Administrar cheats",
|
||||
"ControllerSettingsStickRange" : "Alcance:",
|
||||
"DialogStopEmulationTitle" : "Ryujinx - Detener emulación",
|
||||
"SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN",
|
||||
"EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.",
|
||||
"GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats",
|
||||
"GameListContextMenuManageCheat": "Administrar cheats",
|
||||
"ControllerSettingsStickRange": "Alcance:",
|
||||
"DialogStopEmulationTitle": "Ryujinx - Detener emulación",
|
||||
"DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?",
|
||||
"SettingsTabCpu": "CPU",
|
||||
"SettingsTabAudio": "Audio",
|
||||
"SettingsTabNetwork": "Red",
|
||||
"SettingsTabNetworkConnection" : "Conexión de red",
|
||||
"SettingsTabCpuCache" : "Caché de CPU",
|
||||
"SettingsTabCpuMemory" : "Memoria de CPU",
|
||||
"ControllerMotionTitle": "Motion Control Settings",
|
||||
"ControllerRumbleTitle": "Rumble Settings"
|
||||
"SettingsTabNetworkConnection": "Conexión de red",
|
||||
"SettingsTabCpuCache": "Caché de CPU",
|
||||
"SettingsTabCpuMemory": "Memoria de CPU",
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.",
|
||||
"UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.",
|
||||
"ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj",
|
||||
"IconSize": "Tamaño de iconos",
|
||||
"IconSizeTooltip": "Cambia el tamaño de los iconos de juegos",
|
||||
"MenuBarOptionsShowConsole": "Mostrar consola",
|
||||
"ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}",
|
||||
"UserErrorNoKeys": "No se encontraron keys",
|
||||
"UserErrorNoFirmware": "No se encontró firmware",
|
||||
"UserErrorFirmwareParsingFailed": "Error al analizar el firmware",
|
||||
"UserErrorApplicationNotFound": "No se encontró la aplicación",
|
||||
"UserErrorUnknown": "Error desconocido",
|
||||
"UserErrorUndefined": "Error indefinido",
|
||||
"UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.",
|
||||
"UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.",
|
||||
"UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.",
|
||||
"UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.",
|
||||
"UserErrorUnknownDescription": "¡Ocurrió un error desconocido!",
|
||||
"UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!",
|
||||
"OpenSetupGuideMessage": "Abrir la guía de instalación",
|
||||
"NoUpdate": "No actualizado",
|
||||
"TitleUpdateVersionLabel": "Versión {0} - {1}",
|
||||
"RyujinxInfo": "Ryujinx - Info",
|
||||
"RyujinxConfirm": "Ryujinx - Confirmación",
|
||||
"FileDialogAllTypes": "Todos los tipos",
|
||||
"Never": "Nunca",
|
||||
"SwkbdMinCharacters": "Debe tener al menos {0} caracteres",
|
||||
"SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres",
|
||||
"SoftwareKeyboard": "Teclado de software",
|
||||
"DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.",
|
||||
"DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.",
|
||||
"DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n",
|
||||
"UpdaterRenaming": "Renombrando archivos viejos...",
|
||||
"UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}",
|
||||
"UpdaterAddingFiles": "Añadiendo nuevos archivos...",
|
||||
"UpdaterExtracting": "Extrayendo actualización...",
|
||||
"UpdaterDownloading": "Descargando actualización...",
|
||||
"Game": "Juego",
|
||||
"Docked": "Dock/TV",
|
||||
"Handheld": "Portátil",
|
||||
"ConnectionError": "Error de conexión.",
|
||||
"AboutPageDeveloperListMore": "{0} y más...",
|
||||
"ApiError": "Error de API.",
|
||||
"LoadingHeading": "Cargando {0}",
|
||||
"CompilingPPTC": "Compilando PTC",
|
||||
"CompilingShaders": "Compilando sombreadores",
|
||||
"AllKeyboards": "Todos los teclados",
|
||||
"OpenFileDialogTitle": "Selecciona un archivo soportado para cargar",
|
||||
"OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado",
|
||||
"AllSupportedFormats": "Todos los formatos soportados",
|
||||
"RyujinxUpdater": "Actualizador de Ryujinx",
|
||||
"SettingsTabHotkeys": "Atajos de teclado",
|
||||
"SettingsTabHotkeysHotkeys": "Atajos de teclado",
|
||||
"SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:",
|
||||
"SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:",
|
||||
"SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:",
|
||||
"SettingsTabHotkeysPauseHotkey": "Pausar:",
|
||||
"SettingsTabHotkeysToggleMuteHotkey": "Silenciar:",
|
||||
"ControllerMotionTitle": "Opciones de controles de movimiento",
|
||||
"ControllerRumbleTitle": "Opciones de vibración",
|
||||
"SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema",
|
||||
"SettingsXamlThemeFile": "Archivo de tema Xaml",
|
||||
"AvatarWindowTitle": "Administrar cuentas - Avatar",
|
||||
"Amiibo": "Amiibo",
|
||||
"Unknown": "Desconocido",
|
||||
"Usage": "Usage",
|
||||
"Writable": "Escribible",
|
||||
"SelectDlcDialogTitle": "Selecciona archivo(s) de DLC",
|
||||
"SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización",
|
||||
"UserProfileWindowTitle": "Administrar perfiles de usuario",
|
||||
"CheatWindowTitle": "Administrar cheats",
|
||||
"DlcWindowTitle": "Administrar contenido descargable",
|
||||
"UpdateWindowTitle": "Administrar actualizaciones",
|
||||
"CheatWindowHeading": "Cheats disponibles para {0} [{1}]",
|
||||
"DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]",
|
||||
"GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]"
|
||||
}
|
||||
|
@ -1,34 +1,34 @@
|
||||
{
|
||||
"MenuBarFileOpenApplet": "打开小程序",
|
||||
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的Mii小程序",
|
||||
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序",
|
||||
"SettingsTabInputDirectMouseAccess": "直通鼠标操作",
|
||||
"SettingsTabSystemMemoryManagerMode": "内存管理模式:",
|
||||
"SettingsTabSystemMemoryManagerModeSoftware": "软件",
|
||||
"SettingsTabSystemMemoryManagerModeHost": "本机 (快速)",
|
||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快速)",
|
||||
"MenuBarFile": "文件(_F)",
|
||||
"MenuBarFileOpenFromFile": "加载文件中的程序(_L)",
|
||||
"MenuBarFileOpenUnpacked": "加载解包后的游戏(_U)",
|
||||
"MenuBarFileOpenEmuFolder": "打开Ryujinx文件夹",
|
||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)",
|
||||
"MenuBarFile": "文件",
|
||||
"MenuBarFileOpenFromFile": "加载文件",
|
||||
"MenuBarFileOpenUnpacked": "加载解包后的游戏",
|
||||
"MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹",
|
||||
"MenuBarFileOpenLogsFolder": "打开日志文件夹",
|
||||
"MenuBarFileExit": "退出(_E)",
|
||||
"MenuBarFileExit": "退出",
|
||||
"MenuBarOptions": "选项",
|
||||
"MenuBarOptionsToggleFullscreen": "切换全屏",
|
||||
"MenuBarOptionsStartGamesInFullscreen": "以全屏模式启动游戏",
|
||||
"MenuBarOptionsStopEmulation": "中止模拟",
|
||||
"MenuBarOptionsSettings": "设置(_S)",
|
||||
"MenuBarOptionsManageUserProfiles": "管理用户账户(_M)",
|
||||
"MenuBarActions": "行动(_A)",
|
||||
"MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏",
|
||||
"MenuBarOptionsStopEmulation": "停止模拟",
|
||||
"MenuBarOptionsSettings": "设置",
|
||||
"MenuBarOptionsManageUserProfiles": "管理用户账户",
|
||||
"MenuBarActions": "操作",
|
||||
"MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息",
|
||||
"MenuBarActionsScanAmiibo": "扫描Amiibo",
|
||||
"MenuBarTools": "工具(_T)",
|
||||
"MenuBarActionsScanAmiibo": "扫描 Amiibo",
|
||||
"MenuBarTools": "工具",
|
||||
"MenuBarToolsInstallFirmware": "安装固件",
|
||||
"MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件",
|
||||
"MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件",
|
||||
"MenuBarHelp": "帮助",
|
||||
"MenuBarHelpCheckForUpdates": "检查更新",
|
||||
"MenuBarHelpAbout": "关于",
|
||||
"MenuSearch": "搜索...",
|
||||
"MenuSearch": "搜索……",
|
||||
"GameListHeaderFavorite": "收藏",
|
||||
"GameListHeaderIcon": "图标",
|
||||
"GameListHeaderApplication": "名称",
|
||||
@ -43,13 +43,13 @@
|
||||
"GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录",
|
||||
"GameListContextMenuOpenUserDeviceDirectory": "打开应用系统目录",
|
||||
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "打开包含游戏系统设置的目录",
|
||||
"GameListContextMenuOpenUserBcatDirectory": "打开BCAT目录",
|
||||
"GameListContextMenuOpenUserBcatDirectoryToolTip": "打开包含游戏BCAT数据的目录",
|
||||
"GameListContextMenuOpenUserBcatDirectory": "打开 BCAT 目录",
|
||||
"GameListContextMenuOpenUserBcatDirectoryToolTip": "打开包含游戏 BCAT 数据的目录",
|
||||
"GameListContextMenuManageTitleUpdates": "管理游戏更新",
|
||||
"GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理窗口",
|
||||
"GameListContextMenuManageDlc": "管理DLC",
|
||||
"GameListContextMenuManageDlc": "管理 DLC",
|
||||
"GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口",
|
||||
"GameListContextMenuOpenModsDirectory": "打开MOD目录",
|
||||
"GameListContextMenuOpenModsDirectory": "打开 MOD 目录",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏MOD的目录",
|
||||
"GameListContextMenuCacheManagement": "缓存管理",
|
||||
"GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 缓存",
|
||||
@ -72,10 +72,10 @@
|
||||
"Settings": "设置",
|
||||
"SettingsTabGeneral": "用户界面",
|
||||
"SettingsTabGeneralGeneral": "常规",
|
||||
"SettingsTabGeneralEnableDiscordRichPresence": "启用Discord在线状态展示",
|
||||
"SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示",
|
||||
"SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新",
|
||||
"SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框",
|
||||
"SettingsTabGeneralHideCursorOnIdle": "空闲时隐藏鼠标",
|
||||
"SettingsTabGeneralHideCursorOnIdle": "自动隐藏鼠标",
|
||||
"SettingsTabGeneralGameDirectories": "游戏目录",
|
||||
"SettingsTabGeneralAdd": "添加",
|
||||
"SettingsTabGeneralRemove": "删除",
|
||||
@ -119,7 +119,7 @@
|
||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||
"SettingsTabSystemHacks": "修正",
|
||||
"SettingsTabSystemHacksNote": " - 会引起模拟器不稳定",
|
||||
"SettingsTabSystemExpandDramSize": "将模拟RAM大小扩展至 6GB",
|
||||
"SettingsTabSystemExpandDramSize": "将模拟RAM大小扩展到 6GB",
|
||||
"SettingsTabSystemIgnoreMissingServices": "忽略缺少的服务",
|
||||
"SettingsTabGraphics": "图像",
|
||||
"SettingsTabGraphicsEnhancements": "增强",
|
||||
@ -184,12 +184,12 @@
|
||||
"ControllerSettingsDeviceDisabled": "关闭",
|
||||
"ControllerSettingsControllerType": "手柄类型",
|
||||
"ControllerSettingsControllerTypeHandheld": "掌机",
|
||||
"ControllerSettingsControllerTypeProController": "Pro手柄",
|
||||
"ControllerSettingsControllerTypeProController": "Pro 手柄",
|
||||
"ControllerSettingsControllerTypeJoyConPair": "JoyCon",
|
||||
"ControllerSettingsControllerTypeJoyConLeft": "左JoyCon",
|
||||
"ControllerSettingsControllerTypeJoyConRight": "右JoyCon",
|
||||
"ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon",
|
||||
"ControllerSettingsControllerTypeJoyConRight": "右 JoyCon",
|
||||
"ControllerSettingsProfile": "预设",
|
||||
"ControllerSettingsProfileDefault": "默认",
|
||||
"ControllerSettingsProfileDefault": "Default",
|
||||
"ControllerSettingsLoad": "加载",
|
||||
"ControllerSettingsAdd": "新建",
|
||||
"ControllerSettingsRemove": "删除",
|
||||
@ -243,19 +243,17 @@
|
||||
"ControllerSettingsMisc": "其他",
|
||||
"ControllerSettingsTriggerThreshold": "扳机阈值:",
|
||||
"ControllerSettingsMotion": "体感",
|
||||
"ControllerSettingsCemuHook": "CemuHook",
|
||||
"ControllerSettingsMotionEnableMotionControls": "启用体感操作",
|
||||
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用CemuHook体感协议",
|
||||
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议",
|
||||
"ControllerSettingsMotionControllerSlot": "手柄:",
|
||||
"ControllerSettingsMotionMirrorInput": "镜像操作",
|
||||
"ControllerSettingsMotionRightJoyConSlot": "右JoyCon:",
|
||||
"ControllerSettingsMotionServerHost": "服务器Host:",
|
||||
"ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:",
|
||||
"ControllerSettingsMotionServerHost": "服务器 Host:",
|
||||
"ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:",
|
||||
"ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:",
|
||||
"ControllerSettingsSave": "保存",
|
||||
"ControllerSettingsClose": "关闭",
|
||||
"UserProfilesSelectedUserProfile": "选择用户账户:",
|
||||
"UserProfilesSaveProfileName": "保存账户名",
|
||||
"UserProfilesSelectedUserProfile": "选择的用户账户:",
|
||||
"UserProfilesSaveProfileName": "保存名称",
|
||||
"UserProfilesChangeProfileImage": "更换头像",
|
||||
"UserProfilesAvailableUserProfiles": "现有的账户:",
|
||||
"UserProfilesAddNewProfile": "新建账户",
|
||||
@ -281,7 +279,7 @@
|
||||
"ControllerSettingsSaveProfileToolTip": "保存预设",
|
||||
"MenuBarFileToolsTakeScreenshot": "保存截图",
|
||||
"MenuBarFileToolsHideUi": "隐藏UI",
|
||||
"GameListContextMenuToggleFavorite": "标记为收藏",
|
||||
"GameListContextMenuToggleFavorite": "收藏",
|
||||
"GameListContextMenuToggleFavoriteToolTip": "启用或取消收藏标记",
|
||||
"SettingsTabGeneralTheme": "主题",
|
||||
"SettingsTabGeneralThemeCustomTheme": "自选主题路径",
|
||||
@ -290,9 +288,8 @@
|
||||
"SettingsTabGeneralThemeBaseStyleLight": "浅色",
|
||||
"SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面",
|
||||
"ButtonBrowse": "浏览",
|
||||
"ControllerSettingsMotionConfigureCemuHookSettings": "配置CemuHook体感",
|
||||
"ControllerSettingsConfigureGeneral": "配置",
|
||||
"ControllerSettingsRumble": "震动",
|
||||
"ControllerSettingsRumbleEnable": "启用震动",
|
||||
"ControllerSettingsRumbleStrongMultiplier": "强震动调节",
|
||||
"ControllerSettingsRumbleWeakMultiplier": "弱震动调节",
|
||||
"DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档",
|
||||
@ -302,9 +299,9 @@
|
||||
"DialogErrorTitle": "Ryujinx - 错误",
|
||||
"DialogWarningTitle": "Ryujinx - 警告",
|
||||
"DialogExitTitle": "Ryujinx - 关闭",
|
||||
"DialogErrorMessage": "Ryujinx遇到了错误",
|
||||
"DialogExitMessage": "是否关闭Ryujinx?",
|
||||
"DialogExitSubMessage": "所有未保存的进度会丢失!",
|
||||
"DialogErrorMessage": "Ryujinx 遇到了错误",
|
||||
"DialogExitMessage": "是否关闭 Ryujinx?",
|
||||
"DialogExitSubMessage": "未保存的进度会丢失",
|
||||
"DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错: {0}",
|
||||
"DialogMessageFindSaveErrorMessage": "查找特定的存档时出错: {0}",
|
||||
"FolderDialogExtractTitle": "选择要解压到的文件夹",
|
||||
@ -315,9 +312,9 @@
|
||||
"DialogNcaExtractionSuccessMessage": "提取成功。",
|
||||
"DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。",
|
||||
"DialogUpdaterCancelUpdateMessage": "更新取消!",
|
||||
"DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的Ryujinx是最新版本。",
|
||||
"DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。可能由于 GitHub Actions 正在编译新版本。请过几分钟重试。",
|
||||
"DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本转换。",
|
||||
"DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。",
|
||||
"DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\\n可能由于 GitHub Actions 正在编译新版本。请过几分钟重试。",
|
||||
"DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。",
|
||||
"DialogUpdaterDownloadingMessage": "下载新版本中...",
|
||||
"DialogUpdaterExtractionMessage": "正在提取更新...",
|
||||
"DialogUpdaterRenamingMessage": "正在删除旧文件...",
|
||||
@ -328,7 +325,7 @@
|
||||
"DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)",
|
||||
"DialogUpdaterNoInternetMessage": "没有连接到互联网",
|
||||
"DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。",
|
||||
"DialogUpdaterDirtyBuildMessage": "不能更新第三方版本的 Ryujinx!",
|
||||
"DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!",
|
||||
"DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。",
|
||||
"DialogRestartRequiredMessage": "需要重启模拟器",
|
||||
"DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。",
|
||||
@ -344,7 +341,7 @@
|
||||
"DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错: {0}",
|
||||
"DialogUserErrorDialogMessage": "{0}: {1}",
|
||||
"DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。",
|
||||
"DialogUserErrorDialogTitle": "Ryujinx错误 ({0})",
|
||||
"DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})",
|
||||
"DialogAmiiboApiTitle": "Amiibo API",
|
||||
"DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。",
|
||||
"DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有网络连接。",
|
||||
@ -357,7 +354,7 @@
|
||||
"DialogPPTCDeletionErrorMessage": "清除位于{0}的 PPTC 缓存时出错: {1}",
|
||||
"DialogShaderDeletionMessage": "您即将删除:\n\n{0}的着色器缓存\n\n确定吗?",
|
||||
"DialogShaderDeletionErrorMessage": "清除位于{0}的着色器缓存时出错: {1}",
|
||||
"DialogRyujinxErrorMessage": "Ryujinx遇到错误",
|
||||
"DialogRyujinxErrorMessage": "Ryujinx 遇到错误",
|
||||
"DialogInvalidTitleIdErrorMessage": "UI 错误:所选游戏没有有效的标题ID",
|
||||
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径{0}找不到有效的系统固件。",
|
||||
"DialogFirmwareInstallerFirmwareInstallTitle": "安装固件{0}",
|
||||
@ -376,11 +373,11 @@
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?",
|
||||
"DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。",
|
||||
"DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?",
|
||||
"DialogLoadAppGameAlreadyLoadedMessage": "当前已加载有游戏",
|
||||
"DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行",
|
||||
"DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。",
|
||||
"DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!",
|
||||
"DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程",
|
||||
"DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。根据您的硬件,您开启该选项时,可能需要手动禁用驱动程序本身的GL多线程。",
|
||||
"DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\\n根据您的硬件,您开启该选项时,可能需要手动禁用驱动程序本身的GL多线程。",
|
||||
"SettingsTabGraphicsFeaturesOptions": "功能",
|
||||
"SettingsTabGraphicsBackendMultithreading": "后端多线程:",
|
||||
"CommonAuto": "自动(推荐)",
|
||||
@ -391,23 +388,23 @@
|
||||
"DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。",
|
||||
"MenuBarOptionsPauseEmulation": "暂停",
|
||||
"MenuBarOptionsResumeEmulation": "继续",
|
||||
"AboutUrlTooltipMessage": "在浏览器中打开Ryujinx的官网。",
|
||||
"AboutDisclaimerMessage": "Ryujinx以任何方式都与Nintendo™以及任何商业伙伴没有关联",
|
||||
"AboutAmiiboDisclaimerMessage": "我们的Amiibo模拟使用了\nAmiiboAPI (www.amiiboapi.com) ",
|
||||
"AboutPatreonUrlTooltipMessage": "在浏览器中打开Ryujinx的Patreon赞助页。",
|
||||
"AboutGithubUrlTooltipMessage": "在浏览器中打开Ryujinx的GitHub代码库。",
|
||||
"AboutDiscordUrlTooltipMessage": "在浏览器中打开Ryujinx的Discord邀请链接。",
|
||||
"AboutTwitterUrlTooltipMessage": "在浏览器中打开Ryujinx的Twitter主页。",
|
||||
"AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 的官网。",
|
||||
"AboutDisclaimerMessage": "Ryujinx 以任何方式与Nintendo™及其任何商业伙伴都没有关联",
|
||||
"AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ",
|
||||
"AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。",
|
||||
"AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。",
|
||||
"AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。",
|
||||
"AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。",
|
||||
"AboutRyujinxAboutTitle": "关于:",
|
||||
"AboutRyujinxAboutContent": "Ryujinx是Nintendo Switch™的模拟器.\n您可以在Patreon上支持Ryujinx。\n关注Twitter或者Discord可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来GitHub和Discord加入我们!",
|
||||
"AboutRyujinxAboutContent": "Ryujinx是一款Nintendo Switch™模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 和 Discord 加入我们!",
|
||||
"AboutRyujinxMaintainersTitle": "由以下作者维护:",
|
||||
"AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者的网页",
|
||||
"AboutRyujinxSupprtersTitle": "感谢Patreon的赞助者:",
|
||||
"AmiiboSeriesLabel": "Amiibo系列",
|
||||
"AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:",
|
||||
"AmiiboSeriesLabel": "Amiibo 系列",
|
||||
"AmiiboCharacterLabel": "角色",
|
||||
"AmiiboScanButtonLabel": "扫描",
|
||||
"AmiiboOptionsShowAllLabel": "显示所有 Amiibo",
|
||||
"AmiiboOptionsUsRandomTagLabel": "修正: 使用随机标记的Uuid",
|
||||
"AmiiboOptionsUsRandomTagLabel": "修正:使用随机标记的 Uuid",
|
||||
"DlcManagerTableHeadingEnabledLabel": "启用",
|
||||
"DlcManagerTableHeadingTitleIdLabel": "游戏ID",
|
||||
"DlcManagerTableHeadingContainerPathLabel": "文件夹路径",
|
||||
@ -421,59 +418,59 @@
|
||||
"OrderDescending": "从大到小",
|
||||
"SettingsTabGraphicsFeatures": "额外功能",
|
||||
"ErrorWindowTitle": "错误窗口",
|
||||
"ToggleDiscordTooltip": "启用或关闭Discord详细在线状态展示",
|
||||
"ToggleDiscordTooltip": "启用或关闭 Discord 详细在线状态展示",
|
||||
"AddGameDirBoxTooltip": "输入要添加的游戏目录",
|
||||
"AddGameDirTooltip": "添加游戏目录到列表中",
|
||||
"RemoveGameDirTooltip": "移除选中的目录",
|
||||
"CustomThemeCheckTooltip": "启用或关闭自定义主题",
|
||||
"CustomThemePathTooltip": "自定义主题的目录",
|
||||
"CustomThemeBrowseTooltip": "查找自定义主题",
|
||||
"DockModeToggleTooltip": "是否开启Swith的主机模式",
|
||||
"DirectKeyboardTooltip": "是否开启\"直连键盘访问(HID)支持\" (部分游戏可以使用您的键盘输入文字)",
|
||||
"DirectMouseTooltip": "是否开启\"直连鼠标访问(HID)支持\" (部分游戏可以使用您的鼠标导航)",
|
||||
"DockModeToggleTooltip": "是否开启 Switch 的主机模式",
|
||||
"DirectKeyboardTooltip": "是否开启\"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)",
|
||||
"DirectMouseTooltip": "是否开启\"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)",
|
||||
"RegionTooltip": "更改系统区域",
|
||||
"LanguageTooltip": "更改系统语言",
|
||||
"TimezoneTooltip": "更改系统时区",
|
||||
"TimeTooltip": "更改系统时钟",
|
||||
"VSyncToggleTooltip": "开启可以消除帧撕裂,关闭可以提高性能",
|
||||
"PptcToggleTooltip": "开启以后减少游戏启动时间",
|
||||
"VSyncToggleTooltip": "关闭后,部分使用动态帧率的游戏可以超过60Hz的刷新率",
|
||||
"PptcToggleTooltip": "开启以后减少游戏启动时间和卡顿",
|
||||
"FsIntegrityToggleTooltip": "是否检查游戏文件内容的完整性",
|
||||
"AudioBackendTooltip": "默认推荐SDL,但每种音频后端对各类游戏兼容性可能不同",
|
||||
"MemoryManagerTooltip": "改变Switch内存映射到电脑内存的方式,会影响CPU性能消耗",
|
||||
"AudioBackendTooltip": "默认推荐SDL,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端",
|
||||
"MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗",
|
||||
"MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢",
|
||||
"MemoryManagerHostTooltip": "直接映射内存页到电脑内存,JIT效率很高",
|
||||
"MemoryManagerUnsafeTooltip": "直接映射内存页,但是不检查内存溢出,JIT效率最高。Ryujinx可以访问任何位置的内存,所以相对不安全。此模式下只应运行您信任的游戏或软件(即官方游戏)",
|
||||
"DRamTooltip": "扩展模拟的Switch内存为6GB,某些高清纹理MOD或4K MOD需要此选项",
|
||||
"MemoryManagerHostTooltip": "直接映射内存页到电脑内存,JIT效率高",
|
||||
"MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,JIT效率最高。\nRyujinx可以访问任何位置的内存,因而相对不安全。此模式下只应运行您信任的游戏或软件(即官方游戏)",
|
||||
"DRamTooltip": "扩展模拟的 Switch 内存为6GB。\n某些高清纹理MOD或4K MOD需要此选项",
|
||||
"IgnoreMissingServicesTooltip": "忽略某些未实现的系统服务,少部分游戏需要此选项才能启动",
|
||||
"GraphicsBackendThreadingTooltip": "启用后端多线程",
|
||||
"GalThreadingTooltip": "使用模拟器自带的多线程调度,减少着色器编译的卡顿,并提高驱动程序的性能(尤其是缺失多线程的AMD)。NVIDIA用户需要重启模拟器才能禁用驱动本身的多线程,否则您需手动执行禁用获得最佳性能",
|
||||
"ShaderCacheToggleTooltip": "开启后缓存着色器到硬盘,减少画面卡顿",
|
||||
"GalThreadingTooltip": "使用模拟器自带的多线程调度,减少着色器编译的卡顿,并提高驱动程序的性能(尤其是缺失多线程的AMD)。\nNVIDIA用户需要重启模拟器才能禁用驱动本身的多线程,否则您需手动执行禁用获得最佳性能",
|
||||
"ShaderCacheToggleTooltip": "开启后缓存着色器到本地,减少游戏卡顿",
|
||||
"ResolutionScaleTooltip": "缩放渲染的分辨率",
|
||||
"ResolutionScaleEntryTooltip": "尽量使用如1.5的浮点倍数。非整数的倍率易引起错误",
|
||||
"AnisotropyTooltip": "各向异性过滤等级。能提高倾斜视角纹理的清晰度('自动'使用游戏默认指定的等级)",
|
||||
"AspectRatioTooltip": "模拟器渲染窗口的宽高比",
|
||||
"ResolutionScaleEntryTooltip": "尽量使用例如1.5的浮点倍数。非整数的倍率易引起 BUG",
|
||||
"AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认指定的等级)",
|
||||
"AspectRatioTooltip": "渲染窗口的宽高比",
|
||||
"ShaderDumpPathTooltip": "转储图形着色器的路径",
|
||||
"FileLogTooltip": "是否保存日志文件到硬盘",
|
||||
"StubLogTooltip": "记录stub消息",
|
||||
"InfoLogTooltip": "记录info消息",
|
||||
"WarnLogTooltip": "记录warning消息",
|
||||
"ErrorLogTooltip": "记录error消息",
|
||||
"TraceLogTooltip": "记录trace消息",
|
||||
"GuestLogTooltip": "记录guest消息",
|
||||
"StubLogTooltip": "记录Stub消息",
|
||||
"InfoLogTooltip": "记录Info消息",
|
||||
"WarnLogTooltip": "记录Warning消息",
|
||||
"ErrorLogTooltip": "记录Error消息",
|
||||
"TraceLogTooltip": "记录Trace消息",
|
||||
"GuestLogTooltip": "记录Guest消息",
|
||||
"FileAccessLogTooltip": "记录文件访问消息",
|
||||
"FSAccessLogModeTooltip": "记录FS访问消息,输出到控制台。可选的模式是0-3",
|
||||
"DeveloperOptionTooltip": "使用请谨慎",
|
||||
"OpenGlLogLevel": "需要打开适当的日志等级",
|
||||
"DebugLogTooltip": "记录debug消息",
|
||||
"LoadApplicationFileTooltip": "选择Switch格式的游戏并加载",
|
||||
"LoadApplicationFolderTooltip": "选择一个解包后格式的Switch游戏并加载",
|
||||
"OpenRyujinxFolderTooltip": "打开Ryujinx系统目录",
|
||||
"DebugLogTooltip": "记录Debug消息",
|
||||
"LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载",
|
||||
"LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载",
|
||||
"OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录",
|
||||
"OpenRyujinxLogsTooltip": "打开日志存放的目录",
|
||||
"ExitTooltip": "关闭Ryujinx",
|
||||
"ExitTooltip": "关闭 Ryujinx",
|
||||
"OpenSettingsTooltip": "打开设置窗口",
|
||||
"OpenProfileManagerTooltip": "打开用户账号管理器",
|
||||
"StopEmulationTooltip": "停止运行当前游戏并回到选择界面",
|
||||
"CheckUpdatesTooltip": "检查新版本Ryujinx",
|
||||
"OpenProfileManagerTooltip": "打开用户账户管理界面",
|
||||
"StopEmulationTooltip": "停止运行当前游戏并回到主界面",
|
||||
"CheckUpdatesTooltip": "检查 Ryujinx 新版本",
|
||||
"OpenAboutTooltip": "打开'关于'窗口",
|
||||
"GridSize": "网格尺寸",
|
||||
"GridSizeTooltip": "调整网格模式的大小",
|
||||
@ -482,7 +479,7 @@
|
||||
"SettingsTabSystemAudioVolume": "音量: ",
|
||||
"AudioVolumeTooltip": "调节音量",
|
||||
"SettingsTabSystemEnableInternetAccess": "启用网络连接",
|
||||
"EnableInternetAccessTooltip": "开启互联网访问。此选项打开后,效果类似于Switch连接到互联网的状态。注意即使此选项关闭,应用程序也偶尔有可能连接到网络",
|
||||
"EnableInternetAccessTooltip": "开启互联网访问。此选项打开后,效果类似于 Switch 连接到互联网的状态。\n注意即使此选项关闭,应用程序偶尔也有可能连接到网络",
|
||||
"GameListContextMenuManageCheatToolTip": "管理金手指",
|
||||
"GameListContextMenuManageCheat": "管理金手指",
|
||||
"ControllerSettingsStickRange": "范围",
|
||||
@ -496,8 +493,8 @@
|
||||
"SettingsTabCpuMemory": "CPU 内存",
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。",
|
||||
"UpdaterDisabledWarningTitle": "更新已禁用!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "打开Atmosphere MOD目录",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "打开包含应用程序MOD的其他Atmosphere SD卡目录",
|
||||
"GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "打开包含应用程序 MOD 的额外 Atmosphere SD 卡目录",
|
||||
"ControllerSettingsRotate90": "顺时针旋转 90°",
|
||||
"IconSize": "图标尺寸",
|
||||
"IconSizeTooltip": "更改游戏图标大小",
|
||||
@ -509,14 +506,14 @@
|
||||
"UserErrorApplicationNotFound": "找不到应用程序",
|
||||
"UserErrorUnknown": "未知错误",
|
||||
"UserErrorUndefined": "未定义错误",
|
||||
"UserErrorNoKeysDescription": "Ryujinx找不到 'prod.keys' 文件",
|
||||
"UserErrorNoFirmwareDescription": "Ryujinx找不到任何已安装的固件",
|
||||
"UserErrorFirmwareParsingFailedDescription": "Ryujinx无法解密选择的固件。通常是由于过旧的密钥。",
|
||||
"UserErrorApplicationNotFoundDescription": "Ryujinx在选中路径找不到有效的应用程序。",
|
||||
"UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件",
|
||||
"UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件",
|
||||
"UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于密钥过旧。",
|
||||
"UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。",
|
||||
"UserErrorUnknownDescription": "发生未知错误!",
|
||||
"UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!",
|
||||
"OpenSetupGuideMessage": "打开设置教程",
|
||||
"NoUpdate": "没有新版",
|
||||
"NoUpdate": "无更新",
|
||||
"TitleUpdateVersionLabel": "版本 {0} - {1}",
|
||||
"RyujinxInfo": "Ryujinx - 信息",
|
||||
"RyujinxConfirm": "Ryujinx - 确认",
|
||||
@ -527,7 +524,7 @@
|
||||
"SoftwareKeyboard": "软件键盘",
|
||||
"DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家()持有:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}请打开设置界面,配置手柄;或者关闭窗口。",
|
||||
"DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家()持有 with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}请打开设置界面,配置手柄;或者关闭窗口。",
|
||||
"DialogControllerAppletDockModeSet": "现在处于主机模式,无法使用掌机操作方式\n\n",
|
||||
"DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式\n\n",
|
||||
"UpdaterRenaming": "正在删除旧文件...",
|
||||
"UpdaterRenameFailed": "更新过程中无法重命名文件: {0}",
|
||||
"UpdaterAddingFiles": "安装更新中...",
|
||||
@ -537,14 +534,39 @@
|
||||
"Docked": "主机模式",
|
||||
"Handheld": "掌机模式",
|
||||
"ConnectionError": "连接错误。",
|
||||
"AboutPageDeveloperListMore": "{0} 以及等人...",
|
||||
"AboutPageDeveloperListMore": "{0} 等开发者...",
|
||||
"ApiError": "API错误。",
|
||||
"LoadingHeading": "正在加载 {0}",
|
||||
"CompilingPPTC": "编译PPTC中",
|
||||
"LoadingHeading": "正在启动 {0}",
|
||||
"CompilingPPTC": "编译PPTC缓存中",
|
||||
"CompilingShaders": "编译着色器中",
|
||||
"AllKeyboards": "所有键盘",
|
||||
"OpenFileDialogTitle": "选择支持的文件格式",
|
||||
"OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹",
|
||||
"AllSupportedFormats": "全部支持的格式",
|
||||
"RyujinxUpdater": "Ryujinx 更新程序"
|
||||
}
|
||||
"RyujinxUpdater": "Ryujinx 更新程序",
|
||||
"SettingsTabHotkeys": "快捷键",
|
||||
"SettingsTabHotkeysHotkeys": "键盘快捷键",
|
||||
"SettingsTabHotkeysToggleVsyncHotkey": "切换VSync",
|
||||
"SettingsTabHotkeysScreenshotHotkey": "截屏",
|
||||
"SettingsTabHotkeysShowUiHotkey": "隐藏UI",
|
||||
"SettingsTabHotkeysPauseHotkey": "暂停",
|
||||
"SettingsTabHotkeysToggleMuteHotkey": "静音",
|
||||
"ControllerMotionTitle": "体感操作设置",
|
||||
"ControllerRumbleTitle": "震动设置",
|
||||
"SettingsSelectThemeFileDialogTitle": "选择主题文件",
|
||||
"SettingsXamlThemeFile": "Xaml 主题文件",
|
||||
"AvatarWindowTitle": "管理账户 - 头像",
|
||||
"Amiibo": "Amiibo",
|
||||
"Unknown": "未知",
|
||||
"Usage": "使用",
|
||||
"Writable": "可写入",
|
||||
"SelectDlcDialogTitle": "选择 DLC 文件",
|
||||
"SelectUpdateDialogTitle": "选择更新文件",
|
||||
"UserProfileWindowTitle": "管理用户账户",
|
||||
"CheatWindowTitle": "管理游戏金手指",
|
||||
"DlcWindowTitle": "管理游戏 DLC",
|
||||
"UpdateWindowTitle": "管理游戏更新",
|
||||
"CheatWindowHeading": "适用于 {0} [{1}] 的金手指",
|
||||
"DlcWindowHeading": "适用于 {0} [{1}] 的 DLC",
|
||||
"GameUpdateWindowHeading": "适用于 {0} [{1}] 的更新"
|
||||
}
|
||||
|
@ -54,5 +54,6 @@
|
||||
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
|
||||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@ -49,5 +49,6 @@
|
||||
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
|
||||
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
@ -221,6 +221,7 @@
|
||||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
|
||||
</Style>
|
||||
<Styles.Resources>
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
|
||||
@ -232,7 +233,7 @@
|
||||
Color="{DynamicResource SystemBaseMediumLowColor}" />
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
|
||||
|
@ -23,6 +23,7 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
@ -77,8 +78,12 @@ namespace Ryujinx.Ava.Common
|
||||
|
||||
if (result.IsFailure())
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName()));
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(
|
||||
_owner,
|
||||
string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName()));
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -94,8 +99,11 @@ namespace Ryujinx.Ava.Common
|
||||
return true;
|
||||
}
|
||||
|
||||
ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -137,7 +145,7 @@ namespace Ryujinx.Ava.Common
|
||||
}
|
||||
}
|
||||
|
||||
public static async void ExtractSection(NcaSectionType ncaSectionType, string titleFilePath,
|
||||
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath,
|
||||
int programIndex = 0)
|
||||
{
|
||||
OpenFolderDialog folderDialog = new() { Title = LocaleManager.Instance["FolderDialogExtractTitle"] };
|
||||
@ -222,9 +230,9 @@ namespace Ryujinx.Ava.Common
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
"Extraction failure. The main NCA was not present in the selected file");
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -263,9 +271,9 @@ namespace Ryujinx.Ava.Common
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"LibHac returned error code: {resultCode.Value.ErrorCode}");
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]);
|
||||
});
|
||||
}
|
||||
else if (resultCode.Value.IsSuccess())
|
||||
@ -288,9 +296,9 @@ namespace Ryujinx.Ava.Common
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -73,8 +73,11 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
catch
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -106,7 +109,10 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
@ -121,7 +127,10 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
@ -131,7 +140,10 @@ namespace Ryujinx.Modules
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
||||
ContentDialogHelper.CreateErrorDialog(mainWindow, LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(mainWindow, LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -142,8 +154,11 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
catch
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -152,7 +167,10 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
});
|
||||
}
|
||||
|
||||
Running = false;
|
||||
@ -180,10 +198,12 @@ namespace Ryujinx.Modules
|
||||
_buildSize = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Show a message asking the user if they want to update
|
||||
UpdaterWindow updateDialog = new(mainWindow, newVersion, _buildUrl);
|
||||
await updateDialog.ShowDialog(mainWindow);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
// Show a message asking the user if they want to update
|
||||
UpdaterWindow updateDialog = new(mainWindow, newVersion, _buildUrl);
|
||||
await updateDialog.ShowDialog(mainWindow);
|
||||
});
|
||||
}
|
||||
|
||||
private static HttpClient ConstructHttpClient()
|
||||
@ -522,6 +542,7 @@ namespace Ryujinx.Modules
|
||||
updateDialog.ButtonBox.IsVisible = true;
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
public static bool CanUpdate(bool showWarnings, StyleableWindow parent)
|
||||
{
|
||||
#if !DISABLE_UPDATER
|
||||
@ -577,6 +598,7 @@ namespace Ryujinx.Modules
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
// NOTE: This method should always reflect the latest build layout.s
|
||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||
|
@ -5,8 +5,6 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<RootNamespace>Ryujinx.Ava</RootNamespace>
|
||||
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
||||
|
@ -92,7 +92,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
|
||||
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
|
||||
|
||||
dialogCloseEvent.Set();
|
||||
}
|
||||
@ -126,7 +126,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = true;
|
||||
ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex));
|
||||
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -181,7 +181,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||
catch (Exception ex)
|
||||
{
|
||||
dialogCloseEvent.Set();
|
||||
ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex));
|
||||
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -235,7 +235,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
return new(mainText, secondaryText);
|
||||
}
|
||||
|
||||
internal static async void CreateUpdaterInfoDialog(StyleableWindow window, string primary, string secondaryText)
|
||||
internal static async Task CreateUpdaterInfoDialog(StyleableWindow window, string primary, string secondaryText)
|
||||
{
|
||||
await ShowContentDialog(
|
||||
window,
|
||||
@ -248,7 +248,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
(int)Symbol.Important);
|
||||
}
|
||||
|
||||
internal static async void ShowNotAvailableMessage(StyleableWindow window)
|
||||
internal static async Task ShowNotAvailableMessage(StyleableWindow window)
|
||||
{
|
||||
// Temporary placeholder for features to be added
|
||||
await ShowContentDialog(
|
||||
@ -262,7 +262,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
(int)Symbol.Important);
|
||||
}
|
||||
|
||||
internal static async void CreateWarningDialog(StyleableWindow window, string primary, string secondaryText)
|
||||
internal static async Task CreateWarningDialog(StyleableWindow window, string primary, string secondaryText)
|
||||
{
|
||||
await ShowContentDialog(
|
||||
window,
|
||||
@ -275,7 +275,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||
(int)Symbol.Important);
|
||||
}
|
||||
|
||||
internal static async void CreateErrorDialog(StyleableWindow owner, string errorMessage, string secondaryErrorMessage = "")
|
||||
internal static async Task CreateErrorDialog(StyleableWindow owner, string errorMessage, string secondaryErrorMessage = "")
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
|
||||
|
35
Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml
Normal file
35
Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml
Normal file
@ -0,0 +1,35 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Title="{Locale:Locale ProfileImageSelectionTitle}"
|
||||
CanResize="false">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="70" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Grid.Row="1"
|
||||
Text="{Locale:Locale ProfileImageSelectionHeader}" />
|
||||
<TextBlock FontWeight="Bold" Grid.Row="2" Margin="10" MaxWidth="400" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center" TextAlignment="Center" Text="{Locale:Locale ProfileImageSelectionNote}" />
|
||||
<StackPanel Margin="5,0" Spacing="10" Grid.Row="4" HorizontalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button Name="Import" Click="Import_OnClick" Width="200">
|
||||
<TextBlock Text="{Locale:Locale ProfileImageSelectionImportImage}" />
|
||||
</Button>
|
||||
<Button Name="SelectFirmwareImage" IsEnabled="{Binding FirmwareFound}" Click="SelectFirmwareImage_OnClick"
|
||||
Width="200">
|
||||
<TextBlock Text="{Locale:Locale ProfileImageSelectionSelectAvatar}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
105
Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs
Normal file
105
Ryujinx.Ava/Ui/Controls/ProfileImageSelectionDialog.axaml.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.IO;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Controls
|
||||
{
|
||||
public class ProfileImageSelectionDialog : StyleableWindow
|
||||
{
|
||||
private readonly ContentManager _contentManager;
|
||||
|
||||
public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null;
|
||||
|
||||
public byte[] BufferImageProfile { get; set; }
|
||||
|
||||
public ProfileImageSelectionDialog(ContentManager contentManager)
|
||||
{
|
||||
_contentManager = contentManager;
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
public ProfileImageSelectionDialog()
|
||||
{
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private async void Import_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog dialog = new();
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = LocaleManager.Instance["AllSupportedFormats"],
|
||||
Extensions = { "jpg", "jpeg", "png", "bmp" }
|
||||
});
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "JPEG", Extensions = { "jpg", "jpeg" } });
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "PNG", Extensions = { "png" } });
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "BMP", Extensions = { "bmp" } });
|
||||
|
||||
dialog.AllowMultiple = false;
|
||||
|
||||
string[] image = await dialog.ShowAsync(this);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
if (image.Length > 0)
|
||||
{
|
||||
string imageFile = image[0];
|
||||
|
||||
ProcessProfileImage(File.ReadAllBytes(imageFile));
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (FirmwareFound)
|
||||
{
|
||||
AvatarWindow window = new(_contentManager);
|
||||
|
||||
await window.ShowDialog(this);
|
||||
|
||||
BufferImageProfile = window.SelectedImage;
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessProfileImage(byte[] buffer)
|
||||
{
|
||||
using (Image image = Image.Load(buffer))
|
||||
{
|
||||
image.Mutate(x => x.Resize(256, 256));
|
||||
|
||||
using (MemoryStream streamJpg = new())
|
||||
{
|
||||
image.SaveAsJpeg(streamJpg);
|
||||
|
||||
BufferImageProfile = streamJpg.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Ryujinx.Ava/Ui/Models/Amiibo.cs
Normal file
72
Ryujinx.Ava/Ui/Models/Amiibo.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class Amiibo
|
||||
{
|
||||
public struct AmiiboJson
|
||||
{
|
||||
[JsonPropertyName("amiibo")] public List<AmiiboApi> Amiibo { get; set; }
|
||||
[JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
|
||||
public struct AmiiboApi
|
||||
{
|
||||
[JsonPropertyName("name")] public string Name { get; set; }
|
||||
[JsonPropertyName("head")] public string Head { get; set; }
|
||||
[JsonPropertyName("tail")] public string Tail { get; set; }
|
||||
[JsonPropertyName("image")] public string Image { get; set; }
|
||||
[JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; }
|
||||
[JsonPropertyName("character")] public string Character { get; set; }
|
||||
[JsonPropertyName("gameSeries")] public string GameSeries { get; set; }
|
||||
[JsonPropertyName("type")] public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("release")] public Dictionary<string, string> Release { get; set; }
|
||||
|
||||
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public string GetId()
|
||||
{
|
||||
return Head + Tail;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is AmiiboApi amiibo)
|
||||
{
|
||||
return amiibo.Head + amiibo.Tail == Head + Tail;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class AmiiboApiGamesSwitch
|
||||
{
|
||||
[JsonPropertyName("amiiboUsage")] public List<AmiiboApiUsage> AmiiboUsage { get; set; }
|
||||
|
||||
[JsonPropertyName("gameID")] public List<string> GameId { get; set; }
|
||||
|
||||
[JsonPropertyName("gameName")] public string GameName { get; set; }
|
||||
}
|
||||
|
||||
public class AmiiboApiUsage
|
||||
{
|
||||
[JsonPropertyName("Usage")] public string Usage { get; set; }
|
||||
|
||||
[JsonPropertyName("write")] public bool Write { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
37
Ryujinx.Ava/Ui/Models/CheatModel.cs
Normal file
37
Ryujinx.Ava/Ui/Models/CheatModel.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class CheatModel : BaseModel
|
||||
{
|
||||
private bool _isEnabled;
|
||||
|
||||
public event EventHandler<bool> EnableToggled;
|
||||
|
||||
public CheatModel(string name, string buildId, bool isEnabled)
|
||||
{
|
||||
Name = name;
|
||||
BuildId = buildId;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
_isEnabled = value;
|
||||
EnableToggled?.Invoke(this, _isEnabled);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string BuildId { get; }
|
||||
|
||||
public string BuildIdKey => $"{BuildId}-{Name}";
|
||||
public string Name { get; }
|
||||
|
||||
public string CleanName => Name.Substring(1, Name.Length - 8);
|
||||
}
|
||||
}
|
51
Ryujinx.Ava/Ui/Models/CheatsList.cs
Normal file
51
Ryujinx.Ava/Ui/Models/CheatsList.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class CheatsList : ObservableCollection<CheatModel>
|
||||
{
|
||||
public CheatsList(string buildId, string path)
|
||||
{
|
||||
BuildId = buildId;
|
||||
Path = path;
|
||||
CollectionChanged += CheatsList_CollectionChanged;
|
||||
}
|
||||
|
||||
private void CheatsList_CollectionChanged(object sender,
|
||||
NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
(e.NewItems[0] as CheatModel).EnableToggled += Item_EnableToggled;
|
||||
}
|
||||
}
|
||||
|
||||
private void Item_EnableToggled(object sender, bool e)
|
||||
{
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled)));
|
||||
}
|
||||
|
||||
public string BuildId { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ToList().TrueForAll(x => x.IsEnabled);
|
||||
}
|
||||
set
|
||||
{
|
||||
foreach (var cheat in this)
|
||||
{
|
||||
cheat.IsEnabled = value;
|
||||
}
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
Ryujinx.Ava/Ui/Models/DlcModel.cs
Normal file
18
Ryujinx.Ava/Ui/Models/DlcModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class DlcModel
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
public string TitleId { get; }
|
||||
public string ContainerPath { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
public DlcModel(string titleId, string containerPath, string fullPath, bool isEnabled)
|
||||
{
|
||||
TitleId = titleId;
|
||||
ContainerPath = containerPath;
|
||||
FullPath = fullPath;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
internal class ProfileImageModel
|
||||
public class ProfileImageModel
|
||||
{
|
||||
public ProfileImageModel(string name, byte[] data)
|
||||
{
|
||||
|
@ -9,8 +9,11 @@ namespace Ryujinx.Ava.Ui.Models
|
||||
public bool IsNoUpdate { get; }
|
||||
public ApplicationControlProperty Control { get; }
|
||||
public string Path { get; }
|
||||
public string Label => IsNoUpdate ? LocaleManager.Instance["NoUpdate"] :
|
||||
string.Format(LocaleManager.Instance["TitleUpdateVersionLabel"], Control.DisplayVersionString.ToString(), Path);
|
||||
|
||||
public string Label => IsNoUpdate
|
||||
? LocaleManager.Instance["NoUpdate"]
|
||||
: string.Format(LocaleManager.Instance["TitleUpdateVersionLabel"], Control.DisplayVersionString.ToString(),
|
||||
Path);
|
||||
|
||||
public TitleUpdateModel(ApplicationControlProperty control, string path, bool isNoUpdate = false)
|
||||
{
|
||||
|
61
Ryujinx.Ava/Ui/Models/UserProfile.cs
Normal file
61
Ryujinx.Ava/Ui/Models/UserProfile.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Models
|
||||
{
|
||||
public class UserProfile : BaseModel
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private byte[] _image;
|
||||
private string _name;
|
||||
private UserId _userId;
|
||||
|
||||
public byte[] Image
|
||||
{
|
||||
get => _image;
|
||||
set
|
||||
{
|
||||
_image = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public UserId UserId
|
||||
{
|
||||
get => _userId;
|
||||
set
|
||||
{
|
||||
_userId = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public UserProfile(Profile profile)
|
||||
{
|
||||
_profile = profile;
|
||||
|
||||
Image = profile.Image;
|
||||
Name = profile.Name;
|
||||
UserId = profile.UserId;
|
||||
}
|
||||
|
||||
public bool IsOpened => _profile.AccountState == AccountState.Open;
|
||||
|
||||
public void UpdateState()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsOpened));
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
}
|
450
Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs
Normal file
450
Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs
Normal file
@ -0,0 +1,450 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
public class AmiiboWindowViewModel : BaseModel, IDisposable
|
||||
{
|
||||
private const string DefaultJson = "{ \"amiibo\": [] }";
|
||||
private const float AmiiboImageSize = 350f;
|
||||
|
||||
private readonly string _amiiboJsonPath;
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly StyleableWindow _owner;
|
||||
|
||||
private Bitmap _amiiboImage;
|
||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
|
||||
private int _amiiboSelectedIndex;
|
||||
private int _seriesSelectedIndex;
|
||||
private bool _enableScanning;
|
||||
private bool _showAllAmiibo;
|
||||
private bool _useRandomUuid;
|
||||
private string _usage;
|
||||
|
||||
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
||||
{
|
||||
_owner = owner;
|
||||
_httpClient = new HttpClient { Timeout = TimeSpan.FromMilliseconds(5000) };
|
||||
LastScannedAmiiboId = lastScannedAmiiboId;
|
||||
TitleId = titleId;
|
||||
|
||||
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||
|
||||
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
||||
_amiiboList = new List<Amiibo.AmiiboApi>();
|
||||
_amiiboSeries = new ObservableCollection<string>();
|
||||
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
|
||||
|
||||
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
||||
|
||||
_ = LoadContentAsync();
|
||||
}
|
||||
|
||||
public AmiiboWindowViewModel() { }
|
||||
|
||||
public string TitleId { get; set; }
|
||||
public string LastScannedAmiiboId { get; set; }
|
||||
|
||||
public UserResult Response { get; private set; }
|
||||
|
||||
public bool UseRandomUuid
|
||||
{
|
||||
get => _useRandomUuid;
|
||||
set
|
||||
{
|
||||
_useRandomUuid = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowAllAmiibo
|
||||
{
|
||||
get => _showAllAmiibo;
|
||||
set
|
||||
{
|
||||
_showAllAmiibo = value;
|
||||
|
||||
#pragma warning disable 4014
|
||||
ParseAmiiboData();
|
||||
#pragma warning restore 4014
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<Amiibo.AmiiboApi> AmiiboList
|
||||
{
|
||||
get => _amiibos;
|
||||
set
|
||||
{
|
||||
_amiibos = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<string> AmiiboSeries
|
||||
{
|
||||
get => _amiiboSeries;
|
||||
set
|
||||
{
|
||||
_amiiboSeries = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int SeriesSelectedIndex
|
||||
{
|
||||
get => _seriesSelectedIndex;
|
||||
set
|
||||
{
|
||||
_seriesSelectedIndex = value;
|
||||
|
||||
FilterAmiibo();
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int AmiiboSelectedIndex
|
||||
{
|
||||
get => _amiiboSelectedIndex;
|
||||
set
|
||||
{
|
||||
_amiiboSelectedIndex = value;
|
||||
|
||||
EnableScanning = _amiiboSelectedIndex >= 0 && _amiiboSelectedIndex < _amiibos.Count;
|
||||
|
||||
SetAmiiboDetails();
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap AmiiboImage
|
||||
{
|
||||
get => _amiiboImage;
|
||||
set
|
||||
{
|
||||
_amiiboImage = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Usage
|
||||
{
|
||||
get => _usage;
|
||||
set
|
||||
{
|
||||
_usage = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableScanning
|
||||
{
|
||||
get => _enableScanning;
|
||||
set
|
||||
{
|
||||
_enableScanning = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
|
||||
private async Task LoadContentAsync()
|
||||
{
|
||||
string amiiboJsonString = DefaultJson;
|
||||
|
||||
if (File.Exists(_amiiboJsonPath))
|
||||
{
|
||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
||||
|
||||
if (await NeedsUpdate(JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
|
||||
{
|
||||
amiiboJsonString = await DownloadAmiiboJson();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
amiiboJsonString = await DownloadAmiiboJson();
|
||||
}
|
||||
catch
|
||||
{
|
||||
ShowInfoDialog();
|
||||
}
|
||||
}
|
||||
|
||||
_amiiboList = JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
|
||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||
|
||||
ParseAmiiboData();
|
||||
}
|
||||
|
||||
private void ParseAmiiboData()
|
||||
{
|
||||
_amiiboSeries.Clear();
|
||||
_amiibos.Clear();
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
{
|
||||
if (!_amiiboSeries.Contains(_amiiboList[i].AmiiboSeries))
|
||||
{
|
||||
if (!ShowAllAmiibo)
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
if (game.GameId.Contains(TitleId))
|
||||
{
|
||||
AmiiboSeries.Add(_amiiboList[i].AmiiboSeries);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AmiiboSeries.Add(_amiiboList[i].AmiiboSeries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LastScannedAmiiboId != "")
|
||||
{
|
||||
SelectLastScannedAmiibo();
|
||||
}
|
||||
else
|
||||
{
|
||||
SeriesSelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectLastScannedAmiibo()
|
||||
{
|
||||
Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
||||
|
||||
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||
}
|
||||
|
||||
private void FilterAmiibo()
|
||||
{
|
||||
_amiibos.Clear();
|
||||
|
||||
if (_seriesSelectedIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
|
||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||
|
||||
for (int i = 0; i < amiiboSortedList.Count; i++)
|
||||
{
|
||||
if (!_amiibos.Contains(amiiboSortedList[i]))
|
||||
{
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
if (game.GameId.Contains(TitleId))
|
||||
{
|
||||
_amiibos.Add(amiiboSortedList[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_amiibos.Add(amiiboSortedList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AmiiboSelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void SetAmiiboDetails()
|
||||
{
|
||||
ResetAmiiboPreview();
|
||||
|
||||
Usage = string.Empty;
|
||||
|
||||
if (_amiiboSelectedIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
|
||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||
|
||||
string usageString = "";
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
{
|
||||
if (_amiiboList[i].Equals(selected))
|
||||
{
|
||||
bool writable = false;
|
||||
|
||||
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (item.GameId.Contains(TitleId))
|
||||
{
|
||||
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
{
|
||||
usageString += Environment.NewLine +
|
||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||
|
||||
writable = usageItem.Write;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (usageString.Length == 0)
|
||||
{
|
||||
usageString = LocaleManager.Instance["Unknown"] + ".";
|
||||
}
|
||||
|
||||
Usage = $"{LocaleManager.Instance["Usage"]} {(writable ? $" ({LocaleManager.Instance["Writable"]})" : "")} : {usageString}";
|
||||
}
|
||||
}
|
||||
|
||||
_ = UpdateAmiiboPreview(imageUrl);
|
||||
}
|
||||
|
||||
private async Task<bool> NeedsUpdate(DateTime oldLastModified)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response =
|
||||
await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return response.Content.Headers.LastModified != oldLastModified;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ShowInfoDialog();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> DownloadAmiiboJson()
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
string amiiboJsonString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
using (FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
|
||||
}
|
||||
|
||||
return amiiboJsonString;
|
||||
}
|
||||
|
||||
await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"],
|
||||
LocaleManager.Instance["DialogAmiiboApiFailFetchMessage"],
|
||||
LocaleManager.Instance["InputDialogOk"],
|
||||
"",
|
||||
LocaleManager.Instance["RyujinxInfo"]);
|
||||
|
||||
Close();
|
||||
|
||||
return DefaultJson;
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
Dispatcher.UIThread.Post(_owner.Close);
|
||||
}
|
||||
|
||||
private async Task UpdateAmiiboPreview(string imageUrl)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(imageUrl);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
using (MemoryStream memoryStream = new(amiiboPreviewBytes))
|
||||
{
|
||||
Bitmap bitmap = new(memoryStream);
|
||||
|
||||
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
|
||||
AmiiboImageSize / bitmap.Size.Height);
|
||||
|
||||
int resizeHeight = (int)(bitmap.Size.Height * ratio);
|
||||
int resizeWidth = (int)(bitmap.Size.Width * ratio);
|
||||
|
||||
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetAmiiboPreview()
|
||||
{
|
||||
using (MemoryStream memoryStream = new(_amiiboLogoBytes))
|
||||
{
|
||||
Bitmap bitmap = new(memoryStream);
|
||||
|
||||
AmiiboImage = bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ShowInfoDialog()
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"],
|
||||
LocaleManager.Instance["DialogAmiiboApiConnectErrorMessage"],
|
||||
LocaleManager.Instance["InputDialogOk"],
|
||||
"",
|
||||
LocaleManager.Instance["RyujinxInfo"]);
|
||||
}
|
||||
}
|
||||
}
|
363
Ryujinx.Ava/Ui/ViewModels/AvatarProfileViewModel.cs
Normal file
363
Ryujinx.Ava/Ui/ViewModels/AvatarProfileViewModel.cs
Normal file
@ -0,0 +1,363 @@
|
||||
using Avalonia.Media;
|
||||
using DynamicData;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Color = Avalonia.Media.Color;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
internal class AvatarProfileViewModel : BaseModel, IDisposable
|
||||
{
|
||||
private const int MaxImageTasks = 4;
|
||||
|
||||
private static readonly Dictionary<string, byte[]> _avatarStore = new();
|
||||
private static bool _isPreloading;
|
||||
private static Action _loadCompleteAction;
|
||||
|
||||
private ObservableCollection<ProfileImageModel> _images;
|
||||
private Color _backgroundColor = Colors.White;
|
||||
|
||||
private int _selectedIndex;
|
||||
private int _imagesLoaded;
|
||||
private bool _isActive;
|
||||
private byte[] _selectedImage;
|
||||
private bool _isIndeterminate = true;
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get => _isActive;
|
||||
set => _isActive = value;
|
||||
}
|
||||
|
||||
public AvatarProfileViewModel()
|
||||
{
|
||||
_images = new ObservableCollection<ProfileImageModel>();
|
||||
}
|
||||
|
||||
public AvatarProfileViewModel(Action loadCompleteAction)
|
||||
{
|
||||
_images = new ObservableCollection<ProfileImageModel>();
|
||||
|
||||
if (_isPreloading)
|
||||
{
|
||||
_loadCompleteAction = loadCompleteAction;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReloadImages();
|
||||
}
|
||||
}
|
||||
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get => _backgroundColor;
|
||||
set
|
||||
{
|
||||
_backgroundColor = value;
|
||||
|
||||
IsActive = false;
|
||||
|
||||
ReloadImages();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileImageModel> Images
|
||||
{
|
||||
get => _images;
|
||||
set
|
||||
{
|
||||
_images = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsIndeterminate
|
||||
{
|
||||
get => _isIndeterminate;
|
||||
set
|
||||
{
|
||||
_isIndeterminate = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int ImageCount => _avatarStore.Count;
|
||||
|
||||
public int ImagesLoaded
|
||||
{
|
||||
get => _imagesLoaded;
|
||||
set
|
||||
{
|
||||
_imagesLoaded = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get => _selectedIndex;
|
||||
set
|
||||
{
|
||||
_selectedIndex = value;
|
||||
|
||||
if (_selectedIndex == -1)
|
||||
{
|
||||
SelectedImage = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedImage = _images[_selectedIndex].Data;
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] SelectedImage
|
||||
{
|
||||
get => _selectedImage;
|
||||
private set => _selectedImage = value;
|
||||
}
|
||||
|
||||
public void ReloadImages()
|
||||
{
|
||||
if (_isPreloading)
|
||||
{
|
||||
IsIndeterminate = false;
|
||||
return;
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
Images.Clear();
|
||||
int selectedIndex = _selectedIndex;
|
||||
int index = 0;
|
||||
|
||||
ImagesLoaded = 0;
|
||||
IsIndeterminate = false;
|
||||
|
||||
var keys = _avatarStore.Keys.ToList();
|
||||
|
||||
var newImages = new List<ProfileImageModel>();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
for (int i = 0; i < MaxImageTasks; i++)
|
||||
{
|
||||
var start = i;
|
||||
tasks.Add(Task.Run(() => ImageTask(start)));
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
Images.AddRange(newImages);
|
||||
|
||||
void ImageTask(int start)
|
||||
{
|
||||
for (int i = start; i < keys.Count; i += MaxImageTasks)
|
||||
{
|
||||
if (!IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = keys[i];
|
||||
var image = _avatarStore[keys[i]];
|
||||
|
||||
var data = ProcessImage(image);
|
||||
newImages.Add(new ProfileImageModel(key, data));
|
||||
if (index++ == selectedIndex)
|
||||
{
|
||||
SelectedImage = data;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _imagesLoaded);
|
||||
OnPropertyChanged(nameof(ImagesLoaded));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private byte[] ProcessImage(byte[] data)
|
||||
{
|
||||
using (MemoryStream streamJpg = new())
|
||||
{
|
||||
Image avatarImage = Image.Load(data, new PngDecoder());
|
||||
|
||||
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(BackgroundColor.R,
|
||||
BackgroundColor.G,
|
||||
BackgroundColor.B,
|
||||
BackgroundColor.A)));
|
||||
avatarImage.SaveAsJpeg(streamJpg);
|
||||
|
||||
return streamJpg.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_avatarStore.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isPreloading = true;
|
||||
|
||||
string contentPath =
|
||||
contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem,
|
||||
NcaContentType.Data);
|
||||
string avatarPath = virtualFileSystem.SwitchPathToSystemPath(contentPath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(avatarPath))
|
||||
{
|
||||
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
|
||||
{
|
||||
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
||||
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
|
||||
foreach (DirectoryEntryEx item in romfs.EnumerateEntries())
|
||||
{
|
||||
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
|
||||
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") &&
|
||||
item.FullPath.Contains("szs"))
|
||||
{
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
||||
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
|
||||
.ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new())
|
||||
using (MemoryStream streamPng = new())
|
||||
{
|
||||
file.Get.AsStream().CopyTo(stream);
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
|
||||
|
||||
avatarImage.SaveAsPng(streamPng);
|
||||
|
||||
_avatarStore.Add(item.FullPath, streamPng.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPreloading = false;
|
||||
_loadCompleteAction?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] DecompressYaz0(Stream stream)
|
||||
{
|
||||
using (BinaryReader reader = new(stream))
|
||||
{
|
||||
reader.ReadInt32(); // Magic
|
||||
|
||||
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
|
||||
|
||||
reader.ReadInt64(); // Padding
|
||||
|
||||
byte[] input = new byte[stream.Length - stream.Position];
|
||||
stream.Read(input, 0, input.Length);
|
||||
|
||||
uint inputOffset = 0;
|
||||
|
||||
byte[] output = new byte[decodedLength];
|
||||
uint outputOffset = 0;
|
||||
|
||||
ushort mask = 0;
|
||||
byte header = 0;
|
||||
|
||||
while (outputOffset < decodedLength)
|
||||
{
|
||||
if ((mask >>= 1) == 0)
|
||||
{
|
||||
header = input[inputOffset++];
|
||||
mask = 0x80;
|
||||
}
|
||||
|
||||
if ((header & mask) != 0)
|
||||
{
|
||||
if (outputOffset == output.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
output[outputOffset++] = input[inputOffset++];
|
||||
}
|
||||
else
|
||||
{
|
||||
byte byte1 = input[inputOffset++];
|
||||
byte byte2 = input[inputOffset++];
|
||||
|
||||
uint dist = (uint)((byte1 & 0xF) << 8) | byte2;
|
||||
uint position = outputOffset - (dist + 1);
|
||||
|
||||
uint length = (uint)byte1 >> 4;
|
||||
if (length == 0)
|
||||
{
|
||||
length = (uint)input[inputOffset++] + 0x12;
|
||||
}
|
||||
else
|
||||
{
|
||||
length += 2;
|
||||
}
|
||||
|
||||
uint gap = outputOffset - position;
|
||||
uint nonOverlappingLength = length;
|
||||
|
||||
if (nonOverlappingLength > gap)
|
||||
{
|
||||
nonOverlappingLength = gap;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength);
|
||||
outputOffset += nonOverlappingLength;
|
||||
position += nonOverlappingLength;
|
||||
length -= nonOverlappingLength;
|
||||
|
||||
while (length-- > 0)
|
||||
{
|
||||
output[outputOffset++] = output[position++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loadCompleteAction = null;
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -658,7 +658,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
return config;
|
||||
}
|
||||
|
||||
public void LoadProfile()
|
||||
public async void LoadProfile()
|
||||
{
|
||||
if (Device == 0)
|
||||
{
|
||||
@ -700,9 +700,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
catch (JsonException) { }
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
|
||||
String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
|
||||
Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
|
||||
String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -736,7 +736,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -769,7 +769,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
private bool _isPaused;
|
||||
private bool _showContent = true;
|
||||
private bool _isLoadingIndeterminate = true;
|
||||
private bool _showAll;
|
||||
private string _lastScannedAmiiboId;
|
||||
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
|
||||
|
||||
public string TitleName { get; internal set; }
|
||||
@ -695,15 +697,28 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAmiiboWindow()
|
||||
public async void OpenAmiiboWindow()
|
||||
{
|
||||
if (!_isAmiiboRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||
{
|
||||
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
|
||||
AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
|
||||
|
||||
// TODO : Implement Amiibo window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
await window.ShowDialog(_owner);
|
||||
|
||||
if (window.IsScanned)
|
||||
{
|
||||
_showAll = window.ViewModel.ShowAllAmiibo;
|
||||
_lastScannedAmiiboId = window.ScannedAmiibo.GetId();
|
||||
|
||||
_owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleShaderProgress(Switch emulationContext)
|
||||
@ -953,10 +968,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
LoadConfigurableHotKeys();
|
||||
}
|
||||
|
||||
public void ManageProfiles()
|
||||
public async void ManageProfiles()
|
||||
{
|
||||
// TODO : Implement Profiles window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
UserProfileWindow window = new(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
|
||||
|
||||
await window.ShowDialog(_owner);
|
||||
}
|
||||
|
||||
public async void OpenAboutWindow()
|
||||
@ -1031,8 +1047,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
||||
out ulong titleIdNumber))
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -1139,7 +1158,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1200,7 +1219,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1213,7 +1232,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1227,33 +1246,60 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenTitleUpdateManager()
|
||||
public async void OpenTitleUpdateManager()
|
||||
{
|
||||
// TODO : Implement Update window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
TitleUpdateWindow titleUpdateManager =
|
||||
new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
|
||||
|
||||
await titleUpdateManager.ShowDialog(_owner);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenDlcManager()
|
||||
public async void OpenDlcManager()
|
||||
{
|
||||
// TODO : Implement Dlc window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
DlcManagerWindow dlcManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName);
|
||||
|
||||
await dlcManager.ShowDialog(_owner);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenCheatManager()
|
||||
public async void OpenCheatManager()
|
||||
{
|
||||
// TODO : Implement cheat window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
|
||||
|
||||
await cheatManager.ShowDialog(_owner);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenCheatManagerForCurrentApp()
|
||||
public async void OpenCheatManagerForCurrentApp()
|
||||
{
|
||||
if (!IsGameRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO : Implement cheat window
|
||||
ContentDialogHelper.ShowNotAvailableMessage(_owner);
|
||||
var application = _owner.AppHost.Device.Application;
|
||||
|
||||
if (application != null)
|
||||
{
|
||||
CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName);
|
||||
|
||||
await cheatManager.ShowDialog(_owner);
|
||||
|
||||
_owner.AppHost.Device.EnableCheats();
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenDeviceSaveDirectory()
|
||||
@ -1267,8 +1313,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
||||
out ulong titleIdNumber))
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -1290,8 +1339,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
||||
out ulong titleIdNumber))
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -1307,30 +1359,30 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
ApplicationHelper.OpenSaveDir(in filter, titleId, data.ControlHolder, data.TitleName);
|
||||
}
|
||||
|
||||
private void ExtractLogo()
|
||||
private async void ExtractLogo()
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
{
|
||||
ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path);
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractRomFs()
|
||||
private async void ExtractRomFs()
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
{
|
||||
ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path);
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractExeFs()
|
||||
private async void ExtractExeFs()
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
{
|
||||
ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path);
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1339,7 +1391,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
_owner.Close();
|
||||
}
|
||||
|
||||
private async void HandleFirmwareInstallation(string path)
|
||||
private async Task HandleFirmwareInstallation(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1349,7 +1401,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
if (firmwareVersion == null)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -1414,11 +1466,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
|
||||
});
|
||||
}
|
||||
finally
|
||||
@ -1439,7 +1491,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1454,7 +1506,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
if (file != null && file.Length > 0)
|
||||
{
|
||||
HandleFirmwareInstallation(file[0]);
|
||||
await HandleFirmwareInstallation(file[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1466,7 +1518,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
HandleFirmwareInstallation(folder);
|
||||
await HandleFirmwareInstallation(folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
171
Ryujinx.Ava/Ui/ViewModels/UserProfileViewModel.cs
Normal file
171
Ryujinx.Ava/Ui/ViewModels/UserProfileViewModel.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.ViewModels
|
||||
{
|
||||
public class UserProfileViewModel : BaseModel, IDisposable
|
||||
{
|
||||
private const uint MaxProfileNameLength = 0x20;
|
||||
|
||||
private readonly UserProfileWindow _owner;
|
||||
|
||||
private UserProfile _selectedProfile;
|
||||
private string _tempUserName;
|
||||
|
||||
public UserProfileViewModel()
|
||||
{
|
||||
Profiles = new ObservableCollection<UserProfile>();
|
||||
}
|
||||
|
||||
public UserProfileViewModel(UserProfileWindow owner) : this()
|
||||
{
|
||||
_owner = owner;
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
||||
|
||||
public UserProfile SelectedProfile
|
||||
{
|
||||
get => _selectedProfile;
|
||||
set
|
||||
{
|
||||
_selectedProfile = value;
|
||||
|
||||
OnPropertyChanged(nameof(SelectedProfile));
|
||||
OnPropertyChanged(nameof(IsSelectedProfileDeletable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelectedProfileDeletable =>
|
||||
_selectedProfile != null && _selectedProfile.UserId != AccountManager.DefaultUserId;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void LoadProfiles()
|
||||
{
|
||||
Profiles.Clear();
|
||||
|
||||
var profiles = _owner.AccountManager.GetAllUsers()
|
||||
.OrderByDescending(x => x.AccountState == AccountState.Open);
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
Profiles.Add(new UserProfile(profile));
|
||||
}
|
||||
|
||||
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
||||
|
||||
if (SelectedProfile == null)
|
||||
{
|
||||
SelectedProfile = Profiles.First();
|
||||
|
||||
if (SelectedProfile != null)
|
||||
{
|
||||
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void ChooseProfileImage()
|
||||
{
|
||||
await SelectProfileImage();
|
||||
}
|
||||
|
||||
public async Task SelectProfileImage(bool isNewUser = false)
|
||||
{
|
||||
ProfileImageSelectionDialog selectionDialog = new(_owner.ContentManager);
|
||||
|
||||
await selectionDialog.ShowDialog(_owner);
|
||||
|
||||
if (selectionDialog.BufferImageProfile != null)
|
||||
{
|
||||
if (isNewUser)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_tempUserName))
|
||||
{
|
||||
_owner.AccountManager.AddUser(_tempUserName, selectionDialog.BufferImageProfile);
|
||||
}
|
||||
}
|
||||
else if (SelectedProfile != null)
|
||||
{
|
||||
_owner.AccountManager.SetUserImage(SelectedProfile.UserId, selectionDialog.BufferImageProfile);
|
||||
SelectedProfile.Image = selectionDialog.BufferImageProfile;
|
||||
|
||||
SelectedProfile = null;
|
||||
}
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
}
|
||||
|
||||
public async void AddUser()
|
||||
{
|
||||
var dlgTitle = LocaleManager.Instance["InputDialogAddNewProfileTitle"];
|
||||
var dlgMainText = LocaleManager.Instance["InputDialogAddNewProfileHeader"];
|
||||
var dlgSubText = string.Format(LocaleManager.Instance["InputDialogAddNewProfileSubtext"],
|
||||
MaxProfileNameLength);
|
||||
|
||||
_tempUserName =
|
||||
await ContentDialogHelper.CreateInputDialog(dlgTitle, dlgMainText, dlgSubText, _owner,
|
||||
MaxProfileNameLength);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_tempUserName))
|
||||
{
|
||||
await SelectProfileImage(true);
|
||||
}
|
||||
|
||||
_tempUserName = String.Empty;
|
||||
}
|
||||
|
||||
public async void DeleteUser()
|
||||
{
|
||||
if (_selectedProfile != null)
|
||||
{
|
||||
var lastUserId = _owner.AccountManager.LastOpenedUser.UserId;
|
||||
|
||||
if (_selectedProfile.UserId == lastUserId)
|
||||
{
|
||||
// If we are deleting the currently open profile, then we must open something else before deleting.
|
||||
var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(_owner,
|
||||
LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_owner.AccountManager.OpenUser(profile.UserId);
|
||||
}
|
||||
|
||||
var result =
|
||||
await ContentDialogHelper.CreateConfirmationDialog(_owner,
|
||||
LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
|
||||
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_owner.AccountManager.DeleteUser(_selectedProfile.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
}
|
||||
}
|
68
Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml
Normal file
68
Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml
Normal file
@ -0,0 +1,68 @@
|
||||
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.AmiiboWindow"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="800" MinHeight="650" Height="650"
|
||||
SizeToContent="Manual"
|
||||
MinWidth="600">
|
||||
<Design.DataContext>
|
||||
<viewModels:AmiiboWindowViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboSeriesLabel}" />
|
||||
<ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" Items="{Binding AmiiboSeries}" MinWidth="100" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="10" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboCharacterLabel}" />
|
||||
<ComboBox SelectedIndex="{Binding AmiiboSelectedIndex}" MinWidth="100" Items="{Binding AmiiboList}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Margin="20" Grid.Row="2">
|
||||
<Image Source="{Binding AmiiboImage}" Height="350" Width="350" 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" />
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Margin="10" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding ShowAllAmiibo}"
|
||||
Content="{locale:Locale AmiiboOptionsShowAllLabel}" />
|
||||
<CheckBox Margin="10" VerticalContentAlignment="Center" Grid.Column="1" IsChecked="{Binding UseRandomUuid}"
|
||||
Content="{locale:Locale AmiiboOptionsUsRandomTagLabel}" />
|
||||
|
||||
<Button Grid.Column="3" IsEnabled="{Binding EnableScanning}" Width="80"
|
||||
Content="{locale:Locale AmiiboScanButtonLabel}" Name="ScanButton"
|
||||
Click="ScanButton_Click" />
|
||||
<Button Grid.Column="4" Margin="10,0" Width="80" Content="{locale:Locale InputDialogCancel}"
|
||||
Name="CancelButton"
|
||||
Click="CancelButton_Click" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
70
Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs
Normal file
70
Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public class AmiiboWindow : StyleableWindow
|
||||
{
|
||||
public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId)
|
||||
{
|
||||
ViewModel = new AmiiboWindowViewModel(this, lastScannedAmiiboId, titleId);
|
||||
|
||||
ViewModel.ShowAllAmiibo = showAll;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"];
|
||||
}
|
||||
|
||||
public AmiiboWindow()
|
||||
{
|
||||
ViewModel = new AmiiboWindowViewModel(this, string.Empty, string.Empty);
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"];
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsScanned { get; set; }
|
||||
public Amiibo.AmiiboApi ScannedAmiibo { get; set; }
|
||||
public AmiiboWindowViewModel ViewModel { get; set; }
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void ScanButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.AmiiboSelectedIndex > -1)
|
||||
{
|
||||
Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
||||
ScannedAmiibo = amiibo;
|
||||
IsScanned = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsScanned = false;
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
53
Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml
Normal file
53
Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml
Normal file
@ -0,0 +1,53 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.AvatarWindow"
|
||||
CanResize="False"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:AvatarProfileViewModel"
|
||||
SizeToContent="WidthAndHeight">
|
||||
<Design.DataContext>
|
||||
<viewModels:AvatarProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Window.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</Window.Resources>
|
||||
<Grid Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Width="600" Height="500"
|
||||
Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" MaxWidth="600" Margin="0" HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Image Margin="5" Height="96" Width="96"
|
||||
Source="{Binding Data, Converter={StaticResource ByteImage}}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<ProgressBar Grid.Row="2" IsIndeterminate="{Binding IsIndeterminate}" Value="{Binding ImagesLoaded}" HorizontalAlignment="Stretch" Margin="5"
|
||||
Maximum="{Binding ImageCount}" Minimum="0" />
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center">
|
||||
<Button Content="{Locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" />
|
||||
<ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" />
|
||||
<Button HorizontalAlignment="Right" Content="{Locale:Locale AvatarClose}" Click="CloseButton_OnClick"
|
||||
Name="CloseButton"
|
||||
Width="200" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
71
Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs
Normal file
71
Ryujinx.Ava/Ui/Windows/AvatarWindow.axaml.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public class AvatarWindow : StyleableWindow
|
||||
{
|
||||
public AvatarWindow(ContentManager contentManager)
|
||||
{
|
||||
ContentManager = contentManager;
|
||||
ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
|
||||
}
|
||||
|
||||
public AvatarWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
|
||||
}
|
||||
}
|
||||
|
||||
public ContentManager ContentManager { get; }
|
||||
|
||||
public byte[] SelectedImage { get; set; }
|
||||
|
||||
internal AvatarProfileViewModel ViewModel { get; set; }
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
ViewModel.Dispose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
private void CloseButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.SelectedIndex > -1)
|
||||
{
|
||||
SelectedImage = ViewModel.SelectedImage;
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
96
Ryujinx.Ava/Ui/Windows/CheatWindow.axaml
Normal file
96
Ryujinx.Ava/Ui/Windows/CheatWindow.axaml
Normal file
@ -0,0 +1,96 @@
|
||||
<window:StyleableWindow x:Class="Ryujinx.Ava.Ui.Windows.CheatWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:model="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
Width="500" MinHeight="500" Height="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="500">
|
||||
<Window.Styles>
|
||||
<Style Selector="TreeViewItem">
|
||||
<Setter Property="IsExpanded" Value="True" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
<Grid Name="DlcGrid" Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="20,15,20,20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="500"
|
||||
LineHeight="18"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Heading}"
|
||||
TextAlignment="Center" />
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1">
|
||||
<TreeView Items="{Binding LoadedCheats}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Name="CheatsView"
|
||||
MinHeight="300">
|
||||
<TreeView.Styles>
|
||||
<Styles>
|
||||
<Style Selector="TreeViewItem:empty /template/ ItemsPresenter">
|
||||
<Setter Property="IsVisible" Value="False"/>
|
||||
</Style>
|
||||
</Styles>
|
||||
</TreeView.Styles>
|
||||
<TreeView.DataTemplates>
|
||||
<TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<CheckBox IsChecked="{Binding IsEnabled}" MinWidth="20" />
|
||||
<TextBlock Width="150"
|
||||
Text="{Binding BuildId}" />
|
||||
<TextBlock
|
||||
Text="{Binding Path}" />
|
||||
</StackPanel>
|
||||
</TreeDataTemplate>
|
||||
<DataTemplate x:DataType="model:CheatModel">
|
||||
<StackPanel Orientation="Horizontal" Margin="0" HorizontalAlignment="Left">
|
||||
<CheckBox IsChecked="{Binding IsEnabled}" Padding="0" Margin="5,0" MinWidth="20" />
|
||||
<TextBlock Text="{Binding CleanName}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</TreeView.DataTemplates>
|
||||
</TreeView>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="3"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
IsVisible="{Binding !NoCheatsFound}"
|
||||
Command="{Binding Save}">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
137
Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs
Normal file
137
Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public class CheatWindow : StyleableWindow
|
||||
{
|
||||
private readonly string _enabledCheatsPath;
|
||||
public bool NoCheatsFound { get; }
|
||||
|
||||
private AvaloniaList<CheatsList> LoadedCheats { get; }
|
||||
|
||||
public string Heading { get; }
|
||||
|
||||
public CheatWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
AttachDebugDevTools();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"];
|
||||
}
|
||||
|
||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName)
|
||||
{
|
||||
LoadedCheats = new AvaloniaList<CheatsList>();
|
||||
|
||||
Heading = string.Format(LocaleManager.Instance["CheatWindowHeading"], titleName, titleId.ToUpper());
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
|
||||
string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath();
|
||||
string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
|
||||
ulong titleIdValue = ulong.Parse(titleId, System.Globalization.NumberStyles.HexNumber);
|
||||
|
||||
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
||||
|
||||
string[] enabled = { };
|
||||
|
||||
if (File.Exists(_enabledCheatsPath))
|
||||
{
|
||||
enabled = File.ReadAllLines(_enabledCheatsPath);
|
||||
}
|
||||
|
||||
int cheatAdded = 0;
|
||||
|
||||
var mods = new ModLoader.ModCache();
|
||||
|
||||
ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue);
|
||||
|
||||
string currentCheatFile = string.Empty;
|
||||
string buildId = string.Empty;
|
||||
string parentPath = string.Empty;
|
||||
|
||||
CheatsList currentGroup = null;
|
||||
|
||||
foreach (var cheat in mods.Cheats)
|
||||
{
|
||||
if (cheat.Path.FullName != currentCheatFile)
|
||||
{
|
||||
currentCheatFile = cheat.Path.FullName;
|
||||
parentPath = currentCheatFile.Replace(titleModsPath, "");
|
||||
|
||||
buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper();
|
||||
currentGroup = new CheatsList(buildId, parentPath);
|
||||
|
||||
LoadedCheats.Add(currentGroup);
|
||||
}
|
||||
|
||||
var model = new CheatModel(cheat.Name, buildId, enabled.Contains($"{buildId}-{cheat.Name}"));
|
||||
currentGroup?.Add(model);
|
||||
|
||||
cheatAdded++;
|
||||
}
|
||||
|
||||
if (cheatAdded == 0)
|
||||
{
|
||||
NoCheatsFound = true;
|
||||
}
|
||||
|
||||
DataContext = this;
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"];
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void AttachDebugDevTools()
|
||||
{
|
||||
this.AttachDevTools();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (NoCheatsFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> enabledCheats = new List<string>();
|
||||
|
||||
foreach (var cheats in LoadedCheats)
|
||||
{
|
||||
foreach (var cheat in cheats)
|
||||
{
|
||||
if (cheat.IsEnabled)
|
||||
{
|
||||
enabledCheats.Add(cheat.BuildIdKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath));
|
||||
|
||||
File.WriteAllLines(_enabledCheatsPath, enabledCheats);
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
@ -127,9 +128,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
}
|
||||
else if (device.Type == Models.DeviceType.Controller)
|
||||
{
|
||||
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.Id == ViewModel.SelectedGamepad.Id);
|
||||
|
||||
assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (config as StandardControllerInputConfig).TriggerThreshold, forStick);
|
||||
assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -184,8 +183,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
|
||||
if (e.AddedItems.Count > 0)
|
||||
{
|
||||
(PlayerIndex key, _) = (KeyValuePair<PlayerIndex, string>)e.AddedItems[0];
|
||||
ViewModel.PlayerId = key;
|
||||
var player = (PlayerModel)e.AddedItems[0];
|
||||
ViewModel.PlayerId = player.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
132
Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml
Normal file
132
Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml
Normal file
@ -0,0 +1,132 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.DlcManagerWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
SizeToContent="Height"
|
||||
Width="600" MinHeight="500" Height="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="600"
|
||||
mc:Ignorable="d">
|
||||
<Grid Name="DlcGrid" Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="20,15,20,20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="500"
|
||||
LineHeight="18"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Heading}"
|
||||
TextAlignment="Center" />
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1">
|
||||
<DataGrid
|
||||
MinHeight="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
Items="{Binding Dlcs}"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="90">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox
|
||||
Width="50"
|
||||
MinWidth="40"
|
||||
HorizontalAlignment="Right"
|
||||
IsChecked="{Binding IsEnabled}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
|
||||
</DataGridTemplateColumn.Header>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn
|
||||
Width="190"
|
||||
Binding="{Binding TitleId}"
|
||||
CanUserResize="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding ContainerPath}"
|
||||
CanUserResize="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding FullPath}"
|
||||
CanUserResize="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="3"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveSelected}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Save}">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
266
Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs
Normal file
266
Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs
Normal file
@ -0,0 +1,266 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public class DlcManagerWindow : StyleableWindow
|
||||
{
|
||||
private readonly List<DlcContainer> _dlcContainerList;
|
||||
private readonly string _dlcJsonPath;
|
||||
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
|
||||
public AvaloniaList<DlcModel> Dlcs { get; set; }
|
||||
public Grid DlcGrid { get; private set; }
|
||||
public ulong TitleId { get; }
|
||||
public string TitleName { get; }
|
||||
|
||||
public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16"));
|
||||
|
||||
public DlcManagerWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
AttachDebugDevTools();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
|
||||
}
|
||||
|
||||
public DlcManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
TitleId = titleId;
|
||||
TitleName = titleName;
|
||||
|
||||
_dlcJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||
|
||||
try
|
||||
{
|
||||
_dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_dlcContainerList = new List<DlcContainer>();
|
||||
}
|
||||
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
AttachDebugDevTools();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
|
||||
|
||||
LoadDlcs();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void AttachDebugDevTools()
|
||||
{
|
||||
this.AttachDevTools();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Dlcs = new AvaloniaList<DlcModel>();
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
DlcGrid = this.FindControl<Grid>("DlcGrid");
|
||||
}
|
||||
|
||||
private void LoadDlcs()
|
||||
{
|
||||
foreach (DlcContainer dlcContainer in _dlcContainerList)
|
||||
{
|
||||
using FileStream containerFile = File.OpenRead(dlcContainer.Path);
|
||||
|
||||
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
|
||||
|
||||
VirtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
pfs.OpenFile(ref ncaFile.Ref(), dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.Path);
|
||||
|
||||
if (nca != null)
|
||||
{
|
||||
Dlcs.Add(new DlcModel(nca.Header.TitleId.ToString("X16"), dlcContainer.Path, dlcNca.Path,
|
||||
dlcNca.Enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Nca(VirtualFileSystem.KeySet, ncaStorage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(this,
|
||||
string.Format(LocaleManager.Instance[
|
||||
"DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath));
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task AddDlc(string path)
|
||||
{
|
||||
if (!File.Exists(path) || Dlcs.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (FileStream containerFile = File.OpenRead(path))
|
||||
{
|
||||
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
|
||||
bool containsDlc = false;
|
||||
|
||||
VirtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
|
||||
|
||||
if (nca == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Dlcs.Add(new DlcModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
|
||||
|
||||
containsDlc = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDlc)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(this, LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveDlcs(bool removeSelectedOnly = false)
|
||||
{
|
||||
if (removeSelectedOnly)
|
||||
{
|
||||
Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
Dlcs.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveSelected()
|
||||
{
|
||||
RemoveDlcs(true);
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
RemoveDlcs();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog() { Title = LocaleManager.Instance["SelectDlcDialogTitle"], AllowMultiple = true };
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
|
||||
|
||||
string[] files = await dialog.ShowAsync(this);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
await AddDlc(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_dlcContainerList.Clear();
|
||||
|
||||
DlcContainer container = default;
|
||||
|
||||
foreach (DlcModel dlc in Dlcs)
|
||||
{
|
||||
if (container.Path != dlc.ContainerPath)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(container.Path))
|
||||
{
|
||||
_dlcContainerList.Add(container);
|
||||
}
|
||||
|
||||
container = new DlcContainer { Path = dlc.ContainerPath, DlcNcaList = new List<DlcNca>() };
|
||||
}
|
||||
|
||||
container.DlcNcaList.Add(new DlcNca
|
||||
{
|
||||
Enabled = dlc.IsEnabled,
|
||||
TitleId = Convert.ToUInt64(dlc.TitleId, 16),
|
||||
Path = dlc.FullPath
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(container.Path))
|
||||
{
|
||||
_dlcContainerList.Add(container);
|
||||
}
|
||||
|
||||
using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true)));
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@ -120,8 +120,6 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
|
||||
|
||||
LoadGameList();
|
||||
|
||||
CheckLaunchState();
|
||||
}
|
||||
|
||||
_rendererWaitEvent = new AutoResetEvent(false);
|
||||
@ -451,7 +449,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
RefreshFirmwareStatus();
|
||||
}
|
||||
|
||||
protected async void CheckLaunchState()
|
||||
protected void CheckLaunchState()
|
||||
{
|
||||
if (ShowKeyErrorOnLoad)
|
||||
{
|
||||
@ -470,7 +468,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
|
||||
{
|
||||
await Updater.BeginParse(this, false).ContinueWith(task =>
|
||||
Updater.BeginParse(this, false).ContinueWith(task =>
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
@ -537,6 +535,13 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||
LoadHotKeys();
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
|
||||
CheckLaunchState();
|
||||
}
|
||||
|
||||
public static void UpdateGraphicsConfig()
|
||||
{
|
||||
int resScale = ConfigurationState.Instance.Graphics.ResScale;
|
||||
|
104
Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml
Normal file
104
Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml
Normal file
@ -0,0 +1,104 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.TitleUpdateWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
SizeToContent="Height"
|
||||
Width="600" MinHeight="500" Height="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="600"
|
||||
mc:Ignorable="d">
|
||||
<Grid Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="20,15,20,20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="500"
|
||||
LineHeight="18"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Heading}"
|
||||
TextAlignment="Center" />
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer
|
||||
Width="550"
|
||||
MinHeight="200"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding TitleUpdates}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
||||
<Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" />
|
||||
</RadioButton>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="3"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveSelected}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Save}">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
272
Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs
Normal file
272
Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs
Normal file
@ -0,0 +1,272 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.Controls;
|
||||
using Ryujinx.Ava.Ui.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public class TitleUpdateWindow : StyleableWindow
|
||||
{
|
||||
private readonly string _updateJsonPath;
|
||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
|
||||
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; }
|
||||
public string TitleId { get; }
|
||||
public string TitleName { get; }
|
||||
|
||||
public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper());
|
||||
|
||||
public TitleUpdateWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
AttachDebugDevTools();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
|
||||
}
|
||||
|
||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName)
|
||||
{
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
TitleId = titleId;
|
||||
TitleName = titleName;
|
||||
|
||||
_updateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata {Selected = "", Paths = new List<string>()};
|
||||
}
|
||||
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
AttachDebugDevTools();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
|
||||
|
||||
LoadUpdates();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void AttachDebugDevTools()
|
||||
{
|
||||
this.AttachDevTools();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
TitleUpdates = new AvaloniaList<TitleUpdateModel>();
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void LoadUpdates()
|
||||
{
|
||||
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
||||
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
if (_titleUpdateWindowData.Selected == "")
|
||||
{
|
||||
TitleUpdates[0].IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
||||
List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList();
|
||||
|
||||
foreach (TitleUpdateModel update in enabled)
|
||||
{
|
||||
update.IsEnabled = false;
|
||||
}
|
||||
|
||||
if (selected != null)
|
||||
{
|
||||
selected.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path))
|
||||
{
|
||||
using (FileStream file = new(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) =
|
||||
ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new ApplicationControlProperty();
|
||||
|
||||
using var nacpFile = new UniqueRef<IFile>();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None)
|
||||
.OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read)
|
||||
.ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None)
|
||||
.ThrowIfFailure();
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(this,
|
||||
LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(this,
|
||||
string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUpdates(bool removeSelectedOnly = false)
|
||||
{
|
||||
if (removeSelectedOnly)
|
||||
{
|
||||
TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
||||
}
|
||||
|
||||
TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void RemoveSelected()
|
||||
{
|
||||
RemoveUpdates(true);
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
RemoveUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog() { Title = LocaleManager.Instance["SelectUpdateDialogTitle"], AllowMultiple = true };
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
|
||||
|
||||
string[] files = await dialog.ShowAsync(this);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
AddUpdate(file);
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
private void SortUpdates()
|
||||
{
|
||||
var list = TitleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString())
|
||||
.CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
TitleUpdates.Clear();
|
||||
|
||||
TitleUpdates.AddRange(list);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (TitleUpdateModel update in TitleUpdates)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
|
||||
if (update.IsEnabled)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
}
|
||||
|
||||
if (Owner is MainWindow window)
|
||||
{
|
||||
window.ViewModel.LoadApplications();
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
107
Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml
Normal file
107
Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml
Normal file
@ -0,0 +1,107 @@
|
||||
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.UserProfileWindow"
|
||||
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
CanResize="False"
|
||||
Width="850" MinHeight="550" Height="550"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
SizeToContent="Manual"
|
||||
MinWidth="600">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Window.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</Window.Resources>
|
||||
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<ui:ContentDialog Name="ContentDialog"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="False" />
|
||||
</ContentControl>
|
||||
<TextBlock Text="{Locale:Locale UserProfilesSelectedUserProfile}" />
|
||||
<Grid Grid.Row="1" Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Height="96" Width="96"
|
||||
Source="{Binding SelectedProfile.Image, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
|
||||
Margin="5, 10">
|
||||
<TextBox Name="NameBox" Text="{Binding SelectedProfile.Name, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding SelectedProfile.UserId}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="2" Spacing="10"
|
||||
Margin="5">
|
||||
<Button Content="{Locale:Locale UserProfilesSaveProfileName}" Name="SetNameButton"
|
||||
Click="SetNameButton_OnClick" />
|
||||
<Button Name="SelectProfileImage" Command="{Binding ChooseProfileImage}"
|
||||
Content="{Locale:Locale UserProfilesChangeProfileImage}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{Locale:Locale UserProfilesAvailableUserProfiles}" />
|
||||
<ListBox Grid.Row="1" Margin="10" Name="ProfilesList" DoubleTapped="ProfilesList_DoubleTapped"
|
||||
Items="{Binding Profiles}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0" Background="{DynamicResource ThemeAccentColorBrush}"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="5" MinWidth="5"
|
||||
IsVisible="{Binding IsOpened}" />
|
||||
<Image Grid.Column="0" Height="96" Width="96"
|
||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel Margin="10" Orientation="Vertical" HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center" Grid.Column="1">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
<TextBlock Text="{Binding UserId}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Stretch">
|
||||
<Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
|
||||
<Button IsEnabled="{Binding IsSelectedProfileDeletable}"
|
||||
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
|
||||
<Button HorizontalAlignment="Right" Content="{Locale:Locale UserProfilesClose}" Click="CloseButton_OnClick"
|
||||
Name="CloseButton" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
102
Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs
Normal file
102
Ryujinx.Ava/Ui/Windows/UserProfileWindow.axaml.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Ui.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.Ui.Windows
|
||||
{
|
||||
public class UserProfileWindow : StyleableWindow
|
||||
{
|
||||
private TextBox _nameBox;
|
||||
|
||||
public UserProfileWindow(AccountManager accountManager, ContentManager contentManager,
|
||||
VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
AccountManager = accountManager;
|
||||
ContentManager = contentManager;
|
||||
ViewModel = new UserProfileViewModel(this);
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
if (contentManager.GetCurrentFirmwareVersion() != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
|
||||
});
|
||||
}
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
|
||||
}
|
||||
|
||||
public UserProfileWindow()
|
||||
{
|
||||
ViewModel = new UserProfileViewModel();
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
|
||||
}
|
||||
|
||||
public AccountManager AccountManager { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
_nameBox = this.FindControl<TextBox>("NameBox");
|
||||
}
|
||||
|
||||
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
int selectedIndex = listBox.SelectedIndex;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
||||
{
|
||||
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
|
||||
|
||||
AccountManager.OpenUser(ViewModel.SelectedProfile.UserId);
|
||||
|
||||
ViewModel.LoadProfiles();
|
||||
|
||||
foreach (UserProfile profile in ViewModel.Profiles)
|
||||
{
|
||||
profile.UpdateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void SetNameButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_nameBox.Text))
|
||||
{
|
||||
ViewModel.SelectedProfile.Name = _nameBox.Text;
|
||||
AccountManager.SetUserName(ViewModel.SelectedProfile.UserId, _nameBox.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 1;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 3069;
|
||||
private const uint CodeGenVersion = 3457;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -45,6 +45,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
if (!(node.Value is Operation operation) || isUnused)
|
||||
{
|
||||
if (node.Value is PhiNode phi && !isUnused)
|
||||
{
|
||||
isUnused = PropagatePhi(phi);
|
||||
}
|
||||
|
||||
if (isUnused)
|
||||
{
|
||||
RemoveNode(block, node);
|
||||
@ -101,6 +106,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
{
|
||||
// Propagate copy source operand to all uses of
|
||||
// the destination operand.
|
||||
|
||||
Operand dest = copyOp.Dest;
|
||||
Operand src = copyOp.GetSource(0);
|
||||
|
||||
@ -118,6 +124,53 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PropagatePhi(PhiNode phi)
|
||||
{
|
||||
// If all phi sources are the same, we can propagate it and remove the phi.
|
||||
|
||||
Operand firstSrc = phi.GetSource(0);
|
||||
|
||||
for (int index = 1; index < phi.SourcesCount; index++)
|
||||
{
|
||||
if (!IsSameOperand(firstSrc, phi.GetSource(index)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// All sources are equal, we can propagate the value.
|
||||
|
||||
Operand dest = phi.Dest;
|
||||
|
||||
INode[] uses = dest.UseOps.ToArray();
|
||||
|
||||
foreach (INode useNode in uses)
|
||||
{
|
||||
for (int index = 0; index < useNode.SourcesCount; index++)
|
||||
{
|
||||
if (useNode.GetSource(index) == dest)
|
||||
{
|
||||
useNode.SetSource(index, firstSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSameOperand(Operand x, Operand y)
|
||||
{
|
||||
if (x.Type != y.Type || x.Value != y.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return x.Type == OperandType.Attribute ||
|
||||
x.Type == OperandType.AttributePerPatch ||
|
||||
x.Type == OperandType.Constant ||
|
||||
x.Type == OperandType.ConstantBuffer;
|
||||
}
|
||||
|
||||
private static bool PropagatePack(Operation packOp)
|
||||
{
|
||||
// Propagate pack source operands to uses by unpack
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@ -6,8 +6,6 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<!-- As we already provide GTK3 on Windows via GtkSharp.Dependencies this is redundant. -->
|
||||
<SkipGtkInstall>true</SkipGtkInstall>
|
||||
|
Reference in New Issue
Block a user