Merge branch 'master' into translation-it

This commit is contained in:
Nicola
2024-11-14 18:03:50 +01:00
committed by GitHub
101 changed files with 4556 additions and 576 deletions

View File

@@ -207,6 +207,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
ConfigurationState.Instance.Multiplayer.LdnPassphrase.Event += UpdateLdnPassphraseState;
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
_gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
@@ -491,6 +494,21 @@ namespace Ryujinx.Ava
Device.Configuration.MultiplayerMode = e.NewValue;
}
private void UpdateLdnPassphraseState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLdnPassphrase = e.NewValue;
}
private void UpdateLdnServerState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLdnServer = e.NewValue;
}
private void UpdateDisableP2pState(object sender, ReactiveEventArgs<bool> e)
{
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
}
public void ToggleVSync()
{
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
@@ -863,10 +881,11 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId,
ConfigurationState.Instance.Multiplayer.Mode
)
);
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
ConfigurationState.Instance.Multiplayer.Mode,
ConfigurationState.Instance.Multiplayer.DisableP2p,
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
ConfigurationState.Instance.Multiplayer.LdnServer));
}
private static IHardwareDeviceDriver InitializeAudio()
@@ -1050,7 +1069,7 @@ namespace Ryujinx.Ava
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
UpdateShaderCount();
if (GraphicsConfig.ResScale != 1)
{
dockedMode += $" ({GraphicsConfig.ResScale}x)";

View File

@@ -848,5 +848,17 @@
"MultiplayerMode": "Mode:",
"MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
"MultiplayerModeDisabled": "Disabled",
"MultiplayerModeLdnMitm": "ldn_mitm"
"MultiplayerModeLdnMitm": "ldn_mitm",
"MultiplayerModeLdnRyu": "RyuLDN",
"MultiplayerDisableP2P": "Disable P2P Network Hosting (may increase latency)",
"MultiplayerDisableP2PTooltip": "Disable P2P network hosting, peers will proxy through the master server instead of connecting to you directly.",
"LdnPassphrase": "Network Passphrase:",
"LdnPassphraseTooltip": "You will only be able to see hosted games with the same passphrase as you.",
"LdnPassphraseInputTooltip": "Enter a passphrase in the format Ryujinx-<8 hex chars>. You will only be able to see hosted games with the same passphrase as you.",
"LdnPassphraseInputPublic": "(public)",
"GenLdnPass": "Generate Random",
"GenLdnPassTooltip": "Generates a new passphrase, which can be shared with other players.",
"ClearLdnPass": "Clear",
"ClearLdnPassTooltip": "Clears the current passphrase, returning to the public network.",
"InvalidLdnPassphrase": "Invalid Passphrase! Must be in the format \"Ryujinx-<8 hex chars>\""
}

File diff suppressed because it is too large Load Diff

View File

@@ -99,7 +99,7 @@ namespace Ryujinx.Ava.Common.Locale
_ => false
};
public static string FormatDynamicValue(LocaleKeys key, params object[] values)
public static string FormatDynamicValue(LocaleKeys key, params object[] values)
=> Instance.UpdateAndGetDynamicValue(key, values);
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)

View File

