Compare commits

..

3 Commits

Author SHA1 Message Date
Evan Husted
1d88771d1b Play Report Analyzer v4
You can now access the *entire* play report data in any given value formatter.
The input types have been restructured and, notably, not every instance of Value has an ApplicationMetadata on it. It's now on the container type that also contains the matched values and the entire play report.
2025-02-08 00:22:34 -06:00
Evan Husted
4e8157688e UI: See what games do/don't have an image & dynamic RPC support in the Game Info popup 2025-02-07 18:34:11 -06:00
Evan Husted
5085af0050 UI: Changed the color of "Ingame" from yellow to orange to stand out better in light mode 2025-02-07 18:28:32 -06:00
30 changed files with 399 additions and 883 deletions

View File

@@ -1,5 +1,6 @@
using MsgPack;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Memory;
using System;
using System.Threading;
@@ -8,7 +9,7 @@ namespace Ryujinx.Horizon
{
public static class HorizonStatic
{
internal static void HandlePlayReport(MessagePackObject report) =>
internal static void HandlePlayReport(PlayReport report) =>
new Thread(() => PlayReport?.Invoke(report))
{
Name = "HLE.PlayReportEvent",
@@ -16,7 +17,7 @@ namespace Ryujinx.Horizon
Priority = ThreadPriority.AboveNormal
}.Start();
public static event Action<MessagePackObject> PlayReport;
public static event Action<PlayReport> PlayReport;
[field: ThreadStatic]
public static HorizonOptions Options { get; private set; }

View File

@@ -1,4 +1,3 @@
using Gommon;
using MsgPack;
using MsgPack.Serialization;
using Ryujinx.Common.Logging;
@@ -12,19 +11,12 @@ using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Text;
using System.Threading;
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
namespace Ryujinx.Horizon.Prepo.Ipc
{
partial class PrepoService : IPrepoService
{
enum PlayReportKind
{
Normal,
System,
}
private readonly ArpApi _arp;
private readonly PrepoServicePermissionLevel _permissionLevel;
private ulong _systemSessionId;
@@ -196,10 +188,17 @@ namespace Ryujinx.Horizon.Prepo.Ipc
{
return PrepoResult.InvalidBufferSize;
}
StringBuilder builder = new();
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
PlayReport playReport = new()
{
Kind = playReportKind,
Room = gameRoom,
ReportData = deserializedReport
};
builder.AppendLine();
builder.AppendLine("PlayReport log:");
builder.AppendLine($" Kind: {playReportKind}");
@@ -209,10 +208,12 @@ namespace Ryujinx.Horizon.Prepo.Ipc
if (pid != 0)
{
builder.AppendLine($" Pid: {pid}");
playReport.Pid = pid;
}
else
{
builder.AppendLine($" ApplicationId: {applicationId}");
playReport.AppId = applicationId;
}
Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid);
@@ -223,17 +224,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
_arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure();
playReport.Version = applicationLaunchProperty.Version;
builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}");
if (!userId.IsNull)
{
builder.AppendLine($" UserId: {userId}");
playReport.UserId = userId;
}
builder.AppendLine($" Room: {gameRoom}");
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
HorizonStatic.HandlePlayReport(deserializedReport);
HorizonStatic.HandlePlayReport(playReport);
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());

View File

@@ -0,0 +1,24 @@
using MsgPack;
using Ryujinx.Horizon.Sdk.Account;
using Ryujinx.Horizon.Sdk.Ncm;
namespace Ryujinx.Horizon.Prepo.Types
{
public struct PlayReport
{
public PlayReportKind Kind { get; init; }
public string Room { get; init; }
public MessagePackObject ReportData { get; init; }
public ApplicationId? AppId;
public ulong? Pid;
public uint Version;
public Uid? UserId;
}
public enum PlayReportKind
{
Normal,
System,
}
}

View File

@@ -582,10 +582,6 @@ namespace Ryujinx.Ava
Rainbow.Disable();
Rainbow.Reset();
//Reload settings when the game is turned off
//(resets custom settings if there were any)
Program.ReloadConfig();
_isStopped = true;
Stop();
}

View File

