Merge branch 'master' into Master_PR

This commit is contained in:
Vladimir Sokolov 2025-02-28 20:54:11 +10:00 committed by GitHub
commit 9dc36646c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 1340 additions and 233 deletions

View file

@ -97,7 +97,7 @@ If you are planning to contribute or just want to learn more about this project
- **Input** - **Input**
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. We currently have support for keyboard, mouse, touch input, Joy-Con input support, and nearly all controllers.
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required. Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
In all scenarios, you can set up everything inside the input configuration menu. In all scenarios, you can set up everything inside the input configuration menu.

View file

@ -24,7 +24,7 @@ namespace ARMeilleure.Translation.Cache
private static JitCacheInvalidation _jitCacheInvalidator; private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator; private static List<CacheMemoryAllocator> _cacheAllocators = [];
private static readonly List<CacheEntry> _cacheEntries = []; private static readonly List<CacheEntry> _cacheEntries = [];
@ -40,37 +40,48 @@ namespace ARMeilleure.Translation.Cache
public static void Initialize(IJitMemoryAllocator allocator) public static void Initialize(IJitMemoryAllocator allocator)
{ {
if (_initialized)
{
return;
}
lock (_lock) lock (_lock)
{ {
if (_initialized) if (_initialized)
{ {
return; if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(
_jitRegions[0].Pointer);
}
for (int i = 0; i < _jitRegions.Count; i++)
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
} }
else
{
_initialized = true;
}
_activeRegionIndex = 0;
ReservedRegion firstRegion = new(allocator, CacheSize); ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion); _jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
_jitCacheInvalidator = new JitCacheInvalidation(allocator); _jitCacheInvalidator = new JitCacheInvalidation(allocator);
} }
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
JitUnwindWindows.InstallFunctionTableHandler( JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize) firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
); );
} }
_initialized = true;
} }
} }
@ -136,7 +147,7 @@ namespace ARMeilleure.Translation.Cache
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); _cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex); _cacheEntries.RemoveAt(entryIndex);
} }
@ -167,16 +178,12 @@ namespace ARMeilleure.Translation.Cache
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++) int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
{
int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0) if (allocOffset >= 0)
{ {
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); _jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i; return allocOffset;
return allocOffset;
}
} }
int exhaustedRegion = _activeRegionIndex; int exhaustedRegion = _activeRegionIndex;
@ -184,13 +191,11 @@ namespace ARMeilleure.Translation.Cache
_jitRegions.Add(newRegion); _jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1; _activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex; Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation)."); _cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
_cacheAllocator = new CacheMemoryAllocator(CacheSize); int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0) if (allocOffsetNew < 0)
{ {
throw new OutOfMemoryException("Failed to allocate in new Cache Region!"); throw new OutOfMemoryException("Failed to allocate in new Cache Region!");

View file

@ -52,6 +52,11 @@ namespace ARMeilleure.Translation.Cache
nint context, nint context,
[MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll); [MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool RtlDeleteFunctionTable(
ulong tableIdentifier);
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
private static int _sizeOfRuntimeFunction; private static int _sizeOfRuntimeFunction;
@ -91,6 +96,23 @@ namespace ARMeilleure.Translation.Cache
} }
} }
public static void RemoveFunctionTableHandler(nint codeCachePointer)
{
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
bool result;
unsafe
{
result = RtlDeleteFunctionTable(codeCachePtr | 3);
}
if (!result)
{
throw new InvalidOperationException("Failure removing function table callback.");
}
}
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context) private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{ {
int offset = (int)((long)controlPc - context.ToInt64()); int offset = (int)((long)controlPc - context.ToInt64());

View file

@ -5,15 +5,34 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Common.Helper namespace Ryujinx.Common.Helper
{ {
public enum OperatingSystemType
{
MacOS,
Linux,
Windows
}
public static class RunningPlatform public static class RunningPlatform
{ {
public static readonly OperatingSystemType CurrentOS
= IsMacOS
? OperatingSystemType.MacOS
: IsWindows
? OperatingSystemType.Windows
: IsLinux
? OperatingSystemType.Linux
: throw new PlatformNotSupportedException();
public static Architecture Architecture => RuntimeInformation.OSArchitecture;
public static Architecture CurrentProcessArchitecture => RuntimeInformation.ProcessArchitecture;
public static bool IsMacOS => OperatingSystem.IsMacOS(); public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows(); public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux(); public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64; public static bool IsArm => Architecture is Architecture.Arm64;
public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64; public static bool IsX64 => Architecture is Architecture.X64;
public static bool IsIntelMac => IsMacOS && IsX64; public static bool IsIntelMac => IsMacOS && IsX64;
public static bool IsArmMac => IsMacOS && IsArm; public static bool IsArmMac => IsMacOS && IsArm;

View file

@ -71,16 +71,24 @@ namespace Ryujinx.HLE.Loaders.Mods
int patchOffset = (int)offset; int patchOffset = (int)offset;
int patchSize = patch.Length; int patchSize = patch.Length;
if (patchOffset < protectedOffset || patchOffset > memory.Length) if (patchOffset < protectedOffset)
{ {
continue; // Add warning? Logger.Warning?.Print(LogClass.ModLoader, $"Attempted to patch protected memory ({patchOffset:x} is within protected boundary of {protectedOffset:x}).");
continue;
}
if (patchOffset > memory.Length)
{
Logger.Warning?.Print(LogClass.ModLoader, $"Attempted to patch out of bounds memory (offset {patchOffset} ({patchOffset:x}) exceeds memory buffer length {memory.Length}).");
continue;
} }
patchOffset -= protectedOffset; patchOffset -= protectedOffset;
if (patchOffset + patchSize > memory.Length) if (patchOffset + patchSize > memory.Length)
{ {
patchSize = memory.Length - patchOffset; // Add warning? Logger.Warning?.Print(LogClass.ModLoader, $"Patch offset ({patchOffset:x}) + size ({patchSize}) is greater than the size of the memory buffer ({memory.Length}). Attempting to fix this...");
patchSize = memory.Length - patchOffset;
} }
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}"); Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");

View file

@ -1113,6 +1113,13 @@ namespace Ryujinx.Ava
}); });
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
// Reload settings when the game is turned off
// (resets custom settings if there were any)
Program.ReloadConfig();
// Reload application list (changes the status of the user setting if it was added or removed during the game)
Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.LoadApplications());
} }
public void InitStatus() public void InitStatus()

View file

@ -2,6 +2,7 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"> xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
<Design.PreviewWith> <Design.PreviewWith>
<Border Height="2000" <Border Height="2000"
@ -30,7 +31,8 @@
<Button <Button
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add"
Classes="red"/>
<TextBox <TextBox
Width="100" Width="100"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -41,7 +43,12 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<ui:NumberBox Value="1" /> <ui:NumberBox Value="1" />
</StackPanel> <MenuItem
Header="123 0000"
ToolTip.Tip="What this"/>
<TextBlock
Classes="globalConfigMarker"/>
</StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="DropDownButton"> <Style Selector="DropDownButton">
@ -331,12 +338,11 @@
<Setter Property="Margin" <Setter Property="Margin"
Value="0,5,0,0" /> Value="0,5,0,0" />
</Style> </Style>
<Style Selector="TextBlock.pending" > <Style Selector="TextBlock.globalConfigMarker" >
<Setter Property="Foreground" Value="SlateGray"/> <Setter Property="Foreground" Value="SeaGreen"/>
<Setter Property="Margin" Value="5,0,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="Text" Value="{ext:Locale GameSpecificConfigurationGlobal}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Text" Value="(pending!)"/>
</Style> </Style>
<Style Selector="StackPanel.globalConfigMarker"> <Style Selector="StackPanel.globalConfigMarker">
</Style> </Style>
@ -382,6 +388,19 @@
<Setter Property="Background" <Setter Property="Background"
Value="{DynamicResource AppListHoverBackgroundColor}" /> Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style> </Style>
<Style Selector="Button.red /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style Selector="Button.red:pointerover /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="{DynamicResource WarningBackgroundColor}" />
<Setter Property="Foreground" Value="White"/>
</Style>
<Styles.Resources> <Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush" <SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" /> Color="{DynamicResource SystemAccentColor}" />

View file

@ -1,4 +1,4 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries> <ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default"> <ResourceDictionary x:Key="Default">
@ -12,11 +12,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color> <Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color> <Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color> <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color> <Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color> <Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color> <Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary> </ResourceDictionary>
<ResourceDictionary x:Key="Light"> <ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@ -29,11 +31,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color> <Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color> <Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color> <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#13c3a4</Color> <Color x:Key="Switch">#13c3a4</Color>
<Color x:Key="Unbounded">#FFFF4554</Color> <Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color> <Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary> </ResourceDictionary>
<ResourceDictionary x:Key="Dark"> <ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@ -46,11 +50,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color> <Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color> <Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color> <Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color> <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color> <Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color> <Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color> <Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#FFA500</Color>
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries> </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary> </ResourceDictionary>

View file