@@ -103,7 +103,7 @@ namespace Ryujinx.Ava
Console.Title = $"{App.FullAppName} Console {Version}";
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e)
AppDomain.CurrentDomain.UnhandledException += (sender, e)
=> ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit();
@@ -224,16 +224,14 @@ namespace Ryujinx.Ava
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
Logger.Notice.Print(LogClass.Application, $"{App.FullAppName} Version: {Version}");
SystemInfo.Gather().Print();
var enabledLogLevels = Logger.GetEnabledLevels().ToArray();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {
(enabledLogLevels.Length is 0
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogLevels.Length is 0
? "<None>"
: enabledLogLevels.JoinToString(", "))
}");
: enabledLogLevels.JoinToString(", "))}");
Logger.Notice.Print(LogClass.Application,
AppDataManager.Mode == AppDataManager.LaunchMode.Custom
@@ -245,13 +243,13 @@ namespace Ryujinx.Ava
{
Logger.Log log = Logger.Error ?? Logger.Notice;
string message = $"Unhandled exception caught: {ex}";
// ReSharper disable once ConstantConditionalAccessQualifier
if (sender?.GetType()?.AsPrettyString() is {} senderName)
if (sender?.GetType()?.AsPrettyString() is { } senderName)
log.Print(LogClass.Application, message, senderName);
else
log.PrintMsg(LogClass.Application, message);
if (isTerminating)
Exit();
}

View File

@@ -31,7 +31,7 @@ namespace Ryujinx.Ava.UI.Applet
public bool DisplayMessageDialog(ControllerAppletUIArgs args)
{
ManualResetEvent dialogCloseEvent = new(false);
bool okPressed = false;
if (ConfigurationState.Instance.IgnoreApplet)

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Ava.UI.Applet
public AvaloniaDynamicTextInputHandler(MainWindow parent)
{
_parent = parent;
if (_parent.InputManager.KeyboardDriver is AvaloniaKeyboardDriver avaloniaKeyboardDriver)
{
avaloniaKeyboardDriver.KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed;
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Applet
avaloniaKeyboardDriver.KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease;
avaloniaKeyboardDriver.TextInput -= AvaloniaDynamicTextInputHandler_TextInput;
}
_textChangedSubscription?.Dispose();
_selectionStartChangedSubscription?.Dispose();
_selectionEndtextChangedSubscription?.Dispose();

View File

@@ -37,8 +37,8 @@ namespace Ryujinx.Ava.UI.Applet
public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args)
{
PlayerCount = args.PlayerCountMin == args.PlayerCountMax
? args.PlayerCountMin.ToString()
PlayerCount = args.PlayerCountMin == args.PlayerCountMax
? args.PlayerCountMin.ToString()
: $"{args.PlayerCountMin} - {args.PlayerCountMax}";
SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0;

View File

@@ -7,6 +7,7 @@
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:converters="clr-namespace:Avalonia.Data.Converters;assembly=Avalonia.Base"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
@@ -110,6 +111,11 @@
Text="{Binding FileExtension}"
TextAlignment="Start"
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding Converter={helpers:MultiplayerInfoConverter}}"
TextAlignment="Start"
TextWrapping="Wrap"/>
</StackPanel>
<StackPanel
Grid.Column="4"

View File

@@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.Controls
if (contentManager.GetCurrentFirmwareVersion() != null)
Task.Run(() => UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem));
InitializeComponent();
}
@@ -60,13 +60,13 @@ namespace Ryujinx.Ava.UI.Controls
LoadProfiles();
}
public void Navigate(Type sourcePageType, object parameter)
public void Navigate(Type sourcePageType, object parameter)
=> ContentFrame.Navigate(sourcePageType, parameter);
public static async Task Show(
AccountManager ownerAccountManager,
AccountManager ownerAccountManager,
ContentManager ownerContentManager,
VirtualFileSystem ownerVirtualFileSystem,
VirtualFileSystem ownerVirtualFileSystem,
HorizonClient ownerHorizonClient)
{
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
@@ -158,9 +158,9 @@ namespace Ryujinx.Ava.UI.Controls
_ = Dispatcher.UIThread.InvokeAsync(async ()
=> await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]));
return;
}
}
AccountManager.OpenUser(profile.UserId);
}

View File

@@ -22,9 +22,9 @@ namespace Ryujinx.Ava.UI.Helpers
_key = key;
}
public string this[string key] =>
public string this[string key] =>
_glyphs.TryGetValue(Enum.Parse<Glyph>(key), out var val)
? val
? val
: string.Empty;
public override object ProvideValue(IServiceProvider serviceProvider) => this[_key];

View File

@@ -0,0 +1,44 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
internal class MultiplayerInfoConverter : MarkupExtension, IValueConverter
{
private static readonly MultiplayerInfoConverter _instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ApplicationData applicationData)
{
if (applicationData.PlayerCount != 0 && applicationData.GameCount != 0)
{
return $"Hosted Games: {applicationData.GameCount}\nOnline Players: {applicationData.PlayerCount}";
}
else
{
return "";
}
}
else
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _instance;
}
}
}