@@ -1,8 +1,7 @@
<Styles
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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">
<Design.PreviewWith>
<Border Height="2000"
@@ -28,11 +27,10 @@
Height="28"
HorizontalAlignment="Right"
Content="Addy" />
<Button
<Button
Name="btnRem"
HorizontalAlignment="Right"
Content="Add"
Classes="red"/>
Content="Add" />
<TextBox
Width="100"
VerticalAlignment="Center"
@@ -43,12 +41,7 @@
</StackPanel>
</Grid>
<ui:NumberBox Value="1" />
<MenuItem
IconSource="Settings"
Header="123 0000"
ToolTip.Tip="What this"/>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="DropDownButton">
@@ -380,16 +373,6 @@
<Setter Property="Background"
Value="{DynamicResource AppListHoverBackgroundColor}" />
</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>
<SolidColorBrush x:Key="ThemeAccentColorBrush"
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">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
@@ -12,13 +12,11 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -31,13 +29,11 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#13c3a4</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -50,13 +46,11 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#FFA500</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

View File

@@ -2722,31 +2722,6 @@
"zh_TW": "建立桌面捷徑,啟動選取的應用程式"
}
},
{
"ID": "GameListContextMenuEditGameConfiguration",
"Translations": {
"ar_SA": "تعديل تكوين اللعبة",
"de_DE": "Spielkonfiguration bearbeiten",
"el_GR": "Επεξεργασία ρυθμίσεων παιχνιδιού",
"en_US": "Edit Game Configuration",
"es_ES": "Editar configuración del juego",
"fr_FR": "Modifier la configuration du jeu",
"he_IL": "ערוך את הגדרת המשחק",
"it_IT": "Modifica configurazione gioco",
"ja_JP": "ゲーム設定を編集",
"ko_KR": "게임 설정 편집",
"no_NO": "Rediger spillkonfig.",
"pl_PL": "Edytuj konfigurację gry",
"pt_BR": "Editar configuração do jogo",
"ru_RU": "Редактировать конфигурацию игры",
"sv_SE": "Redigera spelkonfiguration",
"th_TH": "แก้ไขการตั้งค่าเกม",
"tr_TR": "Oyunun yapılandırmasını düzenle",
"uk_UA": "Редагувати конфігурацію гри",
"zh_CN": "编辑游戏配置",
"zh_TW": "編輯遊戲設定"
}
},
{
"ID": "GameListContextMenuCreateShortcutToolTipMacOS",
"Translations": {
@@ -2772,31 +2747,6 @@
"zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式"
}
},
{
"ID": "EditGameConfigurationToolTip",
"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 current 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": "GameListContextMenuShowCompatEntry",
"Translations": {
@@ -23722,31 +23672,6 @@
"zh_TW": ""
}
},
{
"ID": "UserConfigurationHeader",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "User 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": "ExtractAocListHeader",
"Translations": {
@@ -23771,6 +23696,56 @@
"zh_CN": "选择一个要解压的 DLC",
"zh_TW": ""
}
},
{
"ID": "GameInfoRpcImage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Rich Presence Image",
"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": "GameInfoRpcDynamic",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Dynamic Rich Presence",
"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": ""
}
}
]
}

View File

