Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 262debec5a |
@@ -33,7 +33,6 @@
|
|||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||||
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
|
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ namespace Ryujinx.Common.Configuration.Multiplayer
|
|||||||
public enum MultiplayerMode
|
public enum MultiplayerMode
|
||||||
{
|
{
|
||||||
Disabled,
|
Disabled,
|
||||||
LdnRyu,
|
|
||||||
LdnMitm,
|
LdnMitm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -803,6 +803,18 @@ namespace Ryujinx.Common.Memory
|
|||||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Array256<T> : IArray<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
T _e0;
|
||||||
|
Array128<T> _other;
|
||||||
|
Array127<T> _other2;
|
||||||
|
public readonly int Length => 256;
|
||||||
|
public ref T this[int index] => ref AsSpan()[index];
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||||
|
}
|
||||||
|
|
||||||
public struct Array140<T> : IArray<T> where T : unmanaged
|
public struct Array140<T> : IArray<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
T _e0;
|
T _e0;
|
||||||
@@ -816,18 +828,6 @@ namespace Ryujinx.Common.Memory
|
|||||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Array256<T> : IArray<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T _e0;
|
|
||||||
Array128<T> _other;
|
|
||||||
Array127<T> _other2;
|
|
||||||
public readonly int Length => 256;
|
|
||||||
public ref T this[int index] => ref AsSpan()[index];
|
|
||||||
|
|
||||||
[Pure]
|
|
||||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Array384<T> : IArray<T> where T : unmanaged
|
public struct Array384<T> : IArray<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
T _e0;
|
T _e0;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
@@ -66,11 +65,6 @@ namespace Ryujinx.Common.Utilities
|
|||||||
return (targetProperties, targetAddressInfo);
|
return (targetProperties, targetAddressInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool SupportsDynamicDns()
|
|
||||||
{
|
|
||||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint ConvertIpv4Address(IPAddress ipAddress)
|
public static uint ConvertIpv4Address(IPAddress ipAddress)
|
||||||
{
|
{
|
||||||
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
|
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
IPipeline Pipeline { get; }
|
IPipeline Pipeline { get; }
|
||||||
|
|
||||||
IWindow Window { get; }
|
IWindow Window { get; }
|
||||||
|
|
||||||
uint ProgramCount { get; }
|
uint ProgramCount { get; }
|
||||||
|
|
||||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
{
|
{
|
||||||
ProgramCount++;
|
ProgramCount++;
|
||||||
|
|
||||||
return new Program(shaders, info.FragmentOutputMap);
|
return new Program(shaders, info.FragmentOutputMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
if (_handle != BufferHandle.Null)
|
if (_handle != BufferHandle.Null)
|
||||||
{
|
{
|
||||||
// May need to restride the vertex buffer.
|
// May need to restride the vertex buffer.
|
||||||
//
|
|
||||||
// Fix divide by zero when recovering from missed draw (Oct. 16 2024)
|
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && (_stride % alignment) != 0)
|
||||||
// (fixes crash in 'Baldo: The Guardian Owls' opening cutscene)
|
|
||||||
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && alignment != 0 && (_stride % alignment) != 0)
|
|
||||||
{
|
{
|
||||||
autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment);
|
autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment);
|
||||||
|
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
||||||
{
|
{
|
||||||
ProgramCount++;
|
ProgramCount++;
|
||||||
|
|
||||||
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
||||||
|
|
||||||
if (info.State.HasValue || isCompute)
|
if (info.State.HasValue || isCompute)
|
||||||
|
|||||||
@@ -164,21 +164,6 @@ namespace Ryujinx.HLE
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiplayerMode MultiplayerMode { internal get; set; }
|
public MultiplayerMode MultiplayerMode { internal get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disable P2P mode
|
|
||||||
/// </summary>
|
|
||||||
public bool MultiplayerDisableP2p { internal get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Multiplayer Passphrase
|
|
||||||
/// </summary>
|
|
||||||
public string MultiplayerLdnPassphrase { internal get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// LDN Server
|
|
||||||
/// </summary>
|
|
||||||
public string MultiplayerLdnServer { internal get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -209,10 +194,7 @@ namespace Ryujinx.HLE
|
|||||||
float audioVolume,
|
float audioVolume,
|
||||||
bool useHypervisor,
|
bool useHypervisor,
|
||||||
string multiplayerLanInterfaceId,
|
string multiplayerLanInterfaceId,
|
||||||
MultiplayerMode multiplayerMode,
|
MultiplayerMode multiplayerMode)
|
||||||
bool multiplayerDisableP2p,
|
|
||||||
string multiplayerLdnPassphrase,
|
|
||||||
string multiplayerLdnServer)
|
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
LibHacHorizonManager = libHacHorizonManager;
|
LibHacHorizonManager = libHacHorizonManager;
|
||||||
@@ -240,9 +222,6 @@ namespace Ryujinx.HLE
|
|||||||
UseHypervisor = useHypervisor;
|
UseHypervisor = useHypervisor;
|
||||||
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
||||||
MultiplayerMode = multiplayerMode;
|
MultiplayerMode = multiplayerMode;
|
||||||
MultiplayerDisableP2p = multiplayerDisableP2p;
|
|
||||||
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
|
|
||||||
MultiplayerLdnServer = multiplayerLdnServer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
{
|
{
|
||||||
@@ -26,14 +25,5 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CommandCmif(350)]
|
|
||||||
// OpenSystemApplicationProxy(u64, pid, handle<copy>) -> object<nn::am::service::IApplicationProxy>
|
|
||||||
public ResultCode OpenSystemApplicationProxy(ServiceCtx context)
|
|
||||||
{
|
|
||||||
MakeObject(context, new IApplicationProxy(context.Request.HandleDesc.PId));
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 8)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||||
struct NetworkConfig
|
struct NetworkConfig
|
||||||
{
|
{
|
||||||
public IntentId IntentId;
|
public IntentId IntentId;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x60, Pack = 8)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
|
||||||
struct ScanFilter
|
struct ScanFilter
|
||||||
{
|
{
|
||||||
public NetworkId NetworkId;
|
public NetworkId NetworkId;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x44, Pack = 2)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x44)]
|
||||||
struct SecurityConfig
|
struct SecurityConfig
|
||||||
{
|
{
|
||||||
public SecurityMode SecurityMode;
|
public SecurityMode SecurityMode;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||||
struct SecurityParameter
|
struct SecurityParameter
|
||||||
{
|
{
|
||||||
public Array16<byte> Data;
|
public Array16<byte> Data;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x30, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x30)]
|
||||||
struct UserConfig
|
struct UserConfig
|
||||||
{
|
{
|
||||||
public Array33<byte> UserName;
|
public Array33<byte> UserName;
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
public Array8<NodeLatestUpdate> LatestUpdates = new();
|
public Array8<NodeLatestUpdate> LatestUpdates = new();
|
||||||
public bool Connected { get; private set; }
|
public bool Connected { get; private set; }
|
||||||
|
|
||||||
public ProxyConfig Config => _parent.NetworkClient.Config;
|
|
||||||
|
|
||||||
public AccessPoint(IUserLocalCommunicationService parent)
|
public AccessPoint(IUserLocalCommunicationService parent)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
@@ -26,12 +24,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_parent?.NetworkClient != null)
|
_parent.NetworkClient.DisconnectNetwork();
|
||||||
{
|
|
||||||
_parent.NetworkClient.DisconnectNetwork();
|
|
||||||
|
|
||||||
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
|
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
interface INetworkClient : IDisposable
|
interface INetworkClient : IDisposable
|
||||||
{
|
{
|
||||||
ProxyConfig Config { get; }
|
|
||||||
bool NeedsRealId { get; }
|
bool NeedsRealId { get; }
|
||||||
|
|
||||||
event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||||
|
|||||||
+9
-53
@@ -9,8 +9,6 @@ using Ryujinx.HLE.HOS.Ipc;
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
|
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
@@ -23,9 +21,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
class IUserLocalCommunicationService : IpcService, IDisposable
|
class IUserLocalCommunicationService : IpcService, IDisposable
|
||||||
{
|
{
|
||||||
public static string DefaultLanPlayHost = "ryuldn.vudjun.com";
|
|
||||||
public static short LanPlayPort = 30456;
|
|
||||||
|
|
||||||
public INetworkClient NetworkClient { get; private set; }
|
public INetworkClient NetworkClient { get; private set; }
|
||||||
|
|
||||||
private const int NifmRequestID = 90;
|
private const int NifmRequestID = 90;
|
||||||
@@ -180,37 +175,19 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
if (_state == NetworkState.AccessPointCreated || _state == NetworkState.StationConnected)
|
if (_state == NetworkState.AccessPointCreated || _state == NetworkState.StationConnected)
|
||||||
{
|
{
|
||||||
ProxyConfig config = _state switch
|
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId);
|
||||||
|
|
||||||
|
if (unicastAddress == null)
|
||||||
{
|
{
|
||||||
NetworkState.AccessPointCreated => _accessPoint.Config,
|
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress));
|
||||||
NetworkState.StationConnected => _station.Config,
|
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask));
|
||||||
|
|
||||||
_ => default
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.ProxyIp == 0)
|
|
||||||
{
|
|
||||||
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId);
|
|
||||||
|
|
||||||
if (unicastAddress == null)
|
|
||||||
{
|
|
||||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress));
|
|
||||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
|
|
||||||
|
|
||||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
|
|
||||||
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
|
Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
|
||||||
|
|
||||||
context.ResponseData.Write(config.ProxyIp);
|
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
|
||||||
context.ResponseData.Write(config.ProxySubnetMask);
|
context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1089,27 +1066,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case MultiplayerMode.LdnRyu:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string ldnServer = context.Device.Configuration.MultiplayerLdnServer;
|
|
||||||
if (string.IsNullOrEmpty(ldnServer))
|
|
||||||
{
|
|
||||||
ldnServer = DefaultLanPlayHost;
|
|
||||||
}
|
|
||||||
if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress))
|
|
||||||
{
|
|
||||||
ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0];
|
|
||||||
}
|
|
||||||
NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), LanPlayPort, context.Device.Configuration);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate LdnRyu server. Defaulting to stubbed wireless.");
|
|
||||||
Logger.Error?.Print(LogClass.ServiceLdn, ex.Message);
|
|
||||||
NetworkClient = new LdnDisabledClient();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MultiplayerMode.LdnMitm:
|
case MultiplayerMode.LdnMitm:
|
||||||
NetworkClient = new LdnMitmClient(context.Device.Configuration);
|
NetworkClient = new LdnMitmClient(context.Device.Configuration);
|
||||||
break;
|
break;
|
||||||
@@ -1147,7 +1103,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
_accessPoint?.Dispose();
|
_accessPoint?.Dispose();
|
||||||
_accessPoint = null;
|
_accessPoint = null;
|
||||||
|
|
||||||
NetworkClient?.DisconnectAndStop();
|
NetworkClient?.Dispose();
|
||||||
NetworkClient = null;
|
NetworkClient = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||||
using System;
|
using System;
|
||||||
@@ -7,14 +6,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
{
|
{
|
||||||
class LdnDisabledClient : INetworkClient
|
class LdnDisabledClient : INetworkClient
|
||||||
{
|
{
|
||||||
public ProxyConfig Config { get; }
|
|
||||||
public bool NeedsRealId => true;
|
public bool NeedsRealId => true;
|
||||||
|
|
||||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||||
|
|
||||||
public NetworkError Connect(ConnectRequest request)
|
public NetworkError Connect(ConnectRequest request)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to connect to a network, but Multiplayer is disabled!");
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||||
|
|
||||||
return NetworkError.None;
|
return NetworkError.None;
|
||||||
@@ -22,7 +19,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
|
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to connect to a network, but Multiplayer is disabled!");
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||||
|
|
||||||
return NetworkError.None;
|
return NetworkError.None;
|
||||||
@@ -30,7 +26,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
|
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to create a network, but Multiplayer is disabled!");
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -38,7 +33,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
|
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to create a network, but Multiplayer is disabled!");
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -55,7 +49,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
|
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "Attempted to scan for networks, but Multiplayer is disabled!");
|
|
||||||
return Array.Empty<NetworkInfo>();
|
return Array.Empty<NetworkInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class LdnMitmClient : INetworkClient
|
internal class LdnMitmClient : INetworkClient
|
||||||
{
|
{
|
||||||
public ProxyConfig Config { get; }
|
|
||||||
public bool NeedsRealId => false;
|
public bool NeedsRealId => false;
|
||||||
|
|
||||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|
||||||
{
|
|
||||||
interface IProxyClient
|
|
||||||
{
|
|
||||||
bool SendAsync(byte[] buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,645 +0,0 @@
|
|||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
||||||
using Ryujinx.HLE.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.NetworkInformation;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using TcpClient = NetCoreServer.TcpClient;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|
||||||
{
|
|
||||||
class LdnMasterProxyClient : TcpClient, INetworkClient, IProxyClient
|
|
||||||
{
|
|
||||||
public bool NeedsRealId => true;
|
|
||||||
|
|
||||||
private static InitializeMessage InitializeMemory = new InitializeMessage();
|
|
||||||
|
|
||||||
private const int InactiveTimeout = 6000;
|
|
||||||
private const int FailureTimeout = 4000;
|
|
||||||
private const int ScanTimeout = 1000;
|
|
||||||
|
|
||||||
private bool _useP2pProxy;
|
|
||||||
private NetworkError _lastError;
|
|
||||||
|
|
||||||
private readonly ManualResetEvent _connected = new ManualResetEvent(false);
|
|
||||||
private readonly ManualResetEvent _error = new ManualResetEvent(false);
|
|
||||||
private readonly ManualResetEvent _scan = new ManualResetEvent(false);
|
|
||||||
private readonly ManualResetEvent _reject = new ManualResetEvent(false);
|
|
||||||
private readonly AutoResetEvent _apConnected = new AutoResetEvent(false);
|
|
||||||
|
|
||||||
private readonly RyuLdnProtocol _protocol;
|
|
||||||
private readonly NetworkTimeout _timeout;
|
|
||||||
|
|
||||||
private readonly List<NetworkInfo> _availableGames = new List<NetworkInfo>();
|
|
||||||
private DisconnectReason _disconnectReason;
|
|
||||||
|
|
||||||
private P2pProxyServer _hostedProxy;
|
|
||||||
private P2pProxyClient _connectedProxy;
|
|
||||||
|
|
||||||
private bool _networkConnected;
|
|
||||||
|
|
||||||
private string _passphrase;
|
|
||||||
private byte[] _gameVersion = new byte[0x10];
|
|
||||||
|
|
||||||
private readonly HLEConfiguration _config;
|
|
||||||
|
|
||||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
|
||||||
|
|
||||||
public ProxyConfig Config { get; private set; }
|
|
||||||
|
|
||||||
public LdnMasterProxyClient(string address, int port, HLEConfiguration config) : base(address, port)
|
|
||||||
{
|
|
||||||
if (ProxyHelpers.SupportsNoDelay())
|
|
||||||
{
|
|
||||||
OptionNoDelay = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_protocol = new RyuLdnProtocol();
|
|
||||||
_timeout = new NetworkTimeout(InactiveTimeout, TimeoutConnection);
|
|
||||||
|
|
||||||
_protocol.Initialize += HandleInitialize;
|
|
||||||
_protocol.Connected += HandleConnected;
|
|
||||||
_protocol.Reject += HandleReject;
|
|
||||||
_protocol.RejectReply += HandleRejectReply;
|
|
||||||
_protocol.SyncNetwork += HandleSyncNetwork;
|
|
||||||
_protocol.ProxyConfig += HandleProxyConfig;
|
|
||||||
_protocol.Disconnected += HandleDisconnected;
|
|
||||||
|
|
||||||
_protocol.ScanReply += HandleScanReply;
|
|
||||||
_protocol.ScanReplyEnd += HandleScanReplyEnd;
|
|
||||||
_protocol.ExternalProxy += HandleExternalProxy;
|
|
||||||
|
|
||||||
_protocol.Ping += HandlePing;
|
|
||||||
_protocol.NetworkError += HandleNetworkError;
|
|
||||||
|
|
||||||
_config = config;
|
|
||||||
_useP2pProxy = !config.MultiplayerDisableP2p;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TimeoutConnection()
|
|
||||||
{
|
|
||||||
_connected.Reset();
|
|
||||||
|
|
||||||
DisconnectAsync();
|
|
||||||
|
|
||||||
while (IsConnected)
|
|
||||||
{
|
|
||||||
Thread.Yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool EnsureConnected()
|
|
||||||
{
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_error.Reset();
|
|
||||||
|
|
||||||
ConnectAsync();
|
|
||||||
|
|
||||||
int index = WaitHandle.WaitAny(new WaitHandle[] { _connected, _error }, FailureTimeout);
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Initialize, InitializeMemory));
|
|
||||||
}
|
|
||||||
|
|
||||||
return index == 0 && IsConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePassphraseIfNeeded()
|
|
||||||
{
|
|
||||||
string passphrase = _config.MultiplayerLdnPassphrase ?? "";
|
|
||||||
if (passphrase != _passphrase)
|
|
||||||
{
|
|
||||||
_passphrase = passphrase;
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Passphrase, StringUtils.GetFixedLengthBytes(passphrase, 0x80, Encoding.UTF8)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnConnected()
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client connected a new session with Id {Id}");
|
|
||||||
|
|
||||||
UpdatePassphraseIfNeeded();
|
|
||||||
|
|
||||||
_connected.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDisconnected()
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client disconnected a session with Id {Id}");
|
|
||||||
|
|
||||||
_passphrase = null;
|
|
||||||
|
|
||||||
_connected.Reset();
|
|
||||||
|
|
||||||
if (_networkConnected)
|
|
||||||
{
|
|
||||||
DisconnectInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisconnectAndStop()
|
|
||||||
{
|
|
||||||
_timeout.Dispose();
|
|
||||||
|
|
||||||
DisconnectAsync();
|
|
||||||
|
|
||||||
while (IsConnected)
|
|
||||||
{
|
|
||||||
Thread.Yield();
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnReceived(byte[] buffer, long offset, long size)
|
|
||||||
{
|
|
||||||
_protocol.Read(buffer, (int)offset, (int)size);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnError(SocketError error)
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LDN TCP client caught an error with code {error}");
|
|
||||||
|
|
||||||
_error.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void HandleInitialize(LdnHeader header, InitializeMessage initialize)
|
|
||||||
{
|
|
||||||
InitializeMemory = initialize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleExternalProxy(LdnHeader header, ExternalProxyConfig config)
|
|
||||||
{
|
|
||||||
int length = config.AddressFamily switch
|
|
||||||
{
|
|
||||||
AddressFamily.InterNetwork => 4,
|
|
||||||
AddressFamily.InterNetworkV6 => 16,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
return; // Invalid external proxy.
|
|
||||||
}
|
|
||||||
|
|
||||||
IPAddress address = new(config.ProxyIp.AsSpan()[..length].ToArray());
|
|
||||||
P2pProxyClient proxy = new(address.ToString(), config.ProxyPort);
|
|
||||||
|
|
||||||
_connectedProxy = proxy;
|
|
||||||
|
|
||||||
bool success = proxy.PerformAuth(config);
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
DisconnectInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandlePing(LdnHeader header, PingMessage ping)
|
|
||||||
{
|
|
||||||
if (ping.Requester == 0) // Server requested.
|
|
||||||
{
|
|
||||||
// Send the ping message back.
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Ping, ping));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleNetworkError(LdnHeader header, NetworkErrorMessage error)
|
|
||||||
{
|
|
||||||
if (error.Error == NetworkError.PortUnreachable)
|
|
||||||
{
|
|
||||||
_useP2pProxy = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lastError = error.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private NetworkError ConsumeNetworkError()
|
|
||||||
{
|
|
||||||
NetworkError result = _lastError;
|
|
||||||
|
|
||||||
_lastError = NetworkError.None;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleSyncNetwork(LdnHeader header, NetworkInfo info)
|
|
||||||
{
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleConnected(LdnHeader header, NetworkInfo info)
|
|
||||||
{
|
|
||||||
_networkConnected = true;
|
|
||||||
_disconnectReason = DisconnectReason.None;
|
|
||||||
|
|
||||||
_apConnected.Set();
|
|
||||||
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDisconnected(LdnHeader header, DisconnectMessage message)
|
|
||||||
{
|
|
||||||
DisconnectInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleReject(LdnHeader header, RejectRequest reject)
|
|
||||||
{
|
|
||||||
// When the client receives a Reject request, we have been rejected and will be disconnected shortly.
|
|
||||||
_disconnectReason = reject.DisconnectReason;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleRejectReply(LdnHeader header)
|
|
||||||
{
|
|
||||||
_reject.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleScanReply(LdnHeader header, NetworkInfo info)
|
|
||||||
{
|
|
||||||
_availableGames.Add(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleScanReplyEnd(LdnHeader obj)
|
|
||||||
{
|
|
||||||
_scan.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisconnectInternal()
|
|
||||||
{
|
|
||||||
if (_networkConnected)
|
|
||||||
{
|
|
||||||
_networkConnected = false;
|
|
||||||
|
|
||||||
_hostedProxy?.Dispose();
|
|
||||||
_hostedProxy = null;
|
|
||||||
|
|
||||||
_connectedProxy?.Dispose();
|
|
||||||
_connectedProxy = null;
|
|
||||||
|
|
||||||
_apConnected.Reset();
|
|
||||||
|
|
||||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false, _disconnectReason));
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
_timeout.RefreshTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisconnectNetwork()
|
|
||||||
{
|
|
||||||
if (_networkConnected)
|
|
||||||
{
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Disconnect, new DisconnectMessage()));
|
|
||||||
|
|
||||||
DisconnectInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)
|
|
||||||
{
|
|
||||||
if (_networkConnected)
|
|
||||||
{
|
|
||||||
_reject.Reset();
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Reject, new RejectRequest(disconnectReason, nodeId)));
|
|
||||||
|
|
||||||
int index = WaitHandle.WaitAny(new WaitHandle[] { _reject, _error }, InactiveTimeout);
|
|
||||||
|
|
||||||
if (index == 0)
|
|
||||||
{
|
|
||||||
return (ConsumeNetworkError() != NetworkError.None) ? ResultCode.InvalidState : ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultCode.InvalidState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetAdvertiseData(byte[] data)
|
|
||||||
{
|
|
||||||
// TODO: validate we're the owner (the server will do this anyways tho)
|
|
||||||
if (_networkConnected)
|
|
||||||
{
|
|
||||||
SendAsync(_protocol.Encode(PacketId.SetAdvertiseData, data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetGameVersion(byte[] versionString)
|
|
||||||
{
|
|
||||||
_gameVersion = versionString;
|
|
||||||
|
|
||||||
if (_gameVersion.Length < 0x10)
|
|
||||||
{
|
|
||||||
Array.Resize(ref _gameVersion, 0x10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
|
|
||||||
{
|
|
||||||
// TODO: validate we're the owner (the server will do this anyways tho)
|
|
||||||
if (_networkConnected)
|
|
||||||
{
|
|
||||||
SendAsync(_protocol.Encode(PacketId.SetAcceptPolicy, new SetAcceptPolicyRequest
|
|
||||||
{
|
|
||||||
StationAcceptPolicy = acceptPolicy
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeProxy()
|
|
||||||
{
|
|
||||||
_hostedProxy?.Dispose();
|
|
||||||
_hostedProxy = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConfigureAccessPoint(ref RyuNetworkConfig request)
|
|
||||||
{
|
|
||||||
_gameVersion.AsSpan().CopyTo(request.GameVersion.AsSpan());
|
|
||||||
|
|
||||||
if (_useP2pProxy)
|
|
||||||
{
|
|
||||||
// Before sending the request, attempt to set up a proxy server.
|
|
||||||
// This can be on a range of private ports, which can be exposed on a range of public
|
|
||||||
// ports via UPnP. If any of this fails, we just fall back to using the master server.
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (; i < P2pProxyServer.PrivatePortRange; i++)
|
|
||||||
{
|
|
||||||
_hostedProxy = new P2pProxyServer(this, (ushort)(P2pProxyServer.PrivatePortBase + i), _protocol);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_hostedProxy.Start();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
_hostedProxy.Dispose();
|
|
||||||
_hostedProxy = null;
|
|
||||||
|
|
||||||
if (e.SocketErrorCode != SocketError.AddressAlreadyInUse)
|
|
||||||
{
|
|
||||||
i = P2pProxyServer.PrivatePortRange; // Immediately fail.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool openSuccess = i < P2pProxyServer.PrivatePortRange;
|
|
||||||
|
|
||||||
if (openSuccess)
|
|
||||||
{
|
|
||||||
Task<ushort> natPunchResult = _hostedProxy.NatPunch();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (natPunchResult.Result != 0)
|
|
||||||
{
|
|
||||||
// Tell the server that we are hosting the proxy.
|
|
||||||
request.ExternalProxyPort = natPunchResult.Result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
|
|
||||||
if (request.ExternalProxyPort == 0)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.ServiceLdn, "Failed to open a port with UPnP for P2P connection. Proxying through the master server instead. Expect higher latency.");
|
|
||||||
_hostedProxy.Dispose();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.ServiceLdn, $"Created a wireless P2P network on port {request.ExternalProxyPort}.");
|
|
||||||
_hostedProxy.Start();
|
|
||||||
|
|
||||||
(_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface();
|
|
||||||
|
|
||||||
unicastAddress.Address.GetAddressBytes().AsSpan().CopyTo(request.PrivateIp.AsSpan());
|
|
||||||
request.InternalProxyPort = _hostedProxy.PrivatePort;
|
|
||||||
request.AddressFamily = unicastAddress.Address.AddressFamily;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.ServiceLdn, "Cannot create a P2P server. Proxying through the master server instead. Expect higher latency.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CreateNetworkCommon()
|
|
||||||
{
|
|
||||||
bool signalled = _apConnected.WaitOne(FailureTimeout);
|
|
||||||
|
|
||||||
if (!_useP2pProxy && _hostedProxy != null)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.ServiceLdn, "Locally hosted proxy server was not externally reachable. Proxying through the master server instead. Expect higher latency.");
|
|
||||||
|
|
||||||
DisposeProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signalled && _connectedProxy != null)
|
|
||||||
{
|
|
||||||
_connectedProxy.EnsureProxyReady();
|
|
||||||
|
|
||||||
Config = _connectedProxy.ProxyConfig;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisposeProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
return signalled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
|
|
||||||
{
|
|
||||||
_timeout.DisableTimeout();
|
|
||||||
|
|
||||||
ConfigureAccessPoint(ref request.RyuNetworkConfig);
|
|
||||||
|
|
||||||
if (!EnsureConnected())
|
|
||||||
{
|
|
||||||
DisposeProxy();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdatePassphraseIfNeeded();
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.CreateAccessPoint, request, advertiseData));
|
|
||||||
|
|
||||||
// Send a network change event with dummy data immediately. Necessary to avoid crashes in some games
|
|
||||||
var networkChangeEvent = new NetworkChangeEventArgs(new NetworkInfo()
|
|
||||||
{
|
|
||||||
Common = new CommonNetworkInfo()
|
|
||||||
{
|
|
||||||
MacAddress = InitializeMemory.MacAddress,
|
|
||||||
Channel = request.NetworkConfig.Channel,
|
|
||||||
LinkLevel = 3,
|
|
||||||
NetworkType = 2,
|
|
||||||
Ssid = new Ssid()
|
|
||||||
{
|
|
||||||
Length = 32
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ldn = new LdnNetworkInfo()
|
|
||||||
{
|
|
||||||
AdvertiseDataSize = (ushort)advertiseData.Length,
|
|
||||||
AuthenticationId = 0,
|
|
||||||
NodeCount = 1,
|
|
||||||
NodeCountMax = request.NetworkConfig.NodeCountMax,
|
|
||||||
SecurityMode = (ushort)request.SecurityConfig.SecurityMode
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
networkChangeEvent.Info.Ldn.Nodes[0] = new NodeInfo()
|
|
||||||
{
|
|
||||||
Ipv4Address = 175243265,
|
|
||||||
IsConnected = 1,
|
|
||||||
LocalCommunicationVersion = request.NetworkConfig.LocalCommunicationVersion,
|
|
||||||
MacAddress = InitializeMemory.MacAddress,
|
|
||||||
NodeId = 0,
|
|
||||||
UserName = request.UserConfig.UserName
|
|
||||||
};
|
|
||||||
"12345678123456781234567812345678"u8.ToArray().CopyTo(networkChangeEvent.Info.Common.Ssid.Name.AsSpan());
|
|
||||||
NetworkChange?.Invoke(this, networkChangeEvent);
|
|
||||||
|
|
||||||
return CreateNetworkCommon();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
|
|
||||||
{
|
|
||||||
_timeout.DisableTimeout();
|
|
||||||
|
|
||||||
ConfigureAccessPoint(ref request.RyuNetworkConfig);
|
|
||||||
|
|
||||||
if (!EnsureConnected())
|
|
||||||
{
|
|
||||||
DisposeProxy();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdatePassphraseIfNeeded();
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.CreateAccessPointPrivate, request, advertiseData));
|
|
||||||
|
|
||||||
return CreateNetworkCommon();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
|
|
||||||
{
|
|
||||||
if (!_networkConnected)
|
|
||||||
{
|
|
||||||
_timeout.RefreshTimeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
_availableGames.Clear();
|
|
||||||
|
|
||||||
int index = -1;
|
|
||||||
|
|
||||||
if (EnsureConnected())
|
|
||||||
{
|
|
||||||
UpdatePassphraseIfNeeded();
|
|
||||||
|
|
||||||
_scan.Reset();
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Scan, scanFilter));
|
|
||||||
|
|
||||||
index = WaitHandle.WaitAny(new WaitHandle[] { _scan, _error }, ScanTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index != 0)
|
|
||||||
{
|
|
||||||
// An error occurred or timeout. Write 0 games.
|
|
||||||
return Array.Empty<NetworkInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _availableGames.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private NetworkError ConnectCommon()
|
|
||||||
{
|
|
||||||
bool signalled = _apConnected.WaitOne(FailureTimeout);
|
|
||||||
|
|
||||||
NetworkError error = ConsumeNetworkError();
|
|
||||||
|
|
||||||
if (error != NetworkError.None)
|
|
||||||
{
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signalled && _connectedProxy != null)
|
|
||||||
{
|
|
||||||
_connectedProxy.EnsureProxyReady();
|
|
||||||
|
|
||||||
Config = _connectedProxy.ProxyConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
return signalled ? NetworkError.None : NetworkError.ConnectTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkError Connect(ConnectRequest request)
|
|
||||||
{
|
|
||||||
_timeout.DisableTimeout();
|
|
||||||
|
|
||||||
if (!EnsureConnected())
|
|
||||||
{
|
|
||||||
return NetworkError.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.Connect, request));
|
|
||||||
|
|
||||||
var networkChangeEvent = new NetworkChangeEventArgs(new NetworkInfo()
|
|
||||||
{
|
|
||||||
Common = request.NetworkInfo.Common,
|
|
||||||
Ldn = request.NetworkInfo.Ldn
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
NetworkChange?.Invoke(this, networkChangeEvent);
|
|
||||||
|
|
||||||
return ConnectCommon();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
|
|
||||||
{
|
|
||||||
_timeout.DisableTimeout();
|
|
||||||
|
|
||||||
if (!EnsureConnected())
|
|
||||||
{
|
|
||||||
return NetworkError.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.ConnectPrivate, request));
|
|
||||||
|
|
||||||
return ConnectCommon();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleProxyConfig(LdnHeader header, ProxyConfig config)
|
|
||||||
{
|
|
||||||
Config = config;
|
|
||||||
|
|
||||||
SocketHelpers.RegisterProxy(new LdnProxy(config, this, _protocol));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|
||||||
{
|
|
||||||
class NetworkTimeout : IDisposable
|
|
||||||
{
|
|
||||||
private readonly int _idleTimeout;
|
|
||||||
private readonly Action _timeoutCallback;
|
|
||||||
private CancellationTokenSource _cancel;
|
|
||||||
|
|
||||||
private readonly object _lock = new object();
|
|
||||||
|
|
||||||
public NetworkTimeout(int idleTimeout, Action timeoutCallback)
|
|
||||||
{
|
|
||||||
_idleTimeout = idleTimeout;
|
|
||||||
_timeoutCallback = timeoutCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task TimeoutTask()
|
|
||||||
{
|
|
||||||
CancellationTokenSource cts;
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
cts = _cancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cts == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(_idleTimeout, cts.Token);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
return; // Timeout cancelled.
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
// Run the timeout callback. If the cancel token source has been replaced, we have _just_ been cancelled.
|
|
||||||
if (cts == _cancel)
|
|
||||||
{
|
|
||||||
_timeoutCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RefreshTimeout()
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_cancel?.Cancel();
|
|
||||||
|
|
||||||
_cancel = new CancellationTokenSource();
|
|
||||||
|
|
||||||
Task.Run(TimeoutTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableTimeout()
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_cancel?.Cancel();
|
|
||||||
|
|
||||||
_cancel = new CancellationTokenSource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DisableTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
public class EphemeralPortPool
|
|
||||||
{
|
|
||||||
private const ushort EphemeralBase = 49152;
|
|
||||||
|
|
||||||
private readonly List<ushort> _ephemeralPorts = new List<ushort>();
|
|
||||||
|
|
||||||
private readonly object _lock = new object();
|
|
||||||
|
|
||||||
public ushort Get()
|
|
||||||
{
|
|
||||||
ushort port = EphemeralBase;
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
// Starting at the ephemeral port base, return an ephemeral port that is not in use.
|
|
||||||
// Returns 0 if the range is exhausted.
|
|
||||||
|
|
||||||
for (int i = 0; i < _ephemeralPorts.Count; i++)
|
|
||||||
{
|
|
||||||
ushort existingPort = _ephemeralPorts[i];
|
|
||||||
|
|
||||||
if (existingPort > port)
|
|
||||||
{
|
|
||||||
// The port was free - take it.
|
|
||||||
_ephemeralPorts.Insert(i, port);
|
|
||||||
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
port++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port != 0)
|
|
||||||
{
|
|
||||||
_ephemeralPorts.Add(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Return(ushort port)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_ephemeralPorts.Remove(port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
class LdnProxy : IDisposable
|
|
||||||
{
|
|
||||||
public EndPoint LocalEndpoint { get; }
|
|
||||||
public IPAddress LocalAddress { get; }
|
|
||||||
|
|
||||||
private readonly List<LdnProxySocket> _sockets = new List<LdnProxySocket>();
|
|
||||||
private readonly Dictionary<ProtocolType, EphemeralPortPool> _ephemeralPorts = new Dictionary<ProtocolType, EphemeralPortPool>();
|
|
||||||
|
|
||||||
private readonly IProxyClient _parent;
|
|
||||||
private RyuLdnProtocol _protocol;
|
|
||||||
private readonly uint _subnetMask;
|
|
||||||
private readonly uint _localIp;
|
|
||||||
private readonly uint _broadcast;
|
|
||||||
|
|
||||||
public LdnProxy(ProxyConfig config, IProxyClient client, RyuLdnProtocol protocol)
|
|
||||||
{
|
|
||||||
_parent = client;
|
|
||||||
_protocol = protocol;
|
|
||||||
|
|
||||||
_ephemeralPorts[ProtocolType.Udp] = new EphemeralPortPool();
|
|
||||||
_ephemeralPorts[ProtocolType.Tcp] = new EphemeralPortPool();
|
|
||||||
|
|
||||||
byte[] address = BitConverter.GetBytes(config.ProxyIp);
|
|
||||||
Array.Reverse(address);
|
|
||||||
LocalAddress = new IPAddress(address);
|
|
||||||
|
|
||||||
_subnetMask = config.ProxySubnetMask;
|
|
||||||
_localIp = config.ProxyIp;
|
|
||||||
_broadcast = _localIp | (~_subnetMask);
|
|
||||||
|
|
||||||
RegisterHandlers(protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Supported(AddressFamily domain, SocketType type, ProtocolType protocol)
|
|
||||||
{
|
|
||||||
if (protocol == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Tcp proxy networking is untested. Please report this game so that it can be tested.");
|
|
||||||
}
|
|
||||||
return domain == AddressFamily.InterNetwork && (protocol == ProtocolType.Tcp || protocol == ProtocolType.Udp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterHandlers(RyuLdnProtocol protocol)
|
|
||||||
{
|
|
||||||
protocol.ProxyConnect += HandleConnectionRequest;
|
|
||||||
protocol.ProxyConnectReply += HandleConnectionResponse;
|
|
||||||
protocol.ProxyData += HandleData;
|
|
||||||
protocol.ProxyDisconnect += HandleDisconnect;
|
|
||||||
|
|
||||||
_protocol = protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnregisterHandlers(RyuLdnProtocol protocol)
|
|
||||||
{
|
|
||||||
protocol.ProxyConnect -= HandleConnectionRequest;
|
|
||||||
protocol.ProxyConnectReply -= HandleConnectionResponse;
|
|
||||||
protocol.ProxyData -= HandleData;
|
|
||||||
protocol.ProxyDisconnect -= HandleDisconnect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ushort GetEphemeralPort(ProtocolType type)
|
|
||||||
{
|
|
||||||
return _ephemeralPorts[type].Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReturnEphemeralPort(ProtocolType type, ushort port)
|
|
||||||
{
|
|
||||||
_ephemeralPorts[type].Return(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterSocket(LdnProxySocket socket)
|
|
||||||
{
|
|
||||||
lock (_sockets)
|
|
||||||
{
|
|
||||||
_sockets.Add(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnregisterSocket(LdnProxySocket socket)
|
|
||||||
{
|
|
||||||
lock (_sockets)
|
|
||||||
{
|
|
||||||
_sockets.Remove(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ForRoutedSockets(ProxyInfo info, Action<LdnProxySocket> action)
|
|
||||||
{
|
|
||||||
lock (_sockets)
|
|
||||||
{
|
|
||||||
foreach (LdnProxySocket socket in _sockets)
|
|
||||||
{
|
|
||||||
// Must match protocol and destination port.
|
|
||||||
if (socket.ProtocolType != info.Protocol || socket.LocalEndPoint is not IPEndPoint endpoint || endpoint.Port != info.DestPort)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can assume packets routed to us have been sent to our destination.
|
|
||||||
// They will either be sent to us, or broadcast packets.
|
|
||||||
|
|
||||||
action(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleConnectionRequest(LdnHeader header, ProxyConnectRequest request)
|
|
||||||
{
|
|
||||||
ForRoutedSockets(request.Info, (socket) =>
|
|
||||||
{
|
|
||||||
socket.HandleConnectRequest(request);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleConnectionResponse(LdnHeader header, ProxyConnectResponse response)
|
|
||||||
{
|
|
||||||
ForRoutedSockets(response.Info, (socket) =>
|
|
||||||
{
|
|
||||||
socket.HandleConnectResponse(response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleData(LdnHeader header, ProxyDataHeader proxyHeader, byte[] data)
|
|
||||||
{
|
|
||||||
ProxyDataPacket packet = new ProxyDataPacket() { Header = proxyHeader, Data = data };
|
|
||||||
|
|
||||||
ForRoutedSockets(proxyHeader.Info, (socket) =>
|
|
||||||
{
|
|
||||||
socket.IncomingData(packet);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleDisconnect(LdnHeader header, ProxyDisconnectMessage disconnect)
|
|
||||||
{
|
|
||||||
ForRoutedSockets(disconnect.Info, (socket) =>
|
|
||||||
{
|
|
||||||
socket.HandleDisconnect(disconnect);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private uint GetIpV4(IPEndPoint endpoint)
|
|
||||||
{
|
|
||||||
if (endpoint.AddressFamily != AddressFamily.InterNetwork)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] address = endpoint.Address.GetAddressBytes();
|
|
||||||
Array.Reverse(address);
|
|
||||||
|
|
||||||
return BitConverter.ToUInt32(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProxyInfo MakeInfo(IPEndPoint localEp, IPEndPoint remoteEP, ProtocolType type)
|
|
||||||
{
|
|
||||||
return new ProxyInfo
|
|
||||||
{
|
|
||||||
SourceIpV4 = GetIpV4(localEp),
|
|
||||||
SourcePort = (ushort)localEp.Port,
|
|
||||||
|
|
||||||
DestIpV4 = GetIpV4(remoteEP),
|
|
||||||
DestPort = (ushort)remoteEP.Port,
|
|
||||||
|
|
||||||
Protocol = type
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RequestConnection(IPEndPoint localEp, IPEndPoint remoteEp, ProtocolType type)
|
|
||||||
{
|
|
||||||
// We must ask the other side to initialize a connection, so they can accept a socket for us.
|
|
||||||
|
|
||||||
ProxyConnectRequest request = new ProxyConnectRequest
|
|
||||||
{
|
|
||||||
Info = MakeInfo(localEp, remoteEp, type)
|
|
||||||
};
|
|
||||||
|
|
||||||
_parent.SendAsync(_protocol.Encode(PacketId.ProxyConnect, request));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SignalConnected(IPEndPoint localEp, IPEndPoint remoteEp, ProtocolType type)
|
|
||||||
{
|
|
||||||
// We must tell the other side that we have accepted their request for connection.
|
|
||||||
|
|
||||||
ProxyConnectResponse request = new ProxyConnectResponse
|
|
||||||
{
|
|
||||||
Info = MakeInfo(localEp, remoteEp, type)
|
|
||||||
};
|
|
||||||
|
|
||||||
_parent.SendAsync(_protocol.Encode(PacketId.ProxyConnectReply, request));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndConnection(IPEndPoint localEp, IPEndPoint remoteEp, ProtocolType type)
|
|
||||||
{
|
|
||||||
// We must tell the other side that our connection is dropped.
|
|
||||||
|
|
||||||
ProxyDisconnectMessage request = new ProxyDisconnectMessage
|
|
||||||
{
|
|
||||||
Info = MakeInfo(localEp, remoteEp, type),
|
|
||||||
DisconnectReason = 0 // TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
_parent.SendAsync(_protocol.Encode(PacketId.ProxyDisconnect, request));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, IPEndPoint localEp, IPEndPoint remoteEp, ProtocolType type)
|
|
||||||
{
|
|
||||||
// We send exactly as much as the user wants us to, currently instantly.
|
|
||||||
// TODO: handle over "virtual mtu" (we have a max packet size to worry about anyways). fragment if tcp? throw if udp?
|
|
||||||
|
|
||||||
ProxyDataHeader request = new ProxyDataHeader
|
|
||||||
{
|
|
||||||
Info = MakeInfo(localEp, remoteEp, type),
|
|
||||||
DataLength = (uint)buffer.Length
|
|
||||||
};
|
|
||||||
|
|
||||||
_parent.SendAsync(_protocol.Encode(PacketId.ProxyData, request, buffer.ToArray()));
|
|
||||||
|
|
||||||
return buffer.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsBroadcast(uint ip)
|
|
||||||
{
|
|
||||||
return ip == _broadcast;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsMyself(uint ip)
|
|
||||||
{
|
|
||||||
return ip == _localIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
UnregisterHandlers(_protocol);
|
|
||||||
|
|
||||||
lock (_sockets)
|
|
||||||
{
|
|
||||||
foreach (LdnProxySocket socket in _sockets)
|
|
||||||
{
|
|
||||||
socket.ProxyDestroyed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,797 +0,0 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This socket is forwarded through a TCP stream that goes through the Ldn server.
|
|
||||||
/// The Ldn server will then route the packets we send (or need to receive) within the virtual adhoc network.
|
|
||||||
/// </summary>
|
|
||||||
class LdnProxySocket : ISocketImpl
|
|
||||||
{
|
|
||||||
private readonly LdnProxy _proxy;
|
|
||||||
|
|
||||||
private bool _isListening;
|
|
||||||
private readonly List<LdnProxySocket> _listenSockets = new List<LdnProxySocket>();
|
|
||||||
|
|
||||||
private readonly Queue<ProxyConnectRequest> _connectRequests = new Queue<ProxyConnectRequest>();
|
|
||||||
|
|
||||||
private readonly AutoResetEvent _acceptEvent = new AutoResetEvent(false);
|
|
||||||
private readonly int _acceptTimeout = -1;
|
|
||||||
|
|
||||||
private readonly Queue<int> _errors = new Queue<int>();
|
|
||||||
|
|
||||||
private readonly AutoResetEvent _connectEvent = new AutoResetEvent(false);
|
|
||||||
private ProxyConnectResponse _connectResponse;
|
|
||||||
|
|
||||||
private int _receiveTimeout = -1;
|
|
||||||
private readonly AutoResetEvent _receiveEvent = new AutoResetEvent(false);
|
|
||||||
private readonly Queue<ProxyDataPacket> _receiveQueue = new Queue<ProxyDataPacket>();
|
|
||||||
|
|
||||||
// private int _sendTimeout = -1; // Sends are techically instant right now, so not _really_ used.
|
|
||||||
|
|
||||||
private bool _connecting;
|
|
||||||
private bool _broadcast;
|
|
||||||
private bool _readShutdown;
|
|
||||||
// private bool _writeShutdown;
|
|
||||||
private bool _closed;
|
|
||||||
|
|
||||||
private readonly Dictionary<SocketOptionName, int> _socketOptions = new Dictionary<SocketOptionName, int>()
|
|
||||||
{
|
|
||||||
{ SocketOptionName.Broadcast, 0 }, //TODO: honor this value
|
|
||||||
{ SocketOptionName.DontLinger, 0 },
|
|
||||||
{ SocketOptionName.Debug, 0 },
|
|
||||||
{ SocketOptionName.Error, 0 },
|
|
||||||
{ SocketOptionName.KeepAlive, 0 },
|
|
||||||
{ SocketOptionName.OutOfBandInline, 0 },
|
|
||||||
{ SocketOptionName.ReceiveBuffer, 131072 },
|
|
||||||
{ SocketOptionName.ReceiveTimeout, -1 },
|
|
||||||
{ SocketOptionName.SendBuffer, 131072 },
|
|
||||||
{ SocketOptionName.SendTimeout, -1 },
|
|
||||||
{ SocketOptionName.Type, 0 },
|
|
||||||
{ SocketOptionName.ReuseAddress, 0 } //TODO: honor this value
|
|
||||||
};
|
|
||||||
|
|
||||||
public EndPoint RemoteEndPoint { get; private set; }
|
|
||||||
|
|
||||||
public EndPoint LocalEndPoint { get; private set; }
|
|
||||||
|
|
||||||
public bool Connected { get; private set; }
|
|
||||||
|
|
||||||
public bool IsBound { get; private set; }
|
|
||||||
|
|
||||||
public AddressFamily AddressFamily { get; }
|
|
||||||
|
|
||||||
public SocketType SocketType { get; }
|
|
||||||
|
|
||||||
public ProtocolType ProtocolType { get; }
|
|
||||||
|
|
||||||
public bool Blocking { get; set; }
|
|
||||||
|
|
||||||
public int Available
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
foreach (ProxyDataPacket data in _receiveQueue)
|
|
||||||
{
|
|
||||||
result += data.Data.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Readable
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_isListening)
|
|
||||||
{
|
|
||||||
lock (_connectRequests)
|
|
||||||
{
|
|
||||||
return _connectRequests.Count > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_readShutdown)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
return _receiveQueue.Count > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public bool Writable => Connected || ProtocolType == ProtocolType.Udp;
|
|
||||||
public bool Error => false;
|
|
||||||
|
|
||||||
public LdnProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, LdnProxy proxy)
|
|
||||||
{
|
|
||||||
AddressFamily = addressFamily;
|
|
||||||
SocketType = socketType;
|
|
||||||
ProtocolType = protocolType;
|
|
||||||
|
|
||||||
_proxy = proxy;
|
|
||||||
_socketOptions[SocketOptionName.Type] = (int)socketType;
|
|
||||||
|
|
||||||
proxy.RegisterSocket(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IPEndPoint EnsureLocalEndpoint(bool replace)
|
|
||||||
{
|
|
||||||
if (LocalEndPoint != null)
|
|
||||||
{
|
|
||||||
if (replace)
|
|
||||||
{
|
|
||||||
_proxy.ReturnEphemeralPort(ProtocolType, (ushort)((IPEndPoint)LocalEndPoint).Port);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return (IPEndPoint)LocalEndPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IPEndPoint localEp = new IPEndPoint(_proxy.LocalAddress, _proxy.GetEphemeralPort(ProtocolType));
|
|
||||||
LocalEndPoint = localEp;
|
|
||||||
|
|
||||||
return localEp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LdnProxySocket AsAccepted(IPEndPoint remoteEp)
|
|
||||||
{
|
|
||||||
Connected = true;
|
|
||||||
RemoteEndPoint = remoteEp;
|
|
||||||
|
|
||||||
IPEndPoint localEp = EnsureLocalEndpoint(true);
|
|
||||||
|
|
||||||
_proxy.SignalConnected(localEp, remoteEp, ProtocolType);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SignalError(WsaError error)
|
|
||||||
{
|
|
||||||
lock (_errors)
|
|
||||||
{
|
|
||||||
_errors.Enqueue((int)error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IPEndPoint GetEndpoint(uint ipv4, ushort port)
|
|
||||||
{
|
|
||||||
byte[] address = BitConverter.GetBytes(ipv4);
|
|
||||||
Array.Reverse(address);
|
|
||||||
|
|
||||||
return new IPEndPoint(new IPAddress(address), port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IncomingData(ProxyDataPacket packet)
|
|
||||||
{
|
|
||||||
bool isBroadcast = _proxy.IsBroadcast(packet.Header.Info.DestIpV4);
|
|
||||||
|
|
||||||
if (!_closed && (_broadcast || !isBroadcast))
|
|
||||||
{
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
_receiveQueue.Enqueue(packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISocketImpl Accept()
|
|
||||||
{
|
|
||||||
if (!_isListening)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept a pending request to this socket.
|
|
||||||
|
|
||||||
lock (_connectRequests)
|
|
||||||
{
|
|
||||||
if (!Blocking && _connectRequests.Count == 0)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
_acceptEvent.WaitOne(_acceptTimeout);
|
|
||||||
|
|
||||||
lock (_connectRequests)
|
|
||||||
{
|
|
||||||
while (_connectRequests.Count > 0)
|
|
||||||
{
|
|
||||||
ProxyConnectRequest request = _connectRequests.Dequeue();
|
|
||||||
|
|
||||||
if (_connectRequests.Count > 0)
|
|
||||||
{
|
|
||||||
_acceptEvent.Set(); // Still more accepts to do.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this request made for us?
|
|
||||||
IPEndPoint endpoint = GetEndpoint(request.Info.DestIpV4, request.Info.DestPort);
|
|
||||||
|
|
||||||
if (Equals(endpoint, LocalEndPoint))
|
|
||||||
{
|
|
||||||
// Yes - let's accept.
|
|
||||||
IPEndPoint remoteEndpoint = GetEndpoint(request.Info.SourceIpV4, request.Info.SourcePort);
|
|
||||||
|
|
||||||
LdnProxySocket socket = new LdnProxySocket(AddressFamily, SocketType, ProtocolType, _proxy).AsAccepted(remoteEndpoint);
|
|
||||||
|
|
||||||
lock (_listenSockets)
|
|
||||||
{
|
|
||||||
_listenSockets.Add(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Bind(EndPoint localEP)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(localEP);
|
|
||||||
|
|
||||||
if (LocalEndPoint != null)
|
|
||||||
{
|
|
||||||
_proxy.ReturnEphemeralPort(ProtocolType, (ushort)((IPEndPoint)LocalEndPoint).Port);
|
|
||||||
}
|
|
||||||
var asIPEndpoint = (IPEndPoint)localEP;
|
|
||||||
if (asIPEndpoint.Port == 0)
|
|
||||||
{
|
|
||||||
asIPEndpoint.Port = (ushort)_proxy.GetEphemeralPort(ProtocolType);
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalEndPoint = (IPEndPoint)localEP;
|
|
||||||
|
|
||||||
IsBound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
_closed = true;
|
|
||||||
|
|
||||||
_proxy.UnregisterSocket(this);
|
|
||||||
|
|
||||||
if (Connected)
|
|
||||||
{
|
|
||||||
Disconnect(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_listenSockets)
|
|
||||||
{
|
|
||||||
foreach (LdnProxySocket socket in _listenSockets)
|
|
||||||
{
|
|
||||||
socket.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isListening = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Connect(EndPoint remoteEP)
|
|
||||||
{
|
|
||||||
if (_isListening || !IsBound)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remoteEP is not IPEndPoint)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IPEndPoint localEp = EnsureLocalEndpoint(true);
|
|
||||||
|
|
||||||
_connecting = true;
|
|
||||||
|
|
||||||
_proxy.RequestConnection(localEp, (IPEndPoint)remoteEP, ProtocolType);
|
|
||||||
|
|
||||||
if (!Blocking && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
||||||
}
|
|
||||||
|
|
||||||
_connectEvent.WaitOne(); //timeout?
|
|
||||||
|
|
||||||
if (_connectResponse.Info.SourceIpV4 == 0)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAECONNREFUSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
_connectResponse = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleConnectResponse(ProxyConnectResponse obj)
|
|
||||||
{
|
|
||||||
if (!_connecting)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_connecting = false;
|
|
||||||
|
|
||||||
if (_connectResponse.Info.SourceIpV4 != 0)
|
|
||||||
{
|
|
||||||
IPEndPoint remoteEp = GetEndpoint(obj.Info.SourceIpV4, obj.Info.SourcePort);
|
|
||||||
RemoteEndPoint = remoteEp;
|
|
||||||
|
|
||||||
Connected = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Connection failed
|
|
||||||
|
|
||||||
SignalError(WsaError.WSAECONNREFUSED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disconnect(bool reuseSocket)
|
|
||||||
{
|
|
||||||
if (Connected)
|
|
||||||
{
|
|
||||||
ConnectionEnded();
|
|
||||||
|
|
||||||
// The other side needs to be notified that connection ended.
|
|
||||||
_proxy.EndConnection(LocalEndPoint as IPEndPoint, RemoteEndPoint as IPEndPoint, ProtocolType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConnectionEnded()
|
|
||||||
{
|
|
||||||
if (Connected)
|
|
||||||
{
|
|
||||||
RemoteEndPoint = null;
|
|
||||||
Connected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
|
|
||||||
{
|
|
||||||
if (optionLevel != SocketOptionLevel.Socket)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_socketOptions.TryGetValue(optionName, out int result))
|
|
||||||
{
|
|
||||||
byte[] data = BitConverter.GetBytes(result);
|
|
||||||
Array.Copy(data, 0, optionValue, 0, Math.Min(data.Length, optionValue.Length));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Listen(int backlog)
|
|
||||||
{
|
|
||||||
if (!IsBound)
|
|
||||||
{
|
|
||||||
throw new SocketException();
|
|
||||||
}
|
|
||||||
|
|
||||||
_isListening = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleConnectRequest(ProxyConnectRequest obj)
|
|
||||||
{
|
|
||||||
lock (_connectRequests)
|
|
||||||
{
|
|
||||||
_connectRequests.Enqueue(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
_connectEvent.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleDisconnect(ProxyDisconnectMessage message)
|
|
||||||
{
|
|
||||||
Disconnect(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Receive(Span<byte> buffer)
|
|
||||||
{
|
|
||||||
EndPoint dummy = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
|
|
||||||
return ReceiveFrom(buffer, SocketFlags.None, ref dummy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Receive(Span<byte> buffer, SocketFlags flags)
|
|
||||||
{
|
|
||||||
EndPoint dummy = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
|
|
||||||
return ReceiveFrom(buffer, flags, ref dummy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Receive(Span<byte> buffer, SocketFlags flags, out SocketError socketError)
|
|
||||||
{
|
|
||||||
EndPoint dummy = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
|
|
||||||
return ReceiveFrom(buffer, flags, out socketError, ref dummy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ReceiveFrom(Span<byte> buffer, SocketFlags flags, ref EndPoint remoteEp)
|
|
||||||
{
|
|
||||||
// We just receive all packets meant for us anyways regardless of EP in the actual implementation.
|
|
||||||
// The point is mostly to return the endpoint that we got the data from.
|
|
||||||
|
|
||||||
if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
if (_receiveQueue.Count > 0)
|
|
||||||
{
|
|
||||||
return ReceiveFromQueue(buffer, flags, ref remoteEp);
|
|
||||||
}
|
|
||||||
else if (_readShutdown)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else if (!Blocking)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int timeout = _receiveTimeout;
|
|
||||||
|
|
||||||
_receiveEvent.WaitOne(timeout == 0 ? -1 : timeout);
|
|
||||||
|
|
||||||
if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
if (_receiveQueue.Count > 0)
|
|
||||||
{
|
|
||||||
return ReceiveFromQueue(buffer, flags, ref remoteEp);
|
|
||||||
}
|
|
||||||
else if (_readShutdown)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAETIMEDOUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ReceiveFrom(Span<byte> buffer, SocketFlags flags, out SocketError socketError, ref EndPoint remoteEp)
|
|
||||||
{
|
|
||||||
// We just receive all packets meant for us anyways regardless of EP in the actual implementation.
|
|
||||||
// The point is mostly to return the endpoint that we got the data from.
|
|
||||||
|
|
||||||
if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
socketError = SocketError.ConnectionReset;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
if (_receiveQueue.Count > 0)
|
|
||||||
{
|
|
||||||
return ReceiveFromQueue(buffer, flags, out socketError, ref remoteEp);
|
|
||||||
}
|
|
||||||
else if (_readShutdown)
|
|
||||||
{
|
|
||||||
socketError = SocketError.Success;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else if (!Blocking)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAEWOULDBLOCK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int timeout = _receiveTimeout;
|
|
||||||
|
|
||||||
_receiveEvent.WaitOne(timeout == 0 ? -1 : timeout);
|
|
||||||
|
|
||||||
if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_receiveQueue)
|
|
||||||
{
|
|
||||||
if (_receiveQueue.Count > 0)
|
|
||||||
{
|
|
||||||
return ReceiveFromQueue(buffer, flags, out socketError, ref remoteEp);
|
|
||||||
}
|
|
||||||
else if (_readShutdown)
|
|
||||||
{
|
|
||||||
socketError = SocketError.Success;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
socketError = SocketError.TimedOut;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ReceiveFromQueue(Span<byte> buffer, SocketFlags flags, ref EndPoint remoteEp)
|
|
||||||
{
|
|
||||||
int size = buffer.Length;
|
|
||||||
|
|
||||||
// Assumes we have the receive queue lock, and at least one item in the queue.
|
|
||||||
ProxyDataPacket packet = _receiveQueue.Peek();
|
|
||||||
|
|
||||||
remoteEp = GetEndpoint(packet.Header.Info.SourceIpV4, packet.Header.Info.SourcePort);
|
|
||||||
|
|
||||||
bool peek = (flags & SocketFlags.Peek) != 0;
|
|
||||||
|
|
||||||
int read;
|
|
||||||
|
|
||||||
if (packet.Data.Length > size)
|
|
||||||
{
|
|
||||||
read = size;
|
|
||||||
|
|
||||||
// Cannot fit in the output buffer. Copy up to what we've got.
|
|
||||||
packet.Data.AsSpan(0, size).CopyTo(buffer);
|
|
||||||
|
|
||||||
if (ProtocolType == ProtocolType.Udp)
|
|
||||||
{
|
|
||||||
// Udp overflows, loses the data, then throws an exception.
|
|
||||||
|
|
||||||
if (!peek)
|
|
||||||
{
|
|
||||||
_receiveQueue.Dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SocketException((int)WsaError.WSAEMSGSIZE);
|
|
||||||
}
|
|
||||||
else if (ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
// Split the data at the buffer boundary. It will stay on the recieve queue.
|
|
||||||
|
|
||||||
byte[] newData = new byte[packet.Data.Length - size];
|
|
||||||
Array.Copy(packet.Data, size, newData, 0, newData.Length);
|
|
||||||
|
|
||||||
packet.Data = newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
read = packet.Data.Length;
|
|
||||||
|
|
||||||
packet.Data.AsSpan(0, packet.Data.Length).CopyTo(buffer);
|
|
||||||
|
|
||||||
if (!peek)
|
|
||||||
{
|
|
||||||
_receiveQueue.Dequeue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ReceiveFromQueue(Span<byte> buffer, SocketFlags flags, out SocketError socketError, ref EndPoint remoteEp)
|
|
||||||
{
|
|
||||||
int size = buffer.Length;
|
|
||||||
|
|
||||||
// Assumes we have the receive queue lock, and at least one item in the queue.
|
|
||||||
ProxyDataPacket packet = _receiveQueue.Peek();
|
|
||||||
|
|
||||||
remoteEp = GetEndpoint(packet.Header.Info.SourceIpV4, packet.Header.Info.SourcePort);
|
|
||||||
|
|
||||||
bool peek = (flags & SocketFlags.Peek) != 0;
|
|
||||||
|
|
||||||
int read;
|
|
||||||
|
|
||||||
if (packet.Data.Length > size)
|
|
||||||
{
|
|
||||||
read = size;
|
|
||||||
|
|
||||||
// Cannot fit in the output buffer. Copy up to what we've got.
|
|
||||||
packet.Data.AsSpan(0, size).CopyTo(buffer);
|
|
||||||
|
|
||||||
if (ProtocolType == ProtocolType.Udp)
|
|
||||||
{
|
|
||||||
// Udp overflows, loses the data, then throws an exception.
|
|
||||||
|
|
||||||
if (!peek)
|
|
||||||
{
|
|
||||||
_receiveQueue.Dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
socketError = SocketError.MessageSize;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
// Split the data at the buffer boundary. It will stay on the recieve queue.
|
|
||||||
|
|
||||||
byte[] newData = new byte[packet.Data.Length - size];
|
|
||||||
Array.Copy(packet.Data, size, newData, 0, newData.Length);
|
|
||||||
|
|
||||||
packet.Data = newData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
read = packet.Data.Length;
|
|
||||||
|
|
||||||
packet.Data.AsSpan(0, packet.Data.Length).CopyTo(buffer);
|
|
||||||
|
|
||||||
if (!peek)
|
|
||||||
{
|
|
||||||
_receiveQueue.Dequeue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socketError = SocketError.Success;
|
|
||||||
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Send(ReadOnlySpan<byte> buffer)
|
|
||||||
{
|
|
||||||
// Send to the remote host chosen when we "connect" or "accept".
|
|
||||||
if (!Connected)
|
|
||||||
{
|
|
||||||
throw new SocketException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendTo(buffer, SocketFlags.None, RemoteEndPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Send(ReadOnlySpan<byte> buffer, SocketFlags flags)
|
|
||||||
{
|
|
||||||
// Send to the remote host chosen when we "connect" or "accept".
|
|
||||||
if (!Connected)
|
|
||||||
{
|
|
||||||
throw new SocketException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendTo(buffer, flags, RemoteEndPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Send(ReadOnlySpan<byte> buffer, SocketFlags flags, out SocketError socketError)
|
|
||||||
{
|
|
||||||
// Send to the remote host chosen when we "connect" or "accept".
|
|
||||||
if (!Connected)
|
|
||||||
{
|
|
||||||
throw new SocketException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendTo(buffer, flags, out socketError, RemoteEndPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, EndPoint remoteEP)
|
|
||||||
{
|
|
||||||
if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
throw new SocketException((int)WsaError.WSAECONNRESET);
|
|
||||||
}
|
|
||||||
|
|
||||||
IPEndPoint localEp = EnsureLocalEndpoint(false);
|
|
||||||
|
|
||||||
if (remoteEP is not IPEndPoint)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, out SocketError socketError, EndPoint remoteEP)
|
|
||||||
{
|
|
||||||
if (!Connected && ProtocolType == ProtocolType.Tcp)
|
|
||||||
{
|
|
||||||
socketError = SocketError.ConnectionReset;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
IPEndPoint localEp = EnsureLocalEndpoint(false);
|
|
||||||
|
|
||||||
if (remoteEP is not IPEndPoint)
|
|
||||||
{
|
|
||||||
// throw new NotSupportedException();
|
|
||||||
socketError = SocketError.OperationNotSupported;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
socketError = SocketError.Success;
|
|
||||||
|
|
||||||
return _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Poll(int microSeconds, SelectMode mode)
|
|
||||||
{
|
|
||||||
return mode switch
|
|
||||||
{
|
|
||||||
SelectMode.SelectRead => Readable,
|
|
||||||
SelectMode.SelectWrite => Writable,
|
|
||||||
SelectMode.SelectError => Error,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
|
|
||||||
{
|
|
||||||
if (optionLevel != SocketOptionLevel.Socket)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (optionName)
|
|
||||||
{
|
|
||||||
case SocketOptionName.SendTimeout:
|
|
||||||
//_sendTimeout = optionValue;
|
|
||||||
break;
|
|
||||||
case SocketOptionName.ReceiveTimeout:
|
|
||||||
_receiveTimeout = optionValue;
|
|
||||||
break;
|
|
||||||
case SocketOptionName.Broadcast:
|
|
||||||
_broadcast = optionValue != 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_socketOptions)
|
|
||||||
{
|
|
||||||
_socketOptions[optionName] = optionValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, object optionValue)
|
|
||||||
{
|
|
||||||
// Just linger uses this for now in BSD, which we ignore.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Shutdown(SocketShutdown how)
|
|
||||||
{
|
|
||||||
switch (how)
|
|
||||||
{
|
|
||||||
case SocketShutdown.Both:
|
|
||||||
_readShutdown = true;
|
|
||||||
// _writeShutdown = true;
|
|
||||||
break;
|
|
||||||
case SocketShutdown.Receive:
|
|
||||||
_readShutdown = true;
|
|
||||||
break;
|
|
||||||
case SocketShutdown.Send:
|
|
||||||
// _writeShutdown = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProxyDestroyed()
|
|
||||||
{
|
|
||||||
// Do nothing, for now. Will likely be more useful with TCP.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Threading;
|
|
||||||
using TcpClient = NetCoreServer.TcpClient;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
class P2pProxyClient : TcpClient, IProxyClient
|
|
||||||
{
|
|
||||||
private const int FailureTimeout = 4000;
|
|
||||||
|
|
||||||
public ProxyConfig ProxyConfig { get; private set; }
|
|
||||||
|
|
||||||
private readonly RyuLdnProtocol _protocol;
|
|
||||||
|
|
||||||
private readonly ManualResetEvent _connected = new ManualResetEvent(false);
|
|
||||||
private readonly ManualResetEvent _ready = new ManualResetEvent(false);
|
|
||||||
private readonly AutoResetEvent _error = new AutoResetEvent(false);
|
|
||||||
|
|
||||||
public P2pProxyClient(string address, int port) : base(address, port)
|
|
||||||
{
|
|
||||||
if (ProxyHelpers.SupportsNoDelay())
|
|
||||||
{
|
|
||||||
OptionNoDelay = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_protocol = new RyuLdnProtocol();
|
|
||||||
|
|
||||||
_protocol.ProxyConfig += HandleProxyConfig;
|
|
||||||
|
|
||||||
ConnectAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnConnected()
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Proxy TCP client connected a new session with Id {Id}");
|
|
||||||
|
|
||||||
_connected.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDisconnected()
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Proxy TCP client disconnected a session with Id {Id}");
|
|
||||||
|
|
||||||
SocketHelpers.UnregisterProxy();
|
|
||||||
|
|
||||||
_connected.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnReceived(byte[] buffer, long offset, long size)
|
|
||||||
{
|
|
||||||
_protocol.Read(buffer, (int)offset, (int)size);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnError(SocketError error)
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Proxy TCP client caught an error with code {error}");
|
|
||||||
|
|
||||||
_error.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleProxyConfig(LdnHeader header, ProxyConfig config)
|
|
||||||
{
|
|
||||||
ProxyConfig = config;
|
|
||||||
|
|
||||||
SocketHelpers.RegisterProxy(new LdnProxy(config, this, _protocol));
|
|
||||||
|
|
||||||
_ready.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EnsureProxyReady()
|
|
||||||
{
|
|
||||||
return _ready.WaitOne(FailureTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool PerformAuth(ExternalProxyConfig config)
|
|
||||||
{
|
|
||||||
bool signalled = _connected.WaitOne(FailureTimeout);
|
|
||||||
|
|
||||||
if (!signalled)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SendAsync(_protocol.Encode(PacketId.ExternalProxy, config));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,388 +0,0 @@
|
|||||||
using NetCoreServer;
|
|
||||||
using Open.Nat;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
class P2pProxyServer : TcpServer, IDisposable
|
|
||||||
{
|
|
||||||
public const ushort PrivatePortBase = 39990;
|
|
||||||
public const int PrivatePortRange = 10;
|
|
||||||
|
|
||||||
private const ushort PublicPortBase = 39990;
|
|
||||||
private const int PublicPortRange = 10;
|
|
||||||
|
|
||||||
private const ushort PortLeaseLength = 60;
|
|
||||||
private const ushort PortLeaseRenew = 50;
|
|
||||||
|
|
||||||
private const ushort AuthWaitSeconds = 1;
|
|
||||||
|
|
||||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
|
||||||
|
|
||||||
public ushort PrivatePort { get; }
|
|
||||||
|
|
||||||
private ushort _publicPort;
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
private readonly CancellationTokenSource _disposedCancellation = new CancellationTokenSource();
|
|
||||||
|
|
||||||
private NatDevice _natDevice;
|
|
||||||
private Mapping _portMapping;
|
|
||||||
|
|
||||||
private readonly List<P2pProxySession> _players = new List<P2pProxySession>();
|
|
||||||
|
|
||||||
private readonly List<ExternalProxyToken> _waitingTokens = new List<ExternalProxyToken>();
|
|
||||||
private readonly AutoResetEvent _tokenEvent = new AutoResetEvent(false);
|
|
||||||
|
|
||||||
private uint _broadcastAddress;
|
|
||||||
|
|
||||||
private readonly LdnMasterProxyClient _master;
|
|
||||||
private readonly RyuLdnProtocol _masterProtocol;
|
|
||||||
private readonly RyuLdnProtocol _protocol;
|
|
||||||
|
|
||||||
public P2pProxyServer(LdnMasterProxyClient master, ushort port, RyuLdnProtocol masterProtocol) : base(IPAddress.Any, port)
|
|
||||||
{
|
|
||||||
if (ProxyHelpers.SupportsNoDelay())
|
|
||||||
{
|
|
||||||
OptionNoDelay = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivatePort = port;
|
|
||||||
|
|
||||||
_master = master;
|
|
||||||
_masterProtocol = masterProtocol;
|
|
||||||
|
|
||||||
_masterProtocol.ExternalProxyState += HandleStateChange;
|
|
||||||
_masterProtocol.ExternalProxyToken += HandleToken;
|
|
||||||
|
|
||||||
_protocol = new RyuLdnProtocol();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleToken(LdnHeader header, ExternalProxyToken token)
|
|
||||||
{
|
|
||||||
_lock.EnterWriteLock();
|
|
||||||
|
|
||||||
_waitingTokens.Add(token);
|
|
||||||
|
|
||||||
_lock.ExitWriteLock();
|
|
||||||
|
|
||||||
_tokenEvent.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleStateChange(LdnHeader header, ExternalProxyConnectionState state)
|
|
||||||
{
|
|
||||||
if (!state.Connected)
|
|
||||||
{
|
|
||||||
_lock.EnterWriteLock();
|
|
||||||
|
|
||||||
_waitingTokens.RemoveAll(token => token.VirtualIp == state.IpAddress);
|
|
||||||
|
|
||||||
_players.RemoveAll(player =>
|
|
||||||
{
|
|
||||||
if (player.VirtualIpAddress == state.IpAddress)
|
|
||||||
{
|
|
||||||
player.DisconnectAndStop();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
_lock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Configure(ProxyConfig config)
|
|
||||||
{
|
|
||||||
_broadcastAddress = config.ProxyIp | (~config.ProxySubnetMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ushort> NatPunch()
|
|
||||||
{
|
|
||||||
NatDiscoverer discoverer = new NatDiscoverer();
|
|
||||||
CancellationTokenSource cts = new CancellationTokenSource(1000);
|
|
||||||
|
|
||||||
NatDevice device;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
device = await discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts);
|
|
||||||
}
|
|
||||||
catch (NatDeviceNotFoundException)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_publicPort = PublicPortBase;
|
|
||||||
|
|
||||||
for (int i = 0; i < PublicPortRange; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_portMapping = new Mapping(Protocol.Tcp, PrivatePort, _publicPort, PortLeaseLength, "Ryujinx Local Multiplayer");
|
|
||||||
|
|
||||||
await device.CreatePortMapAsync(_portMapping);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (MappingException)
|
|
||||||
{
|
|
||||||
_publicPort++;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == PublicPortRange - 1)
|
|
||||||
{
|
|
||||||
_publicPort = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_publicPort != 0)
|
|
||||||
{
|
|
||||||
_ = Task.Delay(PortLeaseRenew * 1000, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease));
|
|
||||||
}
|
|
||||||
|
|
||||||
_natDevice = device;
|
|
||||||
|
|
||||||
return _publicPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy handlers
|
|
||||||
|
|
||||||
private void RouteMessage(P2pProxySession sender, ref ProxyInfo info, Action<P2pProxySession> action)
|
|
||||||
{
|
|
||||||
if (info.SourceIpV4 == 0)
|
|
||||||
{
|
|
||||||
// If they sent from a connection bound on 0.0.0.0, make others see it as them.
|
|
||||||
info.SourceIpV4 = sender.VirtualIpAddress;
|
|
||||||
}
|
|
||||||
else if (info.SourceIpV4 != sender.VirtualIpAddress)
|
|
||||||
{
|
|
||||||
// Can't pretend to be somebody else.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint destIp = info.DestIpV4;
|
|
||||||
|
|
||||||
if (destIp == 0xc0a800ff)
|
|
||||||
{
|
|
||||||
destIp = _broadcastAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isBroadcast = destIp == _broadcastAddress;
|
|
||||||
|
|
||||||
_lock.EnterReadLock();
|
|
||||||
|
|
||||||
if (isBroadcast)
|
|
||||||
{
|
|
||||||
_players.ForEach(player =>
|
|
||||||
{
|
|
||||||
action(player);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
P2pProxySession target = _players.FirstOrDefault(player => player.VirtualIpAddress == destIp);
|
|
||||||
|
|
||||||
if (target != null)
|
|
||||||
{
|
|
||||||
action(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_lock.ExitReadLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleProxyDisconnect(P2pProxySession sender, LdnHeader header, ProxyDisconnectMessage message)
|
|
||||||
{
|
|
||||||
RouteMessage(sender, ref message.Info, (target) =>
|
|
||||||
{
|
|
||||||
target.SendAsync(sender.Protocol.Encode(PacketId.ProxyDisconnect, message));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleProxyData(P2pProxySession sender, LdnHeader header, ProxyDataHeader message, byte[] data)
|
|
||||||
{
|
|
||||||
RouteMessage(sender, ref message.Info, (target) =>
|
|
||||||
{
|
|
||||||
target.SendAsync(sender.Protocol.Encode(PacketId.ProxyData, message, data));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleProxyConnectReply(P2pProxySession sender, LdnHeader header, ProxyConnectResponse message)
|
|
||||||
{
|
|
||||||
RouteMessage(sender, ref message.Info, (target) =>
|
|
||||||
{
|
|
||||||
target.SendAsync(sender.Protocol.Encode(PacketId.ProxyConnectReply, message));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleProxyConnect(P2pProxySession sender, LdnHeader header, ProxyConnectRequest message)
|
|
||||||
{
|
|
||||||
RouteMessage(sender, ref message.Info, (target) =>
|
|
||||||
{
|
|
||||||
target.SendAsync(sender.Protocol.Encode(PacketId.ProxyConnect, message));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// End proxy handlers
|
|
||||||
|
|
||||||
private async Task RefreshLease()
|
|
||||||
{
|
|
||||||
if (_disposed || _natDevice == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _natDevice.CreatePortMapAsync(_portMapping);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = Task.Delay(PortLeaseRenew, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryRegisterUser(P2pProxySession session, ExternalProxyConfig config)
|
|
||||||
{
|
|
||||||
_lock.EnterWriteLock();
|
|
||||||
|
|
||||||
// Attempt to find matching configuration. If we don't find one, wait for a bit and try again.
|
|
||||||
// Woken by new tokens coming in from the master server.
|
|
||||||
|
|
||||||
IPAddress address = (session.Socket.RemoteEndPoint as IPEndPoint).Address;
|
|
||||||
byte[] addressBytes = ProxyHelpers.AddressTo16Byte(address);
|
|
||||||
|
|
||||||
long time;
|
|
||||||
long endTime = Stopwatch.GetTimestamp() + Stopwatch.Frequency * AuthWaitSeconds;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _waitingTokens.Count; i++)
|
|
||||||
{
|
|
||||||
ExternalProxyToken waitToken = _waitingTokens[i];
|
|
||||||
|
|
||||||
// Allow any client that has a private IP to connect. (indicated by the server as all 0 in the token)
|
|
||||||
|
|
||||||
bool isPrivate = waitToken.PhysicalIp.AsSpan().SequenceEqual(new byte[16]);
|
|
||||||
bool ipEqual = isPrivate || waitToken.AddressFamily == address.AddressFamily && waitToken.PhysicalIp.AsSpan().SequenceEqual(addressBytes);
|
|
||||||
|
|
||||||
if (ipEqual && waitToken.Token.AsSpan().SequenceEqual(config.Token.AsSpan()))
|
|
||||||
{
|
|
||||||
// This is a match.
|
|
||||||
|
|
||||||
_waitingTokens.RemoveAt(i);
|
|
||||||
|
|
||||||
session.SetIpv4(waitToken.VirtualIp);
|
|
||||||
|
|
||||||
ProxyConfig pconfig = new ProxyConfig
|
|
||||||
{
|
|
||||||
ProxyIp = session.VirtualIpAddress,
|
|
||||||
ProxySubnetMask = 0xFFFF0000 // TODO: Use from server.
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_players.Count == 0)
|
|
||||||
{
|
|
||||||
Configure(pconfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
_players.Add(session);
|
|
||||||
|
|
||||||
session.SendAsync(_protocol.Encode(PacketId.ProxyConfig, pconfig));
|
|
||||||
|
|
||||||
_lock.ExitWriteLock();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Couldn't find the token.
|
|
||||||
// It may not have arrived yet, so wait for one to arrive.
|
|
||||||
|
|
||||||
_lock.ExitWriteLock();
|
|
||||||
|
|
||||||
time = Stopwatch.GetTimestamp();
|
|
||||||
int remainingMs = (int)((endTime - time) / (Stopwatch.Frequency / 1000));
|
|
||||||
|
|
||||||
if (remainingMs < 0)
|
|
||||||
{
|
|
||||||
remainingMs = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_tokenEvent.WaitOne(remainingMs);
|
|
||||||
|
|
||||||
_lock.EnterWriteLock();
|
|
||||||
|
|
||||||
} while (time < endTime);
|
|
||||||
|
|
||||||
_lock.ExitWriteLock();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisconnectProxyClient(P2pProxySession session)
|
|
||||||
{
|
|
||||||
_lock.EnterWriteLock();
|
|
||||||
|
|
||||||
bool removed = _players.Remove(session);
|
|
||||||
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
_master.SendAsync(_masterProtocol.Encode(PacketId.ExternalProxyState, new ExternalProxyConnectionState
|
|
||||||
{
|
|
||||||
IpAddress = session.VirtualIpAddress,
|
|
||||||
Connected = false
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_lock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
public new void Dispose()
|
|
||||||
{
|
|
||||||
base.Dispose();
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
_disposedCancellation.Cancel();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Task delete = _natDevice?.DeletePortMapAsync(new Mapping(Protocol.Tcp, PrivatePort, _publicPort, 60, "Ryujinx Local Multiplayer"));
|
|
||||||
|
|
||||||
// Just absorb any exceptions.
|
|
||||||
delete?.ContinueWith((task) => { });
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Fail silently.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override TcpSession CreateSession()
|
|
||||||
{
|
|
||||||
return new P2pProxySession(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnError(SocketError error)
|
|
||||||
{
|
|
||||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Proxy TCP server caught an error with code {error}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
using NetCoreServer;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
class P2pProxySession : TcpSession
|
|
||||||
{
|
|
||||||
public uint VirtualIpAddress { get; private set; }
|
|
||||||
public RyuLdnProtocol Protocol { get; }
|
|
||||||
|
|
||||||
private readonly P2pProxyServer _parent;
|
|
||||||
|
|
||||||
private bool _masterClosed;
|
|
||||||
|
|
||||||
public P2pProxySession(P2pProxyServer server) : base(server)
|
|
||||||
{
|
|
||||||
_parent = server;
|
|
||||||
|
|
||||||
Protocol = new RyuLdnProtocol();
|
|
||||||
|
|
||||||
Protocol.ProxyDisconnect += HandleProxyDisconnect;
|
|
||||||
Protocol.ProxyData += HandleProxyData;
|
|
||||||
Protocol.ProxyConnectReply += HandleProxyConnectReply;
|
|
||||||
Protocol.ProxyConnect += HandleProxyConnect;
|
|
||||||
|
|
||||||
Protocol.ExternalProxy += HandleAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleAuthentication(LdnHeader header, ExternalProxyConfig token)
|
|
||||||
{
|
|
||||||
if (!_parent.TryRegisterUser(this, token))
|
|
||||||
{
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetIpv4(uint ip)
|
|
||||||
{
|
|
||||||
VirtualIpAddress = ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisconnectAndStop()
|
|
||||||
{
|
|
||||||
_masterClosed = true;
|
|
||||||
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDisconnected()
|
|
||||||
{
|
|
||||||
if (!_masterClosed)
|
|
||||||
{
|
|
||||||
_parent.DisconnectProxyClient(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnReceived(byte[] buffer, long offset, long size)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Protocol.Read(buffer, (int)offset, (int)size);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleProxyDisconnect(LdnHeader header, ProxyDisconnectMessage message)
|
|
||||||
{
|
|
||||||
_parent.HandleProxyDisconnect(this, header, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleProxyData(LdnHeader header, ProxyDataHeader message, byte[] data)
|
|
||||||
{
|
|
||||||
_parent.HandleProxyData(this, header, message, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleProxyConnectReply(LdnHeader header, ProxyConnectResponse data)
|
|
||||||
{
|
|
||||||
_parent.HandleProxyConnectReply(this, header, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleProxyConnect(LdnHeader header, ProxyConnectRequest message)
|
|
||||||
{
|
|
||||||
_parent.HandleProxyConnect(this, header, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|
||||||
{
|
|
||||||
static class ProxyHelpers
|
|
||||||
{
|
|
||||||
public static byte[] AddressTo16Byte(IPAddress address)
|
|
||||||
{
|
|
||||||
byte[] ipBytes = new byte[16];
|
|
||||||
byte[] srcBytes = address.GetAddressBytes();
|
|
||||||
|
|
||||||
Array.Copy(srcBytes, 0, ipBytes, 0, srcBytes.Length);
|
|
||||||
|
|
||||||
return ipBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool SupportsNoDelay()
|
|
||||||
{
|
|
||||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu
|
|
||||||
{
|
|
||||||
class RyuLdnProtocol
|
|
||||||
{
|
|
||||||
private const byte CurrentProtocolVersion = 1;
|
|
||||||
private const int Magic = ('R' << 0) | ('L' << 8) | ('D' << 16) | ('N' << 24);
|
|
||||||
private const int MaxPacketSize = 131072;
|
|
||||||
|
|
||||||
private readonly int _headerSize = Marshal.SizeOf<LdnHeader>();
|
|
||||||
|
|
||||||
private readonly byte[] _buffer = new byte[MaxPacketSize];
|
|
||||||
private int _bufferEnd = 0;
|
|
||||||
|
|
||||||
// Client Packets.
|
|
||||||
public event Action<LdnHeader, InitializeMessage> Initialize;
|
|
||||||
public event Action<LdnHeader, PassphraseMessage> Passphrase;
|
|
||||||
public event Action<LdnHeader, NetworkInfo> Connected;
|
|
||||||
public event Action<LdnHeader, NetworkInfo> SyncNetwork;
|
|
||||||
public event Action<LdnHeader, NetworkInfo> ScanReply;
|
|
||||||
public event Action<LdnHeader> ScanReplyEnd;
|
|
||||||
public event Action<LdnHeader, DisconnectMessage> Disconnected;
|
|
||||||
|
|
||||||
// External Proxy Packets.
|
|
||||||
public event Action<LdnHeader, ExternalProxyConfig> ExternalProxy;
|
|
||||||
public event Action<LdnHeader, ExternalProxyConnectionState> ExternalProxyState;
|
|
||||||
public event Action<LdnHeader, ExternalProxyToken> ExternalProxyToken;
|
|
||||||
|
|
||||||
// Server Packets.
|
|
||||||
public event Action<LdnHeader, CreateAccessPointRequest, byte[]> CreateAccessPoint;
|
|
||||||
public event Action<LdnHeader, CreateAccessPointPrivateRequest, byte[]> CreateAccessPointPrivate;
|
|
||||||
public event Action<LdnHeader, RejectRequest> Reject;
|
|
||||||
public event Action<LdnHeader> RejectReply;
|
|
||||||
public event Action<LdnHeader, SetAcceptPolicyRequest> SetAcceptPolicy;
|
|
||||||
public event Action<LdnHeader, byte[]> SetAdvertiseData;
|
|
||||||
public event Action<LdnHeader, ConnectRequest> Connect;
|
|
||||||
public event Action<LdnHeader, ConnectPrivateRequest> ConnectPrivate;
|
|
||||||
public event Action<LdnHeader, ScanFilter> Scan;
|
|
||||||
|
|
||||||
// Proxy Packets.
|
|
||||||
public event Action<LdnHeader, ProxyConfig> ProxyConfig;
|
|
||||||
public event Action<LdnHeader, ProxyConnectRequest> ProxyConnect;
|
|
||||||
public event Action<LdnHeader, ProxyConnectResponse> ProxyConnectReply;
|
|
||||||
public event Action<LdnHeader, ProxyDataHeader, byte[]> ProxyData;
|
|
||||||
public event Action<LdnHeader, ProxyDisconnectMessage> ProxyDisconnect;
|
|
||||||
|
|
||||||
// Lifecycle Packets.
|
|
||||||
public event Action<LdnHeader, NetworkErrorMessage> NetworkError;
|
|
||||||
public event Action<LdnHeader, PingMessage> Ping;
|
|
||||||
|
|
||||||
public RyuLdnProtocol() { }
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
_bufferEnd = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Read(byte[] data, int offset, int size)
|
|
||||||
{
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
while (index < size)
|
|
||||||
{
|
|
||||||
if (_bufferEnd < _headerSize)
|
|
||||||
{
|
|
||||||
// Assemble the header first.
|
|
||||||
|
|
||||||
int copyable = Math.Min(size - index, Math.Min(size, _headerSize - _bufferEnd));
|
|
||||||
|
|
||||||
Array.Copy(data, index + offset, _buffer, _bufferEnd, copyable);
|
|
||||||
|
|
||||||
index += copyable;
|
|
||||||
_bufferEnd += copyable;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_bufferEnd >= _headerSize)
|
|
||||||
{
|
|
||||||
// The header is available. Make sure we received all the data (size specified in the header)
|
|
||||||
|
|
||||||
LdnHeader ldnHeader = MemoryMarshal.Cast<byte, LdnHeader>(_buffer)[0];
|
|
||||||
|
|
||||||
if (ldnHeader.Magic != Magic)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Invalid magic number in received packet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ldnHeader.Version != CurrentProtocolVersion)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Protocol version mismatch. Expected ${CurrentProtocolVersion}, was ${ldnHeader.Version}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int finalSize = _headerSize + ldnHeader.DataSize;
|
|
||||||
|
|
||||||
if (finalSize >= MaxPacketSize)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Max packet size {MaxPacketSize} exceeded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int copyable = Math.Min(size - index, Math.Min(size, finalSize - _bufferEnd));
|
|
||||||
|
|
||||||
Array.Copy(data, index + offset, _buffer, _bufferEnd, copyable);
|
|
||||||
|
|
||||||
index += copyable;
|
|
||||||
_bufferEnd += copyable;
|
|
||||||
|
|
||||||
if (finalSize == _bufferEnd)
|
|
||||||
{
|
|
||||||
// The full packet has been retrieved. Send it to be decoded.
|
|
||||||
|
|
||||||
byte[] ldnData = new byte[ldnHeader.DataSize];
|
|
||||||
|
|
||||||
Array.Copy(_buffer, _headerSize, ldnData, 0, ldnData.Length);
|
|
||||||
|
|
||||||
DecodeAndHandle(ldnHeader, ldnData);
|
|
||||||
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private (T, byte[]) ParseWithData<T>(byte[] data) where T : struct
|
|
||||||
{
|
|
||||||
T str = default;
|
|
||||||
int size = Marshal.SizeOf(str);
|
|
||||||
|
|
||||||
byte[] remainder = new byte[data.Length - size];
|
|
||||||
|
|
||||||
if (remainder.Length > 0)
|
|
||||||
{
|
|
||||||
Array.Copy(data, size, remainder, 0, remainder.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MemoryMarshal.Read<T>(data), remainder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DecodeAndHandle(LdnHeader header, byte[] data)
|
|
||||||
{
|
|
||||||
switch ((PacketId)header.Type)
|
|
||||||
{
|
|
||||||
// Client Packets.
|
|
||||||
case PacketId.Initialize:
|
|
||||||
{
|
|
||||||
Initialize?.Invoke(header, MemoryMarshal.Read<InitializeMessage>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.Passphrase:
|
|
||||||
{
|
|
||||||
Passphrase?.Invoke(header, MemoryMarshal.Read<PassphraseMessage>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.Connected:
|
|
||||||
{
|
|
||||||
Connected?.Invoke(header, MemoryMarshal.Read<NetworkInfo>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.SyncNetwork:
|
|
||||||
{
|
|
||||||
SyncNetwork?.Invoke(header, MemoryMarshal.Read<NetworkInfo>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ScanReply:
|
|
||||||
{
|
|
||||||
ScanReply?.Invoke(header, MemoryMarshal.Read<NetworkInfo>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case PacketId.ScanReplyEnd:
|
|
||||||
{
|
|
||||||
ScanReplyEnd?.Invoke(header);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.Disconnect:
|
|
||||||
{
|
|
||||||
Disconnected?.Invoke(header, MemoryMarshal.Read<DisconnectMessage>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// External Proxy Packets.
|
|
||||||
case PacketId.ExternalProxy:
|
|
||||||
{
|
|
||||||
ExternalProxy?.Invoke(header, MemoryMarshal.Read<ExternalProxyConfig>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ExternalProxyState:
|
|
||||||
{
|
|
||||||
ExternalProxyState?.Invoke(header, MemoryMarshal.Read<ExternalProxyConnectionState>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ExternalProxyToken:
|
|
||||||
{
|
|
||||||
ExternalProxyToken?.Invoke(header, MemoryMarshal.Read<ExternalProxyToken>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server Packets.
|
|
||||||
case PacketId.CreateAccessPoint:
|
|
||||||
{
|
|
||||||
(CreateAccessPointRequest packet, byte[] extraData) = ParseWithData<CreateAccessPointRequest>(data);
|
|
||||||
CreateAccessPoint?.Invoke(header, packet, extraData);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.CreateAccessPointPrivate:
|
|
||||||
{
|
|
||||||
(CreateAccessPointPrivateRequest packet, byte[] extraData) = ParseWithData<CreateAccessPointPrivateRequest>(data);
|
|
||||||
CreateAccessPointPrivate?.Invoke(header, packet, extraData);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.Reject:
|
|
||||||
{
|
|
||||||
Reject?.Invoke(header, MemoryMarshal.Read<RejectRequest>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.RejectReply:
|
|
||||||
{
|
|
||||||
RejectReply?.Invoke(header);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.SetAcceptPolicy:
|
|
||||||
{
|
|
||||||
SetAcceptPolicy?.Invoke(header, MemoryMarshal.Read<SetAcceptPolicyRequest>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.SetAdvertiseData:
|
|
||||||
{
|
|
||||||
SetAdvertiseData?.Invoke(header, data);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.Connect:
|
|
||||||
{
|
|
||||||
Connect?.Invoke(header, MemoryMarshal.Read<ConnectRequest>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ConnectPrivate:
|
|
||||||
{
|
|
||||||
ConnectPrivate?.Invoke(header, MemoryMarshal.Read<ConnectPrivateRequest>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.Scan:
|
|
||||||
{
|
|
||||||
Scan?.Invoke(header, MemoryMarshal.Read<ScanFilter>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy Packets
|
|
||||||
case PacketId.ProxyConfig:
|
|
||||||
{
|
|
||||||
ProxyConfig?.Invoke(header, MemoryMarshal.Read<ProxyConfig>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ProxyConnect:
|
|
||||||
{
|
|
||||||
ProxyConnect?.Invoke(header, MemoryMarshal.Read<ProxyConnectRequest>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ProxyConnectReply:
|
|
||||||
{
|
|
||||||
ProxyConnectReply?.Invoke(header, MemoryMarshal.Read<ProxyConnectResponse>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ProxyData:
|
|
||||||
{
|
|
||||||
(ProxyDataHeader packet, byte[] extraData) = ParseWithData<ProxyDataHeader>(data);
|
|
||||||
|
|
||||||
ProxyData?.Invoke(header, packet, extraData);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.ProxyDisconnect:
|
|
||||||
{
|
|
||||||
ProxyDisconnect?.Invoke(header, MemoryMarshal.Read<ProxyDisconnectMessage>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle Packets.
|
|
||||||
case PacketId.Ping:
|
|
||||||
{
|
|
||||||
Ping?.Invoke(header, MemoryMarshal.Read<PingMessage>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketId.NetworkError:
|
|
||||||
{
|
|
||||||
NetworkError?.Invoke(header, MemoryMarshal.Read<NetworkErrorMessage>(data));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LdnHeader GetHeader(PacketId type, int dataSize)
|
|
||||||
{
|
|
||||||
return new LdnHeader()
|
|
||||||
{
|
|
||||||
Magic = Magic,
|
|
||||||
Version = CurrentProtocolVersion,
|
|
||||||
Type = (byte)type,
|
|
||||||
DataSize = dataSize
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Encode(PacketId type)
|
|
||||||
{
|
|
||||||
LdnHeader header = GetHeader(type, 0);
|
|
||||||
|
|
||||||
return SpanHelpers.AsSpan<LdnHeader, byte>(ref header).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Encode(PacketId type, byte[] data)
|
|
||||||
{
|
|
||||||
LdnHeader header = GetHeader(type, data.Length);
|
|
||||||
|
|
||||||
byte[] result = SpanHelpers.AsSpan<LdnHeader, byte>(ref header).ToArray();
|
|
||||||
|
|
||||||
Array.Resize(ref result, result.Length + data.Length);
|
|
||||||
Array.Copy(data, 0, result, Marshal.SizeOf<LdnHeader>(), data.Length);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Encode<T>(PacketId type, T packet) where T : unmanaged
|
|
||||||
{
|
|
||||||
byte[] packetData = SpanHelpers.AsSpan<T, byte>(ref packet).ToArray();
|
|
||||||
|
|
||||||
LdnHeader header = GetHeader(type, packetData.Length);
|
|
||||||
|
|
||||||
byte[] result = SpanHelpers.AsSpan<LdnHeader, byte>(ref header).ToArray();
|
|
||||||
|
|
||||||
Array.Resize(ref result, result.Length + packetData.Length);
|
|
||||||
Array.Copy(packetData, 0, result, Marshal.SizeOf<LdnHeader>(), packetData.Length);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Encode<T>(PacketId type, T packet, byte[] data) where T : unmanaged
|
|
||||||
{
|
|
||||||
byte[] packetData = SpanHelpers.AsSpan<T, byte>(ref packet).ToArray();
|
|
||||||
|
|
||||||
LdnHeader header = GetHeader(type, packetData.Length + data.Length);
|
|
||||||
|
|
||||||
byte[] result = SpanHelpers.AsSpan<LdnHeader, byte>(ref header).ToArray();
|
|
||||||
|
|
||||||
Array.Resize(ref result, result.Length + packetData.Length + data.Length);
|
|
||||||
Array.Copy(packetData, 0, result, Marshal.SizeOf<LdnHeader>(), packetData.Length);
|
|
||||||
Array.Copy(data, 0, result, Marshal.SizeOf<LdnHeader>() + packetData.Length, data.Length);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x4)]
|
|
||||||
struct DisconnectMessage
|
|
||||||
{
|
|
||||||
public uint DisconnectIP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sent by the server to point a client towards an external server being used as a proxy.
|
|
||||||
/// The client then forwards this to the external proxy after connecting, to verify the connection worked.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x26, Pack = 1)]
|
|
||||||
struct ExternalProxyConfig
|
|
||||||
{
|
|
||||||
public Array16<byte> ProxyIp;
|
|
||||||
public AddressFamily AddressFamily;
|
|
||||||
public ushort ProxyPort;
|
|
||||||
public Array16<byte> Token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a change in connection state for the given client.
|
|
||||||
/// Is sent to notify the master server when connection is first established.
|
|
||||||
/// Can be sent by the external proxy to the master server to notify it of a proxy disconnect.
|
|
||||||
/// Can be sent by the master server to notify the external proxy of a user leaving a room.
|
|
||||||
/// Both will result in a force kick.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 4)]
|
|
||||||
struct ExternalProxyConnectionState
|
|
||||||
{
|
|
||||||
public uint IpAddress;
|
|
||||||
public bool Connected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sent by the master server to an external proxy to tell them someone is going to connect.
|
|
||||||
/// This drives authentication, and lets the proxy know what virtual IP to give to each joiner,
|
|
||||||
/// as these are managed by the master server.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
|
|
||||||
struct ExternalProxyToken
|
|
||||||
{
|
|
||||||
public uint VirtualIp;
|
|
||||||
public Array16<byte> Token;
|
|
||||||
public Array16<byte> PhysicalIp;
|
|
||||||
public AddressFamily AddressFamily;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This message is first sent by the client to identify themselves.
|
|
||||||
/// If the server has a token+mac combo that matches the submission, then they are returned their new ID and mac address. (the mac is also reassigned to the new id)
|
|
||||||
/// Otherwise, they are returned a random mac address.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x16)]
|
|
||||||
struct InitializeMessage
|
|
||||||
{
|
|
||||||
// All 0 if we don't have an ID yet.
|
|
||||||
public Array16<byte> Id;
|
|
||||||
|
|
||||||
// All 0 if we don't have a mac yet.
|
|
||||||
public Array6<byte> MacAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0xA)]
|
|
||||||
struct LdnHeader
|
|
||||||
{
|
|
||||||
public uint Magic;
|
|
||||||
public byte Type;
|
|
||||||
public byte Version;
|
|
||||||
public int DataSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
enum PacketId
|
|
||||||
{
|
|
||||||
Initialize,
|
|
||||||
Passphrase,
|
|
||||||
|
|
||||||
CreateAccessPoint,
|
|
||||||
CreateAccessPointPrivate,
|
|
||||||
ExternalProxy,
|
|
||||||
ExternalProxyToken,
|
|
||||||
ExternalProxyState,
|
|
||||||
SyncNetwork,
|
|
||||||
Reject,
|
|
||||||
RejectReply,
|
|
||||||
Scan,
|
|
||||||
ScanReply,
|
|
||||||
ScanReplyEnd,
|
|
||||||
Connect,
|
|
||||||
ConnectPrivate,
|
|
||||||
Connected,
|
|
||||||
Disconnect,
|
|
||||||
|
|
||||||
ProxyConfig,
|
|
||||||
ProxyConnect,
|
|
||||||
ProxyConnectReply,
|
|
||||||
ProxyData,
|
|
||||||
ProxyDisconnect,
|
|
||||||
|
|
||||||
SetAcceptPolicy,
|
|
||||||
SetAdvertiseData,
|
|
||||||
|
|
||||||
Ping = 254,
|
|
||||||
NetworkError = 255
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x80)]
|
|
||||||
struct PassphraseMessage
|
|
||||||
{
|
|
||||||
public Array128<byte> Passphrase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x2)]
|
|
||||||
struct PingMessage
|
|
||||||
{
|
|
||||||
public byte Requester;
|
|
||||||
public byte Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
|
||||||
struct ProxyConnectRequest
|
|
||||||
{
|
|
||||||
public ProxyInfo Info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
|
||||||
struct ProxyConnectResponse
|
|
||||||
{
|
|
||||||
public ProxyInfo Info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents data sent over a transport layer.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x14)]
|
|
||||||
struct ProxyDataHeader
|
|
||||||
{
|
|
||||||
public ProxyInfo Info;
|
|
||||||
public uint DataLength; // Followed by the data with the specified byte length.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
class ProxyDataPacket
|
|
||||||
{
|
|
||||||
public ProxyDataHeader Header;
|
|
||||||
public byte[] Data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x14)]
|
|
||||||
struct ProxyDisconnectMessage
|
|
||||||
{
|
|
||||||
public ProxyInfo Info;
|
|
||||||
public int DisconnectReason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Information included in all proxied communication.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 1)]
|
|
||||||
struct ProxyInfo
|
|
||||||
{
|
|
||||||
public uint SourceIpV4;
|
|
||||||
public ushort SourcePort;
|
|
||||||
|
|
||||||
public uint DestIpV4;
|
|
||||||
public ushort DestPort;
|
|
||||||
|
|
||||||
public ProtocolType Protocol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
|
|
||||||
struct RejectRequest
|
|
||||||
{
|
|
||||||
public uint NodeId;
|
|
||||||
public DisconnectReason DisconnectReason;
|
|
||||||
|
|
||||||
public RejectRequest(DisconnectReason disconnectReason, uint nodeId)
|
|
||||||
{
|
|
||||||
DisconnectReason = disconnectReason;
|
|
||||||
NodeId = nodeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)]
|
|
||||||
struct RyuNetworkConfig
|
|
||||||
{
|
|
||||||
public Array16<byte> GameVersion;
|
|
||||||
|
|
||||||
// PrivateIp is included for external proxies for the case where a client attempts to join from
|
|
||||||
// their own LAN. UPnP forwarding can fail when connecting devices on the same network over the public IP,
|
|
||||||
// so if their public IP is identical, the internal address should be sent instead.
|
|
||||||
|
|
||||||
// The fields below are 0 if not hosting a p2p proxy.
|
|
||||||
|
|
||||||
public Array16<byte> PrivateIp;
|
|
||||||
public AddressFamily AddressFamily;
|
|
||||||
public ushort ExternalProxyPort;
|
|
||||||
public ushort InternalProxyPort;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x1, Pack = 1)]
|
|
||||||
struct SetAcceptPolicyRequest
|
|
||||||
{
|
|
||||||
public AcceptPolicy StationAcceptPolicy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public bool Connected { get; private set; }
|
public bool Connected { get; private set; }
|
||||||
|
|
||||||
public ProxyConfig Config => _parent.NetworkClient.Config;
|
|
||||||
|
|
||||||
public Station(IUserLocalCommunicationService parent)
|
public Station(IUserLocalCommunicationService parent)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
@@ -50,12 +48,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_parent.NetworkClient != null)
|
_parent.NetworkClient.DisconnectNetwork();
|
||||||
{
|
|
||||||
_parent.NetworkClient.DisconnectNetwork();
|
|
||||||
|
|
||||||
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResultCode NetworkErrorToResult(NetworkError error)
|
private ResultCode NetworkErrorToResult(NetworkError error)
|
||||||
|
|||||||
-3
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||||
@@ -15,7 +14,5 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
|||||||
public UserConfig UserConfig;
|
public UserConfig UserConfig;
|
||||||
public NetworkConfig NetworkConfig;
|
public NetworkConfig NetworkConfig;
|
||||||
public AddressList AddressList;
|
public AddressList AddressList;
|
||||||
|
|
||||||
public RyuNetworkConfig RyuNetworkConfig;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
||||||
@@ -7,13 +6,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Advertise data is appended separately (remaining data in the buffer).
|
/// Advertise data is appended separately (remaining data in the buffer).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0xBC, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x94, CharSet = CharSet.Ansi)]
|
||||||
struct CreateAccessPointRequest
|
struct CreateAccessPointRequest
|
||||||
{
|
{
|
||||||
public SecurityConfig SecurityConfig;
|
public SecurityConfig SecurityConfig;
|
||||||
public UserConfig UserConfig;
|
public UserConfig UserConfig;
|
||||||
public NetworkConfig NetworkConfig;
|
public NetworkConfig NetworkConfig;
|
||||||
|
|
||||||
public RyuNetworkConfig RyuNetworkConfig;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
|
|
||||||
struct ProxyConfig
|
|
||||||
{
|
|
||||||
public uint ProxyIp;
|
|
||||||
public uint ProxySubnetMask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -95,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
|
ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol)
|
||||||
{
|
{
|
||||||
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
|
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
|
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -22,21 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; }
|
public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; }
|
||||||
|
|
||||||
public nint Handle => IntPtr.Zero;
|
public nint Handle => Socket.Handle;
|
||||||
|
|
||||||
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;
|
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;
|
||||||
|
|
||||||
public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint;
|
public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint;
|
||||||
|
|
||||||
public ISocketImpl Socket { get; }
|
public Socket Socket { get; }
|
||||||
|
|
||||||
public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, string lanInterfaceId)
|
public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
|
||||||
{
|
{
|
||||||
Socket = SocketHelpers.CreateSocket(addressFamily, socketType, protocolType, lanInterfaceId);
|
Socket = new Socket(addressFamily, socketType, protocolType);
|
||||||
Refcount = 1;
|
Refcount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ManagedSocket(ISocketImpl socket)
|
private ManagedSocket(Socket socket)
|
||||||
{
|
{
|
||||||
Socket = socket;
|
Socket = socket;
|
||||||
Refcount = 1;
|
Refcount = 1;
|
||||||
@@ -186,8 +185,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasEmittedBlockingWarning = false;
|
|
||||||
|
|
||||||
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
|
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
|
||||||
{
|
{
|
||||||
LinuxError result;
|
LinuxError result;
|
||||||
@@ -202,12 +199,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
shouldBlockAfterOperation = true;
|
shouldBlockAfterOperation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Blocking && !hasEmittedBlockingWarning)
|
|
||||||
{
|
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
|
||||||
hasEmittedBlockingWarning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
|
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
|
||||||
|
|
||||||
result = LinuxError.SUCCESS;
|
result = LinuxError.SUCCESS;
|
||||||
@@ -245,12 +236,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
shouldBlockAfterOperation = true;
|
shouldBlockAfterOperation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Blocking && !hasEmittedBlockingWarning)
|
|
||||||
{
|
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
|
||||||
hasEmittedBlockingWarning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Socket.IsBound)
|
if (!Socket.IsBound)
|
||||||
{
|
{
|
||||||
receiveSize = -1;
|
receiveSize = -1;
|
||||||
@@ -328,7 +313,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}");
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}");
|
||||||
optionValue.Clear();
|
optionValue.Clear();
|
||||||
|
|
||||||
return LinuxError.EOPNOTSUPP;
|
return LinuxError.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] tempOptionValue = new byte[optionValue.Length];
|
byte[] tempOptionValue = new byte[optionValue.Length];
|
||||||
@@ -362,7 +347,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
|
||||||
|
|
||||||
return LinuxError.EOPNOTSUPP;
|
return LinuxError.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue);
|
int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue);
|
||||||
@@ -508,7 +493,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int receiveSize = (Socket as DefaultSocket).BaseSocket.Receive(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError);
|
int receiveSize = Socket.Receive(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError);
|
||||||
|
|
||||||
if (receiveSize > 0)
|
if (receiveSize > 0)
|
||||||
{
|
{
|
||||||
@@ -546,7 +531,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int sendSize = (Socket as DefaultSocket).BaseSocket.Send(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError);
|
int sendSize = Socket.Send(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError);
|
||||||
|
|
||||||
if (sendSize > 0)
|
if (sendSize > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
|
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@@ -27,46 +26,45 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount)
|
public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount)
|
||||||
{
|
{
|
||||||
List<ISocketImpl> readEvents = new();
|
List<Socket> readEvents = new();
|
||||||
List<ISocketImpl> writeEvents = new();
|
List<Socket> writeEvents = new();
|
||||||
List<ISocketImpl> errorEvents = new();
|
List<Socket> errorEvents = new();
|
||||||
|
|
||||||
updatedCount = 0;
|
updatedCount = 0;
|
||||||
|
|
||||||
foreach (PollEvent evnt in events)
|
foreach (PollEvent evnt in events)
|
||||||
{
|
{
|
||||||
if (evnt.FileDescriptor is ManagedSocket ms)
|
ManagedSocket socket = (ManagedSocket)evnt.FileDescriptor;
|
||||||
|
|
||||||
|
bool isValidEvent = evnt.Data.InputEvents == 0;
|
||||||
|
|
||||||
|
errorEvents.Add(socket.Socket);
|
||||||
|
|
||||||
|
if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0)
|
||||||
{
|
{
|
||||||
bool isValidEvent = evnt.Data.InputEvents == 0;
|
readEvents.Add(socket.Socket);
|
||||||
|
|
||||||
errorEvents.Add(ms.Socket);
|
isValidEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0)
|
if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0)
|
||||||
{
|
{
|
||||||
readEvents.Add(ms.Socket);
|
readEvents.Add(socket.Socket);
|
||||||
|
|
||||||
isValidEvent = true;
|
isValidEvent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0)
|
if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0)
|
||||||
{
|
{
|
||||||
readEvents.Add(ms.Socket);
|
writeEvents.Add(socket.Socket);
|
||||||
|
|
||||||
isValidEvent = true;
|
isValidEvent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0)
|
if (!isValidEvent)
|
||||||
{
|
{
|
||||||
writeEvents.Add(ms.Socket);
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}");
|
||||||
|
return LinuxError.EINVAL;
|
||||||
isValidEvent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidEvent)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}");
|
|
||||||
return LinuxError.EINVAL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +72,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{
|
{
|
||||||
int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000;
|
int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000;
|
||||||
|
|
||||||
SocketHelpers.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds);
|
Socket.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds);
|
||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
@@ -83,37 +81,34 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
foreach (PollEvent evnt in events)
|
foreach (PollEvent evnt in events)
|
||||||
{
|
{
|
||||||
if (evnt.FileDescriptor is ManagedSocket ms)
|
Socket socket = ((ManagedSocket)evnt.FileDescriptor).Socket;
|
||||||
|
|
||||||
|
PollEventTypeMask outputEvents = evnt.Data.OutputEvents & ~evnt.Data.InputEvents;
|
||||||
|
|
||||||
|
if (errorEvents.Contains(socket))
|
||||||
{
|
{
|
||||||
ISocketImpl socket = ms.Socket;
|
outputEvents |= PollEventTypeMask.Error;
|
||||||
|
|
||||||
PollEventTypeMask outputEvents = evnt.Data.OutputEvents & ~evnt.Data.InputEvents;
|
if (!socket.Connected || !socket.IsBound)
|
||||||
|
|
||||||
if (errorEvents.Contains(ms.Socket))
|
|
||||||
{
|
{
|
||||||
outputEvents |= PollEventTypeMask.Error;
|
outputEvents |= PollEventTypeMask.Disconnected;
|
||||||
|
|
||||||
if (!socket.Connected || !socket.IsBound)
|
|
||||||
{
|
|
||||||
outputEvents |= PollEventTypeMask.Disconnected;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readEvents.Contains(ms.Socket))
|
|
||||||
{
|
|
||||||
if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0)
|
|
||||||
{
|
|
||||||
outputEvents |= PollEventTypeMask.Input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (writeEvents.Contains(ms.Socket))
|
|
||||||
{
|
|
||||||
outputEvents |= PollEventTypeMask.Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
evnt.Data.OutputEvents = outputEvents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (readEvents.Contains(socket))
|
||||||
|
{
|
||||||
|
if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0)
|
||||||
|
{
|
||||||
|
outputEvents |= PollEventTypeMask.Input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writeEvents.Contains(socket))
|
||||||
|
{
|
||||||
|
outputEvents |= PollEventTypeMask.Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
evnt.Data.OutputEvents = outputEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count;
|
updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count;
|
||||||
@@ -123,55 +118,53 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount)
|
public LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount)
|
||||||
{
|
{
|
||||||
List<ISocketImpl> readEvents = new();
|
List<Socket> readEvents = new();
|
||||||
List<ISocketImpl> writeEvents = new();
|
List<Socket> writeEvents = new();
|
||||||
List<ISocketImpl> errorEvents = new();
|
List<Socket> errorEvents = new();
|
||||||
|
|
||||||
updatedCount = 0;
|
updatedCount = 0;
|
||||||
|
|
||||||
foreach (PollEvent pollEvent in events)
|
foreach (PollEvent pollEvent in events)
|
||||||
{
|
{
|
||||||
if (pollEvent.FileDescriptor is ManagedSocket ms)
|
ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor;
|
||||||
|
|
||||||
|
if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Input))
|
||||||
{
|
{
|
||||||
if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Input))
|
readEvents.Add(socket.Socket);
|
||||||
{
|
}
|
||||||
readEvents.Add(ms.Socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Output))
|
if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Output))
|
||||||
{
|
{
|
||||||
writeEvents.Add(ms.Socket);
|
writeEvents.Add(socket.Socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Error))
|
if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Error))
|
||||||
{
|
{
|
||||||
errorEvents.Add(ms.Socket);
|
errorEvents.Add(socket.Socket);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketHelpers.Select(readEvents, writeEvents, errorEvents, timeout);
|
Socket.Select(readEvents, writeEvents, errorEvents, timeout);
|
||||||
|
|
||||||
updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count;
|
updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count;
|
||||||
|
|
||||||
foreach (PollEvent pollEvent in events)
|
foreach (PollEvent pollEvent in events)
|
||||||
{
|
{
|
||||||
if (pollEvent.FileDescriptor is ManagedSocket ms)
|
ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor;
|
||||||
|
|
||||||
|
if (readEvents.Contains(socket.Socket))
|
||||||
{
|
{
|
||||||
if (readEvents.Contains(ms.Socket))
|
pollEvent.Data.OutputEvents |= PollEventTypeMask.Input;
|
||||||
{
|
}
|
||||||
pollEvent.Data.OutputEvents |= PollEventTypeMask.Input;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (writeEvents.Contains(ms.Socket))
|
if (writeEvents.Contains(socket.Socket))
|
||||||
{
|
{
|
||||||
pollEvent.Data.OutputEvents |= PollEventTypeMask.Output;
|
pollEvent.Data.OutputEvents |= PollEventTypeMask.Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorEvents.Contains(ms.Socket))
|
if (errorEvents.Contains(socket.Socket))
|
||||||
{
|
{
|
||||||
pollEvent.Data.OutputEvents |= PollEventTypeMask.Error;
|
pollEvent.Data.OutputEvents |= PollEventTypeMask.Error;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.NetworkInformation;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
|
|
||||||
{
|
|
||||||
class DefaultSocket : ISocketImpl
|
|
||||||
{
|
|
||||||
public Socket BaseSocket { get; }
|
|
||||||
|
|
||||||
public EndPoint RemoteEndPoint => BaseSocket.RemoteEndPoint;
|
|
||||||
|
|
||||||
public EndPoint LocalEndPoint => BaseSocket.LocalEndPoint;
|
|
||||||
|
|
||||||
public bool Connected => BaseSocket.Connected;
|
|
||||||
|
|
||||||
public bool IsBound => BaseSocket.IsBound;
|
|
||||||
|
|
||||||
public AddressFamily AddressFamily => BaseSocket.AddressFamily;
|
|
||||||
|
|
||||||
public SocketType SocketType => BaseSocket.SocketType;
|
|
||||||
|
|
||||||
public ProtocolType ProtocolType => BaseSocket.ProtocolType;
|
|
||||||
|
|
||||||
public bool Blocking { get => BaseSocket.Blocking; set => BaseSocket.Blocking = value; }
|
|
||||||
|
|
||||||
public int Available => BaseSocket.Available;
|
|
||||||
|
|
||||||
private readonly string _lanInterfaceId;
|
|
||||||
|
|
||||||
public DefaultSocket(Socket baseSocket, string lanInterfaceId)
|
|
||||||
{
|
|
||||||
_lanInterfaceId = lanInterfaceId;
|
|
||||||
|
|
||||||
BaseSocket = baseSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultSocket(AddressFamily domain, SocketType type, ProtocolType protocol, string lanInterfaceId)
|
|
||||||
{
|
|
||||||
_lanInterfaceId = lanInterfaceId;
|
|
||||||
|
|
||||||
BaseSocket = new Socket(domain, type, protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureNetworkInterfaceBound()
|
|
||||||
{
|
|
||||||
if (_lanInterfaceId != "0" && !BaseSocket.IsBound)
|
|
||||||
{
|
|
||||||
(_, UnicastIPAddressInformation ipInfo) = NetworkHelpers.GetLocalInterface(_lanInterfaceId);
|
|
||||||
|
|
||||||
BaseSocket.Bind(new IPEndPoint(ipInfo.Address, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISocketImpl Accept()
|
|
||||||
{
|
|
||||||
return new DefaultSocket(BaseSocket.Accept(), _lanInterfaceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Bind(EndPoint localEP)
|
|
||||||
{
|
|
||||||
// NOTE: The guest is able to receive on 0.0.0.0 without it being limited to the chosen network interface.
|
|
||||||
// This is because it must get loopback traffic as well. This could allow other network traffic to leak in.
|
|
||||||
|
|
||||||
BaseSocket.Bind(localEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
BaseSocket.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Connect(EndPoint remoteEP)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
BaseSocket.Connect(remoteEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disconnect(bool reuseSocket)
|
|
||||||
{
|
|
||||||
BaseSocket.Disconnect(reuseSocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
|
|
||||||
{
|
|
||||||
BaseSocket.GetSocketOption(optionLevel, optionName, optionValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Listen(int backlog)
|
|
||||||
{
|
|
||||||
BaseSocket.Listen(backlog);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Receive(Span<byte> buffer)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.Receive(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Receive(Span<byte> buffer, SocketFlags flags)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.Receive(buffer, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Receive(Span<byte> buffer, SocketFlags flags, out SocketError socketError)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.Receive(buffer, flags, out socketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ReceiveFrom(Span<byte> buffer, SocketFlags flags, ref EndPoint remoteEP)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.ReceiveFrom(buffer, flags, ref remoteEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Send(ReadOnlySpan<byte> buffer)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.Send(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Send(ReadOnlySpan<byte> buffer, SocketFlags flags)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.Send(buffer, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Send(ReadOnlySpan<byte> buffer, SocketFlags flags, out SocketError socketError)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.Send(buffer, flags, out socketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, EndPoint remoteEP)
|
|
||||||
{
|
|
||||||
EnsureNetworkInterfaceBound();
|
|
||||||
|
|
||||||
return BaseSocket.SendTo(buffer, flags, remoteEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Poll(int microSeconds, SelectMode mode)
|
|
||||||
{
|
|
||||||
return BaseSocket.Poll(microSeconds, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
|
|
||||||
{
|
|
||||||
BaseSocket.SetSocketOption(optionLevel, optionName, optionValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, object optionValue)
|
|
||||||
{
|
|
||||||
BaseSocket.SetSocketOption(optionLevel, optionName, optionValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Shutdown(SocketShutdown how)
|
|
||||||
{
|
|
||||||
BaseSocket.Shutdown(how);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
BaseSocket.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
|
|
||||||
{
|
|
||||||
interface ISocketImpl : IDisposable
|
|
||||||
{
|
|
||||||
EndPoint RemoteEndPoint { get; }
|
|
||||||
EndPoint LocalEndPoint { get; }
|
|
||||||
bool Connected { get; }
|
|
||||||
bool IsBound { get; }
|
|
||||||
|
|
||||||
AddressFamily AddressFamily { get; }
|
|
||||||
SocketType SocketType { get; }
|
|
||||||
ProtocolType ProtocolType { get; }
|
|
||||||
|
|
||||||
bool Blocking { get; set; }
|
|
||||||
int Available { get; }
|
|
||||||
|
|
||||||
int Receive(Span<byte> buffer);
|
|
||||||
int Receive(Span<byte> buffer, SocketFlags flags);
|
|
||||||
int Receive(Span<byte> buffer, SocketFlags flags, out SocketError socketError);
|
|
||||||
int ReceiveFrom(Span<byte> buffer, SocketFlags flags, ref EndPoint remoteEP);
|
|
||||||
|
|
||||||
int Send(ReadOnlySpan<byte> buffer);
|
|
||||||
int Send(ReadOnlySpan<byte> buffer, SocketFlags flags);
|
|
||||||
int Send(ReadOnlySpan<byte> buffer, SocketFlags flags, out SocketError socketError);
|
|
||||||
int SendTo(ReadOnlySpan<byte> buffer, SocketFlags flags, EndPoint remoteEP);
|
|
||||||
|
|
||||||
bool Poll(int microSeconds, SelectMode mode);
|
|
||||||
|
|
||||||
ISocketImpl Accept();
|
|
||||||
|
|
||||||
void Bind(EndPoint localEP);
|
|
||||||
void Connect(EndPoint remoteEP);
|
|
||||||
void Listen(int backlog);
|
|
||||||
|
|
||||||
void GetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue);
|
|
||||||
void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue);
|
|
||||||
void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, object optionValue);
|
|
||||||
|
|
||||||
void Shutdown(SocketShutdown how);
|
|
||||||
void Disconnect(bool reuseSocket);
|
|
||||||
void Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
|
|
||||||
{
|
|
||||||
static class SocketHelpers
|
|
||||||
{
|
|
||||||
private static LdnProxy _proxy;
|
|
||||||
|
|
||||||
public static void Select(List<ISocketImpl> readEvents, List<ISocketImpl> writeEvents, List<ISocketImpl> errorEvents, int timeout)
|
|
||||||
{
|
|
||||||
var readDefault = readEvents.Select(x => (x as DefaultSocket)?.BaseSocket).Where(x => x != null).ToList();
|
|
||||||
var writeDefault = writeEvents.Select(x => (x as DefaultSocket)?.BaseSocket).Where(x => x != null).ToList();
|
|
||||||
var errorDefault = errorEvents.Select(x => (x as DefaultSocket)?.BaseSocket).Where(x => x != null).ToList();
|
|
||||||
|
|
||||||
if (readDefault.Count != 0 || writeDefault.Count != 0 || errorDefault.Count != 0)
|
|
||||||
{
|
|
||||||
Socket.Select(readDefault, writeDefault, errorDefault, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FilterSockets(List<ISocketImpl> removeFrom, List<Socket> selectedSockets, Func<LdnProxySocket, bool> ldnCheck)
|
|
||||||
{
|
|
||||||
removeFrom.RemoveAll(socket =>
|
|
||||||
{
|
|
||||||
switch (socket)
|
|
||||||
{
|
|
||||||
case DefaultSocket dsocket:
|
|
||||||
return !selectedSockets.Contains(dsocket.BaseSocket);
|
|
||||||
case LdnProxySocket psocket:
|
|
||||||
return !ldnCheck(psocket);
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
FilterSockets(readEvents, readDefault, (socket) => socket.Readable);
|
|
||||||
FilterSockets(writeEvents, writeDefault, (socket) => socket.Writable);
|
|
||||||
FilterSockets(errorEvents, errorDefault, (socket) => socket.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RegisterProxy(LdnProxy proxy)
|
|
||||||
{
|
|
||||||
if (_proxy != null)
|
|
||||||
{
|
|
||||||
UnregisterProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
_proxy = proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnregisterProxy()
|
|
||||||
{
|
|
||||||
_proxy?.Dispose();
|
|
||||||
_proxy = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ISocketImpl CreateSocket(AddressFamily domain, SocketType type, ProtocolType protocol, string lanInterfaceId)
|
|
||||||
{
|
|
||||||
if (_proxy != null)
|
|
||||||
{
|
|
||||||
if (_proxy.Supported(domain, type, protocol))
|
|
||||||
{
|
|
||||||
return new LdnProxySocket(domain, type, protocol, _proxy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DefaultSocket(domain, type, protocol, lanInterfaceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -292,7 +292,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
|
|||||||
{
|
{
|
||||||
string host = MemoryHelper.ReadAsciiString(context.Memory, inputBufferPosition, (int)inputBufferSize);
|
string host = MemoryHelper.ReadAsciiString(context.Memory, inputBufferPosition, (int)inputBufferSize);
|
||||||
|
|
||||||
if (host != "localhost" && !context.Device.Configuration.EnableInternetAccess)
|
if (!context.Device.Configuration.EnableInternetAccess)
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}");
|
Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd;
|
using Ryujinx.HLE.HOS.Services.Sockets.Bsd;
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
|
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Ssl.Types;
|
using Ryujinx.HLE.HOS.Services.Ssl.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -117,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
|
|||||||
public ResultCode Handshake(string hostName)
|
public ResultCode Handshake(string hostName)
|
||||||
{
|
{
|
||||||
StartSslOperation();
|
StartSslOperation();
|
||||||
_stream = new SslStream(new NetworkStream(((DefaultSocket)((ManagedSocket)Socket).Socket).BaseSocket, false), false, null, null);
|
_stream = new SslStream(new NetworkStream(((ManagedSocket)Socket).Socket, false), false, null, null);
|
||||||
hostName = RetrieveHostName(hostName);
|
hostName = RetrieveHostName(hostName);
|
||||||
_stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false);
|
_stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false);
|
||||||
EndSslOperation();
|
EndSslOperation();
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: LibHac npdm currently doesn't support version field.
|
// TODO: LibHac npdm currently doesn't support version field.
|
||||||
string version = ProgramId > 0x0100000000007FFF
|
string version = ProgramId > 0x0100000000007FFF
|
||||||
? DisplayVersion
|
? DisplayVersion
|
||||||
: device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?";
|
: device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?";
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
|
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
<PackageReference Include="SkiaSharp" />
|
<PackageReference Include="SkiaSharp" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
|
||||||
<PackageReference Include="NetCoreServer" />
|
<PackageReference Include="NetCoreServer" />
|
||||||
<PackageReference Include="Open.NAT.Core" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -577,10 +577,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
options.AudioVolume,
|
options.AudioVolume,
|
||||||
options.UseHypervisor ?? true,
|
options.UseHypervisor ?? true,
|
||||||
options.MultiplayerLanInterfaceId,
|
options.MultiplayerLanInterfaceId,
|
||||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
|
Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
|
||||||
false,
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
|
|
||||||
return new Switch(configuration);
|
return new Switch(configuration);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ namespace Ryujinx.UI.App.Common
|
|||||||
public ulong Id { get; set; }
|
public ulong Id { get; set; }
|
||||||
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 int PlayerCount { get; set; }
|
|
||||||
public int GameCount { get; set; }
|
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
public string FileExtension { get; set; }
|
public string FileExtension { get; set; }
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using LibHac.Tools.Fs;
|
|||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Multiplayer;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
@@ -28,12 +27,10 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using ContentType = LibHac.Ncm.ContentType;
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
@@ -44,10 +41,8 @@ namespace Ryujinx.UI.App.Common
|
|||||||
{
|
{
|
||||||
public class ApplicationLibrary
|
public class ApplicationLibrary
|
||||||
{
|
{
|
||||||
public static string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
|
|
||||||
public Language DesiredLanguage { get; set; }
|
public Language DesiredLanguage { get; set; }
|
||||||
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||||
public event EventHandler<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
|
|
||||||
|
|
||||||
public readonly IObservableCache<ApplicationData, ulong> Applications;
|
public readonly IObservableCache<ApplicationData, ulong> Applications;
|
||||||
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
|
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
|
||||||
@@ -67,7 +62,6 @@ namespace Ryujinx.UI.App.Common
|
|||||||
private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
|
private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
|
||||||
|
|
||||||
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
private static readonly LdnGameDataSerializerContext _ldnDataSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel)
|
public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel)
|
||||||
{
|
{
|
||||||
@@ -693,7 +687,7 @@ namespace Ryujinx.UI.App.Common
|
|||||||
(Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.UI.ShownFileTypes.PFS0) ||
|
(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 ".xci" && ConfigurationState.Instance.UI.ShownFileTypes.XCI) ||
|
||||||
(Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.UI.ShownFileTypes.NCA) ||
|
(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)
|
(Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.UI.ShownFileTypes.NSO)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -725,7 +719,6 @@ namespace Ryujinx.UI.App.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Loops through applications list, creating a struct and then firing an event containing the struct for each application
|
// Loops through applications list, creating a struct and then firing an event containing the struct for each application
|
||||||
foreach (string applicationPath in applicationPaths)
|
foreach (string applicationPath in applicationPaths)
|
||||||
{
|
{
|
||||||
@@ -782,46 +775,6 @@ 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.
|
// Replace the currently stored DLC state for the game with the provided DLC state.
|
||||||
public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
|
public void SaveDownloadableContentsForGame(ApplicationData application, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.UI.App.Common
|
|
||||||
{
|
|
||||||
public class LdnGameDataReceivedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public IEnumerable<LdnGameData> LdnData { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.UI.App.Common
|
|
||||||
{
|
|
||||||
[JsonSerializable(typeof(IEnumerable<LdnGameData>))]
|
|
||||||
internal partial class LdnGameDataSerializerContext : JsonSerializerContext
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -392,21 +392,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string MultiplayerLanInterfaceId { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Uses Hypervisor over JIT if available
|
/// Uses Hypervisor over JIT if available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
@@ -703,9 +703,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
|
|
||||||
Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
|
Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
|
||||||
Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode;
|
Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode;
|
||||||
Multiplayer.DisableP2p.Value = configurationFileFormat.MultiplayerDisableP2p;
|
|
||||||
Multiplayer.LdnPassphrase.Value = configurationFileFormat.MultiplayerLdnPassphrase;
|
|
||||||
Multiplayer.LdnServer.Value = configurationFileFormat.LdnServer;
|
|
||||||
|
|
||||||
if (configurationFileUpdated)
|
if (configurationFileUpdated)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
{
|
{
|
||||||
public partial class ConfigurationState
|
public partial class ConfigurationState
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UI configuration section
|
/// UI configuration section
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UISection
|
public class UISection
|
||||||
@@ -25,7 +25,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
public ReactiveObject<bool> AppColumn { get; private set; }
|
public ReactiveObject<bool> AppColumn { get; private set; }
|
||||||
public ReactiveObject<bool> DevColumn { get; private set; }
|
public ReactiveObject<bool> DevColumn { get; private set; }
|
||||||
public ReactiveObject<bool> VersionColumn { 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> TimePlayedColumn { get; private set; }
|
||||||
public ReactiveObject<bool> LastPlayedColumn { get; private set; }
|
public ReactiveObject<bool> LastPlayedColumn { get; private set; }
|
||||||
public ReactiveObject<bool> FileExtColumn { get; private set; }
|
public ReactiveObject<bool> FileExtColumn { get; private set; }
|
||||||
@@ -39,7 +38,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
AppColumn = new ReactiveObject<bool>();
|
AppColumn = new ReactiveObject<bool>();
|
||||||
DevColumn = new ReactiveObject<bool>();
|
DevColumn = new ReactiveObject<bool>();
|
||||||
VersionColumn = new ReactiveObject<bool>();
|
VersionColumn = new ReactiveObject<bool>();
|
||||||
LdnInfoColumn = new ReactiveObject<bool>();
|
|
||||||
TimePlayedColumn = new ReactiveObject<bool>();
|
TimePlayedColumn = new ReactiveObject<bool>();
|
||||||
LastPlayedColumn = new ReactiveObject<bool>();
|
LastPlayedColumn = new ReactiveObject<bool>();
|
||||||
FileExtColumn = new ReactiveObject<bool>();
|
FileExtColumn = new ReactiveObject<bool>();
|
||||||
@@ -574,32 +572,11 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<MultiplayerMode> Mode { get; private set; }
|
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()
|
public MultiplayerSection()
|
||||||
{
|
{
|
||||||
LanInterfaceId = new ReactiveObject<string>();
|
LanInterfaceId = new ReactiveObject<string>();
|
||||||
Mode = new ReactiveObject<MultiplayerMode>();
|
Mode = new ReactiveObject<MultiplayerMode>();
|
||||||
Mode.LogChangesToValue(nameof(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)
|
if (Instance != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Configuration is already initialized");
|
throw new InvalidOperationException("Configuration is already initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance = new ConfigurationState();
|
Instance = new ConfigurationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationFileFormat ToFileFormat()
|
public ConfigurationFileFormat ToFileFormat()
|
||||||
{
|
{
|
||||||
ConfigurationFileFormat configurationFile = new()
|
ConfigurationFileFormat configurationFile = new()
|
||||||
@@ -87,7 +87,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
AppColumn = UI.GuiColumns.AppColumn,
|
AppColumn = UI.GuiColumns.AppColumn,
|
||||||
DevColumn = UI.GuiColumns.DevColumn,
|
DevColumn = UI.GuiColumns.DevColumn,
|
||||||
VersionColumn = UI.GuiColumns.VersionColumn,
|
VersionColumn = UI.GuiColumns.VersionColumn,
|
||||||
LdnInfoColumn = UI.GuiColumns.LdnInfoColumn,
|
|
||||||
TimePlayedColumn = UI.GuiColumns.TimePlayedColumn,
|
TimePlayedColumn = UI.GuiColumns.TimePlayedColumn,
|
||||||
LastPlayedColumn = UI.GuiColumns.LastPlayedColumn,
|
LastPlayedColumn = UI.GuiColumns.LastPlayedColumn,
|
||||||
FileExtColumn = UI.GuiColumns.FileExtColumn,
|
FileExtColumn = UI.GuiColumns.FileExtColumn,
|
||||||
@@ -137,9 +136,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
PreferredGpu = Graphics.PreferredGpu,
|
PreferredGpu = Graphics.PreferredGpu,
|
||||||
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
|
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
|
||||||
MultiplayerMode = Multiplayer.Mode,
|
MultiplayerMode = Multiplayer.Mode,
|
||||||
MultiplayerDisableP2p = Multiplayer.DisableP2p,
|
|
||||||
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
|
|
||||||
LdnServer = Multiplayer.LdnServer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return configurationFile;
|
return configurationFile;
|
||||||
@@ -199,9 +195,6 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
System.UseHypervisor.Value = true;
|
System.UseHypervisor.Value = true;
|
||||||
Multiplayer.LanInterfaceId.Value = "0";
|
Multiplayer.LanInterfaceId.Value = "0";
|
||||||
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
||||||
Multiplayer.DisableP2p.Value = false;
|
|
||||||
Multiplayer.LdnPassphrase.Value = "";
|
|
||||||
Multiplayer.LdnServer.Value = "";
|
|
||||||
UI.GuiColumns.FavColumn.Value = true;
|
UI.GuiColumns.FavColumn.Value = true;
|
||||||
UI.GuiColumns.IconColumn.Value = true;
|
UI.GuiColumns.IconColumn.Value = true;
|
||||||
UI.GuiColumns.AppColumn.Value = true;
|
UI.GuiColumns.AppColumn.Value = true;
|
||||||
@@ -314,5 +307,5 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
|
|
||||||
return GraphicsBackend.OpenGl;
|
return GraphicsBackend.OpenGl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
ConfigurationState.Instance.Logger.EnableError.Event +=
|
ConfigurationState.Instance.Logger.EnableError.Event +=
|
||||||
(_, e) => Logger.SetEnable(LogLevel.Error, e.NewValue);
|
(_, e) => Logger.SetEnable(LogLevel.Error, e.NewValue);
|
||||||
ConfigurationState.Instance.Logger.EnableTrace.Event +=
|
ConfigurationState.Instance.Logger.EnableTrace.Event +=
|
||||||
(_, e) => Logger.SetEnable(LogLevel.Trace, e.NewValue);
|
(_, e) => Logger.SetEnable(LogLevel.Error, e.NewValue);
|
||||||
ConfigurationState.Instance.Logger.EnableGuest.Event +=
|
ConfigurationState.Instance.Logger.EnableGuest.Event +=
|
||||||
(_, e) => Logger.SetEnable(LogLevel.Guest, e.NewValue);
|
(_, e) => Logger.SetEnable(LogLevel.Guest, e.NewValue);
|
||||||
ConfigurationState.Instance.Logger.EnableFsAccessLog.Event +=
|
ConfigurationState.Instance.Logger.EnableFsAccessLog.Event +=
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace Ryujinx.UI.Common.Configuration.UI
|
|||||||
public bool AppColumn { get; set; }
|
public bool AppColumn { get; set; }
|
||||||
public bool DevColumn { get; set; }
|
public bool DevColumn { get; set; }
|
||||||
public bool VersionColumn { get; set; }
|
public bool VersionColumn { get; set; }
|
||||||
public bool LdnInfoColumn { get; set; }
|
|
||||||
public bool TimePlayedColumn { get; set; }
|
public bool TimePlayedColumn { get; set; }
|
||||||
public bool LastPlayedColumn { get; set; }
|
public bool LastPlayedColumn { get; set; }
|
||||||
public bool FileExtColumn { get; set; }
|
public bool FileExtColumn { get; set; }
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Humanizer.Localisation;
|
using LibHac.Bcat;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE.Loaders.Processes;
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -77,13 +78,13 @@ namespace Ryujinx.UI.Common
|
|||||||
Assets = new Assets
|
Assets = new Assets
|
||||||
{
|
{
|
||||||
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText) ? procRes.ProgramIdText : "game",
|
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText) ? procRes.ProgramIdText : "game",
|
||||||
LargeImageText = TruncateToByteLength($"{appMeta.Title} (v{procRes.DisplayVersion})"),
|
LargeImageText = TruncateToByteLength($"{appMeta.Title} | {procRes.DisplayVersion}"),
|
||||||
SmallImageKey = "ryujinx",
|
SmallImageKey = "ryujinx",
|
||||||
SmallImageText = TruncateToByteLength(_description)
|
SmallImageText = TruncateToByteLength(_description)
|
||||||
},
|
},
|
||||||
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
|
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
|
||||||
State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5
|
State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5
|
||||||
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false, maxUnit: TimeUnit.Hour)}"
|
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}"
|
||||||
: "Never played",
|
: "Never played",
|
||||||
Timestamps = Timestamps.Now
|
Timestamps = Timestamps.Now
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-24
@@ -207,9 +207,6 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
|
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
|
||||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||||
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
|
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();
|
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||||
_gpuDoneEvent = new ManualResetEvent(false);
|
_gpuDoneEvent = new ManualResetEvent(false);
|
||||||
@@ -494,21 +491,6 @@ namespace Ryujinx.Ava
|
|||||||
Device.Configuration.MultiplayerMode = e.NewValue;
|
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()
|
public void ToggleVSync()
|
||||||
{
|
{
|
||||||
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
||||||
@@ -881,11 +863,10 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||||
ConfigurationState.Instance.System.AudioVolume,
|
ConfigurationState.Instance.System.AudioVolume,
|
||||||
ConfigurationState.Instance.System.UseHypervisor,
|
ConfigurationState.Instance.System.UseHypervisor,
|
||||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId,
|
||||||
ConfigurationState.Instance.Multiplayer.Mode,
|
ConfigurationState.Instance.Multiplayer.Mode
|
||||||
ConfigurationState.Instance.Multiplayer.DisableP2p,
|
)
|
||||||
ConfigurationState.Instance.Multiplayer.LdnPassphrase,
|
);
|
||||||
ConfigurationState.Instance.Multiplayer.LdnServer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IHardwareDeviceDriver InitializeAudio()
|
private static IHardwareDeviceDriver InitializeAudio()
|
||||||
@@ -1069,7 +1050,7 @@ namespace Ryujinx.Ava
|
|||||||
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
||||||
|
|
||||||
UpdateShaderCount();
|
UpdateShaderCount();
|
||||||
|
|
||||||
if (GraphicsConfig.ResScale != 1)
|
if (GraphicsConfig.ResScale != 1)
|
||||||
{
|
{
|
||||||
dockedMode += $" ({GraphicsConfig.ResScale}x)";
|
dockedMode += $" ({GraphicsConfig.ResScale}x)";
|
||||||
|
|||||||
@@ -848,17 +848,5 @@
|
|||||||
"MultiplayerMode": "Mode:",
|
"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.",
|
"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",
|
"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>\""
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,6 +448,9 @@
|
|||||||
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출에 실패했습니다. 선택한 파일에 기본 NCA가 없습니다.",
|
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출에 실패했습니다. 선택한 파일에 기본 NCA가 없습니다.",
|
||||||
"DialogNcaExtractionCheckLogErrorMessage": "추출에 실패했습니다. 자세한 내용은 로그 파일을 확인하시기 바랍니다.",
|
"DialogNcaExtractionCheckLogErrorMessage": "추출에 실패했습니다. 자세한 내용은 로그 파일을 확인하시기 바랍니다.",
|
||||||
"DialogNcaExtractionSuccessMessage": "성공적으로 추출이 완료되었습니다.",
|
"DialogNcaExtractionSuccessMessage": "성공적으로 추출이 완료되었습니다.",
|
||||||
|
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출에 실패했습니다. 기본 NCA가 선택한 파일에 존재하지 않습니다.",
|
||||||
|
"DialogNcaExtractionCheckLogErrorMessage": "추출에 실패했습니다. 자세한 내용은 로그 파일을 확인하세요.",
|
||||||
|
"DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.",
|
||||||
"DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환할 수 없습니다.",
|
"DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환할 수 없습니다.",
|
||||||
"DialogUpdaterCancelUpdateMessage": "업데이트가 취소되었습니다!",
|
"DialogUpdaterCancelUpdateMessage": "업데이트가 취소되었습니다!",
|
||||||
"DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용 중입니다!",
|
"DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용 중입니다!",
|
||||||
@@ -462,6 +465,9 @@
|
|||||||
"DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!",
|
"DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!",
|
||||||
"DialogUpdaterNoInternetSubMessage": "인터넷이 제대로 연결되어 있는지 확인하세요!",
|
"DialogUpdaterNoInternetSubMessage": "인터넷이 제대로 연결되어 있는지 확인하세요!",
|
||||||
"DialogUpdaterDirtyBuildMessage": "Ryujinx의 더티 빌드는 업데이트할 수 없습니다!",
|
"DialogUpdaterDirtyBuildMessage": "Ryujinx의 더티 빌드는 업데이트할 수 없습니다!",
|
||||||
|
"DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://github.com/GreemDev/Ryujinx/releases/에서 Ryujinx를 내려받으세요.",
|
||||||
|
"DialogRestartRequiredMessage": "다시 시작 필요",
|
||||||
|
"DialogThemeRestartMessage": "테마를 저장했습니다. 테마를 적용하려면 다시 시작해야 합니다.",
|
||||||
"DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾으신다면 https://github.com/GreemDev/Ryujinx/releases/에서 Ryujinx를 내려받으세요.",
|
"DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾으신다면 https://github.com/GreemDev/Ryujinx/releases/에서 Ryujinx를 내려받으세요.",
|
||||||
"DialogRestartRequiredMessage": "다시 시작 필요",
|
"DialogRestartRequiredMessage": "다시 시작 필요",
|
||||||
"DialogThemeRestartMessage": "테마를 저장했습니다. 테마를 적용하려면 다시 시작해야 합니다.",
|
"DialogThemeRestartMessage": "테마를 저장했습니다. 테마를 적용하려면 다시 시작해야 합니다.",
|
||||||
@@ -702,7 +708,7 @@
|
|||||||
"UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음 : {0}",
|
"UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음 : {0}",
|
||||||
"UpdaterAddingFiles": "새 파일 추가...",
|
"UpdaterAddingFiles": "새 파일 추가...",
|
||||||
"UpdaterExtracting": "업데이트 추출...",
|
"UpdaterExtracting": "업데이트 추출...",
|
||||||
"UpdaterDownloading": "업데이트 내려받기 중...",
|
"UpdaterDownloading": ""업데이트 내려받기 중...",
|
||||||
"Game": "게임",
|
"Game": "게임",
|
||||||
"Docked": "도킹",
|
"Docked": "도킹",
|
||||||
"Handheld": "휴대",
|
"Handheld": "휴대",
|
||||||
@@ -848,17 +854,5 @@
|
|||||||
"MultiplayerMode": "모드 :",
|
"MultiplayerMode": "모드 :",
|
||||||
"MultiplayerModeTooltip": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예: Super Smash Bros. Ultimate v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
|
"MultiplayerModeTooltip": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예: Super Smash Bros. Ultimate v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.",
|
||||||
"MultiplayerModeDisabled": "비활성화됨",
|
"MultiplayerModeDisabled": "비활성화됨",
|
||||||
"MultiplayerModeLdnMitm": "ldn_mitm",
|
"MultiplayerModeLdnMitm": "ldn_mitm"
|
||||||
"MultiplayerModeLdnRyu": "RyuLDN",
|
}
|
||||||
"MultiplayerDisableP2P": "P2P 네트워크 호스팅 비활성화(대기 시간이 늘어날 수 있음)",
|
|
||||||
"MultiplayerDisableP2PTooltip": "P2P 네트워크 호스팅을 비활성화하면 피어가 직접 연결하지 않고 마스터 서버를 통해 프록시합니다.",
|
|
||||||
"LdnPassphrase": "네트워크 암호 문구 :",
|
|
||||||
"LdnPassphraseTooltip": "귀하는 귀하와 동일한 암호를 사용하는 호스팅 게임만 볼 수 있습니다.",
|
|
||||||
"LdnPassphraseInputTooltip": "Ryujinx-<8 hex chars> 형식으로 암호를 입력하세요. 귀하는 귀하와 동일한 암호를 사용하는 호스팅 게임만 볼 수 있습니다.",
|
|
||||||
"LdnPassphraseInputPublic": "(일반)",
|
|
||||||
"GenLdnPass": "무작위 생성",
|
|
||||||
"GenLdnPassTooltip": "다른 플레이어와 공유할 수 있는 새로운 암호 문구를 생성합니다.",
|
|
||||||
"ClearLdnPass": "지우기",
|
|
||||||
"ClearLdnPassTooltip": "현재 암호를 지우고 공용 네트워크로 돌아갑니다.",
|
|
||||||
"InvalidLdnPassphrase": "유효하지 않은 암호입니다! \"Ryujinx-<8 hex chars>\" 형식이어야 합니다."
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string FormatDynamicValue(LocaleKeys key, params object[] values)
|
public static string FormatDynamicValue(LocaleKeys key, params object[] values)
|
||||||
=> Instance.UpdateAndGetDynamicValue(key, values);
|
=> Instance.UpdateAndGetDynamicValue(key, values);
|
||||||
|
|
||||||
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
|
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ namespace Ryujinx.Ava
|
|||||||
Console.Title = $"{App.FullAppName} Console {Version}";
|
Console.Title = $"{App.FullAppName} Console {Version}";
|
||||||
|
|
||||||
// Hook unhandled exception and process exit events.
|
// 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);
|
=> ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating);
|
||||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit();
|
AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit();
|
||||||
|
|
||||||
@@ -224,14 +224,16 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private static void PrintSystemInfo()
|
private static void PrintSystemInfo()
|
||||||
{
|
{
|
||||||
Logger.Notice.Print(LogClass.Application, $"{App.FullAppName} Version: {Version}");
|
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
|
||||||
SystemInfo.Gather().Print();
|
SystemInfo.Gather().Print();
|
||||||
|
|
||||||
var enabledLogLevels = Logger.GetEnabledLevels().ToArray();
|
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>"
|
? "<None>"
|
||||||
: enabledLogLevels.JoinToString(", "))}");
|
: enabledLogLevels.JoinToString(", "))
|
||||||
|
}");
|
||||||
|
|
||||||
Logger.Notice.Print(LogClass.Application,
|
Logger.Notice.Print(LogClass.Application,
|
||||||
AppDataManager.Mode == AppDataManager.LaunchMode.Custom
|
AppDataManager.Mode == AppDataManager.LaunchMode.Custom
|
||||||
@@ -243,13 +245,13 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Log log = Logger.Error ?? Logger.Notice;
|
Logger.Log log = Logger.Error ?? Logger.Notice;
|
||||||
string message = $"Unhandled exception caught: {ex}";
|
string message = $"Unhandled exception caught: {ex}";
|
||||||
|
|
||||||
// ReSharper disable once ConstantConditionalAccessQualifier
|
// ReSharper disable once ConstantConditionalAccessQualifier
|
||||||
if (sender?.GetType()?.AsPrettyString() is { } senderName)
|
if (sender?.GetType()?.AsPrettyString() is {} senderName)
|
||||||
log.Print(LogClass.Application, message, senderName);
|
log.Print(LogClass.Application, message, senderName);
|
||||||
else
|
else
|
||||||
log.PrintMsg(LogClass.Application, message);
|
log.PrintMsg(LogClass.Application, message);
|
||||||
|
|
||||||
if (isTerminating)
|
if (isTerminating)
|
||||||
Exit();
|
Exit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
public bool DisplayMessageDialog(ControllerAppletUIArgs args)
|
public bool DisplayMessageDialog(ControllerAppletUIArgs args)
|
||||||
{
|
{
|
||||||
ManualResetEvent dialogCloseEvent = new(false);
|
ManualResetEvent dialogCloseEvent = new(false);
|
||||||
|
|
||||||
bool okPressed = false;
|
bool okPressed = false;
|
||||||
|
|
||||||
if (ConfigurationState.Instance.IgnoreApplet)
|
if (ConfigurationState.Instance.IgnoreApplet)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
public AvaloniaDynamicTextInputHandler(MainWindow parent)
|
public AvaloniaDynamicTextInputHandler(MainWindow parent)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
|
||||||
if (_parent.InputManager.KeyboardDriver is AvaloniaKeyboardDriver avaloniaKeyboardDriver)
|
if (_parent.InputManager.KeyboardDriver is AvaloniaKeyboardDriver avaloniaKeyboardDriver)
|
||||||
{
|
{
|
||||||
avaloniaKeyboardDriver.KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed;
|
avaloniaKeyboardDriver.KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed;
|
||||||
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
avaloniaKeyboardDriver.KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease;
|
avaloniaKeyboardDriver.KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease;
|
||||||
avaloniaKeyboardDriver.TextInput -= AvaloniaDynamicTextInputHandler_TextInput;
|
avaloniaKeyboardDriver.TextInput -= AvaloniaDynamicTextInputHandler_TextInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
_textChangedSubscription?.Dispose();
|
_textChangedSubscription?.Dispose();
|
||||||
_selectionStartChangedSubscription?.Dispose();
|
_selectionStartChangedSubscription?.Dispose();
|
||||||
_selectionEndtextChangedSubscription?.Dispose();
|
_selectionEndtextChangedSubscription?.Dispose();
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args)
|
public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args)
|
||||||
{
|
{
|
||||||
PlayerCount = args.PlayerCountMin == args.PlayerCountMax
|
PlayerCount = args.PlayerCountMin == args.PlayerCountMax
|
||||||
? args.PlayerCountMin.ToString()
|
? args.PlayerCountMin.ToString()
|
||||||
: $"{args.PlayerCountMin} - {args.PlayerCountMax}";
|
: $"{args.PlayerCountMin} - {args.PlayerCountMax}";
|
||||||
|
|
||||||
SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0;
|
SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:converters="clr-namespace:Avalonia.Data.Converters;assembly=Avalonia.Base"
|
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
Focusable="True"
|
Focusable="True"
|
||||||
@@ -111,11 +110,6 @@
|
|||||||
Text="{Binding FileExtension}"
|
Text="{Binding FileExtension}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Text="{Binding Converter={helpers:MultiplayerInfoConverter}}"
|
|
||||||
TextAlignment="Start"
|
|
||||||
TextWrapping="Wrap"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="4"
|
Grid.Column="4"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (contentManager.GetCurrentFirmwareVersion() != null)
|
if (contentManager.GetCurrentFirmwareVersion() != null)
|
||||||
Task.Run(() => UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem));
|
Task.Run(() => UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem));
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,13 +60,13 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Navigate(Type sourcePageType, object parameter)
|
public void Navigate(Type sourcePageType, object parameter)
|
||||||
=> ContentFrame.Navigate(sourcePageType, parameter);
|
=> ContentFrame.Navigate(sourcePageType, parameter);
|
||||||
|
|
||||||
public static async Task Show(
|
public static async Task Show(
|
||||||
AccountManager ownerAccountManager,
|
AccountManager ownerAccountManager,
|
||||||
ContentManager ownerContentManager,
|
ContentManager ownerContentManager,
|
||||||
VirtualFileSystem ownerVirtualFileSystem,
|
VirtualFileSystem ownerVirtualFileSystem,
|
||||||
HorizonClient ownerHorizonClient)
|
HorizonClient ownerHorizonClient)
|
||||||
{
|
{
|
||||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||||
@@ -158,9 +158,9 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
_ = Dispatcher.UIThread.InvokeAsync(async ()
|
_ = Dispatcher.UIThread.InvokeAsync(async ()
|
||||||
=> await ContentDialogHelper.CreateErrorDialog(
|
=> await ContentDialogHelper.CreateErrorDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]));
|
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountManager.OpenUser(profile.UserId);
|
AccountManager.OpenUser(profile.UserId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
_key = key;
|
_key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string this[string key] =>
|
public string this[string key] =>
|
||||||
_glyphs.TryGetValue(Enum.Parse<Glyph>(key), out var val)
|
_glyphs.TryGetValue(Enum.Parse<Glyph>(key), out var val)
|
||||||
? val
|
? val
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
public override object ProvideValue(IServiceProvider serviceProvider) => this[_key];
|
public override object ProvideValue(IServiceProvider serviceProvider) => this[_key];
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
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 static TimeZoneConverter Instance = new();
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
=> value is TimeZone timeZone
|
=> value is TimeZone timeZone
|
||||||
? $"{timeZone.UtcDifference} {timeZone.Location} {timeZone.Abbreviation}"
|
? $"{timeZone.UtcDifference} {timeZone.Location} {timeZone.Abbreviation}"
|
||||||
: null;
|
: 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();
|
=> throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public string DockedMode { get; }
|
public string DockedMode { get; }
|
||||||
public string FifoStatus { get; }
|
public string FifoStatus { get; }
|
||||||
public string GameStatus { get; }
|
public string GameStatus { get; }
|
||||||
|
|
||||||
public uint ShaderCount { get; }
|
public uint ShaderCount { get; }
|
||||||
|
|
||||||
public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount)
|
public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, uint shaderCount)
|
||||||
|
|||||||
@@ -117,8 +117,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public ApplicationData ListSelectedApplication;
|
public ApplicationData ListSelectedApplication;
|
||||||
public ApplicationData GridSelectedApplication;
|
public ApplicationData GridSelectedApplication;
|
||||||
|
|
||||||
public IEnumerable<LdnGameData> LastLdnGameData;
|
|
||||||
|
|
||||||
public static readonly Bitmap IconBitmap =
|
public static readonly Bitmap IconBitmap =
|
||||||
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!);
|
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!);
|
||||||
|
|
||||||
@@ -175,7 +173,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
SwitchToGameControl = switchToGameControl;
|
SwitchToGameControl = switchToGameControl;
|
||||||
SetMainContent = setMainContent;
|
SetMainContent = setMainContent;
|
||||||
TopLevel = topLevel;
|
TopLevel = topLevel;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
topLevel.AttachDevTools(new KeyGesture(Avalonia.Input.Key.F12, KeyModifiers.Control));
|
topLevel.AttachDevTools(new KeyGesture(Avalonia.Input.Key.F12, KeyModifiers.Control));
|
||||||
#endif
|
#endif
|
||||||
@@ -270,7 +268,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool ShowFirmwareStatus => !ShowLoadProgress;
|
public bool ShowFirmwareStatus => !ShowLoadProgress;
|
||||||
|
|
||||||
public bool ShowRightmostSeparator
|
public bool ShowRightmostSeparator
|
||||||
{
|
{
|
||||||
get => _showRightmostSeparator;
|
get => _showRightmostSeparator;
|
||||||
set
|
set
|
||||||
@@ -555,7 +553,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ShaderCountText
|
public string ShaderCountText
|
||||||
{
|
{
|
||||||
get => _shaderCountText;
|
get => _shaderCountText;
|
||||||
@@ -1023,7 +1021,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
? SortExpressionComparer<ApplicationData>.Ascending(selector)
|
? SortExpressionComparer<ApplicationData>.Ascending(selector)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(selector);
|
: SortExpressionComparer<ApplicationData>.Descending(selector);
|
||||||
|
|
||||||
private IComparer<ApplicationData> GetComparer()
|
private IComparer<ApplicationData> GetComparer()
|
||||||
=> SortMode switch
|
=> SortMode switch
|
||||||
{
|
{
|
||||||
#pragma warning disable IDE0055 // Disable formatting
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
@@ -1253,7 +1251,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
{
|
{
|
||||||
RendererHostControl.WindowCreated += RendererHost_Created;
|
RendererHostControl.WindowCreated += RendererHost_Created;
|
||||||
|
|
||||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||||
AppHost.AppExit += AppHost_AppExit;
|
AppHost.AppExit += AppHost_AppExit;
|
||||||
|
|
||||||
@@ -1302,9 +1300,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
GameStatusText = args.GameStatus;
|
GameStatusText = args.GameStatus;
|
||||||
VolumeStatusText = args.VolumeStatus;
|
VolumeStatusText = args.VolumeStatus;
|
||||||
FifoStatusText = args.FifoStatus;
|
FifoStatusText = args.FifoStatus;
|
||||||
|
|
||||||
ShaderCountText = (ShowRightmostSeparator = args.ShaderCount > 0)
|
ShaderCountText = (ShowRightmostSeparator = args.ShaderCount > 0)
|
||||||
? $"{LocaleManager.Instance[LocaleKeys.CompilingShaders]}: {args.ShaderCount}"
|
? $"{LocaleManager.Instance[LocaleKeys.CompilingShaders]}: {args.ShaderCount}"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
ShowStatusSeparator = true;
|
ShowStatusSeparator = true;
|
||||||
@@ -1709,7 +1707,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
RendererHostControl.Focus();
|
RendererHostControl.Focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
public static void UpdateGameMetadata(string titleId)
|
public static void UpdateGameMetadata(string titleId)
|
||||||
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame());
|
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame());
|
||||||
|
|
||||||
public void RefreshFirmwareStatus()
|
public void RefreshFirmwareStatus()
|
||||||
|
|||||||
@@ -25,13 +25,12 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
public partial class SettingsViewModel : BaseModel
|
public class SettingsViewModel : BaseModel
|
||||||
{
|
{
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly ContentManager _contentManager;
|
private readonly ContentManager _contentManager;
|
||||||
@@ -57,8 +56,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public event Action SaveSettingsEvent;
|
public event Action SaveSettingsEvent;
|
||||||
private int _networkInterfaceIndex;
|
private int _networkInterfaceIndex;
|
||||||
private int _multiplayerModeIndex;
|
private int _multiplayerModeIndex;
|
||||||
private string _ldnPassphrase;
|
|
||||||
private string _LdnServer;
|
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
@@ -183,24 +180,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||||
public bool UseHypervisor { get; set; }
|
public bool UseHypervisor { get; set; }
|
||||||
public bool DisableP2P { get; set; }
|
|
||||||
|
|
||||||
public string TimeZone { get; set; }
|
public string TimeZone { get; set; }
|
||||||
public string ShaderDumpPath { 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 Language { get; set; }
|
||||||
public int Region { get; set; }
|
public int Region { get; set; }
|
||||||
public int FsGlobalAccessLogMode { get; set; }
|
public int FsGlobalAccessLogMode { get; set; }
|
||||||
@@ -293,21 +276,6 @@ 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()
|
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
@@ -425,11 +393,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex)));
|
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)
|
public void ValidateAndSetTimeZone(string location)
|
||||||
{
|
{
|
||||||
if (_validTzRegions.Contains(location))
|
if (_validTzRegions.Contains(location))
|
||||||
@@ -534,9 +497,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
|
|
||||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.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()
|
public void SaveSettings()
|
||||||
@@ -653,9 +613,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
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);
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
private CheckBox[] GenerateToggleFileTypeItems() =>
|
private CheckBox[] GenerateToggleFileTypeItems() =>
|
||||||
Enum.GetValues<FileTypes>()
|
Enum.GetValues<FileTypes>()
|
||||||
.Select(it => (FileName: Enum.GetName(it)!, FileType: it))
|
.Select(it => (FileName: Enum.GetName(it)!, FileType: it))
|
||||||
.Select(it =>
|
.Select(it =>
|
||||||
new CheckBox
|
new CheckBox
|
||||||
{
|
{
|
||||||
Content = $".{it.FileName}",
|
Content = $".{it.FileName}",
|
||||||
IsChecked = it.FileType.GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes),
|
IsChecked = it.FileType.GetConfigValue(ConfigurationState.Instance.UI.ShownFileTypes),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@@ -36,57 +36,11 @@
|
|||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
<TextBlock Text="{ext:Locale MultiplayerModeDisabled}" />
|
<TextBlock Text="{ext:Locale MultiplayerModeDisabled}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale MultiplayerModeLdnRyu}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
<TextBlock Text="{ext:Locale MultiplayerModeLdnMitm}" />
|
<TextBlock Text="{ext:Locale MultiplayerModeLdnMitm}" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<CheckBox Margin="10,0,0,0" IsChecked="{Binding DisableP2P}">
|
|
||||||
<TextBlock Text="{ext:Locale MultiplayerDisableP2P}"
|
|
||||||
ToolTip.Tip="{ext:Locale MultiplayerDisableP2PTooltip}" />
|
|
||||||
</CheckBox>
|
|
||||||
</StackPanel>
|
</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" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabNetworkConnection}" />
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabNetworkConnection}" />
|
||||||
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
|
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
|
||||||
|
|||||||
@@ -1,29 +1,12 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.Settings
|
namespace Ryujinx.Ava.UI.Views.Settings
|
||||||
{
|
{
|
||||||
public partial class SettingsNetworkView : UserControl
|
public partial class SettingsNetworkView : UserControl
|
||||||
{
|
{
|
||||||
public SettingsViewModel ViewModel;
|
|
||||||
|
|
||||||
public SettingsNetworkView()
|
public SettingsNetworkView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
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,36 +154,6 @@ 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)
|
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Application != null)
|
if (args.Application != null)
|
||||||
@@ -480,20 +450,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
.Connect()
|
.Connect()
|
||||||
.ObserveOn(SynchronizationContext.Current!)
|
.ObserveOn(SynchronizationContext.Current!)
|
||||||
.Bind(ViewModel.Applications)
|
.Bind(ViewModel.Applications)
|
||||||
.OnItemAdded(UpdateApplicationWithLdnData)
|
|
||||||
.Subscribe();
|
.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();
|
ViewModel.RefreshFirmwareStatus();
|
||||||
|
|
||||||
@@ -502,7 +459,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
LoadApplications();
|
LoadApplications();
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = CheckLaunchState();
|
_ = CheckLaunchState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,26 +588,13 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
switch (fileType)
|
switch (fileType)
|
||||||
{
|
{
|
||||||
case "NSP":
|
case "NSP": ConfigurationState.Instance.UI.ShownFileTypes.NSP.Toggle(); break;
|
||||||
ConfigurationState.Instance.UI.ShownFileTypes.NSP.Toggle();
|
case "PFS0": ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Toggle(); break;
|
||||||
break;
|
case "XCI": ConfigurationState.Instance.UI.ShownFileTypes.XCI.Toggle(); break;
|
||||||
case "PFS0":
|
case "NCA": ConfigurationState.Instance.UI.ShownFileTypes.NCA.Toggle(); break;
|
||||||
ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Toggle();
|
case "NRO": ConfigurationState.Instance.UI.ShownFileTypes.NRO.Toggle(); break;
|
||||||
break;
|
case "NSO": ConfigurationState.Instance.UI.ShownFileTypes.NSO.Toggle(); break;
|
||||||
case "XCI":
|
default: throw new ArgumentOutOfRangeException(fileType);
|
||||||
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);
|
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
NavPanel.Content = AudioPage;
|
NavPanel.Content = AudioPage;
|
||||||
break;
|
break;
|
||||||
case "NetworkPage":
|
case "NetworkPage":
|
||||||
NetworkPage.ViewModel = ViewModel;
|
|
||||||
NavPanel.Content = NetworkPage;
|
NavPanel.Content = NetworkPage;
|
||||||
break;
|
break;
|
||||||
case "LoggingPage":
|
case "LoggingPage":
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
LocaleManager.Instance.LocaleChanged += LocaleChanged;
|
LocaleManager.Instance.LocaleChanged += LocaleChanged;
|
||||||
LocaleChanged();
|
LocaleChanged();
|
||||||
|
|
||||||
Icon = MainWindowViewModel.IconBitmap;
|
Icon = MainWindowViewModel.IconBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user