Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09016ea37f | |||
| 2d30adb3ac | |||
| d679725334 | |||
| 6e5861d768 | |||
| 4efd20c54b | |||
| 2e13cdce0c | |||
| b742bcb75a | |||
| 068f6be609 | |||
| 03b4a2412b | |||
| 9397ef8a8a | |||
| 720a81ef6f | |||
| 25c47368db | |||
| 32df1da773 | |||
| 3a8737f0e6 | |||
| d8cf67d358 | |||
| fd258a1f6a | |||
| b8fe778299 | |||
| d76da6e4df |
@@ -13,12 +13,14 @@ namespace Ryujinx.Common
|
|||||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||||
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||||
|
private const string ConfigFileNameOverride = "%%RYUJINX_CONFIG_FILE_NAME_OVERRIDE%%";
|
||||||
|
|
||||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||||
public const string ReleaseChannelSourceRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO%%";
|
public const string ReleaseChannelSourceRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO%%";
|
||||||
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||||
|
|
||||||
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||||
|
public static string CustomConfigNameOverride => !ConfigFileNameOverride.StartsWith("%%") ? ConfigFileNameOverride : "CustomConfigOverride.json";
|
||||||
|
|
||||||
public static bool IsValid =>
|
public static bool IsValid =>
|
||||||
!BuildGitHash.StartsWith("%%") &&
|
!BuildGitHash.StartsWith("%%") &&
|
||||||
@@ -26,7 +28,8 @@ namespace Ryujinx.Common
|
|||||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||||
!ReleaseChannelSourceRepo.StartsWith("%%") &&
|
!ReleaseChannelSourceRepo.StartsWith("%%") &&
|
||||||
!ReleaseChannelRepo.StartsWith("%%") &&
|
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||||
!ConfigFileName.StartsWith("%%");
|
!ConfigFileName.StartsWith("%%") &&
|
||||||
|
!ConfigFileNameOverride.StartsWith("%%");
|
||||||
|
|
||||||
public static bool IsCanaryBuild => IsValid && ReleaseChannelName.Equals(CanaryChannel);
|
public static bool IsCanaryBuild => IsValid && ReleaseChannelName.Equals(CanaryChannel);
|
||||||
|
|
||||||
|
|||||||
@@ -2,36 +2,35 @@ using MsgPack;
|
|||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Horizon
|
namespace Ryujinx.Horizon
|
||||||
{
|
{
|
||||||
public static class HorizonStatic
|
public static class HorizonStatic
|
||||||
{
|
{
|
||||||
internal static void HandlePlayReport(MessagePackObject report) =>
|
internal static void HandlePlayReport(MessagePackObject report) => PlayReportPrinted?.Invoke(report);
|
||||||
new Thread(() => PlayReport?.Invoke(report))
|
|
||||||
{
|
|
||||||
Name = "HLE.PlayReportEvent",
|
|
||||||
IsBackground = true,
|
|
||||||
Priority = ThreadPriority.AboveNormal
|
|
||||||
}.Start();
|
|
||||||
|
|
||||||
public static event Action<MessagePackObject> PlayReport;
|
public static event Action<MessagePackObject> PlayReportPrinted;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static HorizonOptions _options;
|
||||||
|
|
||||||
[field: ThreadStatic]
|
[ThreadStatic]
|
||||||
public static HorizonOptions Options { get; private set; }
|
private static ISyscallApi _syscall;
|
||||||
|
|
||||||
[field: ThreadStatic]
|
[ThreadStatic]
|
||||||
public static ISyscallApi Syscall { get; private set; }
|
private static IVirtualMemoryManager _addressSpace;
|
||||||
|
|
||||||
[field: ThreadStatic]
|
[ThreadStatic]
|
||||||
public static IVirtualMemoryManager AddressSpace { get; private set; }
|
private static IThreadContext _threadContext;
|
||||||
|
|
||||||
[field: ThreadStatic]
|
[ThreadStatic]
|
||||||
public static IThreadContext ThreadContext { get; private set; }
|
private static int _threadHandle;
|
||||||
|
|
||||||
[field: ThreadStatic]
|
public static HorizonOptions Options => _options;
|
||||||
public static int CurrentThreadHandle { get; private set; }
|
public static ISyscallApi Syscall => _syscall;
|
||||||
|
public static IVirtualMemoryManager AddressSpace => _addressSpace;
|
||||||
|
public static IThreadContext ThreadContext => _threadContext;
|
||||||
|
public static int CurrentThreadHandle => _threadHandle;
|
||||||
|
|
||||||
public static void Register(
|
public static void Register(
|
||||||
HorizonOptions options,
|
HorizonOptions options,
|
||||||
@@ -40,11 +39,11 @@ namespace Ryujinx.Horizon
|
|||||||
IThreadContext threadContext,
|
IThreadContext threadContext,
|
||||||
int threadHandle)
|
int threadHandle)
|
||||||
{
|
{
|
||||||
Options = options;
|
_options = options;
|
||||||
Syscall = syscallApi;
|
_syscall = syscallApi;
|
||||||
AddressSpace = addressSpace;
|
_addressSpace = addressSpace;
|
||||||
ThreadContext = threadContext;
|
_threadContext = threadContext;
|
||||||
CurrentThreadHandle = threadHandle;
|
_threadHandle = threadHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Gommon;
|
|
||||||
using MsgPack;
|
using MsgPack;
|
||||||
using MsgPack.Serialization;
|
using MsgPack.Serialization;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
@@ -12,7 +11,6 @@ using Ryujinx.Horizon.Sdk.Sf;
|
|||||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
|
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Prepo.Ipc
|
namespace Ryujinx.Horizon.Prepo.Ipc
|
||||||
|
|||||||
@@ -23069,7 +23069,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "可游玩",
|
"zh_CN": "可游玩",
|
||||||
"zh_TW": "可暢順遊玩"
|
"zh_TW": "可暢順遊玩 (Playable)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23094,7 +23094,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "进入游戏",
|
"zh_CN": "进入游戏",
|
||||||
"zh_TW": "大致可遊玩"
|
"zh_TW": "大致可遊玩 (Ingame)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23119,7 +23119,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "菜单",
|
"zh_CN": "菜单",
|
||||||
"zh_TW": "只開啟至遊戲開始功能表"
|
"zh_TW": "只開啟至遊戲開始功能表 (Menus)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23144,7 +23144,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "启动",
|
"zh_CN": "启动",
|
||||||
"zh_TW": "只能啟動"
|
"zh_TW": "只能啟動 (Boots)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -23169,7 +23169,7 @@
|
|||||||
"tr_TR": "",
|
"tr_TR": "",
|
||||||
"uk_UA": "",
|
"uk_UA": "",
|
||||||
"zh_CN": "什么都没有",
|
"zh_CN": "什么都没有",
|
||||||
"zh_TW": "無法啟動"
|
"zh_TW": "無法啟動 (Nothing)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
||||||
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
||||||
HorizonStatic.PlayReport += HandlePlayReport;
|
HorizonStatic.PlayReportPrinted += HandlePlayReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
||||||
@@ -117,15 +117,9 @@ namespace Ryujinx.Ava
|
|||||||
_currentApp = appMeta;
|
_currentApp = appMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool UpdatePlayingState()
|
private static void UpdatePlayingState()
|
||||||
{
|
{
|
||||||
if (_discordClient is null) return false;
|
|
||||||
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_discordClient?.SetPresence(_discordPresencePlaying);
|
_discordClient?.SetPresence(_discordPresencePlaying);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SwitchToMainState()
|
private static void SwitchToMainState()
|
||||||
@@ -141,14 +135,21 @@ namespace Ryujinx.Ava
|
|||||||
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
|
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
|
||||||
if (_discordPresencePlaying is null) return;
|
if (_discordPresencePlaying is null) return;
|
||||||
|
|
||||||
PlayReport.Analyzer.FormatPlayReportValue(TitleIDs.CurrentApplication.Value, _currentApp, playReport)
|
PlayReportFormattedValue value = PlayReport.Analyzer.Run(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
|
||||||
.Match(out bool handled,
|
|
||||||
() => _discordPresencePlaying.Details = $"Playing {_currentApp.Title}",
|
|
||||||
formattedString => _discordPresencePlaying.Details = formattedString
|
|
||||||
);
|
|
||||||
|
|
||||||
if (handled && UpdatePlayingState())
|
if (!value.Handled) return;
|
||||||
|
|
||||||
|
if (value.Reset)
|
||||||
|
{
|
||||||
|
_discordPresencePlaying.Details = $"Playing {_currentApp.Title}";
|
||||||
|
Logger.Info?.Print(LogClass.UI, "Reset Discord RPC based on a supported play report value formatter.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_discordPresencePlaying.Details = value.FormattedString;
|
||||||
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
|
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
|
||||||
|
}
|
||||||
|
UpdatePlayingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TruncateToByteLength(string input)
|
private static string TruncateToByteLength(string input)
|
||||||
|
|||||||
+50
-1
@@ -160,6 +160,28 @@ namespace Ryujinx.Ava
|
|||||||
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);
|
||||||
|
|
||||||
|
string overrideLocalConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.CustomConfigNameOverride);
|
||||||
|
string overrideAppDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.CustomConfigNameOverride);
|
||||||
|
|
||||||
|
// Copies and reloads the configuration file if the game was loaded with arguments
|
||||||
|
// based on global configuration
|
||||||
|
if (CommandLineState.CountArguments > 0)
|
||||||
|
{
|
||||||
|
if (File.Exists(localConfigurationPath))
|
||||||
|
{
|
||||||
|
File.Copy(localConfigurationPath, overrideLocalConfigurationPath, overwrite: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
localConfigurationPath = overrideLocalConfigurationPath;
|
||||||
|
|
||||||
|
if (File.Exists(appDataConfigurationPath))
|
||||||
|
{
|
||||||
|
File.Copy(appDataConfigurationPath, overrideAppDataConfigurationPath, overwrite: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
appDataConfigurationPath = overrideAppDataConfigurationPath;
|
||||||
|
}
|
||||||
|
|
||||||
// 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))
|
||||||
{
|
{
|
||||||
@@ -232,8 +254,35 @@ 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 hardware-acceleration was overridden.
|
// 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! )
|
||||||
if (CommandLineState.OverrideHardwareAcceleration != null)
|
if (CommandLineState.OverrideHardwareAcceleration != null)
|
||||||
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
|
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,15 +375,11 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
png.SaveTo(fileStream);
|
png.SaveTo(fileStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
|
public async void CreateApplicationShortcut_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 })
|
||||||
ShortcutHelper.CreateAppShortcut(
|
await new ArgumentsConfigWindows(viewModel).ShowDialog((Window)viewModel.TopLevel);
|
||||||
viewModel.SelectedApplication.Path,
|
|
||||||
viewModel.SelectedApplication.Name,
|
|
||||||
viewModel.SelectedApplication.IdString,
|
|
||||||
viewModel.SelectedApplication.Icon
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void RunApplication_Click(object sender, RoutedEventArgs args)
|
public async void RunApplication_Click(object sender, RoutedEventArgs args)
|
||||||
|
|||||||
@@ -86,13 +86,6 @@
|
|||||||
Text="{Binding Version}"
|
Text="{Binding Version}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
|
||||||
IsVisible="{Binding HasPlayabilityInfo}"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Text="{Binding LocalizedStatus}"
|
|
||||||
Foreground="{Binding PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
|
||||||
TextAlignment="Start"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
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 Gommon;
|
using Gommon;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
using Ryujinx.Audio.Backends.SDL2;
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
@@ -26,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.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -63,12 +66,56 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public event Action CloseWindow;
|
public event Action CloseWindow;
|
||||||
public event Action SaveSettingsEvent;
|
public event Action SaveSettingsEvent;
|
||||||
|
public event Action CompareSettingsEvent;
|
||||||
private int _networkInterfaceIndex;
|
private int _networkInterfaceIndex;
|
||||||
private int _multiplayerModeIndex;
|
private int _multiplayerModeIndex;
|
||||||
private string _ldnPassphrase;
|
private string _ldnPassphrase;
|
||||||
[ObservableProperty] private string _ldnServer;
|
[ObservableProperty] private string _ldnServer;
|
||||||
|
|
||||||
public SettingsHacksViewModel DirtyHacks { get; }
|
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
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
@@ -344,6 +391,30 @@ 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;
|
||||||
|
|
||||||
|
if (Program.PreviewerDetached)
|
||||||
|
{
|
||||||
|
Task.Run(LoadTimeZones);
|
||||||
|
|
||||||
|
DirtyHacks = new SettingsHacksViewModel(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SettingsViewModel()
|
public SettingsViewModel()
|
||||||
{
|
{
|
||||||
GameDirectories = [];
|
GameDirectories = [];
|
||||||
@@ -716,6 +787,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
SaveSettings();
|
SaveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CreateShortcut()
|
||||||
|
{
|
||||||
|
CompareSettingsEvent?.Invoke(); //raises an event to create a shortcut with arguments
|
||||||
|
}
|
||||||
|
|
||||||
public void OkButton()
|
public void OkButton()
|
||||||
{
|
{
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsApplyOverride"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||||
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:DataType="viewModels:SettingsViewModel">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:SettingsViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<ScrollViewer
|
||||||
|
Name="AllSettings"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<Border Classes="settings">
|
||||||
|
<StackPanel
|
||||||
|
Margin="10"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="10">
|
||||||
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabSystemCore}" />
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{ext:Locale SettingsTabSystemSystemRegion}" Width="250" />
|
||||||
|
<ComboBox
|
||||||
|
SelectedIndex="{Binding Region}"
|
||||||
|
ToolTip.Tip="{ext:Locale RegionTooltip}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
Width="350">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionJapan}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionUSA}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionEurope}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionAustralia}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionChina}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionKorea}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemRegionTaiwan}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabSystemSystemLanguage}"
|
||||||
|
ToolTip.Tip="{ext:Locale LanguageTooltip}"
|
||||||
|
Width="250" />
|
||||||
|
<ComboBox
|
||||||
|
SelectedIndex="{Binding Language}"
|
||||||
|
ToolTip.Tip="{ext:Locale LanguageTooltip}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
Width="350">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageJapanese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageFrench}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageGerman}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageItalian}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageSpanish}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageChinese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageKorean}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageDutch}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguagePortuguese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageRussian}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageSwedish}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemSystemLanguageNorwegian}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<Separator Height="1" />
|
||||||
|
|
||||||
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabCpu}" />
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<CheckBox IsChecked="{Binding EnablePptc}">
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemEnablePptc}"
|
||||||
|
ToolTip.Tip="{ext:Locale PptcToggleTooltip}" />
|
||||||
|
</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabSystemMemoryManagerMode}"
|
||||||
|
ToolTip.Tip="{ext:Locale MemoryManagerTooltip}"
|
||||||
|
Width="250" />
|
||||||
|
<ComboBox SelectedIndex="{Binding MemoryMode}"
|
||||||
|
ToolTip.Tip="{ext:Locale MemoryManagerTooltip}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
Width="350">
|
||||||
|
<ComboBoxItem
|
||||||
|
ToolTip.Tip="{ext:Locale MemoryManagerSoftwareTooltip}">
|
||||||
|
<TextBlock
|
||||||
|
Text="{ext:Locale SettingsTabSystemMemoryManagerModeSoftware}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem
|
||||||
|
ToolTip.Tip="{ext:Locale MemoryManagerHostTooltip}">
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemMemoryManagerModeHost}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem
|
||||||
|
ToolTip.Tip="{ext:Locale MemoryManagerUnsafeTooltip}">
|
||||||
|
<TextBlock
|
||||||
|
Text="{ext:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox IsChecked="{Binding UseHypervisor}"
|
||||||
|
IsVisible="{x:Static helper:RunningPlatform.IsArmMac}"
|
||||||
|
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}">
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
|
||||||
|
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
|
||||||
|
</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
<Separator Height="1" />
|
||||||
|
|
||||||
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphics}" />
|
||||||
|
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendTooltip}"
|
||||||
|
Text="{ext:Locale SettingsTabGraphicsBackend}"
|
||||||
|
Width="250" />
|
||||||
|
<ComboBox
|
||||||
|
Name="GraphicsBackendSelector"
|
||||||
|
Width="350"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendTooltip}"
|
||||||
|
SelectedIndex="{Binding GraphicsBackendIndex}">
|
||||||
|
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}" ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendAutoTooltip}" >
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGraphicsBackendAuto}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
||||||
|
<TextBlock Text="Vulkan" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
||||||
|
<TextBlock Text="OpenGL" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem IsEnabled="{x:Static helper:RunningPlatform.IsArmMac}">
|
||||||
|
<TextBlock Text="Metal (ARM Mac only, Experimental)" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="{ext:Locale GraphicsBackendThreadingTooltip}"
|
||||||
|
Text="{ext:Locale SettingsTabGraphicsBackendMultithreading}"
|
||||||
|
Width="250" />
|
||||||
|
<ComboBox Width="350"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
ToolTip.Tip="{ext:Locale GalThreadingTooltip}"
|
||||||
|
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonAuto}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonOff}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonOn}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Views.Settings
|
||||||
|
{
|
||||||
|
public partial class SettingsApplyOverride : UserControl
|
||||||
|
{
|
||||||
|
public SettingsApplyOverride()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<window:StyleableAppWindow
|
||||||
|
x:Class="Ryujinx.Ava.UI.Windows.ArgumentsConfigWindows"
|
||||||
|
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:SettingsApplyOverride Name="AllSettings" />
|
||||||
|
</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
|
||||||
|
Margin="2,10,10,0"
|
||||||
|
Content="{ext:Locale Settings}"
|
||||||
|
Tag="AllSettings">
|
||||||
|
<ui:NavigationViewItem.IconSource>
|
||||||
|
<ui:FontIconSource
|
||||||
|
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
|
||||||
|
Glyph="{helpers:GlyphValueConverter Chip}" />
|
||||||
|
</ui:NavigationViewItem.IconSource>
|
||||||
|
</ui:NavigationViewItem>
|
||||||
|
</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 GameListContextMenuCreateShortcut}"
|
||||||
|
Command="{Binding CreateShortcut}" />
|
||||||
|
<Button
|
||||||
|
HotKey="Escape"
|
||||||
|
Content="{ext:Locale ControllerSettingsClose}"
|
||||||
|
Command="{Binding CancelButton}" />
|
||||||
|
</ReversibleStackPanel>
|
||||||
|
</Grid>
|
||||||
|
</window:StyleableAppWindow>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
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 ArgumentsConfigWindows : StyleableAppWindow
|
||||||
|
{
|
||||||
|
internal readonly SettingsViewModel ViewModel;
|
||||||
|
public string GamePath { get; }
|
||||||
|
public string GameName { get; }
|
||||||
|
public string GameId { get; }
|
||||||
|
public byte[] GameIconData { get; }
|
||||||
|
|
||||||
|
public static int OverrideBackendThreading { get; private set; }
|
||||||
|
public static int OverrideGraphicsBackend { get; private set; }
|
||||||
|
public static int OverrideSystemLanguage { get; private set; }
|
||||||
|
public static int OverrideSystemRegion { get; private set; }
|
||||||
|
public static bool OverridePPTC { get; private set; }
|
||||||
|
public static int OverrideMemoryManagerMode { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
public ArgumentsConfigWindows(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);
|
||||||
|
|
||||||
|
GamePath = viewModel.SelectedApplication.Path;
|
||||||
|
GameName = viewModel.SelectedApplication.Name;
|
||||||
|
GameId = viewModel.SelectedApplication.IdString;
|
||||||
|
GameIconData = viewModel.SelectedApplication.Icon;
|
||||||
|
|
||||||
|
OverrideBackendThreading = ViewModel.GraphicsBackendMultithreadingIndex;
|
||||||
|
OverrideGraphicsBackend = ViewModel.GraphicsBackendIndex;
|
||||||
|
OverrideSystemLanguage = ViewModel.Language;
|
||||||
|
OverrideSystemRegion = ViewModel.Region;
|
||||||
|
OverridePPTC = ViewModel.EnablePptc;
|
||||||
|
OverrideMemoryManagerMode = ViewModel.MemoryMode;
|
||||||
|
|
||||||
|
ViewModel.CloseWindow += Close;
|
||||||
|
ViewModel.CompareSettingsEvent += CompareConfiguration;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
Load();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CompareConfiguration()
|
||||||
|
{
|
||||||
|
ShortcutHelper.CreateAppShortcut(
|
||||||
|
GamePath,
|
||||||
|
GameName,
|
||||||
|
GameId,
|
||||||
|
GameIconData,
|
||||||
|
GetArguments()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetArguments()
|
||||||
|
{
|
||||||
|
|
||||||
|
string line = "";
|
||||||
|
|
||||||
|
if (OverrideBackendThreading != ViewModel.GraphicsBackendMultithreadingIndex)
|
||||||
|
{
|
||||||
|
string _result = Enum.GetName(typeof(BackendThreading), ViewModel.GraphicsBackendMultithreadingIndex);
|
||||||
|
line += " --backend-threading " + _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverrideGraphicsBackend != ViewModel.GraphicsBackendIndex)
|
||||||
|
{
|
||||||
|
string _result = Enum.GetName(typeof(GraphicsBackend), ViewModel.GraphicsBackendIndex);
|
||||||
|
line += " -g " + _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverridePPTC != ViewModel.EnablePptc)
|
||||||
|
{
|
||||||
|
string _result = ViewModel.EnablePptc.ToString();
|
||||||
|
line += " --pptc " + _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverrideMemoryManagerMode != ViewModel.MemoryMode)
|
||||||
|
{
|
||||||
|
string _result = Enum.GetName(typeof(MemoryManagerMode), ViewModel.MemoryMode);
|
||||||
|
line += " -m " + _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverrideSystemRegion != ViewModel.Region)
|
||||||
|
{
|
||||||
|
string _result = Enum.GetName(typeof(RegionCode), ViewModel.Region);
|
||||||
|
line += " --system-region " + _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverrideSystemLanguage != ViewModel.Language)
|
||||||
|
{
|
||||||
|
string _result = Enum.GetName(typeof(SystemLanguage), ViewModel.Language);
|
||||||
|
line += " --system-language " + _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(AllSettings):
|
||||||
|
NavPanel.Content = AllSettings;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosing(WindowClosingEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnClosing(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ using LibHac.Ns;
|
|||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using Ryujinx.Ava.Utilities.Compat;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
@@ -23,30 +21,9 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public byte[] Icon { get; set; }
|
public byte[] Icon { get; set; }
|
||||||
public string Name { get; set; } = "Unknown";
|
public string Name { get; set; } = "Unknown";
|
||||||
|
public ulong Id { get; set; }
|
||||||
private ulong _id;
|
|
||||||
|
|
||||||
public ulong Id
|
|
||||||
{
|
|
||||||
get => _id;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_id = value;
|
|
||||||
PlayabilityStatus = CompatibilityCsv.GetStatus(Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string Developer { get; set; } = "Unknown";
|
public string Developer { get; set; } = "Unknown";
|
||||||
public string Version { get; set; } = "0";
|
public string Version { get; set; } = "0";
|
||||||
|
|
||||||
public bool HasPlayabilityInfo => PlayabilityStatus != null;
|
|
||||||
|
|
||||||
public string LocalizedStatus =>
|
|
||||||
PlayabilityStatus.HasValue
|
|
||||||
? LocaleManager.Instance[PlayabilityStatus!.Value]
|
|
||||||
: string.Empty;
|
|
||||||
|
|
||||||
public LocaleKeys? PlayabilityStatus { get; set; }
|
|
||||||
|
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
public int GameCount { get; set; }
|
public int GameCount { get; set; }
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ 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 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 string Profile { get; private set; }
|
public static string Profile { get; private set; }
|
||||||
@@ -28,6 +32,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":
|
||||||
@@ -85,6 +94,47 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
|
|
||||||
OverrideBackendThreading = args[++i];
|
OverrideBackendThreading = args[++i];
|
||||||
break;
|
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];
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatibility");
|
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatibility");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Unload()
|
||||||
|
{
|
||||||
|
_entries = null;
|
||||||
|
}
|
||||||
|
|
||||||
private static CompatibilityEntry[] _entries;
|
private static CompatibilityEntry[] _entries;
|
||||||
|
|
||||||
public static CompatibilityEntry[] Entries
|
public static CompatibilityEntry[] Entries
|
||||||
@@ -59,11 +64,6 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
return _entries;
|
return _entries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LocaleKeys? GetStatus(string titleId)
|
|
||||||
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId))?.Status;
|
|
||||||
|
|
||||||
public static LocaleKeys? GetStatus(ulong titleId) => GetStatus(titleId.ToString("X16"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CompatibilityEntry
|
public class CompatibilityEntry
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
contentDialog.Styles.Add(closeButtonParent);
|
contentDialog.Styles.Add(closeButtonParent);
|
||||||
|
|
||||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
|
|
||||||
|
CompatibilityCsv.Unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompatibilityList()
|
public CompatibilityList()
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
using PlayReportFormattedValue = Ryujinx.Ava.Utilities.PlayReportAnalyzer.FormattedValue;
|
using Gommon;
|
||||||
|
using MsgPack;
|
||||||
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Common.Helper;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Utilities
|
namespace Ryujinx.Ava.Utilities
|
||||||
{
|
{
|
||||||
@@ -7,55 +13,41 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer()
|
public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer()
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"01007ef00011e000",
|
"01007ef00011e000",
|
||||||
spec => spec
|
spec => spec.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
||||||
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
|
||||||
// reset to normal status when switching between normal & master mode in title screen
|
|
||||||
.AddValueFormatter("AoCVer", PlayReportFormattedValue.AlwaysResets)
|
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec( // Super Mario Odyssey
|
||||||
"0100f2c0115b6000",
|
|
||||||
spec => spec.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
|
|
||||||
.AddSpec(
|
|
||||||
"0100000000010000",
|
"0100000000010000",
|
||||||
spec =>
|
spec =>
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec( // Super Mario Odyssey (China)
|
||||||
"010075000ecbe000",
|
"010075000ECBE000",
|
||||||
spec =>
|
spec =>
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec( // Super Mario 3D World + Bowser's Fury
|
||||||
"010028600ebda000",
|
"010028600EBDA000",
|
||||||
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
||||||
)
|
)
|
||||||
.AddSpec( // Global & China IDs
|
.AddSpec( // Mario Kart 8 Deluxe, Mario Kart 8 Deluxe (China)
|
||||||
["0100152000022000", "010075100e8ec000"],
|
["0100152000022000", "010075100E8EC000"],
|
||||||
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
||||||
);
|
);
|
||||||
|
|
||||||
private static PlayReportFormattedValue BreathOfTheWild_MasterMode(PlayReportValue value)
|
private static PlayReportFormattedValue BreathOfTheWild_MasterMode(ref PlayReportValue value)
|
||||||
=> value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset;
|
=> value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset;
|
||||||
|
|
||||||
private static PlayReportFormattedValue TearsOfTheKingdom_CurrentField(PlayReportValue value) =>
|
private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(ref PlayReportValue value)
|
||||||
value.DoubleValue switch
|
|
||||||
{
|
|
||||||
> 800d => "Exploring the Sky Islands",
|
|
||||||
< -201d => "Exploring the Depths",
|
|
||||||
_ => "Roaming Hyrule"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(PlayReportValue value)
|
|
||||||
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||||
|
|
||||||
private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(PlayReportValue value)
|
private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(ref PlayReportValue value)
|
||||||
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
||||||
|
|
||||||
private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(PlayReportValue value)
|
private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(ref PlayReportValue value)
|
||||||
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
||||||
|
|
||||||
private static PlayReportFormattedValue MarioKart8Deluxe_Mode(PlayReportValue value)
|
private static PlayReportFormattedValue MarioKart8Deluxe_Mode(ref PlayReportValue value)
|
||||||
=> value.StringValue switch
|
=> value.BoxedValue switch
|
||||||
{
|
{
|
||||||
// Single Player
|
// Single Player
|
||||||
"Single" => "Single Player",
|
"Single" => "Single Player",
|
||||||
@@ -82,4 +74,125 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
_ => PlayReportFormattedValue.ForceReset
|
_ => PlayReportFormattedValue.ForceReset
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Analyzer implementation
|
||||||
|
|
||||||
|
public class PlayReportAnalyzer
|
||||||
|
{
|
||||||
|
private readonly List<PlayReportGameSpec> _specs = [];
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..titleIds] }));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> transform)
|
||||||
|
{
|
||||||
|
_specs.Add(new PlayReportGameSpec { TitleIds = [..titleIds] }.Apply(transform));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportFormattedValue Run(string runningGameId, ApplicationMetadata appMeta, MessagePackObject playReport)
|
||||||
|
{
|
||||||
|
if (!playReport.IsDictionary)
|
||||||
|
return PlayReportFormattedValue.Unhandled;
|
||||||
|
|
||||||
|
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec))
|
||||||
|
return PlayReportFormattedValue.Unhandled;
|
||||||
|
|
||||||
|
foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority))
|
||||||
|
{
|
||||||
|
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PlayReportValue value = new()
|
||||||
|
{
|
||||||
|
Application = appMeta,
|
||||||
|
BoxedValue = valuePackObject.ToObject()
|
||||||
|
};
|
||||||
|
|
||||||
|
return formatSpec.ValueFormatter(ref value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlayReportFormattedValue.Unhandled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlayReportGameSpec
|
||||||
|
{
|
||||||
|
public required string[] TitleIds { get; init; }
|
||||||
|
public List<PlayReportValueFormatterSpec> Analyses { get; } = [];
|
||||||
|
|
||||||
|
public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
|
||||||
|
{
|
||||||
|
Analyses.Add(new PlayReportValueFormatterSpec
|
||||||
|
{
|
||||||
|
Priority = Analyses.Count,
|
||||||
|
ReportKey = reportKey,
|
||||||
|
ValueFormatter = valueFormatter
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, PlayReportValueFormatter valueFormatter)
|
||||||
|
{
|
||||||
|
Analyses.Add(new PlayReportValueFormatterSpec
|
||||||
|
{
|
||||||
|
Priority = priority,
|
||||||
|
ReportKey = reportKey,
|
||||||
|
ValueFormatter = valueFormatter
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PlayReportValue
|
||||||
|
{
|
||||||
|
public ApplicationMetadata Application { get; init; }
|
||||||
|
public object BoxedValue { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PlayReportFormattedValue
|
||||||
|
{
|
||||||
|
public bool Handled { get; private init; }
|
||||||
|
|
||||||
|
public bool Reset { get; private init; }
|
||||||
|
|
||||||
|
public string FormattedString { get; private init; }
|
||||||
|
|
||||||
|
public static implicit operator PlayReportFormattedValue(string formattedValue)
|
||||||
|
=> new() { Handled = true, FormattedString = formattedValue };
|
||||||
|
|
||||||
|
public static PlayReportFormattedValue Unhandled => default;
|
||||||
|
public static PlayReportFormattedValue ForceReset => new() { Handled = true, Reset = true };
|
||||||
|
|
||||||
|
public static PlayReportValueFormatter AlwaysResets = AlwaysResetsImpl;
|
||||||
|
|
||||||
|
private static PlayReportFormattedValue AlwaysResetsImpl(ref PlayReportValue _) => ForceReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PlayReportValueFormatterSpec
|
||||||
|
{
|
||||||
|
public required int Priority { get; init; }
|
||||||
|
public required string ReportKey { get; init; }
|
||||||
|
public PlayReportValueFormatter ValueFormatter { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate PlayReportFormattedValue PlayReportValueFormatter(ref PlayReportValue value);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,299 +0,0 @@
|
|||||||
using Gommon;
|
|
||||||
using MsgPack;
|
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Utilities
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The entrypoint for the Play Report analysis system.
|
|
||||||
/// </summary>
|
|
||||||
public class PlayReportAnalyzer
|
|
||||||
{
|
|
||||||
private readonly List<PlayReportGameSpec> _specs = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
|
|
||||||
/// <param name="transform">The configuration function for the analysis spec.</param>
|
|
||||||
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns>
|
|
||||||
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
|
|
||||||
|
|
||||||
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
|
|
||||||
/// <param name="transform">The configuration function for the analysis spec.</param>
|
|
||||||
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns>
|
|
||||||
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
|
|
||||||
|
|
||||||
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
|
|
||||||
/// <param name="transform">The configuration function for the analysis spec.</param>
|
|
||||||
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns>
|
|
||||||
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds,
|
|
||||||
Func<PlayReportGameSpec, PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
string[] tids = titleIds.ToArray();
|
|
||||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
|
|
||||||
|
|
||||||
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..tids] }));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
|
|
||||||
/// <param name="transform">The configuration function for the analysis spec.</param>
|
|
||||||
/// <returns>The current <see cref="PlayReportAnalyzer"/>, for chaining convenience.</returns>
|
|
||||||
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> transform)
|
|
||||||
{
|
|
||||||
string[] tids = titleIds.ToArray();
|
|
||||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
|
|
||||||
|
|
||||||
_specs.Add(new PlayReportGameSpec { TitleIds = [..tids] }.Apply(transform));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the configured <see cref="PlayReportGameSpec.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>
|
|
||||||
/// <param name="playReport">The Play Report received from HLE.</param>
|
|
||||||
/// <returns>A struct representing a possible formatted value.</returns>
|
|
||||||
public FormattedValue FormatPlayReportValue(
|
|
||||||
string runningGameId,
|
|
||||||
ApplicationMetadata appMeta,
|
|
||||||
MessagePackObject playReport
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (!playReport.IsDictionary)
|
|
||||||
return FormattedValue.Unhandled;
|
|
||||||
|
|
||||||
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec))
|
|
||||||
return FormattedValue.Unhandled;
|
|
||||||
|
|
||||||
foreach (PlayReportGameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
|
|
||||||
{
|
|
||||||
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return formatSpec.ValueFormatter(new PlayReportValue
|
|
||||||
{
|
|
||||||
Application = appMeta, PackedValue = valuePackObject
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormattedValue.Unhandled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A potential formatted value returned by a <see cref="PlayReportValueFormatter"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct FormattedValue
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Was any handler able to match anything in the Play Report?
|
|
||||||
/// </summary>
|
|
||||||
public bool Handled { get; private init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Did the handler request the caller of the <see cref="PlayReportAnalyzer"/> to reset the existing value?
|
|
||||||
/// </summary>
|
|
||||||
public bool Reset { get; private init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The formatted value, only present if <see cref="Handled"/> is true, and <see cref="Reset"/> is false.
|
|
||||||
/// </summary>
|
|
||||||
public string FormattedString { get; private init; }
|
|
||||||
|
|
||||||
public void Match(out bool wasHandled, Action onReset, Action<string> onSuccess)
|
|
||||||
{
|
|
||||||
if (!Handled)
|
|
||||||
{
|
|
||||||
wasHandled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Reset)
|
|
||||||
onReset();
|
|
||||||
else
|
|
||||||
onSuccess(FormattedString);
|
|
||||||
|
|
||||||
wasHandled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The intended path of execution for having a string to return: simply return the string.
|
|
||||||
/// This implicit conversion will make the struct for you.<br/><br/>
|
|
||||||
///
|
|
||||||
/// If the input is null, <see cref="Unhandled"/> is returned.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="formattedValue">The formatted string value.</param>
|
|
||||||
/// <returns>The automatically constructed <see cref="FormattedValue"/> struct.</returns>
|
|
||||||
public static implicit operator FormattedValue(string formattedValue)
|
|
||||||
=> formattedValue is not null
|
|
||||||
? new FormattedValue { Handled = true, FormattedString = formattedValue }
|
|
||||||
: Unhandled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return this to tell the caller there is no value to return.
|
|
||||||
/// </summary>
|
|
||||||
public static FormattedValue Unhandled => default;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return this to suggest the caller reset the value it's using the <see cref="PlayReportAnalyzer"/> for.
|
|
||||||
/// </summary>
|
|
||||||
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="PlayReportValueFormatter"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly PlayReportValueFormatter AlwaysResets = _ => ForceReset;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A delegate factory you can use to always return the specified
|
|
||||||
/// <paramref name="formattedValue"/> in a <see cref="PlayReportValueFormatter"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="formattedValue">The string to always return for this delegate instance.</param>
|
|
||||||
public static PlayReportValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A mapping of title IDs to value formatter specs.
|
|
||||||
///
|
|
||||||
/// <remarks>Generally speaking, use the <see cref="PlayReportAnalyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
|
|
||||||
/// </summary>
|
|
||||||
public class PlayReportGameSpec
|
|
||||||
{
|
|
||||||
public required string[] TitleIds { get; init; }
|
|
||||||
public List<FormatterSpec> SimpleValueFormatters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a value formatter to the current <see cref="PlayReportGameSpec"/>
|
|
||||||
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reportKey">The key name to match.</param>
|
|
||||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
|
||||||
/// <returns>The current <see cref="PlayReportGameSpec"/>, for chaining convenience.</returns>
|
|
||||||
public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
|
|
||||||
{
|
|
||||||
SimpleValueFormatters.Add(new FormatterSpec
|
|
||||||
{
|
|
||||||
Priority = SimpleValueFormatters.Count, ReportKey = reportKey, ValueFormatter = valueFormatter
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a value formatter at a specific priority to the current <see cref="PlayReportGameSpec"/>
|
|
||||||
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
|
|
||||||
/// <param name="reportKey">The key name to match.</param>
|
|
||||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
|
||||||
/// <returns>The current <see cref="PlayReportGameSpec"/>, for chaining convenience.</returns>
|
|
||||||
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey,
|
|
||||||
PlayReportValueFormatter valueFormatter)
|
|
||||||
{
|
|
||||||
SimpleValueFormatters.Add(new FormatterSpec
|
|
||||||
{
|
|
||||||
Priority = priority, ReportKey = reportKey, ValueFormatter = valueFormatter
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
|
|
||||||
/// </summary>
|
|
||||||
public struct FormatterSpec
|
|
||||||
{
|
|
||||||
public required int Priority { get; init; }
|
|
||||||
public required string ReportKey { get; init; }
|
|
||||||
public PlayReportValueFormatter ValueFormatter { get; init; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The input data to a <see cref="PlayReportValueFormatter"/>,
|
|
||||||
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
|
|
||||||
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
|
|
||||||
/// </summary>
|
|
||||||
public class PlayReportValue
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The currently running application's <see cref="ApplicationMetadata"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ApplicationMetadata Application { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The matched value from the Play Report.
|
|
||||||
/// </summary>
|
|
||||||
public MessagePackObject PackedValue { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Access the <see cref="PackedValue"/> as its underlying .NET type.<br/>
|
|
||||||
///
|
|
||||||
/// Does not seem to work well with comparing numeric types,
|
|
||||||
/// so use <see cref="PackedValue"/> and the AsX (where X is a numerical type name i.e. Int32) methods for that.
|
|
||||||
/// </summary>
|
|
||||||
public object BoxedValue => PackedValue.ToObject();
|
|
||||||
|
|
||||||
#region AsX accessors
|
|
||||||
|
|
||||||
public bool BooleanValue => PackedValue.AsBoolean();
|
|
||||||
public byte ByteValye => PackedValue.AsByte();
|
|
||||||
public sbyte SByteValye => PackedValue.AsSByte();
|
|
||||||
public short ShortValye => PackedValue.AsInt16();
|
|
||||||
public ushort UShortValye => PackedValue.AsUInt16();
|
|
||||||
public int IntValye => PackedValue.AsInt32();
|
|
||||||
public uint UIntValye => PackedValue.AsUInt32();
|
|
||||||
public long LongValye => PackedValue.AsInt64();
|
|
||||||
public ulong ULongValye => PackedValue.AsUInt64();
|
|
||||||
public float FloatValue => PackedValue.AsSingle();
|
|
||||||
public double DoubleValue => PackedValue.AsDouble();
|
|
||||||
public string StringValue => PackedValue.AsString();
|
|
||||||
public Span<byte> BinaryValue => PackedValue.AsBinary();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The delegate type that powers the entire analysis system (as it currently is).<br/>
|
|
||||||
/// Takes in the result value from the Play Report, and outputs:
|
|
||||||
/// <br/>
|
|
||||||
/// a formatted string,
|
|
||||||
/// <br/>
|
|
||||||
/// a signal that nothing was available to handle it,
|
|
||||||
/// <br/>
|
|
||||||
/// OR a signal to reset the value that the caller is using the <see cref="PlayReportAnalyzer"/> for.
|
|
||||||
/// </summary>
|
|
||||||
public delegate PlayReportAnalyzer.FormattedValue PlayReportValueFormatter(PlayReportValue value);
|
|
||||||
}
|
|
||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user