@@ -10,6 +10,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon;
using Ryujinx.Horizon.Prepo.Types;
using System.Linq;
using System.Text;
namespace Ryujinx.Ava
@@ -37,6 +39,9 @@ namespace Ryujinx.Ava
private static RichPresence _discordPresencePlaying;
private static ApplicationMetadata _currentApp;
public static bool HasAssetImage(string titleId) => TitleIDs.DiscordGameAssetKeys.ContainsIgnoreCase(titleId);
public static bool HasAnalyzer(string titleId) => PlayReports.Analyzer.TitleIds.ContainsIgnoreCase(titleId);
public static void Initialize()
{
_discordPresenceMain = new RichPresence
@@ -120,7 +125,7 @@ namespace Ryujinx.Ava
_currentApp = null;
}
private static void HandlePlayReport(MessagePackObject playReport)
private static void HandlePlayReport(PlayReport playReport)
{
if (_discordClient is null) return;
if (!TitleIDs.CurrentApplication.Value.HasValue) return;

View File

@@ -23,7 +23,6 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Ryujinx.Ava
{
@@ -33,7 +32,6 @@ namespace Ryujinx.Ava
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { 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 UseHardwareAcceleration { get; private set; }
@@ -157,42 +155,11 @@ namespace Ryujinx.Ava
}
}
public static void ReloadGameConfig(string gamedir)
{
if (File.Exists(gamedir))
{
ConfigurationPath = gamedir;
}
}
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
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()
{
//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 appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
@@ -265,35 +232,8 @@ namespace Ryujinx.Ava
_ => 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. MemoryManagerMode ( outdated! )
// Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null)
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}

View File

@@ -19,11 +19,6 @@
Header="{ext:Locale GameListContextMenuCreateShortcut}"
Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<MenuItem
Click="EditGameConfiguration_Click"
Header="{ext:Locale GameListContextMenuEditGameConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditGameConfigurationToolTip}" />
<MenuItem
IsVisible="{Binding HasCompatibilityEntry}"
Click="OpenApplicationCompatibility_Click"

View File

@@ -13,7 +13,6 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Compat;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Helper;
using Ryujinx.HLE.HOS;
@@ -37,21 +36,6 @@ namespace Ryujinx.Ava.UI.Controls
AvaloniaXamlLoader.Load(this);
}
public void ToggleUserControl_Click(object sender, RoutedEventArgs args)
{
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
{
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
viewModel.RefreshView();
}
public void ToggleFavorite_Click(object sender, RoutedEventArgs args)
{
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
@@ -402,27 +386,13 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.SelectedApplication.Icon
);
}
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await new UserConfigWindows(viewModel).ShowDialog((Window)viewModel.TopLevel);
//just checking for file presence
viewModel.SelectedApplication.UserConfig = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();
}
}
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
}
public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

View File

@@ -4,6 +4,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:ext="using:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Controls.ApplicationDataView"
@@ -85,6 +86,49 @@
</StackPanel>
</Grid>
<Separator Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
<StackPanel Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasRichPresenceAsset}"/>
<TextBlock
Foreground="ForestGreen"
HorizontalAlignment="Stretch"
IsVisible="{Binding AppData.HasRichPresenceAsset}"
Text="{ext:Locale GameInfoRpcImage}"
TextAlignment="Start"
TextWrapping="Wrap" >
</TextBlock>
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasRichPresenceAsset}"/>
<TextBlock
Foreground="Red"
HorizontalAlignment="Stretch"
IsVisible="{Binding !AppData.HasRichPresenceAsset}"
Text="{ext:Locale GameInfoRpcImage}"
TextAlignment="Start"
TextWrapping="Wrap" >
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock
Foreground="ForestGreen"
HorizontalAlignment="Stretch"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
Text="{ext:Locale GameInfoRpcDynamic}"
TextAlignment="Start"
TextWrapping="Wrap" >
</TextBlock>
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock
Foreground="Red"
HorizontalAlignment="Stretch"
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"
Text="{ext:Locale GameInfoRpcDynamic}"
TextAlignment="Start"
TextWrapping="Wrap" >
</TextBlock>
</StackPanel>
</StackPanel>
<Separator Margin="0, 10, 0, 10" Height="1" BorderBrush="Gray" Background="Gray" />
<TextBlock
HorizontalAlignment="Stretch"
IsVisible="{Binding AppData.HasLdnGames}"

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Compat;
using System;
@@ -43,19 +42,6 @@ namespace Ryujinx.Ava.UI.Controls
await CompatibilityList.Show((string)playabilityLabel.Tag);
}
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await new UserConfigWindows(viewModel).ShowDialog((Window)viewModel.TopLevel);
//viewModel.SelectedApplication.UserConfig = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString));
viewModel.RefreshView();
}
}
private async void IdString_OnClick(object sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel mwvm)

View File

@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleKeys.CompatibilityListNothing or
LocaleKeys.CompatibilityListBoots or
LocaleKeys.CompatibilityListMenus => Brushes.Red,
LocaleKeys.CompatibilityListIngame => Brushes.Yellow,
LocaleKeys.CompatibilityListIngame => Brushes.DarkOrange,
_ => Brushes.ForestGreen
};

View File

@@ -1076,7 +1076,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.WaitOne();
AppHost?.Start();
AppHost?.DisposeContext();
}
@@ -1540,14 +1540,6 @@ namespace Ryujinx.Ava.UI.ViewModels
#if RELEASE
await PerformanceCheck();
#endif
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
string gameDir = Program.GetDirGameUserConfig(application.IdBaseString, true, true);
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
{
//Program.GetDirGameUserConfig(application.IdBaseString, false);
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, application.IdBaseString);
}
Logger.RestartTime();
@@ -1593,7 +1585,6 @@ namespace Ryujinx.Ava.UI.ViewModels
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
}
public void SwitchToRenderer(bool startFullscreen) =>

