mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-25 10:37:11 +02:00
TESTERS WANTED: RyuLDN implementation (#65)
These changes allow players to matchmake for local wireless using a LDN server. The network implementation originates from Berry's public TCP RyuLDN fork. Logo and unrelated changes have been removed. Additionally displays LDN game status in the game selection window when RyuLDN is enabled. Functionality is only enabled while network mode is set to "RyuLDN" in the settings.
This commit is contained in:
parent
abfcfcaf0f
commit
6d8738c048
93 changed files with 4100 additions and 189 deletions
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
44
src/Ryujinx/UI/Helpers/MultiplayerInfoConverter.cs
Normal file
44
src/Ryujinx/UI/Helpers/MultiplayerInfoConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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}">
|
||||
|
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -80,6 +80,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
NavPanel.Content = AudioPage;
|
||||
break;
|
||||
case "NetworkPage":
|
||||
NetworkPage.ViewModel = ViewModel;
|
||||
NavPanel.Content = NetworkPage;
|
||||
break;
|
||||
case "LoggingPage":
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
|
||||
LocaleManager.Instance.LocaleChanged += LocaleChanged;
|
||||
LocaleChanged();
|
||||
|
||||
|
||||
Icon = MainWindowViewModel.IconBitmap;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue