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:
@@ -27,6 +27,8 @@ namespace Ryujinx.UI.App.Common
|
||||
public ulong Id { get; set; }
|
||||
public string Developer { get; set; } = "Unknown";
|
||||
public string Version { get; set; } = "0";
|
||||
public int PlayerCount { get; set; }
|
||||
public int GameCount { get; set; }
|
||||
public TimeSpan TimePlayed { get; set; }
|
||||
public DateTime? LastPlayed { get; set; }
|
||||
public string FileExtension { get; set; }
|
||||
|
||||
@@ -12,6 +12,7 @@ using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
@@ -27,10 +28,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ContentType = LibHac.Ncm.ContentType;
|
||||
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
||||
using Path = System.IO.Path;
|
||||
@@ -41,8 +44,10 @@ namespace Ryujinx.UI.App.Common
|
||||
{
|
||||
public class ApplicationLibrary
|
||||
{
|
||||
public static string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
|
||||
public Language DesiredLanguage { get; set; }
|
||||
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||
public event EventHandler<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
|
||||
|
||||
public readonly IObservableCache<ApplicationData, ulong> Applications;
|
||||
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
|
||||
@@ -62,6 +67,7 @@ namespace Ryujinx.UI.App.Common
|
||||
private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
|
||||
|
||||
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
private static readonly LdnGameDataSerializerContext _ldnDataSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel)
|
||||
{
|
||||
@@ -687,7 +693,7 @@ namespace Ryujinx.UI.App.Common
|
||||
(Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.UI.ShownFileTypes.PFS0) ||
|
||||
(Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.UI.ShownFileTypes.XCI) ||
|
||||
(Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.UI.ShownFileTypes.NCA) ||
|
||||
(Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.UI.ShownFileTypes.NRO) ||
|
||||
(Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.UI.ShownFileTypes.NRO) ||
|
||||
(Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.UI.ShownFileTypes.NSO)
|
||||
);
|
||||
|
||||
@@ -719,6 +725,7 @@ namespace Ryujinx.UI.App.Common
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Loops through applications list, creating a struct and then firing an event containing the struct for each application
|
||||
foreach (string applicationPath in applicationPaths)
|
||||
{
|
||||
@@ -775,6 +782,46 @@ namespace Ryujinx.UI.App.Common
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshLdn()
|
||||
{
|
||||
|
||||
if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu)
|
||||
{
|
||||
try
|
||||
{
|
||||
string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer;
|
||||
if (string.IsNullOrEmpty(ldnWebHost))
|
||||
{
|
||||
ldnWebHost = DefaultLanPlayWebHost;
|
||||
}
|
||||
IEnumerable<LdnGameData> ldnGameDataArray = Array.Empty<LdnGameData>();
|
||||
using HttpClient httpClient = new HttpClient();
|
||||
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
|
||||
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
|
||||
var evt = new LdnGameDataReceivedEventArgs
|
||||
{
|
||||
LdnData = ldnGameDataArray
|
||||
};
|
||||
LdnGameDataReceived?.Invoke(null, evt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
|
||||
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
|
||||
{
|
||||
LdnData = Array.Empty<LdnGameData>()
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
|
||||
{
|
||||
LdnData = Array.Empty<LdnGameData>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the currently stored DLC state for the game with the provided DLC state.
|
||||
public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
|
||||
{
|
||||
|
||||
16
src/Ryujinx.UI.Common/App/LdnGameData.cs
Normal file
16
src/Ryujinx.UI.Common/App/LdnGameData.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.UI.App.Common
|
||||
{
|
||||
public struct LdnGameData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public int PlayerCount { get; set; }
|
||||
public int MaxPlayerCount { get; set; }
|
||||
public string GameName { get; set; }
|
||||
public string TitleId { get; set; }
|
||||
public string Mode { get; set; }
|
||||
public string Status { get; set; }
|
||||
public IEnumerable<string> Players { get; set; }
|
||||
}
|
||||
}
|
||||
10
src/Ryujinx.UI.Common/App/LdnGameDataReceivedEventArgs.cs
Normal file
10
src/Ryujinx.UI.Common/App/LdnGameDataReceivedEventArgs.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.UI.App.Common
|
||||
{
|
||||
public class LdnGameDataReceivedEventArgs : EventArgs
|
||||
{
|
||||
public IEnumerable<LdnGameData> LdnData { get; set; }
|
||||
}
|
||||
}
|
||||
11
src/Ryujinx.UI.Common/App/LdnGameDataSerializerContext.cs
Normal file
11
src/Ryujinx.UI.Common/App/LdnGameDataSerializerContext.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.UI.App.Common
|
||||
{
|
||||
[JsonSerializable(typeof(IEnumerable<LdnGameData>))]
|
||||
internal partial class LdnGameDataSerializerContext : JsonSerializerContext
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -392,6 +392,21 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
/// </summary>
|
||||
public string MultiplayerLanInterfaceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disable P2p Toggle
|
||||
/// </summary>
|
||||
public bool MultiplayerDisableP2p { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local network passphrase, for private networks.
|
||||
/// </summary>
|
||||
public string MultiplayerLdnPassphrase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom LDN Server
|
||||
/// </summary>
|
||||
public string LdnServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uses Hypervisor over JIT if available
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
@@ -703,6 +703,9 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
|
||||
Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
|
||||
Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode;
|
||||
Multiplayer.DisableP2p.Value = configurationFileFormat.MultiplayerDisableP2p;
|
||||
Multiplayer.LdnPassphrase.Value = configurationFileFormat.MultiplayerLdnPassphrase;
|
||||
Multiplayer.LdnServer.Value = configurationFileFormat.LdnServer;
|
||||
|
||||
if (configurationFileUpdated)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
{
|
||||
public partial class ConfigurationState
|
||||
{
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// UI configuration section
|
||||
/// </summary>
|
||||
public class UISection
|
||||
@@ -25,6 +25,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
public ReactiveObject<bool> AppColumn { get; private set; }
|
||||
public ReactiveObject<bool> DevColumn { get; private set; }
|
||||
public ReactiveObject<bool> VersionColumn { get; private set; }
|
||||
public ReactiveObject<bool> LdnInfoColumn { get; private set; }
|
||||
public ReactiveObject<bool> TimePlayedColumn { get; private set; }
|
||||
public ReactiveObject<bool> LastPlayedColumn { get; private set; }
|
||||
public ReactiveObject<bool> FileExtColumn { get; private set; }
|
||||
@@ -38,6 +39,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
AppColumn = new ReactiveObject<bool>();
|
||||
DevColumn = new ReactiveObject<bool>();
|
||||
VersionColumn = new ReactiveObject<bool>();
|
||||
LdnInfoColumn = new ReactiveObject<bool>();
|
||||
TimePlayedColumn = new ReactiveObject<bool>();
|
||||
LastPlayedColumn = new ReactiveObject<bool>();
|
||||
FileExtColumn = new ReactiveObject<bool>();
|
||||
@@ -572,11 +574,32 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<MultiplayerMode> Mode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Disable P2P
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> DisableP2p { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// LDN PassPhrase
|
||||
/// </summary>
|
||||
public ReactiveObject<string> LdnPassphrase { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// LDN Server
|
||||
/// </summary>
|
||||
public ReactiveObject<string> LdnServer { get; private set; }
|
||||
|
||||
public MultiplayerSection()
|
||||
{
|
||||
LanInterfaceId = new ReactiveObject<string>();
|
||||
Mode = new ReactiveObject<MultiplayerMode>();
|
||||
Mode.LogChangesToValue(nameof(MultiplayerMode));
|
||||
DisableP2p = new ReactiveObject<bool>();
|
||||
DisableP2p.LogChangesToValue(nameof(DisableP2p));
|
||||
LdnPassphrase = new ReactiveObject<string>();
|
||||
LdnPassphrase.LogChangesToValue(nameof(LdnPassphrase));
|
||||
LdnServer = new ReactiveObject<string>();
|
||||
LdnServer.LogChangesToValue(nameof(LdnServer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
if (Instance != null)
|
||||
{
|
||||
throw new InvalidOperationException("Configuration is already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
Instance = new ConfigurationState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ConfigurationFileFormat ToFileFormat()
|
||||
{
|
||||
ConfigurationFileFormat configurationFile = new()
|
||||
@@ -87,6 +87,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
AppColumn = UI.GuiColumns.AppColumn,
|
||||
DevColumn = UI.GuiColumns.DevColumn,
|
||||
VersionColumn = UI.GuiColumns.VersionColumn,
|
||||
LdnInfoColumn = UI.GuiColumns.LdnInfoColumn,
|
||||
TimePlayedColumn = UI.GuiColumns.TimePlayedColumn,
|
||||
LastPlayedColumn = UI.GuiColumns.LastPlayedColumn,
|
||||
FileExtColumn = UI.GuiColumns.FileExtColumn,
|
||||
@@ -136,6 +137,9 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
PreferredGpu = Graphics.PreferredGpu,
|
||||
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
|
||||
MultiplayerMode = Multiplayer.Mode,
|
||||
MultiplayerDisableP2p = Multiplayer.DisableP2p,
|
||||
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
|
||||
LdnServer = Multiplayer.LdnServer,
|
||||
};
|
||||
|
||||
return configurationFile;
|
||||
@@ -195,6 +199,9 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
System.UseHypervisor.Value = true;
|
||||
Multiplayer.LanInterfaceId.Value = "0";
|
||||
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
||||
Multiplayer.DisableP2p.Value = false;
|
||||
Multiplayer.LdnPassphrase.Value = "";
|
||||
Multiplayer.LdnServer.Value = "";
|
||||
UI.GuiColumns.FavColumn.Value = true;
|
||||
UI.GuiColumns.IconColumn.Value = true;
|
||||
UI.GuiColumns.AppColumn.Value = true;
|
||||
@@ -307,5 +314,5 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
|
||||
return GraphicsBackend.OpenGl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Ryujinx.UI.Common.Configuration.UI
|
||||
public bool AppColumn { get; set; }
|
||||
public bool DevColumn { get; set; }
|
||||
public bool VersionColumn { get; set; }
|
||||
public bool LdnInfoColumn { get; set; }
|
||||
public bool TimePlayedColumn { get; set; }
|
||||
public bool LastPlayedColumn { get; set; }
|
||||
public bool FileExtColumn { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user