@ -464,7 +464,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Abrir Pasta de Capturas de Tela", "pt_BR": "Abrir Pasta de Capturas de Tela",
"ru_RU": "Открыть папку со скриншотами", "ru_RU": "Открыть папку со скриншотами",
"sv_SE": "", "sv_SE": "Öppna skärmbildsmappen",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Відкрити теку скріншотів", "uk_UA": "Відкрити теку скріншотів",
@ -1564,7 +1564,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Desenvolvido por {0}", "pt_BR": "Desenvolvido por {0}",
"ru_RU": "Разработана {0}", "ru_RU": "Разработана {0}",
"sv_SE": "", "sv_SE": "Utvecklat av {0}",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Розроблено: {0}", "uk_UA": "Розроблено: {0}",
@ -1864,7 +1864,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Compatibilidade:", "pt_BR": "Compatibilidade:",
"ru_RU": "Совместимость:", "ru_RU": "Совместимость:",
"sv_SE": "", "sv_SE": "Kompatibilitet:",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Сумісність:", "uk_UA": "Сумісність:",
@ -1889,7 +1889,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "ID do Título:", "pt_BR": "ID do Título:",
"ru_RU": "ID приложения", "ru_RU": "ID приложения",
"sv_SE": "", "sv_SE": "Titel-id:",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "ID гри:", "uk_UA": "ID гри:",
@ -1914,7 +1914,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Jogos Hospedados: {0}", "pt_BR": "Jogos Hospedados: {0}",
"ru_RU": "Запущенно игр: {0}", "ru_RU": "Запущенно игр: {0}",
"sv_SE": "", "sv_SE": "Värdskap för spel: {0}",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Розміщені ігри: {0}", "uk_UA": "Розміщені ігри: {0}",
@ -1939,7 +1939,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Jogadores Online: {0}", "pt_BR": "Jogadores Online: {0}",
"ru_RU": "Игроков онлайн: {0}", "ru_RU": "Игроков онлайн: {0}",
"sv_SE": "", "sv_SE": "Online-spelare: {0}",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Гравців онлайн: {0}", "uk_UA": "Гравців онлайн: {0}",
@ -2289,7 +2289,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Limpar Cache PPTC", "pt_BR": "Limpar Cache PPTC",
"ru_RU": "Очистить кэш PPTC", "ru_RU": "Очистить кэш PPTC",
"sv_SE": "", "sv_SE": "Rensa PPTC-cache",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Очистити кеш PPTC", "uk_UA": "Очистити кеш PPTC",
@ -2314,7 +2314,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Apaga os arquivos de cache PPTC do aplicativo", "pt_BR": "Apaga os arquivos de cache PPTC do aplicativo",
"ru_RU": "Удаляет все файлы кэша PPTC для приложения", "ru_RU": "Удаляет все файлы кэша PPTC для приложения",
"sv_SE": "", "sv_SE": "Tar bort alla PPTC-cachefiler för applikationen",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Видаляє всі файли кешу PPTC для застосунку", "uk_UA": "Видаляє всі файли кешу PPTC для застосунку",
@ -2747,6 +2747,56 @@
"zh_TW": "建立桌面捷徑,啟動選取的應用程式" "zh_TW": "建立桌面捷徑,啟動選取的應用程式"
} }
}, },
{
"ID": "GameListContextMenuCreateCustomConfiguration",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Create Custom Configuration",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Создать пользовательскую конфигурацию",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "GameListContextMenuEditCustomConfiguration",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Edit Custom Configuration",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Изменить пользовательскую конфигурацию",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "GameListContextMenuCreateShortcutToolTipMacOS", "ID": "GameListContextMenuCreateShortcutToolTipMacOS",
"Translations": { "Translations": {
@ -2772,6 +2822,56 @@
"zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式" "zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式"
} }
}, },
{
"ID": "CreateCustomConfigurationToolTip",
"Translations": {
"ar_SA": "ينشئ تكوينًا مستقلًا للعبة الحالية",
"de_DE": "Erstellt eine unabhängige Konfiguration für das aktuelle Spiel",
"el_GR": "Δημιουργεί μια ανεξάρτητη διαμόρφωση για το τρέχον παιχνίδι",
"en_US": "Creates an independent configuration for the selected game",
"es_ES": "Crea una configuración independiente para el juego actual",
"fr_FR": "Crée une configuration indépendante pour le jeu en cours",
"he_IL": "יוצר תצורה עצמאית למשחק הנוכחי",
"it_IT": "Crea una configurazione indipendente per il gioco attuale",
"ja_JP": "現在のゲーム用の独立した設定を作成します",
"ko_KR": "현재 게임에 대한 독립적인 설정을 생성합니다",
"no_NO": "Oppretter en uavhengig konfigurasjon for det gjeldende spillet",
"pl_PL": "Tworzy niezależną konfigurację dla bieżącej gry",
"pt_BR": "Cria uma configuração independente para o jogo atual",
"ru_RU": "Создает независимую конфигурацию для текущей игры",
"sv_SE": "Skapar en oberoende konfiguration för det aktuella spelet",
"th_TH": "สร้างการกำหนดค่าที่เป็นอิสระสำหรับเกมปัจจุบัน",
"tr_TR": "Mevcut oyun için bağımsız bir yapılandırma oluşturur",
"uk_UA": "Створює незалежну конфігурацію для поточної гри",
"zh_CN": "为当前游戏创建独立的配置",
"zh_TW": "為當前遊戲創建獨立的配置"
}
},
{
"ID": "EditCustomConfigurationToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Edit your existing independent configuration for the selected game",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Отредактировать существующую независимую конфигурацию для выбранной игры.",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "GameListContextMenuShowCompatEntry", "ID": "GameListContextMenuShowCompatEntry",
"Translations": { "Translations": {
@ -2789,7 +2889,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Mostrar Dados de Compatibilidade", "pt_BR": "Mostrar Dados de Compatibilidade",
"ru_RU": "Показать записи о совместимости", "ru_RU": "Показать записи о совместимости",
"sv_SE": "", "sv_SE": "Visa kompatibilitetspost",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Iнформація про сумісність", "uk_UA": "Iнформація про сумісність",
@ -2814,7 +2914,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Exibe o jogo selecionado na Lista de Compatibilidade, que normalmente pode ser acessada pelo menu Ajuda.", "pt_BR": "Exibe o jogo selecionado na Lista de Compatibilidade, que normalmente pode ser acessada pelo menu Ajuda.",
"ru_RU": "Отобразить выбранную игру в списке совместимости, доступ к которому вы обычно можете получить через меню Справки.", "ru_RU": "Отобразить выбранную игру в списке совместимости, доступ к которому вы обычно можете получить через меню Справки.",
"sv_SE": "", "sv_SE": "Visa valt spel i kompatibilitetslistan som du normalt sett kan komma åt via hjälpmenyn.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Показати цю гру в Списку Сумісності. Список сумісності також можна зайти в меню Довідки.", "uk_UA": "Показати цю гру в Списку Сумісності. Список сумісності також можна зайти в меню Довідки.",
@ -2839,7 +2939,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Mostrar Informações do Jogo", "pt_BR": "Mostrar Informações do Jogo",
"ru_RU": "Показать информацию об игре", "ru_RU": "Показать информацию об игре",
"sv_SE": "", "sv_SE": "Visa spelinformation",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Інформація про гру", "uk_UA": "Інформація про гру",
@ -2864,7 +2964,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Exibe estatísticas e detalhes sobre o jogo selecionado.", "pt_BR": "Exibe estatísticas e detalhes sobre o jogo selecionado.",
"ru_RU": "Показывать статистику и подробную информацию о выбранной игре.", "ru_RU": "Показывать статистику и подробную информацию о выбранной игре.",
"sv_SE": "", "sv_SE": "Visa statistik och detaljer om det aktuella spelet.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Показати статистику та деталі обраної гри.", "uk_UA": "Показати статистику та деталі обраної гри.",
@ -3297,6 +3397,31 @@
"zh_TW": "設定" "zh_TW": "設定"
} }
}, },
{
"ID": "SettingsWithInfo",
"Translations": {
"ar_SA": "{0} - إعدادات",
"de_DE": "Einstellungen - {0}",
"el_GR": "Ρυθμίσεις - {0}",
"en_US": "Settings - {0}",
"es_ES": "Configuración - {0}",
"fr_FR": "Paramètres - {0}",
"he_IL": "{0} - הגדרות",
"it_IT": "Impostazioni - {0}",
"ja_JP": "設定 - {0}",
"ko_KR": "설정 - {0}",
"no_NO": "Innstillinger - {0}",
"pl_PL": "Ustawienia - {0}",
"pt_BR": "Configurações - {0}",
"ru_RU": "Параметры - {0}",
"sv_SE": "Inställningar - {0}",
"th_TH": "ตั้งค่า - {0}",
"tr_TR": "Ayarlar - {0}",
"uk_UA": "Налаштування - {0}",
"zh_CN": "设置 - {0}",
"zh_TW": "設定 - {0}"
}
},
{ {
"ID": "SettingsTabGeneral", "ID": "SettingsTabGeneral",
"Translations": { "Translations": {
@ -3389,7 +3514,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Verificar Atualizações:", "pt_BR": "Verificar Atualizações:",
"ru_RU": "Проверка наличия обновлений", "ru_RU": "Проверка наличия обновлений",
"sv_SE": "", "sv_SE": "Leta efter uppdateringar:",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Перевірка оновлень:", "uk_UA": "Перевірка оновлень:",
@ -3414,7 +3539,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Desligado", "pt_BR": "Desligado",
"ru_RU": "Отключить", "ru_RU": "Отключить",
"sv_SE": "", "sv_SE": "Av",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Вимкнути", "uk_UA": "Вимкнути",
@ -3439,7 +3564,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Ao Abrir", "pt_BR": "Ao Abrir",
"ru_RU": "При запуске", "ru_RU": "При запуске",
"sv_SE": "", "sv_SE": "Fråga",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Запитувати щоразу", "uk_UA": "Запитувати щоразу",
@ -3464,7 +3589,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "2° Plano", "pt_BR": "2° Plano",
"ru_RU": "В фоне", "ru_RU": "В фоне",
"sv_SE": "", "sv_SE": "Bakgrund",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Оновлювати в фоні", "uk_UA": "Оновлювати в фоні",
@ -3489,7 +3614,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Ao Perder o Foco:", "pt_BR": "Ao Perder o Foco:",
"ru_RU": "При выходе эмулятора из фокуса", "ru_RU": "При выходе эмулятора из фокуса",
"sv_SE": "", "sv_SE": "När emulatorn tappar fokus:",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "При втраті фокуса емулятором:", "uk_UA": "При втраті фокуса емулятором:",
@ -3514,7 +3639,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Não Fazer Nada", "pt_BR": "Não Fazer Nada",
"ru_RU": "Ничего не делать", "ru_RU": "Ничего не делать",
"sv_SE": "", "sv_SE": "Gör ingenting",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Нічого не робити", "uk_UA": "Нічого не робити",
@ -3539,7 +3664,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Bloquear Controles", "pt_BR": "Bloquear Controles",
"ru_RU": "Блокировать управление", "ru_RU": "Блокировать управление",
"sv_SE": "", "sv_SE": "Blockera inmatning",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Блокувати введення", "uk_UA": "Блокувати введення",
@ -3564,7 +3689,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Ficar Mudo", "pt_BR": "Ficar Mudo",
"ru_RU": "Отключить звук", "ru_RU": "Отключить звук",
"sv_SE": "", "sv_SE": "Stäng av ljudet",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Вимкнути звук", "uk_UA": "Вимкнути звук",
@ -3589,7 +3714,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Bloquear Controles & Ficar Mudo", "pt_BR": "Bloquear Controles & Ficar Mudo",
"ru_RU": "Блокировать управление и отключить звук", "ru_RU": "Блокировать управление и отключить звук",
"sv_SE": "", "sv_SE": "Blockera inmatningar och stäng av ljudet",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Блокувати введення та Вимкнути звук", "uk_UA": "Блокувати введення та Вимкнути звук",
@ -3614,7 +3739,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Pausar a Emulação", "pt_BR": "Pausar a Emulação",
"ru_RU": "Поставить паузу", "ru_RU": "Поставить паузу",
"sv_SE": "", "sv_SE": "Pausa emuleringen",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Поставити на паузу", "uk_UA": "Поставити на паузу",
@ -3689,7 +3814,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Desativar Controles Quando Estiver Fora de Foco", "pt_BR": "Desativar Controles Quando Estiver Fora de Foco",
"ru_RU": "Отключает управление при выходе из фокуса", "ru_RU": "Отключает управление при выходе из фокуса",
"sv_SE": "", "sv_SE": "Inaktivera inmatning när fokus tappas",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -4714,7 +4839,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Sincronizar com o Sistema PC", "pt_BR": "Sincronizar com o Sistema PC",
"ru_RU": "Соответствовать времени в системе", "ru_RU": "Соответствовать времени в системе",
"sv_SE": "", "sv_SE": "Matcha systemtid",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -4888,7 +5013,7 @@
"no_NO": "Lyd Inn/Ut", "no_NO": "Lyd Inn/Ut",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Выход/Вход звука",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
@ -5139,7 +5264,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Ignorar Applet do Controlador", "pt_BR": "Ignorar Applet do Controlador",
"ru_RU": "Игнорировать апплет контроллера", "ru_RU": "Игнорировать апплет контроллера",
"sv_SE": "", "sv_SE": "Ignorera kontroller-applet",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Ігнорувати Аплет Контролера", "uk_UA": "Ігнорувати Аплет Контролера",
@ -5989,7 +6114,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Habilitar Logs da IU", "pt_BR": "Habilitar Logs da IU",
"ru_RU": "Включить журнал интерфейса", "ru_RU": "Включить журнал интерфейса",
"sv_SE": "", "sv_SE": "Aktivera gränssnittsloggar",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Увімкнути журнали інтерфейсу", "uk_UA": "Увімкнути журнали інтерфейсу",
@ -6389,7 +6514,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Redefinir Configurações", "pt_BR": "Redefinir Configurações",
"ru_RU": "Сбросить настройки", "ru_RU": "Сбросить настройки",
"sv_SE": "", "sv_SE": "Nollställ inställningar",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Скинути налаштування", "uk_UA": "Скинути налаштування",
@ -6414,7 +6539,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Quero redefinir minhas configurações.", "pt_BR": "Quero redefinir minhas configurações.",
"ru_RU": "Я хочу сбросить свои настройки.", "ru_RU": "Я хочу сбросить свои настройки.",
"sv_SE": "", "sv_SE": "Jag vill nollställa mina inställningar.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Я хочу скинути налаштування.", "uk_UA": "Я хочу скинути налаштування.",
@ -6438,7 +6563,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Ок",
"sv_SE": "Ok", "sv_SE": "Ok",
"th_TH": "ตกลง", "th_TH": "ตกลง",
"tr_TR": "Tamam", "tr_TR": "Tamam",
@ -6888,7 +7013,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "Pro Kontroler", "pl_PL": "Pro Kontroler",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Pro контроллер",
"sv_SE": "", "sv_SE": "",
"th_TH": "โปรคอนโทรลเลอร์", "th_TH": "โปรคอนโทรลเลอร์",
"tr_TR": "Profesyonel Kumanda", "tr_TR": "Profesyonel Kumanda",
@ -8464,7 +8589,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Velocidade do Arco-íris", "pt_BR": "Velocidade do Arco-íris",
"ru_RU": "Скорость переливания", "ru_RU": "Скорость переливания",
"sv_SE": "", "sv_SE": "Regnbågshastighet",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
@ -12747,6 +12872,31 @@
"zh_TW": "正在下載更新..." "zh_TW": "正在下載更新..."
} }
}, },
{
"ID": "DialogRebooterMessage",
"Translations": {
"ar_SA": "من فضلك انتظر، المحاكي في طور إعادة التشغيل",
"de_DE": "Bitte warten Sie, der Emulator wird neu gestartet",
"el_GR": "Παρακαλώ περιμένετε, ο εξομοιωτής επανεκκινείται",
"en_US": "Please wait, the emulator is restarting",
"es_ES": "Por favor, espere, el emulador se está reiniciando",
"fr_FR": "Veuillez patienter, l'émulateur est en train de redémarrer",
"he_IL": "אנא המתן, המחקה מתארגן מחדש",
"it_IT": "Attendere prego, l'emulatore si sta riavviando",
"ja_JP": "お待ちください、エミュレーターが再起動しています",
"ko_KR": "잠시만 기다려 주세요, 에뮬레이터가 재시작 중입니다",
"no_NO": "Vennligst vent, emulatoren starter på nytt",
"pl_PL": "Proszę czekać, emulator jest w trakcie ponownego uruchamiania",
"pt_BR": "Por favor, aguarde, o emulador está reiniciando",
"ru_RU": "Пожалуйста, подождите, эмулятор перезапускается",
"sv_SE": "Vänligen vänta, emulatorn startar om",
"th_TH": "กรุณารอสักครู่, ตัวจำลองกำลังเริ่มใหม่",
"tr_TR": "Lütfen bekleyin, emülatör yeniden başlatılıyor",
"uk_UA": "Будь ласка, зачекайте, емулятор перезавантажується",
"zh_CN": "请稍等,模拟器正在重新启动",
"zh_TW": "請稍候,模擬器正在重新啟動"
}
},
{ {
"ID": "DialogUpdaterExtractionMessage", "ID": "DialogUpdaterExtractionMessage",
"Translations": { "Translations": {
@ -13764,7 +13914,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Você está prestes a limpar todos os dados PPTC de:\n\n{0}\n\nTem certeza de que deseja continuar?", "pt_BR": "Você está prestes a limpar todos os dados PPTC de:\n\n{0}\n\nTem certeza de que deseja continuar?",
"ru_RU": "Вы собираетесь удалить все данные PPTC из:\n\n{0}\n\nВы уверены, что хотите продолжить?", "ru_RU": "Вы собираетесь удалить все данные PPTC из:\n\n{0}\n\nВы уверены, что хотите продолжить?",
"sv_SE": "", "sv_SE": "Du är på väg att ta bort allt PPTC-data från:\n\n{0}\n\nÄr du säker på att du vill fortsätta?",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Ви збираєтесь видалити всі дані PPTC з:\n\n{0}\n\nБажаєте продовжити цю операцію?", "uk_UA": "Ви збираєтесь видалити всі дані PPTC з:\n\n{0}\n\nБажаєте продовжити цю операцію?",
@ -16614,7 +16764,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "A caixa de diálogo do Applet do controlador não aparecerá se o controle for desconectado enquanto um aplicativo estiver em execução.\n\nDeixe a opção DESLIGADO se não tiver certeza.", "pt_BR": "A caixa de diálogo do Applet do controlador não aparecerá se o controle for desconectado enquanto um aplicativo estiver em execução.\n\nDeixe a opção DESLIGADO se não tiver certeza.",
"ru_RU": "Диалоговое окно апплета контроллера не будет отображаться, если геймпад отключен во время работы приложения.\n\nОставьте выключенным, если не уверены.", "ru_RU": "Диалоговое окно апплета контроллера не будет отображаться, если геймпад отключен во время работы приложения.\n\nОставьте выключенным, если не уверены.",
"sv_SE": "", "sv_SE": "Handkontroller-appleten kommer inte att visas om gamepaden är frånkopplad under tiden en applikation körs.\n\nLämna AV om du är osäker.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.", "uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.",
@ -17089,7 +17239,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Imprime mensagens de log do Avalonia (UI) no console.", "pt_BR": "Imprime mensagens de log do Avalonia (UI) no console.",
"ru_RU": "Выводит сообщения журнала Avalonia (интерфейс) в консоли.", "ru_RU": "Выводит сообщения журнала Avalonia (интерфейс) в консоли.",
"sv_SE": "", "sv_SE": "Skriver ut loggmeddelanden från Avalonia (användargränssnittet) i konsollen.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Виводити повідомлення журналу Avalonia (UI) в консоль", "uk_UA": "Виводити повідомлення журналу Avalonia (UI) в консоль",
@ -17289,7 +17439,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Abre a pasta de capturas de tela do Ryujinx", "pt_BR": "Abre a pasta de capturas de tela do Ryujinx",
"ru_RU": "Открывает папку скриншотов Ryujinx", "ru_RU": "Открывает папку скриншотов Ryujinx",
"sv_SE": "", "sv_SE": "Öppna Ryujinx skärmbildsmapp",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx", "uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx",
@ -18039,7 +18189,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Atualização Disponível!", "pt_BR": "Atualização Disponível!",
"ru_RU": "Доступно обновление!", "ru_RU": "Доступно обновление!",
"sv_SE": "", "sv_SE": "Uppdatering finns tillgänglig!",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Доступне оновлення!", "uk_UA": "Доступне оновлення!",
@ -19622,6 +19772,31 @@
"zh_TW": "{0} 更新程式" "zh_TW": "{0} 更新程式"
} }
}, },
{
"ID": "RyujinxRebooter",
"Translations": {
"ar_SA": "إعادة تشغيل {0}",
"de_DE": "Neustart von {0}",
"el_GR": "Επανεκκίνηση {0}",
"en_US": "{0} Reboot",
"es_ES": "Reinicio de {0}",
"fr_FR": "Redémarrage de {0}",
"he_IL": "אתחול {0}",
"it_IT": "Riavvio di {0}",
"ja_JP": "{0} 再起動",
"ko_KR": "{0} 재부팅",
"no_NO": "Omstart av {0}",
"pl_PL": "Ponowne uruchomienie {0}",
"pt_BR": "Reinício de {0}",
"ru_RU": "{0} Перезагрузка",
"sv_SE": "Ominläsning av {0}",
"th_TH": "เริ่มต้นใหม่ {0}",
"tr_TR": "{0} Yeniden Başlatma",
"uk_UA": "Перезавантаження {0}",
"zh_CN": "{0} 重启",
"zh_TW": "{0} 重新啟動"
}
},
{ {
"ID": "SettingsTabHotkeys", "ID": "SettingsTabHotkeys",
"Translations": { "Translations": {
@ -19938,7 +20113,7 @@
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
"ru_RU": "", "ru_RU": "Амибо",
"sv_SE": "", "sv_SE": "",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
@ -23989,7 +24164,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Inicializa e roda sem travamentos ou bugs de GPU de qualquer tipo, e em uma velocidade rápida o suficiente para ser aproveitado em um PC comum.", "pt_BR": "Inicializa e roda sem travamentos ou bugs de GPU de qualquer tipo, e em uma velocidade rápida o suficiente para ser aproveitado em um PC comum.",
"ru_RU": "Запускается и работает без любого рода сбоев или графисечких ошибок и на скорости, достаточной для работы на обычном ПК.", "ru_RU": "Запускается и работает без любого рода сбоев или графисечких ошибок и на скорости, достаточной для работы на обычном ПК.",
"sv_SE": "", "sv_SE": "Startar upp och spelas utan några krascher eller GPU-fel av några slag och med en hastighet som är snabb nog för bra upplevelse på en genomsnittlig PC.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Запускається та оптимально працює (без збоїв або графічних багів) на середньостатистичному комп'ютері.", "uk_UA": "Запускається та оптимально працює (без збоїв або графічних багів) на середньостатистичному комп'ютері.",
@ -24014,7 +24189,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Inicializa e entra no jogo, mas sofre de um ou mais dos seguintes: travamentos, deadlocks, bugs de GPU, áudio ruim que distrai ou é simplesmente muito lento. O jogo ainda pode ser jogado até o fim, mas não da forma como foi criado para ser jogado.", "pt_BR": "Inicializa e entra no jogo, mas sofre de um ou mais dos seguintes: travamentos, deadlocks, bugs de GPU, áudio ruim que distrai ou é simplesmente muito lento. O jogo ainda pode ser jogado até o fim, mas não da forma como foi criado para ser jogado.",
"ru_RU": "Запускается и работает, но возникает одна или несколько из следующих проблем: сбои, взаимоблокировки, ошибки GPU, отвлекающие звуки или просто слишком медленная работа. Возможно, игру всё же удастся пройти до конца, но не так, как она задумана.", "ru_RU": "Запускается и работает, но возникает одна или несколько из следующих проблем: сбои, взаимоблокировки, ошибки GPU, отвлекающие звуки или просто слишком медленная работа. Возможно, игру всё же удастся пройти до конца, но не так, как она задумана.",
"sv_SE": "", "sv_SE": "Startar och går in i spelet men lider av ett eller flera av följande: kraschar, deadlocks, GPU-buggar, distraherande dåligt ljud eller är helt enkelt för långsamt. Spelet kan fortfarande spelas hela vägen igenom, men inte så som spelet är avsett att spelas.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Запускається, але в грі на вас чекатимуть одна або декілька наступних проблем: збої, зависання, графічні баги, спотворений звук або ж гра загалом працюватиме надто повільно. Можливо, її все ще можна пройти, але досвід буде не найкращим.", "uk_UA": "Запускається, але в грі на вас чекатимуть одна або декілька наступних проблем: збої, зависання, графічні баги, спотворений звук або ж гра загалом працюватиме надто повільно. Можливо, її все ще можна пройти, але досвід буде не найкращим.",
@ -24039,7 +24214,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Inicializa e passa da tela de título, mas não entra no jogo principal.", "pt_BR": "Inicializa e passa da tela de título, mas não entra no jogo principal.",
"ru_RU": "Загружается титульный экран и можно перейти дальше, но сама игра не работает.", "ru_RU": "Загружается титульный экран и можно перейти дальше, но сама игра не работает.",
"sv_SE": "", "sv_SE": "Startar upp och går förbi titelskärmen men tar sig inte in i huvudspelet.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Запускається та проходить початковий екран, але пограти не вийде.", "uk_UA": "Запускається та проходить початковий екран, але пограти не вийде.",
@ -24064,7 +24239,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Inizializa, mas não passa da tela de título.", "pt_BR": "Inizializa, mas não passa da tela de título.",
"ru_RU": "Загружается, но не проходит дальше титульного экрана.", "ru_RU": "Загружается, но не проходит дальше титульного экрана.",
"sv_SE": "", "sv_SE": "Startar upp men tar sig inte förbi titelskärmen.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Запускається, але не відображає навіть початкового екрану.", "uk_UA": "Запускається, але не відображає навіть початкового екрану.",
@ -24089,7 +24264,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Não inicializa ou não mostra sinais de atividade.", "pt_BR": "Não inicializa ou não mostra sinais de atividade.",
"ru_RU": "Не запускается или не подаёт признаков жизни.", "ru_RU": "Не запускается или не подаёт признаков жизни.",
"sv_SE": "", "sv_SE": "Startar inte upp eller visar någon form av aktivitet.",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Взагалі не запускається.", "uk_UA": "Взагалі не запускається.",
@ -24097,6 +24272,56 @@
"zh_TW": "" "zh_TW": ""
} }
}, },
{
"ID": "GameSpecificConfigurationHeader",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Custom Config",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "GameSpecificConfigurationGlobal",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "(Global)",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "ExtractAocListHeader", "ID": "ExtractAocListHeader",
"Translations": { "Translations": {
@ -24139,7 +24364,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Imagem da Presença do Discord", "pt_BR": "Imagem da Presença do Discord",
"ru_RU": "Изображение для статуса активности", "ru_RU": "Изображение для статуса активности",
"sv_SE": "", "sv_SE": "Bild för Rich Presence",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Зображення картки активності Discord", "uk_UA": "Зображення картки активності Discord",
@ -24164,7 +24389,7 @@
"pl_PL": "", "pl_PL": "",
"pt_BR": "Presença Dinâmica do Discord", "pt_BR": "Presença Dinâmica do Discord",
"ru_RU": "Динамический статус активности", "ru_RU": "Динамический статус активности",
"sv_SE": "", "sv_SE": "Dynamisk Rich Presence",
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Динамічна картка активності Discord", "uk_UA": "Динамічна картка активності Discord",

View file

@ -54,6 +54,7 @@ namespace Ryujinx.Ava.Common.Locale
SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxRebooter, RyujinxApp.FullAppName);
} }
public string this[LocaleKeys key] public string this[LocaleKeys key]

