Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
53bd4c9f60 | ||
|
eca8808649 | ||
|
f6c3f1cdfd | ||
|
8026e1c804 | ||
|
d9f9bbfaa6 |
@@ -21,7 +21,7 @@
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
@@ -49,4 +49,4 @@
|
||||
<PackageVersion Include="System.Management" Version="7.0.2" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@@ -21,6 +21,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
@@ -191,6 +192,7 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
||||
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
|
||||
|
||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||
_gpuDoneEvent = new ManualResetEvent(false);
|
||||
@@ -412,6 +414,11 @@ namespace Ryujinx.Ava
|
||||
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
||||
}
|
||||
|
||||
private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs<MultiplayerMode> e)
|
||||
{
|
||||
Device.Configuration.MultiplayerMode = e.NewValue;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isActive = false;
|
||||
@@ -782,7 +789,8 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||
ConfigurationState.Instance.System.AudioVolume,
|
||||
ConfigurationState.Instance.System.UseHypervisor,
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
||||
ConfigurationState.Instance.Multiplayer.Mode);
|
||||
|
||||
Device = new Switch(configuration);
|
||||
}
|
||||
|
@@ -652,5 +652,8 @@
|
||||
"NetworkInterfaceDefault": "Default",
|
||||
"PackagingShaders": "Packaging Shaders",
|
||||
"AboutChangelogButton": "View Changelog on GitHub",
|
||||
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser."
|
||||
}
|
||||
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.",
|
||||
"SettingsTabNetworkMultiplayer": "Multiplayer",
|
||||
"MultiplayerMode": "Mode:",
|
||||
"MultiplayerModeTooltip": "Change multiplayer mode"
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@ using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
@@ -36,11 +35,9 @@ namespace Ryujinx.Ava.Common
|
||||
private static HorizonClient _horizonClient;
|
||||
private static AccountManager _accountManager;
|
||||
private static VirtualFileSystem _virtualFileSystem;
|
||||
private static StyleableWindow _owner;
|
||||
|
||||
public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, StyleableWindow owner)
|
||||
public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient)
|
||||
{
|
||||
_owner = owner;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_horizonClient = horizonClient;
|
||||
_accountManager = accountManager;
|
||||
|
@@ -10,6 +10,7 @@ using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Vulkan;
|
||||
@@ -54,6 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public event Action CloseWindow;
|
||||
public event Action SaveSettingsEvent;
|
||||
private int _networkInterfaceIndex;
|
||||
private int _multiplayerModeIndex;
|
||||
|
||||
public int ResolutionScale
|
||||
{
|
||||
@@ -251,6 +253,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
get => new(_networkInterfaces.Keys);
|
||||
}
|
||||
|
||||
public AvaloniaList<string> MultiplayerModes
|
||||
{
|
||||
get => new(Enum.GetNames<MultiplayerMode>());
|
||||
}
|
||||
|
||||
public KeyboardHotkeys KeyboardHotkeys
|
||||
{
|
||||
get => _keyboardHotkeys;
|
||||
@@ -272,6 +279,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public int MultiplayerModeIndex
|
||||
{
|
||||
get => _multiplayerModeIndex;
|
||||
set
|
||||
{
|
||||
_multiplayerModeIndex = value;
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
@@ -478,6 +495,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
@@ -579,6 +598,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
|
@@ -23,21 +23,34 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkMultiplayer}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale MultiplayerMode}"
|
||||
ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}"
|
||||
Width="200" />
|
||||
<ComboBox SelectedIndex="{Binding MultiplayerModeIndex}"
|
||||
ToolTip.Tip="{locale:Locale MultiplayerModeTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
ItemsSource="{Binding MultiplayerModes}"
|
||||
Width="250" />
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" />
|
||||
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
|
||||
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
|
||||
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
|
||||
</CheckBox>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabNetworkInterface}"
|
||||
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
|
||||
Width="200" />
|
||||
Text="{locale:Locale SettingsTabNetworkInterface}"
|
||||
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
|
||||
Width="200" />
|
||||
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
|
||||
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
ItemsSource="{Binding NetworkInterfaceList}"
|
||||
Width="250" />
|
||||
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
ItemsSource="{Binding NetworkInterfaceList}"
|
||||
Width="250" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
@@ -15,6 +15,7 @@ using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.Input.SDL2;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
@@ -25,7 +26,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
@@ -251,7 +251,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
VirtualFileSystem.ReloadKeySet();
|
||||
|
||||
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
|
||||
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
|
@@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.Common.Configuration.Multiplayer
|
||||
{
|
||||
public enum MultiplayerMode
|
||||
{
|
||||
Disabled,
|
||||
}
|
||||
}
|
@@ -791,5 +791,34 @@ namespace Ryujinx.Common.Memory
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array140<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
Array64<T> _other2;
|
||||
Array11<T> _other3;
|
||||
public readonly int Length => 140;
|
||||
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
|
||||
{
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
Array64<T> _other2;
|
||||
Array64<T> _other3;
|
||||
Array64<T> _other4;
|
||||
Array64<T> _other5;
|
||||
Array63<T> _other6;
|
||||
public readonly int Length => 384;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0169, IDE0051
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
@@ -62,5 +64,15 @@ namespace Ryujinx.Common.Utilities
|
||||
|
||||
return (targetProperties, targetAddressInfo);
|
||||
}
|
||||
|
||||
public static uint ConvertIpv4Address(IPAddress ipAddress)
|
||||
{
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
public static uint ConvertIpv4Address(string ipAddress)
|
||||
{
|
||||
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -335,6 +335,45 @@ namespace Ryujinx.Graphics.GAL
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a depth or depth-stencil format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the format is a depth or depth-stencil format, false otherwise</returns>
|
||||
public static bool HasDepth(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.D16Unorm:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.D32Float:
|
||||
case Format.D32FloatS8Uint:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is a stencil or depth-stencil format.
|
||||
/// </summary>
|
||||
/// <param name="format">Texture format</param>
|
||||
/// <returns>True if the format is a stencil or depth-stencil format, false otherwise</returns>
|
||||
public static bool HasStencil(this Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.S8UintD24Unorm:
|
||||
case Format.D32FloatS8Uint:
|
||||
case Format.S8Uint:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format is valid to use as image format.
|
||||
/// </summary>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
|
||||
@@ -806,25 +807,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
updateFlags |= RenderTargetUpdateFlags.Layered;
|
||||
}
|
||||
|
||||
if (clearDepth || clearStencil)
|
||||
bool clearDS = clearDepth || clearStencil;
|
||||
|
||||
if (clearDS)
|
||||
{
|
||||
updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil;
|
||||
}
|
||||
|
||||
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
|
||||
|
||||
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
|
||||
// on the screen scissor state, then we need to force only one texture to be bound to avoid
|
||||
// host clipping.
|
||||
var screenScissorState = _state.State.ScreenScissorState;
|
||||
|
||||
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
|
||||
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
|
||||
|
||||
if (clearDS || componentMask == 15)
|
||||
{
|
||||
// A full clear if scissor is disabled, or it matches the screen scissor state.
|
||||
|
||||
bool fullClear = screenScissorState.X == 0 && screenScissorState.Y == 0;
|
||||
|
||||
if (fullClear && clearAffectedByScissor && _state.State.ScissorState[0].Enable)
|
||||
{
|
||||
ref var scissorState = ref _state.State.ScissorState[0];
|
||||
|
||||
fullClear = scissorState.X1 == screenScissorState.X &&
|
||||
scissorState.Y1 == screenScissorState.Y &&
|
||||
scissorState.X2 >= screenScissorState.X + screenScissorState.Width &&
|
||||
scissorState.Y2 >= screenScissorState.Y + screenScissorState.Height;
|
||||
}
|
||||
|
||||
if (fullClear && clearDS)
|
||||
{
|
||||
// Must clear all aspects of the depth-stencil format.
|
||||
|
||||
FormatInfo dsFormat = _state.State.RtDepthStencilState.Format.Convert();
|
||||
|
||||
bool hasDepth = dsFormat.Format.HasDepth();
|
||||
bool hasStencil = dsFormat.Format.HasStencil();
|
||||
|
||||
if (hasStencil && (!clearStencil || (clearAffectedByStencilMask && _state.State.StencilTestState.FrontMask != 0xff)))
|
||||
{
|
||||
fullClear = false;
|
||||
}
|
||||
else if (hasDepth && !clearDepth)
|
||||
{
|
||||
fullClear = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullClear)
|
||||
{
|
||||
updateFlags |= RenderTargetUpdateFlags.DiscardClip;
|
||||
}
|
||||
}
|
||||
|
||||
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
|
||||
|
||||
// Must happen after UpdateRenderTargetState to have up-to-date clip region values.
|
||||
bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 ||
|
||||
screenScissorState.Width != _channel.TextureManager.ClipRegionWidth ||
|
||||
screenScissorState.Height != _channel.TextureManager.ClipRegionHeight;
|
||||
|
||||
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
|
||||
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
|
||||
bool needsCustomScissor = !clearAffectedByScissor || clipMismatch;
|
||||
|
||||
// Scissor and rasterizer discard also affect clears.
|
||||
|
@@ -33,6 +33,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
UpdateDepthStencil = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the data in the clip region can be discarded for the next use.
|
||||
/// </summary>
|
||||
DiscardClip = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Default update flags for draw.
|
||||
/// </summary>
|
||||
|
@@ -447,6 +447,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl);
|
||||
bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered);
|
||||
bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor);
|
||||
bool discard = updateFlags.HasFlag(RenderTargetUpdateFlags.DiscardClip);
|
||||
|
||||
int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
|
||||
|
||||
@@ -486,6 +487,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
memoryManager,
|
||||
colorState,
|
||||
_vtgWritesRtLayer || layered,
|
||||
discard,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
@@ -525,6 +527,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
dsState,
|
||||
dsSize,
|
||||
_vtgWritesRtLayer || layered,
|
||||
discard,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
@@ -570,6 +570,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return Group.CheckDirty(this, consume);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards all data for this texture.
|
||||
/// This clears all dirty flags, modified flags, and pending copies from other textures.
|
||||
/// It should be used if the texture data will be fully overwritten by the next use.
|
||||
/// </summary>
|
||||
public void DiscardData()
|
||||
{
|
||||
Group.DiscardData(this);
|
||||
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes guest and host memory.
|
||||
/// This will overwrite the texture data with the texture data on the guest memory, if a CPU
|
||||
|
@@ -311,7 +311,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
flags |= TextureSearchFlags.NoCreate;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0);
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint: sizeHint);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
@@ -324,6 +324,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
|
||||
/// <param name="colorState">Color buffer texture to find or create</param>
|
||||
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
|
||||
/// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param>
|
||||
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
|
||||
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
@@ -332,6 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
MemoryManager memoryManager,
|
||||
RtColorState colorState,
|
||||
bool layered,
|
||||
bool discard,
|
||||
int samplesInX,
|
||||
int samplesInY,
|
||||
Size sizeHint)
|
||||
@@ -398,7 +400,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
int layerSize = !isLinear ? colorState.LayerSize * 4 : 0;
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize);
|
||||
var flags = TextureSearchFlags.WithUpscale;
|
||||
|
||||
if (discard)
|
||||
{
|
||||
flags |= TextureSearchFlags.DiscardData;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, layerSize, sizeHint: sizeHint);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
@@ -412,6 +421,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="dsState">Depth-stencil buffer texture to find or create</param>
|
||||
/// <param name="size">Size of the depth-stencil texture</param>
|
||||
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
|
||||
/// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param>
|
||||
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
|
||||
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
@@ -421,6 +431,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
RtDepthStencilState dsState,
|
||||
Size3D size,
|
||||
bool layered,
|
||||
bool discard,
|
||||
int samplesInX,
|
||||
int samplesInY,
|
||||
Size sizeHint)
|
||||
@@ -465,7 +476,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
target,
|
||||
formatInfo);
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, dsState.LayerSize * 4);
|
||||
var flags = TextureSearchFlags.WithUpscale;
|
||||
|
||||
if (discard)
|
||||
{
|
||||
flags |= TextureSearchFlags.DiscardData;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, dsState.LayerSize * 4, sizeHint: sizeHint);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
|
||||
@@ -500,6 +518,37 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if texture data should be fully discarded
|
||||
/// based on the size hint region and whether it is set to be discarded.
|
||||
/// </summary>
|
||||
/// <param name="discard">Whether the size hint region should be discarded</param>
|
||||
/// <param name="texture">The texture being discarded</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
/// <returns>True if the data should be discarded, false otherwise</returns>
|
||||
private static bool ShouldDiscard(bool discard, Texture texture, Size? sizeHint)
|
||||
{
|
||||
return discard &&
|
||||
texture.Info.DepthOrLayers == 1 &&
|
||||
sizeHint != null &&
|
||||
texture.Width <= sizeHint.Value.Width &&
|
||||
texture.Height <= sizeHint.Value.Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards texture data if requested and possible.
|
||||
/// </summary>
|
||||
/// <param name="discard">Whether the size hint region should be discarded</param>
|
||||
/// <param name="texture">The texture being discarded</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
private static void DiscardIfNeeded(bool discard, Texture texture, Size? sizeHint)
|
||||
{
|
||||
if (ShouldDiscard(discard, texture, sizeHint))
|
||||
{
|
||||
texture.DiscardData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an existing texture, or create a new one if not found.
|
||||
/// </summary>
|
||||
@@ -507,6 +556,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="flags">The texture search flags, defines texture comparison rules</param>
|
||||
/// <param name="info">Texture information of the texture to be found or created</param>
|
||||
/// <param name="layerSize">Size in bytes of a single texture layer</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
/// <param name="range">Optional ranges of physical memory where the texture data is located</param>
|
||||
/// <returns>The texture</returns>
|
||||
public Texture FindOrCreateTexture(
|
||||
@@ -514,9 +564,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureSearchFlags flags,
|
||||
TextureInfo info,
|
||||
int layerSize = 0,
|
||||
Size? sizeHint = null,
|
||||
MultiRange? range = null)
|
||||
{
|
||||
bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0;
|
||||
bool discard = (flags & TextureSearchFlags.DiscardData) != 0;
|
||||
|
||||
TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0);
|
||||
|
||||
@@ -612,6 +664,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
DiscardIfNeeded(discard, texture, sizeHint);
|
||||
|
||||
texture.SynchronizeMemory();
|
||||
|
||||
return texture;
|
||||
@@ -907,7 +961,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
// We need to synchronize before copying the old view data to the texture,
|
||||
// otherwise the copied data would be overwritten by a future synchronization.
|
||||
texture.InitializeData(false, setData);
|
||||
texture.InitializeData(false, setData && !ShouldDiscard(discard, texture, sizeHint));
|
||||
|
||||
texture.Group.InitializeOverlaps();
|
||||
|
||||
|
@@ -278,6 +278,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards all data for a given texture.
|
||||
/// This clears all dirty flags, modified flags, and pending copies from other textures.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being discarded</param>
|
||||
public void DiscardData(Texture texture)
|
||||
{
|
||||
EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
|
||||
{
|
||||
for (int i = 0; i < regionCount; i++)
|
||||
{
|
||||
TextureGroupHandle group = _handles[baseHandle + i];
|
||||
|
||||
group.DiscardData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize memory for a given texture.
|
||||
/// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
|
||||
|
@@ -2,7 +2,6 @@
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
@@ -155,6 +154,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards all data for this handle.
|
||||
/// This clears all dirty flags, modified flags, and pending copies from other handles.
|
||||
/// </summary>
|
||||
public void DiscardData()
|
||||
{
|
||||
Modified = false;
|
||||
DeferredCopy = null;
|
||||
|
||||
foreach (RegionHandle handle in Handles)
|
||||
{
|
||||
if (handle.Dirty)
|
||||
{
|
||||
handle.Reprotect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate a list of which views overlap this handle.
|
||||
/// </summary>
|
||||
|
@@ -14,5 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
DepthAlias = 1 << 3,
|
||||
WithUpscale = 1 << 4,
|
||||
NoCreate = 1 << 5,
|
||||
DiscardData = 1 << 6,
|
||||
}
|
||||
}
|
||||
|
@@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
pt.AcquireCallback(_context, pt.UserObj);
|
||||
|
||||
Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, pt.Range);
|
||||
Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, range: pt.Range);
|
||||
|
||||
pt.Cache.Tick();
|
||||
|
||||
|
@@ -454,13 +454,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (lastReadStage != PipelineStageFlags.None)
|
||||
{
|
||||
TextureView.InsertMemoryBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
lastReadStage,
|
||||
dstStageFlags);
|
||||
// This would result in a validation error, but is
|
||||
// required on MoltenVK as the generic barrier results in
|
||||
// severe texture flickering in some scenarios.
|
||||
if (_gd.IsMoltenVk)
|
||||
{
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
_lastReadStage,
|
||||
dstStageFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextureView.InsertMemoryBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_lastReadAccess,
|
||||
dstAccessFlags,
|
||||
lastReadStage,
|
||||
dstStageFlags);
|
||||
}
|
||||
|
||||
_lastReadAccess = AccessFlags.None;
|
||||
_lastReadStage = PipelineStageFlags.None;
|
||||
@@ -474,13 +497,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (_lastModificationAccess != AccessFlags.None)
|
||||
{
|
||||
TextureView.InsertMemoryBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_lastModificationAccess,
|
||||
dstAccessFlags,
|
||||
_lastModificationStage,
|
||||
dstStageFlags);
|
||||
// This would result in a validation error, but is
|
||||
// required on MoltenVK as the generic barrier results in
|
||||
// severe texture flickering in some scenarios.
|
||||
if (_gd.IsMoltenVk)
|
||||
{
|
||||
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
|
||||
TextureView.InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_imageAuto.Get(cbs).Value,
|
||||
_lastModificationAccess,
|
||||
dstAccessFlags,
|
||||
_lastModificationStage,
|
||||
dstStageFlags,
|
||||
aspectFlags,
|
||||
0,
|
||||
0,
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextureView.InsertMemoryBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
_lastModificationAccess,
|
||||
dstAccessFlags,
|
||||
_lastModificationStage,
|
||||
dstStageFlags);
|
||||
}
|
||||
|
||||
_lastModificationAccess = AccessFlags.None;
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
@@ -158,6 +159,11 @@ namespace Ryujinx.HLE
|
||||
/// </summary>
|
||||
public string MultiplayerLanInterfaceId { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiplayer Mode
|
||||
/// </summary>
|
||||
public MultiplayerMode MultiplayerMode { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||
/// </summary>
|
||||
@@ -187,7 +193,8 @@ namespace Ryujinx.HLE
|
||||
AspectRatio aspectRatio,
|
||||
float audioVolume,
|
||||
bool useHypervisor,
|
||||
string multiplayerLanInterfaceId)
|
||||
string multiplayerLanInterfaceId,
|
||||
MultiplayerMode multiplayerMode)
|
||||
{
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
LibHacHorizonManager = libHacHorizonManager;
|
||||
@@ -214,6 +221,7 @@ namespace Ryujinx.HLE
|
||||
AudioVolume = audioVolume;
|
||||
UseHypervisor = useHypervisor;
|
||||
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
||||
MultiplayerMode = multiplayerMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -88,6 +88,7 @@ namespace Ryujinx.HLE.HOS
|
||||
internal ServerBase ViServer { get; private set; }
|
||||
internal ServerBase ViServerM { get; private set; }
|
||||
internal ServerBase ViServerS { get; private set; }
|
||||
internal ServerBase LdnServer { get; private set; }
|
||||
|
||||
internal KSharedMemory HidSharedMem { get; private set; }
|
||||
internal KSharedMemory FontSharedMem { get; private set; }
|
||||
@@ -319,6 +320,7 @@ namespace Ryujinx.HLE.HOS
|
||||
ViServer = new ServerBase(KernelContext, "ViServerU");
|
||||
ViServerM = new ServerBase(KernelContext, "ViServerM");
|
||||
ViServerS = new ServerBase(KernelContext, "ViServerS");
|
||||
LdnServer = new ServerBase(KernelContext, "LdnServer");
|
||||
|
||||
StartNewServices();
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
[Service("ldn:u")]
|
||||
class IUserServiceCreator : IpcService
|
||||
{
|
||||
public IUserServiceCreator(ServiceCtx context) { }
|
||||
public IUserServiceCreator(ServiceCtx context) : base(context.Device.System.LdnServer) { }
|
||||
|
||||
[CommandCmif(0)]
|
||||
// CreateUserLocalCommunicationService() -> object<nn::ldn::detail::IUserLocalCommunicationService>
|
||||
|
@@ -7,10 +7,18 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
|
||||
Success = 0,
|
||||
|
||||
DeviceNotAvailable = (16 << ErrorCodeShift) | ModuleId,
|
||||
DeviceDisabled = (22 << ErrorCodeShift) | ModuleId,
|
||||
InvalidState = (32 << ErrorCodeShift) | ModuleId,
|
||||
Unknown1 = (48 << ErrorCodeShift) | ModuleId,
|
||||
NodeNotFound = (48 << ErrorCodeShift) | ModuleId,
|
||||
ConnectFailure = (64 << ErrorCodeShift) | ModuleId,
|
||||
ConnectNotFound = (65 << ErrorCodeShift) | ModuleId,
|
||||
ConnectTimeout = (66 << ErrorCodeShift) | ModuleId,
|
||||
ConnectRejected = (67 << ErrorCodeShift) | ModuleId,
|
||||
InvalidArgument = (96 << ErrorCodeShift) | ModuleId,
|
||||
InvalidObject = (97 << ErrorCodeShift) | ModuleId,
|
||||
VersionTooLow = (113 << ErrorCodeShift) | ModuleId,
|
||||
VersionTooHigh = (114 << ErrorCodeShift) | ModuleId,
|
||||
TooManyPlayers = (144 << ErrorCodeShift) | ModuleId,
|
||||
}
|
||||
}
|
||||
|
10
src/Ryujinx.HLE/HOS/Services/Ldn/Types/AcceptPolicy.cs
Normal file
10
src/Ryujinx.HLE/HOS/Services/Ldn/Types/AcceptPolicy.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
enum AcceptPolicy : byte
|
||||
{
|
||||
AcceptAll,
|
||||
RejectAll,
|
||||
BlackList,
|
||||
WhiteList,
|
||||
}
|
||||
}
|
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressEntry.cs
Normal file
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressEntry.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xC)]
|
||||
struct AddressEntry
|
||||
{
|
||||
public uint Ipv4Address;
|
||||
public Array6<byte> MacAddress;
|
||||
public ushort Reserved;
|
||||
}
|
||||
}
|
11
src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressList.cs
Normal file
11
src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressList.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
|
||||
struct AddressList
|
||||
{
|
||||
public Array8<AddressEntry> Addresses;
|
||||
}
|
||||
}
|
16
src/Ryujinx.HLE/HOS/Services/Ldn/Types/CommonNetworkInfo.cs
Normal file
16
src/Ryujinx.HLE/HOS/Services/Ldn/Types/CommonNetworkInfo.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x30)]
|
||||
struct CommonNetworkInfo
|
||||
{
|
||||
public Array6<byte> MacAddress;
|
||||
public Ssid Ssid;
|
||||
public ushort Channel;
|
||||
public byte LinkLevel;
|
||||
public byte NetworkType;
|
||||
public uint Reserved;
|
||||
}
|
||||
}
|
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/DisconnectReason.cs
Normal file
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/DisconnectReason.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
enum DisconnectReason : uint
|
||||
{
|
||||
None,
|
||||
DisconnectedByUser,
|
||||
DisconnectedBySystem,
|
||||
DestroyedByUser,
|
||||
DestroyedBySystem,
|
||||
Rejected,
|
||||
SignalLost,
|
||||
}
|
||||
}
|
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/IntentId.cs
Normal file
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/IntentId.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
struct IntentId
|
||||
{
|
||||
public long LocalCommunicationId;
|
||||
public ushort Reserved1;
|
||||
public ushort SceneId;
|
||||
public uint Reserved2;
|
||||
}
|
||||
}
|
23
src/Ryujinx.HLE/HOS/Services/Ldn/Types/LdnNetworkInfo.cs
Normal file
23
src/Ryujinx.HLE/HOS/Services/Ldn/Types/LdnNetworkInfo.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x430)]
|
||||
struct LdnNetworkInfo
|
||||
{
|
||||
public Array16<byte> SecurityParameter;
|
||||
public ushort SecurityMode;
|
||||
public AcceptPolicy StationAcceptPolicy;
|
||||
public byte Reserved1;
|
||||
public ushort Reserved2;
|
||||
public byte NodeCountMax;
|
||||
public byte NodeCount;
|
||||
public Array8<NodeInfo> Nodes;
|
||||
public ushort Reserved3;
|
||||
public ushort AdvertiseDataSize;
|
||||
public Array384<byte> AdvertiseData;
|
||||
public Array140<byte> Reserved4;
|
||||
public ulong AuthenticationId;
|
||||
}
|
||||
}
|
16
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs
Normal file
16
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||
struct NetworkConfig
|
||||
{
|
||||
public IntentId IntentId;
|
||||
public ushort Channel;
|
||||
public byte NodeCountMax;
|
||||
public byte Reserved1;
|
||||
public ushort LocalCommunicationVersion;
|
||||
public Array10<byte> Reserved2;
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkId.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkId.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||
struct NetworkId
|
||||
{
|
||||
public IntentId IntentId;
|
||||
public Array16<byte> SessionId;
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkInfo.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkInfo.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x480)]
|
||||
struct NetworkInfo
|
||||
{
|
||||
public NetworkId NetworkId;
|
||||
public CommonNetworkInfo Common;
|
||||
public LdnNetworkInfo Ldn;
|
||||
}
|
||||
}
|
10
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkType.cs
Normal file
10
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
enum NetworkType : uint
|
||||
{
|
||||
None,
|
||||
General,
|
||||
Ldn,
|
||||
All,
|
||||
}
|
||||
}
|
18
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeInfo.cs
Normal file
18
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeInfo.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x40)]
|
||||
struct NodeInfo
|
||||
{
|
||||
public uint Ipv4Address;
|
||||
public Array6<byte> MacAddress;
|
||||
public byte NodeId;
|
||||
public byte IsConnected;
|
||||
public Array33<byte> UserName;
|
||||
public byte Reserved1;
|
||||
public ushort LocalCommunicationVersion;
|
||||
public Array16<byte> Reserved2;
|
||||
}
|
||||
}
|
62
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdate.cs
Normal file
62
src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdate.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 8)]
|
||||
struct NodeLatestUpdate
|
||||
{
|
||||
public NodeLatestUpdateFlags State;
|
||||
public Array7<byte> Reserved;
|
||||
}
|
||||
|
||||
static class NodeLatestUpdateHelper
|
||||
{
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static void CalculateLatestUpdate(this Array8<NodeLatestUpdate> array, Array8<NodeInfo> beforeNodes, Array8<NodeInfo> afterNodes)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (beforeNodes[i].IsConnected == 0)
|
||||
{
|
||||
if (afterNodes[i].IsConnected != 0)
|
||||
{
|
||||
array[i].State |= NodeLatestUpdateFlags.Connect;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (afterNodes[i].IsConnected == 0)
|
||||
{
|
||||
array[i].State |= NodeLatestUpdateFlags.Disconnect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static NodeLatestUpdate[] ConsumeLatestUpdate(this Array8<NodeLatestUpdate> array, int number)
|
||||
{
|
||||
NodeLatestUpdate[] result = new NodeLatestUpdate[number];
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
for (int i = 0; i < number; i++)
|
||||
{
|
||||
result[i].Reserved = new Array7<byte>();
|
||||
|
||||
if (i < 8)
|
||||
{
|
||||
result[i].State = array[i].State;
|
||||
array[i].State = NodeLatestUpdateFlags.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[Flags]
|
||||
enum NodeLatestUpdateFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
Connect = 1 << 0,
|
||||
Disconnect = 1 << 1,
|
||||
}
|
||||
}
|
16
src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs
Normal file
16
src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x60)]
|
||||
struct ScanFilter
|
||||
{
|
||||
public NetworkId NetworkId;
|
||||
public NetworkType NetworkType;
|
||||
public Array6<byte> MacAddress;
|
||||
public Ssid Ssid;
|
||||
public Array16<byte> Reserved;
|
||||
public ScanFilterFlag Flag;
|
||||
}
|
||||
}
|
18
src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilterFlag.cs
Normal file
18
src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilterFlag.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[Flags]
|
||||
enum ScanFilterFlag : byte
|
||||
{
|
||||
LocalCommunicationId = 1 << 0,
|
||||
SessionId = 1 << 1,
|
||||
NetworkType = 1 << 2,
|
||||
MacAddress = 1 << 3,
|
||||
Ssid = 1 << 4,
|
||||
SceneId = 1 << 5,
|
||||
IntentId = LocalCommunicationId | SceneId,
|
||||
NetworkId = IntentId | SessionId,
|
||||
All = NetworkType | IntentId | SessionId | MacAddress | Ssid,
|
||||
}
|
||||
}
|
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs
Normal file
13
src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x44)]
|
||||
struct SecurityConfig
|
||||
{
|
||||
public SecurityMode SecurityMode;
|
||||
public ushort PassphraseSize;
|
||||
public Array64<byte> Passphrase;
|
||||
}
|
||||
}
|
9
src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityMode.cs
Normal file
9
src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityMode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
enum SecurityMode : ushort
|
||||
{
|
||||
All,
|
||||
Retail,
|
||||
Debug,
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityParameter.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityParameter.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||
struct SecurityParameter
|
||||
{
|
||||
public Array16<byte> Data;
|
||||
public Array16<byte> SessionId;
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/Ssid.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/Ssid.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x22)]
|
||||
struct Ssid
|
||||
{
|
||||
public byte Length;
|
||||
public Array33<byte> Name;
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x30)]
|
||||
struct UserConfig
|
||||
{
|
||||
public Array33<byte> UserName;
|
||||
public Array15<byte> Unknown1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class AccessPoint : IDisposable
|
||||
{
|
||||
private byte[] _advertiseData;
|
||||
|
||||
private readonly IUserLocalCommunicationService _parent;
|
||||
|
||||
public NetworkInfo NetworkInfo;
|
||||
public Array8<NodeLatestUpdate> LatestUpdates = new();
|
||||
public bool Connected { get; private set; }
|
||||
|
||||
public AccessPoint(IUserLocalCommunicationService parent)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
_parent.NetworkClient.NetworkChange += NetworkChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_parent.NetworkClient.DisconnectNetwork();
|
||||
|
||||
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
||||
}
|
||||
|
||||
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
|
||||
{
|
||||
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);
|
||||
|
||||
NetworkInfo = e.Info;
|
||||
|
||||
if (Connected != e.Connected)
|
||||
{
|
||||
Connected = e.Connected;
|
||||
|
||||
if (Connected)
|
||||
{
|
||||
_parent.SetState(NetworkState.AccessPointCreated);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedBySystem));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent.SetState();
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode SetAdvertiseData(byte[] advertiseData)
|
||||
{
|
||||
_advertiseData = advertiseData;
|
||||
|
||||
_parent.NetworkClient.SetAdvertiseData(_advertiseData);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
|
||||
{
|
||||
_parent.NetworkClient.SetStationAcceptPolicy(acceptPolicy);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig)
|
||||
{
|
||||
CreateAccessPointRequest request = new()
|
||||
{
|
||||
SecurityConfig = securityConfig,
|
||||
UserConfig = userConfig,
|
||||
NetworkConfig = networkConfig,
|
||||
};
|
||||
|
||||
bool success = _parent.NetworkClient.CreateNetwork(request, _advertiseData ?? Array.Empty<byte>());
|
||||
|
||||
return success ? ResultCode.Success : ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
public ResultCode CreateNetworkPrivate(SecurityConfig securityConfig, SecurityParameter securityParameter, UserConfig userConfig, NetworkConfig networkConfig, AddressList addressList)
|
||||
{
|
||||
CreateAccessPointPrivateRequest request = new()
|
||||
{
|
||||
SecurityConfig = securityConfig,
|
||||
SecurityParameter = securityParameter,
|
||||
UserConfig = userConfig,
|
||||
NetworkConfig = networkConfig,
|
||||
AddressList = addressList,
|
||||
};
|
||||
|
||||
bool success = _parent.NetworkClient.CreateNetworkPrivate(request, _advertiseData);
|
||||
|
||||
return success ? ResultCode.Success : ResultCode.InvalidState;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x4FC)]
|
||||
struct ConnectRequest
|
||||
{
|
||||
public SecurityConfig SecurityConfig;
|
||||
public UserConfig UserConfig;
|
||||
public uint LocalCommunicationVersion;
|
||||
public uint OptionUnknown;
|
||||
public NetworkInfo NetworkInfo;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
|
||||
{
|
||||
/// <remarks>
|
||||
/// Advertise data is appended separately (remaining data in the buffer).
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x94, CharSet = CharSet.Ansi)]
|
||||
struct CreateAccessPointRequest
|
||||
{
|
||||
public SecurityConfig SecurityConfig;
|
||||
public UserConfig UserConfig;
|
||||
public NetworkConfig NetworkConfig;
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
{
|
||||
class DisabledLdnClient : INetworkClient
|
||||
{
|
||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
public NetworkError Connect(ConnectRequest request)
|
||||
{
|
||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||
|
||||
return NetworkError.None;
|
||||
}
|
||||
|
||||
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
|
||||
{
|
||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||
|
||||
return NetworkError.None;
|
||||
}
|
||||
|
||||
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
|
||||
{
|
||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
|
||||
{
|
||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DisconnectAndStop() { }
|
||||
|
||||
public void DisconnectNetwork() { }
|
||||
|
||||
public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)
|
||||
{
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
|
||||
{
|
||||
return Array.Empty<NetworkInfo>();
|
||||
}
|
||||
|
||||
public void SetAdvertiseData(byte[] data) { }
|
||||
|
||||
public void SetGameVersion(byte[] versionString) { }
|
||||
|
||||
public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy) { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
{
|
||||
interface INetworkClient : IDisposable
|
||||
{
|
||||
event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
void DisconnectNetwork();
|
||||
void DisconnectAndStop();
|
||||
NetworkError Connect(ConnectRequest request);
|
||||
NetworkError ConnectPrivate(ConnectPrivateRequest request);
|
||||
ResultCode Reject(DisconnectReason disconnectReason, uint nodeId);
|
||||
NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter);
|
||||
void SetGameVersion(byte[] versionString);
|
||||
void SetStationAcceptPolicy(AcceptPolicy acceptPolicy);
|
||||
void SetAdvertiseData(byte[] data);
|
||||
bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData);
|
||||
bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
{
|
||||
class NetworkChangeEventArgs : EventArgs
|
||||
{
|
||||
public NetworkInfo Info;
|
||||
public bool Connected;
|
||||
public DisconnectReason DisconnectReason;
|
||||
|
||||
public NetworkChangeEventArgs(NetworkInfo info, bool connected, DisconnectReason disconnectReason = DisconnectReason.None)
|
||||
{
|
||||
Info = info;
|
||||
Connected = connected;
|
||||
DisconnectReason = disconnectReason;
|
||||
}
|
||||
|
||||
public DisconnectReason DisconnectReasonOrDefault(DisconnectReason defaultReason)
|
||||
{
|
||||
return DisconnectReason == DisconnectReason.None ? defaultReason : DisconnectReason;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xBC)]
|
||||
struct ConnectPrivateRequest
|
||||
{
|
||||
public SecurityConfig SecurityConfig;
|
||||
public SecurityParameter SecurityParameter;
|
||||
public UserConfig UserConfig;
|
||||
public uint LocalCommunicationVersion;
|
||||
public uint OptionUnknown;
|
||||
public NetworkConfig NetworkConfig;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
{
|
||||
/// <remarks>
|
||||
/// Advertise data is appended separately (remaining data in the buffer).
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x13C, Pack = 1)]
|
||||
struct CreateAccessPointPrivateRequest
|
||||
{
|
||||
public SecurityConfig SecurityConfig;
|
||||
public SecurityParameter SecurityParameter;
|
||||
public UserConfig UserConfig;
|
||||
public NetworkConfig NetworkConfig;
|
||||
public AddressList AddressList;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
{
|
||||
enum NetworkError : int
|
||||
{
|
||||
None,
|
||||
|
||||
PortUnreachable,
|
||||
|
||||
TooManyPlayers,
|
||||
VersionTooLow,
|
||||
VersionTooHigh,
|
||||
|
||||
ConnectFailure,
|
||||
ConnectNotFound,
|
||||
ConnectTimeout,
|
||||
ConnectRejected,
|
||||
|
||||
RejectFailed,
|
||||
|
||||
Unknown = -1,
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x4)]
|
||||
struct NetworkErrorMessage
|
||||
{
|
||||
public NetworkError Error;
|
||||
}
|
||||
}
|
115
src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs
Normal file
115
src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class Station : IDisposable
|
||||
{
|
||||
public NetworkInfo NetworkInfo;
|
||||
public Array8<NodeLatestUpdate> LatestUpdates = new();
|
||||
|
||||
private readonly IUserLocalCommunicationService _parent;
|
||||
|
||||
public bool Connected { get; private set; }
|
||||
|
||||
public Station(IUserLocalCommunicationService parent)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
_parent.NetworkClient.NetworkChange += NetworkChanged;
|
||||
}
|
||||
|
||||
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
|
||||
{
|
||||
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);
|
||||
|
||||
NetworkInfo = e.Info;
|
||||
|
||||
if (Connected != e.Connected)
|
||||
{
|
||||
Connected = e.Connected;
|
||||
|
||||
if (Connected)
|
||||
{
|
||||
_parent.SetState(NetworkState.StationConnected);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedByUser));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent.SetState();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_parent.NetworkClient.DisconnectNetwork();
|
||||
|
||||
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
||||
}
|
||||
|
||||
private ResultCode NetworkErrorToResult(NetworkError error)
|
||||
{
|
||||
return error switch
|
||||
{
|
||||
NetworkError.None => ResultCode.Success,
|
||||
NetworkError.VersionTooLow => ResultCode.VersionTooLow,
|
||||
NetworkError.VersionTooHigh => ResultCode.VersionTooHigh,
|
||||
NetworkError.TooManyPlayers => ResultCode.TooManyPlayers,
|
||||
|
||||
NetworkError.ConnectFailure => ResultCode.ConnectFailure,
|
||||
NetworkError.ConnectNotFound => ResultCode.ConnectNotFound,
|
||||
NetworkError.ConnectTimeout => ResultCode.ConnectTimeout,
|
||||
NetworkError.ConnectRejected => ResultCode.ConnectRejected,
|
||||
|
||||
_ => ResultCode.DeviceNotAvailable,
|
||||
};
|
||||
}
|
||||
|
||||
public ResultCode Connect(
|
||||
SecurityConfig securityConfig,
|
||||
UserConfig userConfig,
|
||||
uint localCommunicationVersion,
|
||||
uint optionUnknown,
|
||||
NetworkInfo networkInfo)
|
||||
{
|
||||
ConnectRequest request = new()
|
||||
{
|
||||
SecurityConfig = securityConfig,
|
||||
UserConfig = userConfig,
|
||||
LocalCommunicationVersion = localCommunicationVersion,
|
||||
OptionUnknown = optionUnknown,
|
||||
NetworkInfo = networkInfo,
|
||||
};
|
||||
|
||||
return NetworkErrorToResult(_parent.NetworkClient.Connect(request));
|
||||
}
|
||||
|
||||
public ResultCode ConnectPrivate(
|
||||
SecurityConfig securityConfig,
|
||||
SecurityParameter securityParameter,
|
||||
UserConfig userConfig,
|
||||
uint localCommunicationVersion,
|
||||
uint optionUnknown,
|
||||
NetworkConfig networkConfig)
|
||||
{
|
||||
ConnectPrivateRequest request = new()
|
||||
{
|
||||
SecurityConfig = securityConfig,
|
||||
SecurityParameter = securityParameter,
|
||||
UserConfig = userConfig,
|
||||
LocalCommunicationVersion = localCommunicationVersion,
|
||||
OptionUnknown = optionUnknown,
|
||||
NetworkConfig = networkConfig,
|
||||
};
|
||||
|
||||
return NetworkErrorToResult(_parent.NetworkClient.ConnectPrivate(request));
|
||||
}
|
||||
}
|
||||
}
|
@@ -320,7 +320,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiFacelineColorRawArray => new byte[]
|
||||
@@ -399,8 +399,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
, };
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiFacelineWrinkleRawArray => new byte[]
|
||||
{
|
||||
@@ -633,8 +633,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
, };
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiFacelineMakeRawArray => new byte[]
|
||||
{
|
||||
@@ -867,8 +867,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
, };
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiHairTypeRawArray => new byte[]
|
||||
{
|
||||
@@ -1101,8 +1101,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
, };
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiHairColorRawArray => new byte[]
|
||||
{
|
||||
@@ -1218,8 +1218,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
, };
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiEyeTypeRawArray => new byte[]
|
||||
{
|
||||
@@ -1452,8 +1452,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
,};
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiEyeColorRawArray => new byte[]
|
||||
{
|
||||
@@ -1493,7 +1493,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiEyebrowTypeRawArray => new byte[]
|
||||
@@ -1727,7 +1727,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiNoseTypeRawArray => new byte[]
|
||||
@@ -1961,7 +1961,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiMouthTypeRawArray => new byte[]
|
||||
@@ -2195,7 +2195,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
private static ReadOnlySpan<byte> RandomMiiGlassTypeRawArray => new byte[]
|
||||
@@ -2236,7 +2236,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
|
@@ -151,11 +151,15 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
SwapBuffers();
|
||||
|
||||
if (IsFullscreen)
|
||||
if (IsExclusiveFullscreen)
|
||||
{
|
||||
Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||
MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||
}
|
||||
else if (IsFullscreen)
|
||||
{
|
||||
// NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow.
|
||||
// we might have to amend this if people run this on a non-primary display set to a different resolution.
|
||||
if (SDL_GetDisplayBounds(0, out SDL_Rect displayBounds) < 0)
|
||||
if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}");
|
||||
|
||||
|
@@ -14,9 +14,21 @@ namespace Ryujinx.Headless.SDL2
|
||||
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
|
||||
public string UserProfile { get; set; }
|
||||
|
||||
[Option("fullscreen", Required = false, HelpText = "Launch the game in fullscreen mode.")]
|
||||
[Option("display-id", Required = false, Default = 0, HelpText = "Set the display to use - especially helpful for fullscreen mode. [0-n]")]
|
||||
public int DisplayId { get; set; }
|
||||
|
||||
[Option("fullscreen", Required = false, Default = false, HelpText = "Launch the game in fullscreen mode.")]
|
||||
public bool IsFullscreen { get; set; }
|
||||
|
||||
[Option("exclusive-fullscreen", Required = false, Default = false, HelpText = "Launch the game in exclusive fullscreen mode.")]
|
||||
public bool IsExclusiveFullscreen { get; set; }
|
||||
|
||||
[Option("exclusive-fullscreen-width", Required = false, Default = 1920, HelpText = "Set horizontal resolution for exclusive fullscreen mode.")]
|
||||
public int ExclusiveFullscreenWidth { get; set; }
|
||||
|
||||
[Option("exclusive-fullscreen-height", Required = false, Default = 1080, HelpText = "Set vertical resolution for exclusive fullscreen mode.")]
|
||||
public int ExclusiveFullscreenHeight { get; set; }
|
||||
|
||||
// Input
|
||||
|
||||
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
|
||||
@@ -196,6 +208,15 @@ namespace Ryujinx.Headless.SDL2
|
||||
[Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")]
|
||||
public string PreferredGPUVendor { get; set; }
|
||||
|
||||
[Option("anti-aliasing", Required = false, Default = AntiAliasing.None, HelpText = "Set the type of anti aliasing being used. [None|Fxaa|SmaaLow|SmaaMedium|SmaaHigh|SmaaUltra]")]
|
||||
public AntiAliasing AntiAliasing { get; set; }
|
||||
|
||||
[Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr]")]
|
||||
public ScalingFilter ScalingFilter { get; set; }
|
||||
|
||||
[Option("scaling-filter-level", Required = false, Default = 0, HelpText = "Set the scaling filter intensity (currently only applies to FSR). [0-100]")]
|
||||
public int ScalingFilterLevel { get; set; }
|
||||
|
||||
// Hacks
|
||||
|
||||
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]
|
||||
|
@@ -556,7 +556,8 @@ namespace Ryujinx.Headless.SDL2
|
||||
options.AspectRatio,
|
||||
options.AudioVolume,
|
||||
options.UseHypervisor ?? true,
|
||||
options.MultiplayerLanInterfaceId);
|
||||
options.MultiplayerLanInterfaceId,
|
||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
|
||||
|
||||
return new Switch(configuration);
|
||||
}
|
||||
@@ -596,6 +597,13 @@ namespace Ryujinx.Headless.SDL2
|
||||
_window = window;
|
||||
|
||||
_window.IsFullscreen = options.IsFullscreen;
|
||||
_window.DisplayId = options.DisplayId;
|
||||
_window.IsExclusiveFullscreen = options.IsExclusiveFullscreen;
|
||||
_window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth;
|
||||
_window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight;
|
||||
_window.AntiAliasing = options.AntiAliasing;
|
||||
_window.ScalingFilter = options.ScalingFilter;
|
||||
_window.ScalingFilterLevel = options.ScalingFilterLevel;
|
||||
|
||||
_emulationContext = InitializeEmulationContext(window, renderer, options);
|
||||
|
||||
|
@@ -29,8 +29,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan
|
||||
|
||||
protected override void InitializeRenderer()
|
||||
{
|
||||
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||
if (IsExclusiveFullscreen)
|
||||
{
|
||||
Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||
MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BasicInvoke(Action action)
|
||||
|
@@ -20,6 +20,8 @@ using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using static SDL2.SDL;
|
||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
|
||||
namespace Ryujinx.Headless.SDL2
|
||||
@@ -28,8 +30,9 @@ namespace Ryujinx.Headless.SDL2
|
||||
{
|
||||
protected const int DefaultWidth = 1280;
|
||||
protected const int DefaultHeight = 720;
|
||||
private const SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN;
|
||||
private const int TargetFps = 60;
|
||||
private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN;
|
||||
private SDL_WindowFlags FullscreenFlag = 0;
|
||||
|
||||
private static readonly ConcurrentQueue<Action> _mainThreadActions = new();
|
||||
|
||||
@@ -54,7 +57,14 @@ namespace Ryujinx.Headless.SDL2
|
||||
public IHostUiTheme HostUiTheme { get; }
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public int DisplayId { get; set; }
|
||||
public bool IsFullscreen { get; set; }
|
||||
public bool IsExclusiveFullscreen { get; set; }
|
||||
public int ExclusiveFullscreenWidth { get; set; }
|
||||
public int ExclusiveFullscreenHeight { get; set; }
|
||||
public AntiAliasing AntiAliasing { get; set; }
|
||||
public ScalingFilter ScalingFilter { get; set; }
|
||||
public int ScalingFilterLevel { get; set; }
|
||||
|
||||
protected SDL2MouseDriver MouseDriver;
|
||||
private readonly InputManager _inputManager;
|
||||
@@ -158,9 +168,24 @@ namespace Ryujinx.Headless.SDL2
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
SDL_WindowFlags fullscreenFlag = IsFullscreen ? SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
|
||||
Width = DefaultWidth;
|
||||
Height = DefaultHeight;
|
||||
|
||||
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | fullscreenFlag | GetWindowFlags());
|
||||
if (IsExclusiveFullscreen)
|
||||
{
|
||||
Width = ExclusiveFullscreenWidth;
|
||||
Height = ExclusiveFullscreenHeight;
|
||||
|
||||
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else if (IsFullscreen)
|
||||
{
|
||||
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags());
|
||||
|
||||
if (WindowHandle == IntPtr.Zero)
|
||||
{
|
||||
@@ -175,9 +200,6 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
_windowId = SDL_GetWindowID(WindowHandle);
|
||||
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
|
||||
|
||||
Width = DefaultWidth;
|
||||
Height = DefaultHeight;
|
||||
}
|
||||
|
||||
private void HandleWindowEvent(SDL_Event evnt)
|
||||
@@ -189,8 +211,8 @@ namespace Ryujinx.Headless.SDL2
|
||||
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
|
||||
// And promptly crashes the process because `Renderer?.window.SetSize` is undefined.
|
||||
// As we don't need this to fire in either case we can test for isFullscreen.
|
||||
if (!IsFullscreen)
|
||||
// As we don't need this to fire in either case we can test for fullscreen.
|
||||
if (!IsFullscreen && !IsExclusiveFullscreen)
|
||||
{
|
||||
Width = evnt.window.data1;
|
||||
Height = evnt.window.data2;
|
||||
@@ -225,6 +247,17 @@ namespace Ryujinx.Headless.SDL2
|
||||
return Renderer.GetHardwareInfo().GpuVendor;
|
||||
}
|
||||
|
||||
private void SetAntiAliasing()
|
||||
{
|
||||
Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)AntiAliasing);
|
||||
}
|
||||
|
||||
private void SetScalingFilter()
|
||||
{
|
||||
Renderer?.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ScalingFilter);
|
||||
Renderer?.Window.SetScalingFilterLevel(ScalingFilterLevel);
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
InitializeWindowRenderer();
|
||||
@@ -233,6 +266,10 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
InitializeRenderer();
|
||||
|
||||
SetAntiAliasing();
|
||||
|
||||
SetScalingFilter();
|
||||
|
||||
_gpuVendorName = GetGpuVendorName();
|
||||
|
||||
Device.Gpu.Renderer.RunLoop(() =>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Configuration.System;
|
||||
@@ -360,6 +361,11 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
/// </summary>
|
||||
public string PreferredGpu { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiplayer Mode
|
||||
/// </summary>
|
||||
public MultiplayerMode MultiplayerMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// GUID for the network interface used by LAN (or 0 for default)
|
||||
/// </summary>
|
||||
|
@@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Ui.Common.Configuration.System;
|
||||
using Ryujinx.Ui.Common.Configuration.Ui;
|
||||
@@ -561,9 +562,15 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<string> LanInterfaceId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiplayer Mode
|
||||
/// </summary>
|
||||
public ReactiveObject<MultiplayerMode> Mode { get; private set; }
|
||||
|
||||
public MultiplayerSection()
|
||||
{
|
||||
LanInterfaceId = new ReactiveObject<string>();
|
||||
Mode = new ReactiveObject<MultiplayerMode>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,6 +748,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
GraphicsBackend = Graphics.GraphicsBackend,
|
||||
PreferredGpu = Graphics.PreferredGpu,
|
||||
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
|
||||
MultiplayerMode = Multiplayer.Mode,
|
||||
};
|
||||
|
||||
return configurationFile;
|
||||
@@ -795,6 +803,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
System.IgnoreMissingServices.Value = false;
|
||||
System.UseHypervisor.Value = true;
|
||||
Multiplayer.LanInterfaceId.Value = "0";
|
||||
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
||||
Ui.GuiColumns.FavColumn.Value = true;
|
||||
Ui.GuiColumns.IconColumn.Value = true;
|
||||
Ui.GuiColumns.AppColumn.Value = true;
|
||||
@@ -1003,6 +1012,8 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
// configurationFileFormat.Version == 13 -> LDN1
|
||||
|
||||
if (configurationFileFormat.Version < 14)
|
||||
{
|
||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 14.");
|
||||
@@ -1039,6 +1050,8 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
// configurationFileFormat.Version == 19 -> LDN2
|
||||
|
||||
if (configurationFileFormat.Version < 20)
|
||||
{
|
||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 20.");
|
||||
@@ -1048,6 +1061,18 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
if (configurationFileFormat.Version < 21)
|
||||
{
|
||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 21.");
|
||||
|
||||
// Initialize network config.
|
||||
|
||||
configurationFileFormat.MultiplayerMode = MultiplayerMode.Disabled;
|
||||
configurationFileFormat.MultiplayerLanInterfaceId = "0";
|
||||
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
if (configurationFileFormat.Version < 22)
|
||||
{
|
||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 22.");
|
||||
@@ -1501,6 +1526,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||
}
|
||||
|
||||
Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId;
|
||||
Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode;
|
||||
|
||||
if (configurationFileUpdated)
|
||||
{
|
||||
|
@@ -13,6 +13,7 @@ using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Cpu;
|
||||
@@ -207,6 +208,9 @@ namespace Ryujinx.Ui
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
||||
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerMode;
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateMultiplayerLanInterfaceId;
|
||||
|
||||
if (ConfigurationState.Instance.Ui.StartFullscreen)
|
||||
{
|
||||
_startFullScreen.Active = true;
|
||||
@@ -331,6 +335,22 @@ namespace Ryujinx.Ui
|
||||
InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver());
|
||||
}
|
||||
|
||||
private void UpdateMultiplayerLanInterfaceId(object sender, ReactiveEventArgs<string> args)
|
||||
{
|
||||
if (_emulationContext != null)
|
||||
{
|
||||
_emulationContext.Configuration.MultiplayerLanInterfaceId = args.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMultiplayerMode(object sender, ReactiveEventArgs<MultiplayerMode> args)
|
||||
{
|
||||
if (_emulationContext != null)
|
||||
{
|
||||
_emulationContext.Configuration.MultiplayerMode = args.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
|
||||
{
|
||||
if (_emulationContext != null)
|
||||
@@ -649,7 +669,8 @@ namespace Ryujinx.Ui
|
||||
ConfigurationState.Instance.Graphics.AspectRatio,
|
||||
ConfigurationState.Instance.System.AudioVolume,
|
||||
ConfigurationState.Instance.System.UseHypervisor,
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
||||
ConfigurationState.Instance.Multiplayer.Mode);
|
||||
|
||||
_emulationContext = new HLE.Switch(configuration);
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ using Ryujinx.Audio.Backends.SDL2;
|
||||
using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
@@ -18,6 +19,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
@@ -87,6 +89,7 @@ namespace Ryujinx.Ui.Windows
|
||||
[GUI] Adjustment _systemTimeHourSpinAdjustment;
|
||||
[GUI] Adjustment _systemTimeMinuteSpinAdjustment;
|
||||
[GUI] ComboBoxText _multiLanSelect;
|
||||
[GUI] ComboBoxText _multiModeSelect;
|
||||
[GUI] CheckButton _custThemeToggle;
|
||||
[GUI] Entry _custThemePath;
|
||||
[GUI] ToggleButton _browseThemePath;
|
||||
@@ -361,6 +364,7 @@ namespace Ryujinx.Ui.Windows
|
||||
_graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox();
|
||||
PopulateNetworkInterfaces();
|
||||
_multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||
_multiModeSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.Mode.Value.ToString());
|
||||
|
||||
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
|
||||
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
|
||||
@@ -658,6 +662,9 @@ namespace Ryujinx.Ui.Windows
|
||||
|
||||
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;
|
||||
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Value = Enum.Parse<MultiplayerMode>(_multiModeSelect.ActiveId);
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId;
|
||||
|
||||
if (_audioBackendSelect.GetActiveIter(out TreeIter activeIter))
|
||||
{
|
||||
ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1);
|
||||
|
@@ -2933,6 +2933,96 @@
|
||||
<property name="margin-right">10</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="CatMultiplayer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="label" translatable="yes">Multiplayer</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="MultiplayerOptions">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin-left">10</property>
|
||||
<property name="margin-right">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="ModeBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Change Multiplayer Mode</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Mode:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="_multiModeSelect">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Change Multiplayer Mode</property>
|
||||
<property name="active-id">Disabled</property>
|
||||
<items>
|
||||
<item id="Disabled" translatable="yes">Disabled</item>
|
||||
</items>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="CatLAN">
|
||||
<property name="visible">True</property>
|
||||
|
Reference in New Issue
Block a user