View File

@@ -9,12 +9,12 @@ namespace Ryujinx.Ava.UI.Helpers
{
public static TimeZoneConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is TimeZone timeZone
? $"{timeZone.UtcDifference} {timeZone.Location} {timeZone.Abbreviation}"
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is TimeZone timeZone
? $"{timeZone.UtcDifference} {timeZone.Location} {timeZone.Abbreviation}"
: null;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
}

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.Ava.UI.Models
public string DockedMode { get; }
public string FifoStatus { get; }
public string GameStatus { get; }
public uint ShaderCount { get; }
public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount)

View File

@@ -117,6 +117,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
public IEnumerable<LdnGameData> LastLdnGameData;
public static readonly Bitmap IconBitmap =
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!);
@@ -173,7 +175,7 @@ namespace Ryujinx.Ava.UI.ViewModels
SwitchToGameControl = switchToGameControl;
SetMainContent = setMainContent;
TopLevel = topLevel;
#if DEBUG
topLevel.AttachDevTools(new KeyGesture(Avalonia.Input.Key.F12, KeyModifiers.Control));
#endif
@@ -268,7 +270,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool ShowFirmwareStatus => !ShowLoadProgress;
public bool ShowRightmostSeparator
public bool ShowRightmostSeparator
{
get => _showRightmostSeparator;
set
@@ -553,7 +555,7 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged();
}
}
public string ShaderCountText
{
get => _shaderCountText;
@@ -1021,7 +1023,7 @@ namespace Ryujinx.Ava.UI.ViewModels
? SortExpressionComparer<ApplicationData>.Ascending(selector)
: SortExpressionComparer<ApplicationData>.Descending(selector);
private IComparer<ApplicationData> GetComparer()
private IComparer<ApplicationData> GetComparer()
=> SortMode switch
{
#pragma warning disable IDE0055 // Disable formatting
@@ -1251,7 +1253,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void InitializeGame()
{
RendererHostControl.WindowCreated += RendererHost_Created;
AppHost.StatusUpdatedEvent += Update_StatusBar;
AppHost.AppExit += AppHost_AppExit;
@@ -1300,9 +1302,9 @@ namespace Ryujinx.Ava.UI.ViewModels
GameStatusText = args.GameStatus;
VolumeStatusText = args.VolumeStatus;
FifoStatusText = args.FifoStatus;
ShaderCountText = (ShowRightmostSeparator = args.ShaderCount > 0)
? $"{LocaleManager.Instance[LocaleKeys.CompilingShaders]}: {args.ShaderCount}"
ShaderCountText = (ShowRightmostSeparator = args.ShaderCount > 0)
? $"{LocaleManager.Instance[LocaleKeys.CompilingShaders]}: {args.ShaderCount}"
: string.Empty;
ShowStatusSeparator = true;
@@ -1707,7 +1709,7 @@ namespace Ryujinx.Ava.UI.ViewModels
RendererHostControl.Focus();
});
public static void UpdateGameMetadata(string titleId)
public static void UpdateGameMetadata(string titleId)
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame());
public void RefreshFirmwareStatus()

View File