View file

@ -32,8 +32,10 @@ namespace Ryujinx.Ava
public static double DesktopScaleFactor { get; set; } = 1.0; public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; } public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; } public static string ConfigurationPath { get; private set; }
public static string GlobalConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; } public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)] [LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
@ -156,11 +158,38 @@ namespace Ryujinx.Ava
} }
} }
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))
{
return "";
}
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
// Should load with the game if there is a custom setting for the game
if (rememberGlobalDir)
{
GlobalConfigurationPath = ConfigurationPath;
}
if (changeFolderForGame)
{
ConfigurationPath = gameDir;
}
return gameDir;
}
public static void ReloadConfig() public static void ReloadConfig()
{ {
//It is necessary that when a user setting appears, the global setting remains available
GlobalConfigurationPath = null;
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered // Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath)) if (File.Exists(localConfigurationPath))
{ {
@ -217,6 +246,11 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.Graphics.BackendThreading _ => ConfigurationState.Instance.Graphics.BackendThreading
}; };
if (CommandLineState.OverrideBackendThreadingAfterReboot is not null)
{
BackendThreadingArg = CommandLineState.OverrideBackendThreadingAfterReboot;
}
// Check if docked mode was overriden. // Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue) if (CommandLineState.OverrideDockedMode.HasValue)
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
@ -232,6 +266,33 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.HideCursor, _ => ConfigurationState.Instance.HideCursor,
}; };
// Check if memoryManagerMode was overridden.
if (CommandLineState.OverrideMemoryManagerMode is not null)
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
{
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
}
// Check if PPTC was overridden.
if (CommandLineState.OverridePPTC is not null)
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
{
ConfigurationState.Instance.System.EnablePtc.Value = result;
}
// Check if region was overridden.
if (CommandLineState.OverrideSystemRegion is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Ryujinx.HLE.HOS.SystemState.RegionCode result))
{
ConfigurationState.Instance.System.Region.Value = (Utilities.Configuration.System.Region)result;
}
//Check if language was overridden.
if (CommandLineState.OverrideSystemLanguage is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Ryujinx.HLE.HOS.SystemState.SystemLanguage result))
{
ConfigurationState.Instance.System.Language.Value = (Utilities.Configuration.System.Language)result;
}
// Check if hardware-acceleration was overridden. // Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null) if (CommandLineState.OverrideHardwareAcceleration != null)