View File

@@ -1,10 +1,8 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Humanizer;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
@@ -28,7 +26,6 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
@@ -70,49 +67,6 @@ namespace Ryujinx.Ava.UI.ViewModels
[ObservableProperty] private string _ldnServer;
public SettingsHacksViewModel DirtyHacks { get; }
public string GamePath { get; }
public string GameName { get; }
private Bitmap _gameIcon;
private string _gameTitle;
private string _gameId;
public Bitmap GameIcon
{
get => _gameIcon;
set
{
if (_gameIcon != value)
{
_gameIcon = value;
}
}
}
public string GameTitle
{
get => _gameTitle;
set
{
if (_gameTitle != value)
{
_gameTitle = value;
}
}
}
public string GameId
{
get => _gameId;
set
{
if (_gameId != value)
{
_gameId = value;
}
}
}
public int ResolutionScale
{
@@ -388,37 +342,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager, string gamePath, string gameName, string gameId, byte[] gameIconData) : this()
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
if (gameIconData != null && gameIconData.Length > 0)
{
using (var ms = new MemoryStream(gameIconData))
{
GameIcon = new Bitmap(ms);
}
}
GameTitle = gameName;
GameId = gameId;
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);
DirtyHacks = new SettingsHacksViewModel(this);
}
}
public SettingsViewModel()
{
GameDirectories = [];
@@ -545,30 +468,27 @@ namespace Ryujinx.Ava.UI.ViewModels
{
ConfigurationState config = ConfigurationState.Instance;
if (string.IsNullOrEmpty(GameId))
// User Interface
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value);
AutoloadDirectories.Clear();
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
BaseStyleIndex = config.UI.BaseStyle.Value switch
{
// User Interface
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value);
AutoloadDirectories.Clear();
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
BaseStyleIndex = config.UI.BaseStyle.Value switch
{
"Auto" => 0,
"Light" => 1,
"Dark" => 2,
_ => 0
};
}
"Auto" => 0,
"Light" => 1,
"Dark" => 2,
_ => 0
};
// Input
EnableDockedMode = config.System.EnableDockedMode;
@@ -649,7 +569,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LdnServer = config.Multiplayer.LdnServer;
}
public void SaveSettings2()
public void SaveSettings()
{
ConfigurationState config = ConfigurationState.Instance;
@@ -663,12 +583,12 @@ namespace Ryujinx.Ava.UI.ViewModels
if (GameDirectoryChanged)
{
config.UI.GameDirs.Value = [.. GameDirectories];
config.UI.GameDirs.Value = [..GameDirectories];
}
if (AutoloadDirectoryChanged)
{
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
}
config.UI.BaseStyle.Value = BaseStyleIndex switch
@@ -679,50 +599,6 @@ namespace Ryujinx.Ava.UI.ViewModels
_ => "Auto"
};
if (!string.IsNullOrEmpty(GameId))
{
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
else
{
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
public void SaveSettings()
{
ConfigurationState config = ConfigurationState.Instance;
if (string.IsNullOrEmpty(GameId))
{
// User Interface
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
if (GameDirectoryChanged)
{
config.UI.GameDirs.Value = [.. GameDirectories];
}
if (AutoloadDirectoryChanged)
{
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
}
config.UI.BaseStyle.Value = BaseStyleIndex switch
{
0 => "Auto",
1 => "Light",
2 => "Dark",
_ => "Auto"
};
}
// Input
config.System.EnableDockedMode.Value = EnableDockedMode;
config.Hid.EnableKeyboard.Value = EnableKeyboard;
@@ -817,9 +693,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
@@ -832,11 +706,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private static void RevertIfNotSaved()
{
// maybe this is an unnecessary check(all options need to be tested)
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
{
Program.ReloadConfig();
}
Program.ReloadConfig();
}
public void ApplyButton()
@@ -844,25 +714,6 @@ namespace Ryujinx.Ava.UI.ViewModels
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();
CloseWindow?.Invoke();
}
public void OkButton()
{
SaveSettings();

View File

@@ -1,144 +0,0 @@
<window:StyleableAppWindow
x:Class="Ryujinx.Ava.UI.Windows.UserConfigWindows"
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="768"
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: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
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
Content="{ext:Locale UserProfilesDelete}"
Command="{Binding DeleteConfigGame}"
Classes="red"/>
</ReversibleStackPanel>
</Grid>
</window:StyleableAppWindow>

View File

@@ -1,114 +0,0 @@
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 UserConfigWindows : StyleableAppWindow
{
internal readonly SettingsViewModel ViewModel;
public UserConfigWindows(MainWindowViewModel viewModel)
{
Title = RyujinxApp.FormatTitle(LocaleKeys.Settings);
DataContext = ViewModel = new SettingsViewModel(
viewModel.VirtualFileSystem,
viewModel.ContentManager,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon);
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
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(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)
{
base.OnClosing(e);
}
}
}

View File

@@ -21,7 +21,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public class ApplicationData
{
public bool Favorite { get; set; }
public bool UserConfig { get; set; }
public byte[] Icon { get; set; }
public string Name { get; set; } = "Unknown";
@@ -64,6 +63,9 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public int GameCount { get; set; }
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; }

View File

@@ -505,7 +505,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
if (data.Id != 0)
{
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
{
{
appMetadata.Title = data.Name;
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
@@ -529,11 +529,10 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
}
});
data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed;
data.LastPlayed = appMetadata.LastPlayed;
data.UserConfig = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // just check for file presence
}
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

View File

@@ -6,15 +6,11 @@ namespace Ryujinx.Ava.Utilities
public static class CommandLineState
{
public static string[] Arguments { get; private set; }
public static int CountArguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; }
public static bool? OverrideHardwareAcceleration { get; private set; }
public static string OverrideGraphicsBackend { get; private set; }
public static string OverrideBackendThreading { 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 BaseDirPathArg { get; private set; }
public static string Profile { get; private set; }
@@ -23,11 +19,6 @@ namespace Ryujinx.Ava.Utilities
public static bool StartFullscreenArg { get; private set; }
public static bool HideAvailableUpdates { get; private set; }
public static void ArgumentsClean()
{
}
public static void ParseArguments(string[] args)
{
List<string> arguments = [];
@@ -37,11 +28,6 @@ namespace Ryujinx.Ava.Utilities
{
string arg = args[i];
if (arg.Contains("-") || arg.Contains("--"))
{
CountArguments++;
}
switch (arg)
{
case "-r":
@@ -99,47 +85,6 @@ namespace Ryujinx.Ava.Utilities
OverrideBackendThreading = 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 "--application-id":
LaunchApplicationId = args[++i];

View File

@@ -18,15 +18,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
{
public partial class ConfigurationState
{
public void Load(ConfigurationFileFormat cff, string configurationFilePath, string gameId="")
public void Load(ConfigurationFileFormat cff, string configurationFilePath)
{
bool configurationFileUpdated = false;
bool LoadSetting = true;
if (!string.IsNullOrEmpty(gameId))
{
LoadSetting = false;
}
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
{
@@ -49,14 +43,14 @@ namespace Ryujinx.Ava.Utilities.Configuration
configurationFileUpdated = true;
}
EnableDiscordIntegration.Value = LoadSetting ? cff.EnableDiscordIntegration : EnableDiscordIntegration.Value;
CheckUpdatesOnStart.Value = LoadSetting ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value;
ShowConfirmExit.Value = LoadSetting ? cff.ShowConfirmExit : ShowConfirmExit.Value;
RememberWindowState.Value = LoadSetting ? cff.RememberWindowState : RememberWindowState.Value;
ShowTitleBar.Value = LoadSetting ? cff.ShowTitleBar : ShowTitleBar.Value;
EnableHardwareAcceleration.Value = LoadSetting ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value;
HideCursor.Value = LoadSetting ? cff.HideCursor : HideCursor.Value;
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
ShowConfirmExit.Value = cff.ShowConfirmExit;
RememberWindowState.Value = cff.RememberWindowState;
ShowTitleBar.Value = cff.ShowTitleBar;
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
HideCursor.Value = cff.HideCursor;
Logger.EnableFileLog.Value = cff.EnableFileLog;
Logger.EnableDebug.Value = cff.LoggingEnableDebug;
Logger.EnableStub.Value = cff.LoggingEnableStub;
@@ -105,44 +99,43 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
System.IgnoreApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = LoadSetting ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = LoadSetting ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
UI.GuiColumns.AppColumn.Value = LoadSetting ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
UI.GuiColumns.DevColumn.Value = LoadSetting ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value;
UI.GuiColumns.VersionColumn.Value = LoadSetting ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value;
UI.GuiColumns.TimePlayedColumn.Value = LoadSetting ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value;
UI.GuiColumns.LastPlayedColumn.Value = LoadSetting ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value;
UI.GuiColumns.FileExtColumn.Value = LoadSetting ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value;
UI.GuiColumns.FileSizeColumn.Value = LoadSetting ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value;
UI.GuiColumns.PathColumn.Value = LoadSetting ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value;
UI.ColumnSort.SortColumnId.Value = LoadSetting ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value;
UI.ColumnSort.SortAscending.Value = LoadSetting ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value;
UI.GameDirs.Value = LoadSetting ? cff.GameDirs : UI.GameDirs.Value;
UI.AutoloadDirs.Value = LoadSetting ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value;
UI.ShownFileTypes.NSP.Value = LoadSetting ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value;
UI.ShownFileTypes.PFS0.Value = LoadSetting ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value;
UI.ShownFileTypes.XCI.Value = LoadSetting ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value;
UI.ShownFileTypes.NCA.Value = LoadSetting ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value;
UI.ShownFileTypes.NRO.Value = LoadSetting ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value;
UI.ShownFileTypes.NSO.Value = LoadSetting ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
UI.LanguageCode.Value = LoadSetting ? cff.LanguageCode : UI.LanguageCode.Value;
UI.BaseStyle.Value = LoadSetting ? cff.BaseStyle : UI.BaseStyle.Value;
UI.GameListViewMode.Value = LoadSetting ? cff.GameListViewMode : UI.GameListViewMode.Value;
UI.ShowNames.Value = LoadSetting ? cff.ShowNames : UI.ShowNames.Value;
UI.IsAscendingOrder.Value = LoadSetting ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;
UI.GridSize.Value = LoadSetting ? cff.GridSize : UI.GridSize.Value;
UI.ApplicationSort.Value = LoadSetting ? cff.ApplicationSort : UI.ApplicationSort.Value;
UI.StartFullscreen.Value = LoadSetting ? cff.StartFullscreen : UI.StartFullscreen.Value;
UI.StartNoUI.Value = LoadSetting ? cff.StartNoUI : UI.StartNoUI.Value;
UI.ShowConsole.Value = LoadSetting ? cff.ShowConsole : UI.ShowConsole.Value;
UI.WindowStartup.WindowSizeWidth.Value = LoadSetting ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value;
UI.WindowStartup.WindowSizeHeight.Value = LoadSetting ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value;
UI.WindowStartup.WindowPositionX.Value = LoadSetting ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
UI.WindowStartup.WindowPositionY.Value = LoadSetting ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
UI.WindowStartup.WindowMaximized.Value = LoadSetting ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn;
UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn;
UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn;
UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn;
UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn;
UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn;
UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn;
UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn;
UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn;
UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId;
UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending;
UI.GameDirs.Value = cff.GameDirs;
UI.AutoloadDirs.Value = cff.AutoloadDirs ?? [];
UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP;
UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0;
UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI;
UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA;
UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO;
UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO;
UI.LanguageCode.Value = cff.LanguageCode;
UI.BaseStyle.Value = cff.BaseStyle;
UI.GameListViewMode.Value = cff.GameListViewMode;
UI.ShowNames.Value = cff.ShowNames;
UI.IsAscendingOrder.Value = cff.IsAscendingOrder;
UI.GridSize.Value = cff.GridSize;
UI.ApplicationSort.Value = cff.ApplicationSort;
UI.StartFullscreen.Value = cff.StartFullscreen;
UI.StartNoUI.Value = cff.StartNoUI;
UI.ShowConsole.Value = cff.ShowConsole;
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX;
UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY;
UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized;
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse;
Hid.Hotkeys.Value = cff.Hotkeys;

View File

@@ -3,6 +3,7 @@ using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
@@ -15,6 +16,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
{
private readonly List<GameSpec> _specs = [];
public string[] TitleIds => Specs.SelectMany(x => x.TitleIds).ToArray();
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
/// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
/// </summary>
@@ -80,7 +85,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <summary>
/// Runs the configured <see cref="GameSpec.FormatterSpec"/> for the specified game title ID.
/// Runs the configured <see cref="FormatterSpec"/> for the specified game title ID.
/// </summary>
/// <param name="runningGameId">The game currently running.</param>
/// <param name="appMeta">The Application metadata information, including localized game name and play time information.</param>
@@ -89,10 +94,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public FormattedValue Format(
string runningGameId,
ApplicationMetadata appMeta,
MessagePackObject playReport
Horizon.Prepo.Types.PlayReport playReport
)
{
if (!playReport.IsDictionary)
if (!playReport.ReportData.IsDictionary)
return FormattedValue.Unhandled;
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
@@ -100,10 +105,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
foreach (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
if (!playReport.ReportData.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
return formatSpec.Formatter(new Value { Application = appMeta, PackedValue = valuePackObject });
return formatSpec.Formatter(new SingleValue(valuePackObject)
{
Application = appMeta,
PlayReport = playReport
});
}
foreach (MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
@@ -111,7 +120,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
List<MessagePackObject> packedObjects = [];
foreach (var reportKey in formatSpec.ReportKeys)
{
if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
continue;
packedObjects.Add(valuePackObject);
@@ -120,23 +129,30 @@ namespace Ryujinx.Ava.Utilities.PlayReport
if (packedObjects.Count != formatSpec.ReportKeys.Length)
return FormattedValue.Unhandled;
return formatSpec.Formatter(packedObjects
.Select(packObject => new Value { Application = appMeta, PackedValue = packObject })
.ToArray());
return formatSpec.Formatter(new MultiValue(packedObjects)
{
Application = appMeta,
PlayReport = playReport
});
}
foreach (SparseMultiFormatterSpec formatSpec in spec.SparseMultiValueFormatters.OrderBy(x => x.Priority))
{
Dictionary<string, Value> packedObjects = [];
Dictionary<string, MessagePackObject> packedObjects = [];
foreach (var reportKey in formatSpec.ReportKeys)
{
if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
continue;
packedObjects.Add(reportKey, new Value { Application = appMeta, PackedValue = valuePackObject });
packedObjects.Add(reportKey, valuePackObject);
}
return formatSpec.Formatter(packedObjects);
return formatSpec.Formatter(
new SparseMultiValue(packedObjects)
{
Application = appMeta,
PlayReport = playReport
});
}
return FormattedValue.Unhandled;

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic;
namespace Ryujinx.Ava.Utilities.PlayReport
namespace Ryujinx.Ava.Utilities.PlayReport
{
/// <summary>
/// The delegate type that powers single value formatters.<br/>
@@ -12,7 +10,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <br/>
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
/// </summary>
public delegate FormattedValue ValueFormatter(Value value);
public delegate FormattedValue ValueFormatter(SingleValue value);
/// <summary>
/// The delegate type that powers multiple value formatters.<br/>
@@ -24,7 +22,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <br/>
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
/// </summary>
public delegate FormattedValue MultiValueFormatter(Value[] value);
public delegate FormattedValue MultiValueFormatter(MultiValue value);
/// <summary>
/// The delegate type that powers multiple value formatters.
@@ -38,5 +36,5 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <br/>
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
/// </summary>
public delegate FormattedValue SparseMultiValueFormatter(Dictionary<string, Value> values);
public delegate FormattedValue SparseMultiValueFormatter(SparseMultiValue value);
}

View File

@@ -0,0 +1,87 @@
using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Utilities.PlayReport
{
public abstract class MatchedValue<T>
{
public MatchedValue(T matched)
{
Matched = matched;
}
/// <summary>
/// The currently running application's <see cref="ApplicationMetadata"/>.
/// </summary>
public ApplicationMetadata Application { get; init; }
/// <summary>
/// The entire play report.
/// </summary>
public Horizon.Prepo.Types.PlayReport PlayReport { get; init; }
/// <summary>
/// The matched value from the Play Report.
/// </summary>
public T Matched { get; init; }
}
/// <summary>
/// The input data to a <see cref="ValueFormatter"/>,
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
/// </summary>
public class SingleValue : MatchedValue<Value>
{
public SingleValue(Value matched) : base(matched)
{
}
public static implicit operator SingleValue(MessagePackObject mpo) => new(mpo);
}
/// <summary>
/// The input data to a <see cref="MultiValueFormatter"/>,
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
/// and the matched <see cref="MessagePackObject"/>s from the Play Report.
/// </summary>
public class MultiValue : MatchedValue<Value[]>
{
public MultiValue(Value[] matched) : base(matched)
{
}
public MultiValue(IEnumerable<MessagePackObject> matched) : base(Value.ConvertPackedObjects(matched))
{
}
public static implicit operator MultiValue(List<MessagePackObject> matched)
=> new(matched.Select(x => new Value(x)).ToArray());
}
/// <summary>
/// The input data to a <see cref="SparseMultiValueFormatter"/>,
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
/// and the matched <see cref="MessagePackObject"/>s from the Play Report.
/// </summary>
public class SparseMultiValue : MatchedValue<Dictionary<string, Value>>
{
public SparseMultiValue(Dictionary<string, Value> matched) : base(matched)
{
}
public SparseMultiValue(Dictionary<string, MessagePackObject> matched) : base(Value.ConvertPackedObjectMap(matched))
{
}
public static implicit operator SparseMultiValue(Dictionary<string, MessagePackObject> matched)
=> new(matched
.ToDictionary(
x => x.Key,
x => new Value(x.Value)
)
);
}
}

View File

@@ -39,28 +39,28 @@
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
);
private static FormattedValue BreathOfTheWild_MasterMode(Value value)
=> value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
=> value.Matched.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
private static FormattedValue TearsOfTheKingdom_CurrentField(Value value) =>
value.DoubleValue switch
private static FormattedValue TearsOfTheKingdom_CurrentField(SingleValue value) =>
value.Matched.DoubleValue switch
{
> 800d => "Exploring the Sky Islands",
< -201d => "Exploring the Depths",
_ => "Roaming Hyrule"
};
private static FormattedValue SuperMarioOdyssey_AssistMode(Value value)
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static FormattedValue SuperMarioOdysseyChina_AssistMode(Value value)
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static FormattedValue SuperMarioOdysseyChina_AssistMode(SingleValue value)
=> value.Matched.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static FormattedValue SuperMario3DWorldOrBowsersFury(Value value)
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static FormattedValue SuperMario3DWorldOrBowsersFury(SingleValue value)
=> value.Matched.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static FormattedValue MarioKart8Deluxe_Mode(Value value)
=> value.StringValue switch
private static FormattedValue MarioKart8Deluxe_Mode(SingleValue value)
=> value.Matched.StringValue switch
{
// Single Player
"Single" => "Single Player",
@@ -87,11 +87,11 @@
_ => FormattedValue.ForceReset
};
private static FormattedValue PokemonSVUnionCircle(Value value)
=> value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
private static FormattedValue PokemonSVUnionCircle(SingleValue value)
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
private static FormattedValue PokemonSVArea(Value value)
=> value.StringValue switch
private static FormattedValue PokemonSVArea(SingleValue value)
=> value.Matched.StringValue switch
{
// Base Game Locations
"a_w01" => "South Area One",

View File

@@ -1,6 +1,8 @@
using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Utilities.PlayReport
{
@@ -9,12 +11,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
/// </summary>
public class Value
public readonly struct Value
{
/// <summary>
/// The currently running application's <see cref="ApplicationMetadata"/>.
/// </summary>
public ApplicationMetadata Application { get; init; }
public Value(MessagePackObject packedValue)
{
PackedValue = packedValue;
}
/// <summary>
/// The matched value from the Play Report.
@@ -37,6 +39,17 @@ namespace Ryujinx.Ava.Utilities.PlayReport
: boxed.ToString();
}
public static implicit operator Value(MessagePackObject matched) => new(matched);
public static Value[] ConvertPackedObjects(IEnumerable<MessagePackObject> packObjects)
=> packObjects.Select(packObject => new Value(packObject)).ToArray();
public static Dictionary<string, Value> ConvertPackedObjectMap(Dictionary<string, MessagePackObject> packObjects)
=> packObjects.ToDictionary(
x => x.Key,
x => new Value(x.Value)
);
#region AsX accessors
public bool BooleanValue => PackedValue.AsBoolean();

View File

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