@@ -25,12 +25,13 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels
{
public class SettingsViewModel : BaseModel
public partial class SettingsViewModel : BaseModel
{
private readonly VirtualFileSystem _virtualFileSystem;
private readonly ContentManager _contentManager;
@@ -56,6 +57,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action SaveSettingsEvent;
private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
private string _ldnPassphrase;
private string _LdnServer;
public int ResolutionScale
{
@@ -180,10 +183,24 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
public bool UseHypervisor { get; set; }
public bool DisableP2P { get; set; }
public string TimeZone { get; set; }
public string ShaderDumpPath { get; set; }
public string LdnPassphrase
{
get => _ldnPassphrase;
set
{
_ldnPassphrase = value;
IsInvalidLdnPassphraseVisible = !ValidateLdnPassphrase(value);
OnPropertyChanged();
OnPropertyChanged(nameof(IsInvalidLdnPassphraseVisible));
}
}
public int Language { get; set; }
public int Region { get; set; }
public int FsGlobalAccessLogMode { get; set; }
@@ -276,6 +293,21 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
[GeneratedRegex("Ryujinx-[0-9a-f]{8}")]
private static partial Regex LdnPassphraseRegex();
public bool IsInvalidLdnPassphraseVisible { get; set; }
public string LdnServer
{
get => _LdnServer;
set
{
_LdnServer = value;
OnPropertyChanged();
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
@@ -393,6 +425,11 @@ namespace Ryujinx.Ava.UI.ViewModels
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex)));
}
private bool ValidateLdnPassphrase(string passphrase)
{
return string.IsNullOrEmpty(passphrase) || (passphrase.Length == 16 && LdnPassphraseRegex().IsMatch(passphrase));
}
public void ValidateAndSetTimeZone(string location)
{
if (_validTzRegions.Contains(location))
@@ -497,6 +534,9 @@ namespace Ryujinx.Ava.UI.ViewModels
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
DisableP2P = config.Multiplayer.DisableP2p.Value;
LdnPassphrase = config.Multiplayer.LdnPassphrase.Value;
LdnServer = config.Multiplayer.LdnServer.Value;
}
public void SaveSettings()
@@ -613,6 +653,9 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
config.Multiplayer.DisableP2p.Value = DisableP2P;
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
config.Multiplayer.LdnServer.Value = LdnServer;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);

View File

@@ -40,8 +40,8 @@ namespace Ryujinx.Ava.UI.Views.Main
private CheckBox[] GenerateToggleFileTypeItems() =>
Enum.GetValues<FileTypes>()
.Select(it => (FileName: Enum.GetName(it)!, FileType: it))
.Select(it =>
new CheckBox
.Select(it =>
new CheckBox
{
Content = $".{it.FileName}",
IsChecked = it.FileType.GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes),

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -36,11 +36,57 @@
<ComboBoxItem>
<TextBlock Text="{ext:Locale MultiplayerModeDisabled}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale MultiplayerModeLdnRyu}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale MultiplayerModeLdnMitm}" />
</ComboBoxItem>
</ComboBox>
<CheckBox Margin="10,0,0,0" IsChecked="{Binding DisableP2P}">
<TextBlock Text="{ext:Locale MultiplayerDisableP2P}"
ToolTip.Tip="{ext:Locale MultiplayerDisableP2PTooltip}" />
</CheckBox>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale LdnPassphrase}"
ToolTip.Tip="{ext:Locale LdnPassphraseTooltip}"
Width="200" />
<TextBox Name="LdnPassphrase"
Text="{Binding LdnPassphrase}"
Width="250"
MaxLength="16"
ToolTip.Tip="{ext:Locale LdnPassphraseInputTooltip}"
Watermark="{ext:Locale LdnPassphraseInputPublic}" />
<Button
Name="GenLdnPassButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale GenLdnPassTooltip}"
Click="GenLdnPassButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale GenLdnPass}" />
</Button>
<Button
Name="ClearLdnPassButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale ClearLdnPassTooltip}"
Click="ClearLdnPassButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale ClearLdnPass}" />
</Button>
</StackPanel>
<TextBlock Margin="10,0,0,0"
VerticalAlignment="Center"
Name="InvalidLdnPassphraseBlock"
FontStyle="Italic"
IsVisible="{Binding IsInvalidLdnPassphraseVisible}"
Focusable="False"
Text="{ext:Locale InvalidLdnPassphrase}" />
<Separator Height="1" />
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabNetworkConnection}" />
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">

View File