76
src/Ryujinx/Rebooter.cs Normal file
View file

@ -0,0 +1,76 @@
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal static class Rebooter
{
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
public static void RebootAppWithGame(string gamePath, List<string> args)
{
_ = Reboot(gamePath, args);
}
private static async Task Reboot(string gamePath, List<string> args)
{
bool shouldRestart = true;
TaskDialog taskDialog = new()
{
Header = LocaleManager.Instance[LocaleKeys.RyujinxRebooter],
SubHeader = LocaleManager.Instance[LocaleKeys.DialogRebooterMessage],
IconSource = new SymbolIconSource { Symbol = Symbol.Games },
XamlRoot = RyujinxApp.MainWindow,
};
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
var dialogTask = taskDialog.ShowAsync(true);
await Task.Delay(500);
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
// Fallback if the executable could not be found.
if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (var arg in args)
{
processStart.ArgumentList.Add(arg);
}
processStart.ArgumentList.Add(gamePath);
Process.Start(processStart);
Environment.Exit(0);
}
}
}
}

View file

@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.Applet
_parent.SettingsWindow = _parent.SettingsWindow =
new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager); new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
await _parent.SettingsWindow.ShowDialog(window); await StyleableAppWindow.ShowAsync(_parent.SettingsWindow, window);
_parent.SettingsWindow = null; _parent.SettingsWindow = null;

View file

@ -19,6 +19,18 @@
Header="{ext:Locale GameListContextMenuCreateShortcut}" Header="{ext:Locale GameListContextMenuCreateShortcut}"
Icon="{ext:Icon fa-solid fa-bookmark}" Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<MenuItem
Click="EditGameConfiguration_Click"
IsVisible="{Binding SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuEditCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
<MenuItem
Click="EditGameConfiguration_Click"
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
<MenuItem <MenuItem
IsVisible="{Binding HasCompatibilityEntry}" IsVisible="{Binding HasCompatibilityEntry}"
Click="OpenApplicationCompatibility_Click" Click="OpenApplicationCompatibility_Click"

View file

@ -91,11 +91,14 @@ namespace Ryujinx.Ava.UI.Controls
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args) public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{ {
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await new CheatWindow( await StyleableAppWindow.ShowAsync(
viewModel.VirtualFileSystem, new CheatWindow(
viewModel.SelectedApplication.IdString, viewModel.VirtualFileSystem,
viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel); viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path
)
);
} }
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args) public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
@ -200,7 +203,7 @@ namespace Ryujinx.Ava.UI.Controls
if (backupDir.Exists) if (backupDir.Exists)
{ {
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
cacheFiles.AddRange(mainDir.EnumerateFiles("*.info")); cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
} }
if (cacheFiles.Count > 0) if (cacheFiles.Count > 0)
@ -387,6 +390,19 @@ namespace Ryujinx.Ava.UI.Controls
); );
} }
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
// just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();
}
}
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args) public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
{ {
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

View file

@ -7,6 +7,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
Focusable="True" Focusable="True"
@ -73,12 +74,18 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}"> IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<TextBlock <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
HorizontalAlignment="Center" <TextBlock
VerticalAlignment="Center" Text="{Binding Name}"
Text="{Binding Name}" TextAlignment="Center"
TextAlignment="Center" TextWrapping="Wrap" />
TextWrapping="Wrap" /> <TextBlock
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
</Panel> </Panel>
</Grid> </Grid>
</Border> </Border>
@ -86,10 +93,28 @@
Margin="5,5,0,0" Margin="5,5,0,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Top" VerticalAlignment="Top"
FontSize="16" FontSize="18"
Foreground="{DynamicResource FavoriteApplicationIconColor}" Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}" IsVisible="{Binding Favorite}"
Symbol="StarFilled" /> Symbol="StarFilled" />
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<Border
Margin="15,35,5,15"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="90"
Height="20"
CornerRadius="4"
IsVisible="{Binding HasIndependentConfiguration}"
Background="{DynamicResource ThemeContentBackgroundColor}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Grid>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>

View file

@ -6,6 +6,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
Focusable="True" Focusable="True"
@ -156,6 +157,13 @@
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}" Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
TextAlignment="Start" TextAlignment="Start"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<TextBlock
HorizontalAlignment="Stretch"
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Start"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Column="4" Grid.Column="4"

View file

@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
@ -385,6 +386,10 @@ namespace Ryujinx.Ava.UI.Helpers
ShowInTaskbar = false, ShowInTaskbar = false,
}; };
#if DEBUG
_contentDialogOverlayWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
parent.PositionChanged += OverlayOnPositionChanged; parent.PositionChanged += OverlayOnPositionChanged;
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)

View file

@ -356,6 +356,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_ => null, _ => null,
}; };
} }
set
{
ListSelectedApplication = value;
GridSelectedApplication = value;
}
} }
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo; public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
@ -1551,8 +1556,50 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public bool InitializeUserConfig(ApplicationData application)
{
// Code where conditions will be met before loading the user configuration (Global Config)
BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
string BackendThreadingInit = Program.BackendThreadingArg;
if (BackendThreadingInit is null)
{
BackendThreadingInit = ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
}
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
string idGame = application.IdBaseString;
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
{
// Loads the user configuration, having previously changed the global configuration to the user configuration
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
}
// Code where conditions will be executed after loading user configuration
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
{
List<string> Arguments = new List<string>
{
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
};
Rebooter.RebootAppWithGame(application.Path, Arguments);
return true;
}
return false;
}
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null) public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
{ {
if (InitializeUserConfig(application))
{
return;
}
if (AppHost != null) if (AppHost != null)
{ {
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
@ -1613,6 +1660,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start(); gameThread.Start();
} }
public void SwitchToRenderer(bool startFullscreen) => public void SwitchToRenderer(bool startFullscreen) =>
@ -1699,7 +1747,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId); AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId);
await window.ShowDialog(Window); await StyleableAppWindow.ShowAsync(window);
if (window.IsScanned) if (window.IsScanned)
{ {

View file

@ -1,5 +1,6 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@ -27,6 +28,7 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -68,6 +70,19 @@ namespace Ryujinx.Ava.UI.ViewModels
public SettingsHacksViewModel DirtyHacks { get; } public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
private Bitmap _gameIcon;
private string _gameTitle;
private string _gamePath;
private string _gameId;
public bool IsGameRunning => _isGameRunning;
public Bitmap GameIcon => _gameIcon;
public string GamePath => _gamePath;
public string GameTitle => _gameTitle;
public string GameId => _gameId;
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
public int ResolutionScale public int ResolutionScale
{ {
get => _resolutionScale; get => _resolutionScale;
@ -335,7 +350,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; } public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_contentManager = contentManager; _contentManager = contentManager;
@ -348,7 +363,51 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public SettingsViewModel() public SettingsViewModel(
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
bool gameRunning,
string gamePath,
string gameName,
string gameId,
byte[] gameIconData,
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
if (gameIconData != null && gameIconData.Length > 0)
{
using (var ms = new MemoryStream(gameIconData))
{
_gameIcon = new Bitmap(ms);
}
}
_isGameRunning = gameRunning;
_gamePath = gamePath;
_gameTitle = gameName;
_gameId = gameId;
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
{
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
}
LoadCurrentConfiguration(); // Needed to load custom configuration
}
if (Program.PreviewerDetached)
{
Task.Run(LoadTimeZones);
}
}
public SettingsViewModel(bool noLoadGlobalConfig = false)
{ {
GameDirectories = []; GameDirectories = [];
AutoloadDirectories = []; AutoloadDirectories = [];
@ -363,7 +422,9 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
Task.Run(LoadAvailableGpus); Task.Run(LoadAvailableGpus);
LoadCurrentConfiguration();
// if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call
LoadCurrentConfiguration();
DirtyHacks = new SettingsHacksViewModel(this); DirtyHacks = new SettingsHacksViewModel(this);
} }
@ -592,8 +653,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.HideCursor.Value = (HideCursorMode)HideCursor; config.HideCursor.Value = (HideCursorMode)HideCursor;
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType; config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType; config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
config.UI.GameDirs.Value = [..GameDirectories]; config.UI.GameDirs.Value = [.. GameDirectories];
config.UI.AutoloadDirs.Value = [..AutoloadDirectories]; config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
config.UI.BaseStyle.Value = BaseStyleIndex switch config.UI.BaseStyle.Value = BaseStyleIndex switch
{ {
@ -712,7 +773,11 @@ namespace Ryujinx.Ava.UI.ViewModels
private static void RevertIfNotSaved() private static void RevertIfNotSaved()
{ {
Program.ReloadConfig(); // maybe this is an unnecessary check(all options need to be tested)
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
{
Program.ReloadConfig();
}
} }
public void ApplyButton() public void ApplyButton()
@ -720,6 +785,26 @@ namespace Ryujinx.Ava.UI.ViewModels
SaveSettings(); SaveSettings();
} }
public void DeleteConfigGame()
{
string gameDir = Program.GetDirGameUserConfig(GameId,false,false);
if (File.Exists(gameDir))
{
File.Delete(gameDir);
}
RevertIfNotSaved();
CloseWindow?.Invoke();
}
public void SaveUserConfig()
{
SaveSettings();
RevertIfNotSaved(); // Revert global configuration after saving user configuration
CloseWindow?.Invoke();
}
public void OkButton() public void OkButton()
{ {
SaveSettings(); SaveSettings();

View file

@ -19,6 +19,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -131,7 +132,24 @@ namespace Ryujinx.Ava.UI.Views.Main
Rainbow.Enable(); Rainbow.Enable();
await Window.SettingsWindow.ShowDialog(Window); if (ViewModel.SelectedApplication is null) // Checks if game data exists
{
await StyleableAppWindow.ShowAsync(Window.SettingsWindow);
}
else
{
bool customConfigExists = File.Exists(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString));
if (!ViewModel.IsGameRunning || !customConfigExists)
{
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
}
else
{
// If there is a custom configuration in the folder
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(ViewModel, customConfigExists));
}
}
Rainbow.Disable(); Rainbow.Disable();
Rainbow.Reset(); Rainbow.Reset();
@ -158,11 +176,13 @@ namespace Ryujinx.Ava.UI.Views.Main
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow( await StyleableAppWindow.ShowAsync(
Window.VirtualFileSystem, new CheatWindow(
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, Window.VirtualFileSystem,
name, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
ViewModel.SelectedApplication.Path).ShowDialog(Window); name,
ViewModel.SelectedApplication.Path)
);
ViewModel.AppHost.Device.EnableCheats(); ViewModel.AppHost.Device.EnableCheats();
} }