@@ -1,12 +1,29 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.ViewModels;
using System;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsNetworkView : UserControl
{
public SettingsViewModel ViewModel;
public SettingsNetworkView()
{
InitializeComponent();
}
private void GenLdnPassButton_OnClick(object sender, RoutedEventArgs e)
{
byte[] code = new byte[4];
new Random().NextBytes(code);
ViewModel.LdnPassphrase = $"Ryujinx-{BitConverter.ToUInt32(code):x8}";
}
private void ClearLdnPassButton_OnClick(object sender, RoutedEventArgs e)
{
ViewModel.LdnPassphrase = "";
}
}
}

View File

@@ -154,6 +154,36 @@ namespace Ryujinx.Ava.UI.Windows
});
}
private void ApplicationLibrary_LdnGameDataReceived(object sender, LdnGameDataReceivedEventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
var ldnGameDataArray = e.LdnData;
ViewModel.LastLdnGameData = ldnGameDataArray;
foreach (var application in ViewModel.Applications)
{
UpdateApplicationWithLdnData(application);
}
ViewModel.RefreshView();
});
}
private void UpdateApplicationWithLdnData(ApplicationData application)
{
if (application.ControlHolder.ByteSpan.Length > 0 && ViewModel.LastLdnGameData != null)
{
IEnumerable<LdnGameData> ldnGameData = ViewModel.LastLdnGameData.Where(game => application.ControlHolder.Value.LocalCommunicationId.Items.Contains(Convert.ToUInt64(game.TitleId, 16)));
application.PlayerCount = ldnGameData.Sum(game => game.PlayerCount);
application.GameCount = ldnGameData.Count();
}
else
{
application.PlayerCount = 0;
application.GameCount = 0;
}
}
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
{
if (args.Application != null)
@@ -450,7 +480,20 @@ namespace Ryujinx.Ava.UI.Windows
.Connect()
.ObserveOn(SynchronizationContext.Current!)
.Bind(ViewModel.Applications)
.OnItemAdded(UpdateApplicationWithLdnData)
.Subscribe();
ApplicationLibrary.LdnGameDataReceived += ApplicationLibrary_LdnGameDataReceived;
ConfigurationState.Instance.Multiplayer.Mode.Event += (sender, evt) =>
{
_ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
};
ConfigurationState.Instance.Multiplayer.LdnServer.Event += (sender, evt) =>
{
_ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
};
_ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
ViewModel.RefreshFirmwareStatus();
@@ -459,7 +502,7 @@ namespace Ryujinx.Ava.UI.Windows
{
LoadApplications();
}
_ = CheckLaunchState();
}
@@ -588,13 +631,26 @@ namespace Ryujinx.Ava.UI.Windows
{
switch (fileType)
{
case "NSP": ConfigurationState.Instance.UI.ShownFileTypes.NSP.Toggle(); break;
case "PFS0": ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Toggle(); break;
case "XCI": ConfigurationState.Instance.UI.ShownFileTypes.XCI.Toggle(); break;
case "NCA": ConfigurationState.Instance.UI.ShownFileTypes.NCA.Toggle(); break;
case "NRO": ConfigurationState.Instance.UI.ShownFileTypes.NRO.Toggle(); break;
case "NSO": ConfigurationState.Instance.UI.ShownFileTypes.NSO.Toggle(); break;
default: throw new ArgumentOutOfRangeException(fileType);
case "NSP":
ConfigurationState.Instance.UI.ShownFileTypes.NSP.Toggle();
break;
case "PFS0":
ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Toggle();
break;
case "XCI":
ConfigurationState.Instance.UI.ShownFileTypes.XCI.Toggle();
break;
case "NCA":
ConfigurationState.Instance.UI.ShownFileTypes.NCA.Toggle();
break;
case "NRO":
ConfigurationState.Instance.UI.ShownFileTypes.NRO.Toggle();
break;
case "NSO":
ConfigurationState.Instance.UI.ShownFileTypes.NSO.Toggle();
break;
default:
throw new ArgumentOutOfRangeException(fileType);
}
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);

View File

@@ -80,6 +80,7 @@ namespace Ryujinx.Ava.UI.Windows
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
NetworkPage.ViewModel = ViewModel;
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
LocaleManager.Instance.LocaleChanged += LocaleChanged;
LocaleChanged();
Icon = MainWindowViewModel.IconBitmap;
}