View file

@ -156,6 +156,8 @@
ValueMemberBinding="{Binding Mode=OneWay, Converter={x:Static helpers:TimeZoneConverter.Instance}}" /> ValueMemberBinding="{Binding Mode=OneWay, Converter={x:Static helpers:TimeZoneConverter.Instance}}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0,0,0,10" Margin="0,0,0,10"
Orientation="Horizontal"> Orientation="Horizontal">
<TextBlock <TextBlock
@ -169,8 +171,11 @@
SelectedDate="{Binding CurrentDate}" SelectedDate="{Binding CurrentDate}"
ToolTip.Tip="{ext:Locale TimeTooltip}" ToolTip.Tip="{ext:Locale TimeTooltip}"
Width="350" /> Width="350" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="250,0,0,10" Margin="250,0,0,10"
Orientation="Horizontal"> Orientation="Horizontal">
<TimePicker <TimePicker
@ -180,8 +185,12 @@
SelectedTime="{Binding CurrentTime}" SelectedTime="{Binding CurrentTime}"
Width="350" Width="350"
ToolTip.Tip="{ext:Locale TimeTooltip}" /> ToolTip.Tip="{ext:Locale TimeTooltip}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Orientation="Horizontal">
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{ext:Locale SettingsTabSystemSystemTimeMatch}" Text="{ext:Locale SettingsTabSystemSystemTimeMatch}"
@ -191,6 +200,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding MatchSystemTime}" IsChecked="{Binding MatchSystemTime}"
ToolTip.Tip="{ext:Locale MatchTimeTooltip}"/> ToolTip.Tip="{ext:Locale MatchTimeTooltip}"/>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel> </StackPanel>
<Separator /> <Separator />
<StackPanel Margin="0,10,0,10" <StackPanel Margin="0,10,0,10"

View file

@ -27,20 +27,42 @@
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGeneral}" /> <TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGeneral}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical"> <StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDiscordIntegration}"> <CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<TextBlock VerticalAlignment="Center" <StackPanel Orientation="Horizontal">
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}" <TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" /> ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</StackPanel>
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}"> <CheckBox
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" /> IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
IsChecked="{Binding ShowConfirmExit}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding RememberWindowState}"> <CheckBox
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" /> IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
IsChecked="{Binding RememberWindowState}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}"> <CheckBox
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" /> IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox> </CheckBox>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal"> <StackPanel
Margin="0, 15, 0, 0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralFocusLossType}" Text="{ext:Locale SettingsTabGeneralFocusLossType}"
Width="150" /> Width="150" />
@ -64,7 +86,11 @@
</ComboBoxItem> </ComboBoxItem>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal"> <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0, 15, 0, 0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
Width="150" /> Width="150" />
@ -81,8 +107,11 @@
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" /> <TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
</ComboBoxItem> </ComboBoxItem>
</ComboBox> </ComboBox>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel> </StackPanel>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal"> <StackPanel
Margin="0, 15, 0, 0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralHideCursor}" Text="{ext:Locale SettingsTabGeneralHideCursor}"
Width="150" /> Width="150" />
@ -100,7 +129,11 @@
</ComboBoxItem> </ComboBoxItem>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal"> <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0, 15, 0, 10"
Orientation="Horizontal">
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralTheme}" Text="{ext:Locale SettingsTabGeneralTheme}"
@ -118,11 +151,17 @@
<TextBlock Text="{ext:Locale SettingsTabGeneralThemeDark}" /> <TextBlock Text="{ext:Locale SettingsTabGeneralThemeDark}" />
</ComboBoxItem> </ComboBoxItem>
</ComboBox> </ComboBox>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<Separator Height="1" /> <Separator Height="1" />
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" /> <StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<StackPanel <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="10,0,0,0" Margin="10,0,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Orientation="Vertical" Orientation="Vertical"
@ -172,10 +211,15 @@
</StackPanel> </StackPanel>
<Separator Height="1" /> <Separator Height="1" />
<StackPanel Orientation="Vertical" Spacing="5"> <StackPanel Orientation="Vertical" Spacing="5">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" /> <StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabGeneralAutoloadNote}" /> <TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabGeneralAutoloadNote}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="10,0,0,0" Margin="10,0,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Orientation="Vertical" Orientation="Vertical"

View file

@ -0,0 +1,155 @@
<window:StyleableAppWindow
x:Class="Ryujinx.Ava.UI.Windows.GameSpecificSettingsWindow"
xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="910"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
<ContentPresenter
x:Name="ContentPresenter"
Grid.Row="1"
IsVisible="False"
KeyboardNavigation.IsTabStop="False"/>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<settings:SettingsUiView Name="UiPage" />
<settings:SettingsInputView Name="InputPage" />
<settings:SettingsSystemView Name="SystemPage" />
<settings:SettingsCPUView Name="CpuPage" />
<settings:SettingsGraphicsView Name="GraphicsPage" />
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsHacksView Name="HacksPage" />
</Grid>
<ui:NavigationView
Grid.Row="1"
IsSettingsVisible="False"
Name="NavPanel"
IsBackEnabled="False"
PaneDisplayMode="Left"
Margin="2,10,10,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
OpenPaneLength="200"
IsPaneToggleButtonVisible="False">
<!-- For image -->
<ui:NavigationView.PaneHeader>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding GameId}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="0" />
<Image Source="{Binding GameIcon}"
Width="160"
Height="160"
Grid.Row="1"
Margin="0,0,0,10"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding GameTitle}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="2" />
<Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
</Grid>
</ui:NavigationView.PaneHeader>
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem
IsSelected="True"
Content="{ext:Locale SettingsTabGeneral}"
Tag="UiPage"
IconSource="New" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabInput}"
Tag="InputPage"
IconSource="Games" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabSystem}"
Tag="SystemPage"
IconSource="Settings" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabCpu}"
Tag="CpuPage">
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Chip}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabGraphics}"
Tag="GraphicsPage"
IconSource="Image" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabAudio}"
IconSource="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabNetwork}"
Tag="NetworkPage"
IconSource="Globe" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabLogging}"
Tag="LoggingPage"
IconSource="Document" />
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"
Tag="HacksPage"
IconSource="Code" />
</ui:NavigationView.MenuItems>
</ui:NavigationView>
<ReversibleStackPanel
Grid.Row="2"
Margin="10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right"
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
<Button
Content="{ext:Locale SettingsButtonSave}"
Command="{Binding SaveUserConfig}" />
<Button
HotKey="Escape"
Content="{ext:Locale SettingsButtonClose}"
Command="{Binding CancelButton}" />
<Button
IsVisible="{Binding IsGameRunning}"
Content="{ext:Locale SettingsButtonApply}"
Command="{Binding ApplyButton}" />
<Button
IsVisible="{Binding !IsGameRunning}"
Content="{ext:Locale UserProfilesDelete}"
Command="{Binding DeleteConfigGame}"
Classes="red"/>
</ReversibleStackPanel>
</Grid>
</window:StyleableAppWindow>

View file

@ -0,0 +1,115 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using System;
using System.IO;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
public partial class GameSpecificSettingsWindow : StyleableAppWindow
{
internal readonly SettingsViewModel ViewModel;
public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true)
{
Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
DataContext = ViewModel = new SettingsViewModel(
viewModel.VirtualFileSystem,
viewModel.ContentManager,
viewModel.IsGameRunning,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon,
findUserConfigDir);
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
InitializeComponent();
Load();
}
public void SaveSettings()
{
InputPage.InputView?.SaveCurrentProfile();
}
private void Load()
{
Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
{
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
{
switch (navItem.Tag.ToString())
{
case nameof(UiPage):
UiPage.ViewModel = ViewModel;
NavPanel.Content = UiPage;
break;
case nameof(InputPage):
NavPanel.Content = InputPage;
break;
case nameof(SystemPage):
SystemPage.ViewModel = ViewModel;
NavPanel.Content = SystemPage;
break;
case nameof(CpuPage):
NavPanel.Content = CpuPage;
break;
case nameof(GraphicsPage):
NavPanel.Content = GraphicsPage;
break;
case nameof(AudioPage):
NavPanel.Content = AudioPage;
break;
case nameof(NetworkPage):
NetworkPage.ViewModel = ViewModel;
NavPanel.Content = NetworkPage;
break;
case nameof(LoggingPage):
NavPanel.Content = LoggingPage;
break;
case nameof(HacksPage):
HacksPage.DataContext = ViewModel;
NavPanel.Content = HacksPage;
break;
default:
throw new NotImplementedException();
}
}
}
protected override void OnClosing(WindowClosingEventArgs e)
{
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
base.OnClosing(e);
}
}
}

View file

@ -385,6 +385,7 @@ namespace Ryujinx.Ava.UI.Windows
if (applicationData != null) if (applicationData != null)
{ {
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen); await ViewModel.LoadApplication(applicationData, _startFullscreen);
} }
else else
@ -396,6 +397,7 @@ namespace Ryujinx.Ava.UI.Windows
else else
{ {
applicationData = applications[0]; applicationData = applications[0];
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen); await ViewModel.LoadApplication(applicationData, _startFullscreen);
} }
} }

View file

@ -12,7 +12,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common" xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100" Width="1100"
Height="768" Height="918"
MinWidth="800" MinWidth="800"
MinHeight="480" MinHeight="480"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"

View file

@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
@ -8,7 +6,6 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Linq; using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
@ -27,10 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent(); InitializeComponent();
Load(); Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
} }
public SettingsWindow() public SettingsWindow()

View file

@ -1,15 +1,26 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using FluentAvalonia.UI.Windowing; using FluentAvalonia.UI.Windowing;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
public abstract class StyleableAppWindow : AppWindow public abstract class StyleableAppWindow : AppWindow
{ {
public static async Task ShowAsync(StyleableAppWindow appWindow, Window owner = null)
{
#if DEBUG
appWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await appWindow.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableAppWindow() protected StyleableAppWindow()
{ {
WindowStartupLocation = WindowStartupLocation.CenterOwner; WindowStartupLocation = WindowStartupLocation.CenterOwner;
@ -36,6 +47,14 @@ namespace Ryujinx.Ava.UI.Windows
public abstract class StyleableWindow : Window public abstract class StyleableWindow : Window
{ {
public static async Task ShowAsync(StyleableWindow window, Window owner = null)
{
#if DEBUG
window.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await window.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableWindow() protected StyleableWindow()
{ {
WindowStartupLocation = WindowStartupLocation.CenterOwner; WindowStartupLocation = WindowStartupLocation.CenterOwner;

View file

@ -44,16 +44,8 @@ namespace Ryujinx.Ava
private static string _buildVer; private static string _buildVer;
private static readonly string _platformExt =
RunningPlatform.IsMacOS private static readonly string _platformExt = BuildPlatformExtension();
? "macos_universal.app.tar.gz"
: RunningPlatform.IsWindows
? "win_x64.zip"
: RunningPlatform.IsX64Linux
? "linux_x64.tar.gz"
: RunningPlatform.IsArmLinux
? "linux_arm64.tar.gz"
: throw new PlatformNotSupportedException();
private static string _buildUrl; private static string _buildUrl;
private static long _buildSize; private static long _buildSize;
@ -780,5 +772,34 @@ namespace Ryujinx.Ava
public static void CleanupUpdate() => public static void CleanupUpdate() =>
Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories) Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)
.ForEach(File.Delete); .ForEach(File.Delete);
private static string BuildPlatformExtension()
{
if (RunningPlatform.IsMacOS)
return "macos_universal.app.tar.gz";
#pragma warning disable CS8509 // It is exhaustive for any values this can contain.
string osPrefix = RunningPlatform.CurrentOS switch
{
OperatingSystemType.Linux => "linux",
OperatingSystemType.Windows => "win"
};
string archSuffix = RunningPlatform.Architecture switch
{
Architecture.Arm64 => "arm64",
Architecture.X64 => "x64",
_ => throw new PlatformNotSupportedException($"Unknown architecture {Enum.GetName(RunningPlatform.Architecture)}."),
};
string fileExtension = RunningPlatform.CurrentOS switch
#pragma warning restore CS8509
{
OperatingSystemType.Linux => "tar.gz",
OperatingSystemType.Windows => "zip"
};
return $"{osPrefix}_{archSuffix}.{fileExtension}";
}
} }
} }

View file

@ -23,6 +23,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public class ApplicationData public class ApplicationData
{ {
public bool Favorite { get; set; } public bool Favorite { get; set; }
public bool HasIndependentConfiguration { get; set; }
public byte[] Icon { get; set; } public byte[] Icon { get; set; }
public string Name { get; set; } = "Unknown"; public string Name { get; set; } = "Unknown";

View file

@ -532,6 +532,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
data.Favorite = appMetadata.Favorite; data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed; data.TimePlayed = appMetadata.TimePlayed;
data.LastPlayed = appMetadata.LastPlayed; data.LastPlayed = appMetadata.LastPlayed;
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
} }
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

View file

@ -7,11 +7,16 @@ namespace Ryujinx.Ava.Utilities
public static class CommandLineState public static class CommandLineState
{ {
public static string[] Arguments { get; private set; } public static string[] Arguments { get; private set; }
public static int CountArguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; } public static bool? OverrideDockedMode { get; private set; }
public static bool? OverrideHardwareAcceleration { get; private set; } public static bool? OverrideHardwareAcceleration { get; private set; }
public static string OverrideGraphicsBackend { get; private set; } public static string OverrideGraphicsBackend { get; private set; }
public static string OverrideBackendThreading { get; private set; } public static string OverrideBackendThreading { get; private set; }
public static string OverrideBackendThreadingAfterReboot { get; private set; }
public static string OverridePPTC { get; private set; }
public static string OverrideMemoryManagerMode { get; private set; }
public static string OverrideSystemRegion { get; private set; }
public static string OverrideSystemLanguage { get; private set; }
public static string OverrideHideCursor { get; private set; } public static string OverrideHideCursor { get; private set; }
public static string BaseDirPathArg { get; private set; } public static string BaseDirPathArg { get; private set; }
public static FilePath FirmwareToInstallPathArg { get; set; } public static FilePath FirmwareToInstallPathArg { get; set; }
@ -21,6 +26,7 @@ namespace Ryujinx.Ava.Utilities
public static bool StartFullscreenArg { get; private set; } public static bool StartFullscreenArg { get; private set; }
public static bool HideAvailableUpdates { get; private set; } public static bool HideAvailableUpdates { get; private set; }
public static void ParseArguments(string[] args) public static void ParseArguments(string[] args)
{ {
List<string> arguments = []; List<string> arguments = [];
@ -30,6 +36,11 @@ namespace Ryujinx.Ava.Utilities
{ {
string arg = args[i]; string arg = args[i];
if (arg.Contains("-") || arg.Contains("--"))
{
CountArguments++;
}
switch (arg) switch (arg)
{ {
case "-r": case "-r":
@ -100,6 +111,57 @@ namespace Ryujinx.Ava.Utilities
OverrideBackendThreading = args[++i]; OverrideBackendThreading = args[++i];
break; break;
case "--bt":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideBackendThreadingAfterReboot = args[++i];
break;
case "--pptc":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverridePPTC = args[++i];
break;
case "-m":
case "--memory-manager-mode":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideMemoryManagerMode = args[++i];
break;
case "--system-region":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemRegion = args[++i];
break;
case "--system-language":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemLanguage = args[++i];
break;
case "-i": case "-i":
case "--application-id": case "--application-id":
LaunchApplicationId = args[++i]; LaunchApplicationId = args[++i];

View file

@ -18,9 +18,10 @@ namespace Ryujinx.Ava.Utilities.Configuration
{ {
public partial class ConfigurationState public partial class ConfigurationState
{ {
public void Load(ConfigurationFileFormat cff, string configurationFilePath) public void Load(ConfigurationFileFormat cff, string configurationFilePath, string titleId = "")
{ {
bool configurationFileUpdated = false; bool configurationFileUpdated = false;
bool shouldLoadFromFile = string.IsNullOrEmpty(titleId);
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion) if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
{ {
@ -43,14 +44,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
configurationFileUpdated = true; configurationFileUpdated = true;
} }
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration; EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart; CheckUpdatesOnStart.Value = shouldLoadFromFile ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only
UpdateCheckerType.Value = cff.UpdateCheckerType; UpdateCheckerType.Value = shouldLoadFromFile ? cff.UpdateCheckerType : UpdateCheckerType.Value; // Get from global config only
FocusLostActionType.Value = cff.FocusLostActionType; FocusLostActionType.Value = cff.FocusLostActionType;
ShowConfirmExit.Value = cff.ShowConfirmExit; ShowConfirmExit.Value = shouldLoadFromFile ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only
RememberWindowState.Value = cff.RememberWindowState; RememberWindowState.Value = shouldLoadFromFile ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only
ShowTitleBar.Value = cff.ShowTitleBar; ShowTitleBar.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration; EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
HideCursor.Value = cff.HideCursor; HideCursor.Value = cff.HideCursor;
Logger.EnableFileLog.Value = cff.EnableFileLog; Logger.EnableFileLog.Value = cff.EnableFileLog;
@ -87,8 +89,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.Language.Value = cff.SystemLanguage; System.Language.Value = cff.SystemLanguage;
System.Region.Value = cff.SystemRegion; System.Region.Value = cff.SystemRegion;
System.TimeZone.Value = cff.SystemTimeZone; System.TimeZone.Value = cff.SystemTimeZone;
System.SystemTimeOffset.Value = cff.SystemTimeOffset; System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
System.MatchSystemTime.Value = cff.MatchSystemTime; System.MatchSystemTime.Value = shouldLoadFromFile ? cff.MatchSystemTime : System.MatchSystemTime.Value; // Get from global config only
System.EnableDockedMode.Value = cff.DockedMode; System.EnableDockedMode.Value = cff.DockedMode;
System.EnablePtc.Value = cff.EnablePtc; System.EnablePtc.Value = cff.EnablePtc;
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc; System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
@ -103,46 +105,47 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.IgnoreControllerApplet.Value = cff.IgnoreApplet; System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor; System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn; UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn; UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn; UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn; UI.GuiColumns.DevColumn.Value = shouldLoadFromFile ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value;
UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn; UI.GuiColumns.VersionColumn.Value = shouldLoadFromFile ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value;
UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn; UI.GuiColumns.TimePlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value;
UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn; UI.GuiColumns.LastPlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value;
UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn; UI.GuiColumns.FileExtColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value;
UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn; UI.GuiColumns.FileSizeColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value;
UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn; UI.GuiColumns.PathColumn.Value = shouldLoadFromFile ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value;
UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId; UI.ColumnSort.SortColumnId.Value = shouldLoadFromFile ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value;
UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending; UI.ColumnSort.SortAscending.Value = shouldLoadFromFile ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value;
UI.GameDirs.Value = cff.GameDirs; UI.GameDirs.Value = shouldLoadFromFile ? cff.GameDirs : UI.GameDirs.Value;
UI.AutoloadDirs.Value = cff.AutoloadDirs ?? []; UI.AutoloadDirs.Value = shouldLoadFromFile ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value;
UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP; UI.ShownFileTypes.NSP.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value;
UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0; UI.ShownFileTypes.PFS0.Value = shouldLoadFromFile ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value;
UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI; UI.ShownFileTypes.XCI.Value = shouldLoadFromFile ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value;
UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA; UI.ShownFileTypes.NCA.Value = shouldLoadFromFile ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value;
UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO; UI.ShownFileTypes.NRO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value;
UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO; UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
UI.LanguageCode.Value = cff.LanguageCode; UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value;
UI.BaseStyle.Value = cff.BaseStyle; UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value;
UI.GameListViewMode.Value = cff.GameListViewMode; UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value;
UI.ShowNames.Value = cff.ShowNames; UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value;
UI.IsAscendingOrder.Value = cff.IsAscendingOrder; UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;
UI.GridSize.Value = cff.GridSize; UI.GridSize.Value = shouldLoadFromFile ? cff.GridSize : UI.GridSize.Value;
UI.ApplicationSort.Value = cff.ApplicationSort; UI.ApplicationSort.Value = shouldLoadFromFile ? cff.ApplicationSort : UI.ApplicationSort.Value;
UI.StartFullscreen.Value = cff.StartFullscreen; UI.StartFullscreen.Value = shouldLoadFromFile ? cff.StartFullscreen : UI.StartFullscreen.Value;
UI.StartNoUI.Value = cff.StartNoUI; UI.StartNoUI.Value = shouldLoadFromFile ? cff.StartNoUI : UI.StartNoUI.Value;
UI.ShowConsole.Value = cff.ShowConsole; UI.ShowConsole.Value = shouldLoadFromFile ? cff.ShowConsole : UI.ShowConsole.Value;
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth; UI.WindowStartup.WindowSizeWidth.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value;
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight; UI.WindowStartup.WindowSizeHeight.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value;
UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX; UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY; UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized; UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
Hid.EnableKeyboard.Value = cff.EnableKeyboard; Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse; Hid.EnableMouse.Value = cff.EnableMouse;
Hid.DisableInputWhenOutOfFocus.Value = cff.DisableInputWhenOutOfFocus; Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus: Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
Hid.Hotkeys.Value = cff.Hotkeys; Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
Hid.InputConfig.Value = cff.InputConfig ?? []; Hid.InputConfig.Value = cff.InputConfig ?? [];
Hid.RainbowSpeed.Value = cff.RainbowSpeed; Hid.RainbowSpeed.Value = cff.RainbowSpeed;

View file

@ -60,11 +60,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
_ => FormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
private static FormattedValue PokemonSVUnionCircle(SingleValue value) private static FormattedValue PokemonSV(MultiValue values)
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group"; {
private static FormattedValue PokemonSVArea(SingleValue value) string playStatus = values.Matched[0].BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
=> value.Matched.StringValue switch
FormattedValue locations = values.Matched[1].ToString() switch
{ {
// Base Game Locations // Base Game Locations
"a_w01" => "South Area One", "a_w01" => "South Area One",
@ -92,11 +93,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"a_w24" => "South Paldean Sea", "a_w24" => "South Paldean Sea",
"a_w25" => "West Paldean Sea", "a_w25" => "West Paldean Sea",
"a_w26" => "East Paldean Sea", "a_w26" => "East Paldean Sea",
"a_w27" => "Nouth Paldean Sea", "a_w27" => "North Paldean Sea",
//TODO DLC Locations //TODO DLC Locations
_ => FormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
return$"{playStatus} in {locations}";
}
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values) private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)
{ {
// Check if the PlayReport is for a challenger approach or an achievement. // Check if the PlayReport is for a challenger approach or an achievement.
@ -115,6 +119,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
return $"Achievement Unlocked - ID: {anniversary}"; return $"Achievement Unlocked - ID: {anniversary}";
} }
if (values.Matched.ContainsKey("is_created"))
{
return "Edited a Custom Stage!";
}
if (values.Matched.ContainsKey("adv_slot")) if (values.Matched.ContainsKey("adv_slot"))
{ {
return return

View file

@ -59,9 +59,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
.AddSpec( .AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"], ["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec spec => spec
.WithDescription("based on what area of Paldea you're exploring.") .WithDescription("based on if you're playing alone or in a group and what area of Paldea you're exploring.")
.AddValueFormatter("area_no", PokemonSVArea) .AddMultiValueFormatter(["team_circle", "area_no"], PokemonSV)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
) )
.AddSpec( .AddSpec(
"01006a800016e000", "01006a800016e000",
@ -71,7 +70,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
[ [
// Metadata to figure out what PlayReport we have. // Metadata to figure out what PlayReport we have.
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count", "match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
"adv_slot", "adv_slot", "is_created",
// List of Fighters // List of Fighters
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter", "player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter", "player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",

View file

@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Utilities
public static class ShortcutHelper public static class ShortcutHelper
{ {
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath) private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath, string args = "")
{ {
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe"); string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
iconPath += ".ico"; iconPath += ".ico";
@ -22,13 +22,13 @@ namespace Ryujinx.Ava.Utilities
image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High); image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
SaveBitmapAsIcon(image, iconPath); SaveBitmapAsIcon(image, iconPath);
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0); Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId, args), iconPath, 0);
shortcut.StringData.NameString = cleanedAppName; shortcut.StringData.NameString = cleanedAppName;
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk")); shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName) private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName, string args = "")
{ {
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh"); string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop"); string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
@ -40,11 +40,11 @@ namespace Ryujinx.Ava.Utilities
data.SaveTo(file); data.SaveTo(file);
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}"); outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId, args)}");
} }
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName) private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName, string args = "")
{ {
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"); string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist"); string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
@ -63,7 +63,7 @@ namespace Ryujinx.Ava.Utilities
string scriptPath = Path.Combine(scriptFolderPath, ScriptName); string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
using StreamWriter scriptFile = new(scriptPath); using StreamWriter scriptFile = new(scriptPath);
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId)); scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId, args));
// Set execute permission // Set execute permission
FileInfo fileInfo = new(scriptPath); FileInfo fileInfo = new(scriptPath);
@ -87,7 +87,7 @@ namespace Ryujinx.Ava.Utilities
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName); outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
} }
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData) public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData, string args = "")
{ {
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars())); string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
@ -96,7 +96,7 @@ namespace Ryujinx.Ava.Utilities
{ {
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app"); string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath); CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath, args);
return; return;
} }
@ -106,14 +106,14 @@ namespace Ryujinx.Ava.Utilities
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx"); string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
Directory.CreateDirectory(iconPath); Directory.CreateDirectory(iconPath);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName); CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName, args);
return; return;
} }
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{ {
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName); CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName, args);
return; return;
} }
@ -121,7 +121,7 @@ namespace Ryujinx.Ava.Utilities
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS."); throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
} }
private static string GetArgsString(string appFilePath, string applicationId) private static string GetArgsString(string appFilePath, string applicationId, string config = "")
{ {
// args are first defined as a list, for easier adjustments in the future // args are first defined as a list, for easier adjustments in the future
List<string> argsList = []; List<string> argsList = [];
@ -132,6 +132,11 @@ namespace Ryujinx.Ava.Utilities
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\""); argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
} }
if (!string.IsNullOrEmpty(config))
{
argsList.Add(config);
}
if (appFilePath.ToLower().EndsWith(".xci")) if (appFilePath.ToLower().EndsWith(".xci"))
{ {
argsList.Add("--application-id"); argsList.Add("--application-id");