Merge branch 'Ryujinx:master' into master

This commit is contained in:
Logan Stromberg 2021-12-30 17:04:29 -08:00 committed by GitHub
commit e2553e6e40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 836 additions and 265 deletions

View file

@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL
public bool HasVectorIndexingBug { get; }
public bool SupportsAstcCompression { get; }
public bool SupportsR4G4Format { get; }
public bool SupportsFragmentShaderInterlock { get; }
public bool SupportsFragmentShaderOrderingIntel { get; }
public bool SupportsImageLoadFormatted { get; }
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.GAL
bool hasFrontFacingBug,
bool hasVectorIndexingBug,
bool supportsAstcCompression,
bool supportsR4G4Format,
bool supportsFragmentShaderInterlock,
bool supportsFragmentShaderOrderingIntel,
bool supportsImageLoadFormatted,
@ -40,6 +42,7 @@ namespace Ryujinx.Graphics.GAL
HasFrontFacingBug = hasFrontFacingBug;
HasVectorIndexingBug = hasVectorIndexingBug;
SupportsAstcCompression = supportsAstcCompression;
SupportsR4G4Format = supportsR4G4Format;
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsImageLoadFormatted = supportsImageLoadFormatted;

View file

@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.GAL
D32FloatS8Uint,
R8G8B8X8Srgb,
R8G8B8A8Srgb,
R4G4Unorm,
R4G4B4A4Unorm,
R5G5B5X1Unorm,
R5G5B5A1Unorm,

View file

@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) },
{ 0x2491e, new FormatInfo(Format.R4G4Unorm, 1, 1, 1, 2) },
{ 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) },
{ 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) },
{ 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) },

View file

@ -785,7 +785,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards.
// - BC4/BC5 is not supported on 3D textures.
if (!_context.Capabilities.SupportsAstcCompression && Info.FormatInfo.Format.IsAstc())
if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc())
{
if (!AstcDecoder.TryDecodeToRgba8P(
data.ToArray(),
@ -805,11 +805,15 @@ namespace Ryujinx.Graphics.Gpu.Image
data = decoded;
}
else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc4())
else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm)
{
data = PixelConverter.ConvertR4G4ToR4G4B4A4(data);
}
else if (Target == Target.Texture3D && Format.IsBc4())
{
data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc4Snorm);
}
else if (Target == Target.Texture3D && Info.FormatInfo.Format.IsBc5())
else if (Target == Target.Texture3D && Format.IsBc5())
{
data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Info.FormatInfo.Format == Format.Bc5Snorm);
}

View file

@ -66,6 +66,11 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm)
{
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
}
if (info.Target == Target.Texture3D)
{
// The host API does not support 3D BC4/BC5 compressed formats.

View file

@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.OpenGL
Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed));
Add(Format.B5G5R5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed));
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Bgra, PixelType.UnsignedShort1555Reversed));
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551));
Add(Format.B8G8R8X8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
Add(Format.B8G8R8X8Srgb, new FormatInfo(4, false, false, All.Srgb8, PixelFormat.Rgba, PixelType.UnsignedByte));

View file

@ -72,7 +72,16 @@ namespace Ryujinx.Graphics.OpenGL.Image
(int)Info.SwizzleA.Convert()
};
if (Info.Format.IsBgr())
if (Info.Format == Format.A1B5G5R5Unorm)
{
int temp = swizzleRgba[0];
int temp2 = swizzleRgba[1];
swizzleRgba[0] = swizzleRgba[3];
swizzleRgba[1] = swizzleRgba[2];
swizzleRgba[2] = temp2;
swizzleRgba[3] = temp;
}
else if (Info.Format.IsBgr())
{
// Swap B <-> R for BGRA formats, as OpenGL has no support for them
// and we need to manually swap the components on read/write on the GPU.

View file

@ -101,21 +101,22 @@ namespace Ryujinx.Graphics.OpenGL
public Capabilities GetCapabilities()
{
return new Capabilities(
HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
HwCapabilities.SupportsAstcCompression,
HwCapabilities.SupportsFragmentShaderInterlock,
HwCapabilities.SupportsFragmentShaderOrdering,
HwCapabilities.SupportsImageLoadFormatted,
HwCapabilities.SupportsMismatchingViewFormat,
HwCapabilities.SupportsNonConstantTextureOffset,
HwCapabilities.SupportsShaderBallot,
HwCapabilities.SupportsTextureShadowLod,
HwCapabilities.SupportsViewportSwizzle,
HwCapabilities.SupportsIndirectParameters,
HwCapabilities.MaximumComputeSharedMemorySize,
HwCapabilities.MaximumSupportedAnisotropy,
HwCapabilities.StorageBufferOffsetAlignment);
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
supportsR4G4Format: false,
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment);
}
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)

View file

@ -0,0 +1,39 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.Graphics.Texture
{
public static class PixelConverter
{
public unsafe static byte[] ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data)
{
byte[] output = new byte[data.Length * 2];
int start = 0;
if (Sse41.IsSupported)
{
int sizeTrunc = data.Length & ~7;
start = sizeTrunc;
fixed (byte* inputPtr = data, outputPtr = output)
{
for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8)
{
Sse2.Store(outputPtr + offset * 2, Sse41.ConvertToVector128Int16(inputPtr + offset).AsByte());
}
}
}
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output);
for (int i = start; i < data.Length; i++)
{
outputSpan[i] = (ushort)data[i];
}
return output;
}
}
}

View file

@ -46,6 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public KAddressArbiter AddressArbiter { get; private set; }
public long[] RandomEntropy { get; private set; }
public KThread[] PinnedThreads { get; private set; }
private bool _signaled;
@ -102,6 +103,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Capabilities = new KProcessCapabilities();
RandomEntropy = new long[KScheduler.CpuCoresCount];
PinnedThreads = new KThread[KScheduler.CpuCoresCount];
// TODO: Remove once we no longer need to initialize it externally.
HandleTable = new KHandleTable(context);
@ -749,7 +751,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.IsSchedulable)
if (currentThread.Owner != null &&
currentThread.GetUserDisableCount() != 0 &&
currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
{
KernelContext.CriticalSection.Enter();
currentThread.Owner.PinThread(currentThread);
currentThread.SetUserInterruptFlag();
if (currentThread.IsSchedulable)
{
KernelContext.Schedulers[currentThread.CurrentCore].Schedule();
}
KernelContext.CriticalSection.Leave();
}
else if (currentThread.IsSchedulable)
{
KernelContext.Schedulers[currentThread.CurrentCore].Schedule();
}
@ -952,6 +971,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
KernelContext.CriticalSection.Enter();
if (currentThread != null && PinnedThreads[currentThread.CurrentCore] == currentThread)
{
UnpinThread(currentThread);
}
foreach (KThread thread in _threads)
{
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
@ -1139,5 +1163,35 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.InvalidState;
}
public void PinThread(KThread thread)
{
if (!thread.TerminationRequested)
{
PinnedThreads[thread.CurrentCore] = thread;
thread.Pin();
KernelContext.ThreadReselectionRequested = true;
}
}
public void UnpinThread(KThread thread)
{
if (!thread.TerminationRequested)
{
thread.Unpin();
PinnedThreads[thread.CurrentCore] = null;
KernelContext.ThreadReselectionRequested = true;
}
}
public bool IsExceptionUserThread(KThread thread)
{
// TODO
return false;
}
}
}

View file

@ -2655,6 +2655,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
};
}
public KernelResult SynchronizePreemptionState()
{
KernelStatic.GetCurrentThread().SynchronizePreemptionState();
return KernelResult.Success;
}
private bool IsPointingInsideKernel(ulong address)
{
return (address + 0x1000000000) < 0xffffff000;

View file

@ -491,5 +491,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
return _syscall.SignalToAddress(address, type, value, count);
}
public KernelResult SynchronizePreemptionState32()
{
return _syscall.SynchronizePreemptionState();
}
}
}

View file

@ -405,5 +405,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
return _syscall.SignalToAddress(address, type, value, count);
}
public KernelResult SynchronizePreemptionState64()
{
return _syscall.SynchronizePreemptionState();
}
}
}

View file

@ -19,7 +19,22 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
public void SvcCall(object sender, InstExceptionEventArgs e)
{
ExecutionContext context = (ExecutionContext)sender;
KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.Owner != null &&
currentThread.GetUserDisableCount() != 0 &&
currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
{
_context.CriticalSection.Enter();
currentThread.Owner.PinThread(currentThread);
currentThread.SetUserInterruptFlag();
_context.CriticalSection.Leave();
}
ExecutionContext context = (ExecutionContext)sender;
if (context.IsAarch32)
{
@ -44,13 +59,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
svcFunc(_syscall64, context);
}
PostSvcHandler();
}
private void PostSvcHandler()
{
KThread currentThread = KernelStatic.GetCurrentThread();
currentThread.HandlePostSyscall();
}
}

View file

@ -71,6 +71,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x33, nameof(Syscall64.GetThreadContext364) },
{ 0x34, nameof(Syscall64.WaitForAddress64) },
{ 0x35, nameof(Syscall64.SignalToAddress64) },
{ 0x36, nameof(Syscall64.SynchronizePreemptionState64) },
{ 0x37, nameof(Syscall64.GetResourceLimitPeakValue64) },
{ 0x40, nameof(Syscall64.CreateSession64) },
{ 0x41, nameof(Syscall64.AcceptSession64) },
@ -145,6 +146,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x33, nameof(Syscall32.GetThreadContext332) },
{ 0x34, nameof(Syscall32.WaitForAddress32) },
{ 0x35, nameof(Syscall32.SignalToAddress32) },
{ 0x36, nameof(Syscall32.SynchronizePreemptionState32) },
{ 0x37, nameof(Syscall32.GetResourceLimitPeakValue32) },
{ 0x40, nameof(Syscall32.CreateSession32) },
{ 0x41, nameof(Syscall32.AcceptSession32) },

View file

@ -36,6 +36,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly KThread _idleThread;
public KThread PreviousThread => _previousThread;
public KThread CurrentThread => _currentThread;
public long LastContextSwitchTime { get; private set; }
public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
@ -87,6 +88,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
if (thread != null &&
thread.Owner != null &&
thread.Owner.PinnedThreads[core] != null &&
thread.Owner.PinnedThreads[core] != thread)
{
KThread candidate = thread.Owner.PinnedThreads[core];
if (candidate.KernelWaitersCount == 0 && !thread.Owner.IsExceptionUserThread(candidate))
{
if (candidate.SchedFlags == ThreadSchedState.Running)
{
thread = candidate;
}
else
{
thread = null;
}
}
}
scheduledCoresMask |= context.Schedulers[core].SelectThread(thread);
}

View file

@ -11,6 +11,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
class KThread : KSynchronizationObject, IKFutureSchedulerObject
{
private const int TlsUserDisableCountOffset = 0x100;
private const int TlsUserInterruptFlagOffset = 0x102;
public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent;
@ -43,6 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable;
public ulong MutexAddress { get; set; }
public int KernelWaitersCount { get; private set; }
public KProcess Owner { get; private set; }
@ -65,11 +69,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private LinkedList<KThread> _mutexWaiters;
private LinkedListNode<KThread> _mutexWaiterNode;
private LinkedList<KThread> _pinnedWaiters;
public KThread MutexOwner { get; private set; }
public int ThreadHandleForUserMutex { get; set; }
private ThreadSchedState _forcePauseFlags;
private ThreadSchedState _forcePausePermissionFlags;
public KernelResult ObjSyncResult { get; set; }
@ -79,11 +86,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public int CurrentCore { get; set; }
public int ActiveCore { get; set; }
private long _affinityMaskOverride;
private int _preferredCoreOverride;
#pragma warning disable CS0649
private int _affinityOverrideCount;
#pragma warning restore CS0649
public bool IsPinned { get; private set; }
private long _originalAffinityMask;
private int _originalPreferredCore;
private int _originalBasePriority;
private int _coreMigrationDisableCount;
public ThreadSchedState SchedFlags { get; private set; }
@ -108,6 +116,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public long LastPc { get; set; }
private object ActivityOperationLock = new object();
public KThread(KernelContext context) : base(context)
{
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@ -116,6 +126,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
_mutexWaiters = new LinkedList<KThread>();
_pinnedWaiters = new LinkedList<KThread>();
}
public KernelResult Initialize(
@ -147,6 +158,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
DynamicPriority = priority;
BasePriority = priority;
CurrentCore = cpuCore;
IsPinned = false;
_entrypoint = entrypoint;
_customThreadStart = customThreadStart;
@ -204,6 +216,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_hasBeenInitialized = true;
_forcePausePermissionFlags = ThreadSchedState.ForcePauseMask;
if (owner != null)
{
owner.SubscribeThreadEventHandlers(Context);
@ -301,6 +315,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
KernelContext.CriticalSection.Enter();
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
{
Owner.UnpinThread(this);
}
ThreadSchedState result;
if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0)
@ -405,6 +424,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
KernelContext.CriticalSection.Enter();
_forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
_forcePausePermissionFlags = 0;
bool decRef = ExitImpl();
@ -433,6 +453,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return decRef;
}
private int GetEffectiveRunningCore()
{
for (int coreNumber = 0; coreNumber < KScheduler.CpuCoresCount; coreNumber++)
{
if (KernelContext.Schedulers[coreNumber].CurrentThread == this)
{
return coreNumber;
}
}
return -1;
}
public KernelResult Sleep(long timeout)
{
KernelContext.CriticalSection.Enter();
@ -465,7 +498,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
KernelContext.CriticalSection.Enter();
BasePriority = priority;
if (IsPinned)
{
_originalBasePriority = priority;
}
else
{
BasePriority = priority;
}
UpdatePriorityInheritance();
@ -497,53 +537,96 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KernelResult SetActivity(bool pause)
{
KernelResult result = KernelResult.Success;
KernelContext.CriticalSection.Enter();
ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running)
lock (ActivityOperationLock)
{
KernelResult result = KernelResult.Success;
KernelContext.CriticalSection.Enter();
ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running)
{
KernelContext.CriticalSection.Leave();
return KernelResult.InvalidState;
}
if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
{
if (pause)
{
// Pause, the force pause flag should be clear (thread is NOT paused).
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
{
Suspend(ThreadSchedState.ThreadPauseFlag);
}
else
{
result = KernelResult.InvalidState;
}
}
else
{
// Unpause, the force pause flag should be set (thread is paused).
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
{
Resume(ThreadSchedState.ThreadPauseFlag);
}
else
{
result = KernelResult.InvalidState;
}
}
}
KernelContext.CriticalSection.Leave();
return KernelResult.InvalidState;
}
KernelContext.CriticalSection.Enter();
if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
{
if (pause)
if (result == KernelResult.Success && pause)
{
// Pause, the force pause flag should be clear (thread is NOT paused).
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
bool isThreadRunning = true;
while (isThreadRunning)
{
Suspend(ThreadSchedState.ThreadPauseFlag);
}
else
{
result = KernelResult.InvalidState;
}
}
else
{
// Unpause, the force pause flag should be set (thread is paused).
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
{
Resume(ThreadSchedState.ThreadPauseFlag);
}
else
{
result = KernelResult.InvalidState;
KernelContext.CriticalSection.Enter();
if (TerminationRequested)
{
KernelContext.CriticalSection.Leave();
break;
}
isThreadRunning = false;
if (IsPinned)
{
KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.TerminationRequested)
{
KernelContext.CriticalSection.Leave();
result = KernelResult.ThreadTerminating;
break;
}
_pinnedWaiters.AddLast(currentThread);
currentThread.Reschedule(ThreadSchedState.Paused);
}
else
{
isThreadRunning = GetEffectiveRunningCore() >= 0;
}
KernelContext.CriticalSection.Leave();
}
}
return result;
}
KernelContext.CriticalSection.Leave();
KernelContext.CriticalSection.Leave();
return result;
}
public void CancelSynchronization()
@ -579,58 +662,105 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask)
{
KernelContext.CriticalSection.Enter();
bool useOverride = _affinityOverrideCount != 0;
// The value -3 is "do not change the preferred core".
if (newCore == -3)
lock (ActivityOperationLock)
{
newCore = useOverride ? _preferredCoreOverride : PreferredCore;
KernelContext.CriticalSection.Enter();
if ((newAffinityMask & (1 << newCore)) == 0)
bool isCoreMigrationDisabled = _coreMigrationDisableCount != 0;
// The value -3 is "do not change the preferred core".
if (newCore == -3)
{
KernelContext.CriticalSection.Leave();
newCore = isCoreMigrationDisabled ? _originalPreferredCore : PreferredCore;
return KernelResult.InvalidCombination;
}
}
if (useOverride)
{
_preferredCoreOverride = newCore;
_affinityMaskOverride = newAffinityMask;
}
else
{
long oldAffinityMask = AffinityMask;
PreferredCore = newCore;
AffinityMask = newAffinityMask;
if (oldAffinityMask != newAffinityMask)
{
int oldCore = ActiveCore;
if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0)
if ((newAffinityMask & (1 << newCore)) == 0)
{
if (PreferredCore < 0)
KernelContext.CriticalSection.Leave();
return KernelResult.InvalidCombination;
}
}
if (isCoreMigrationDisabled)
{
_originalPreferredCore = newCore;
_originalAffinityMask = newAffinityMask;
}
else
{
long oldAffinityMask = AffinityMask;
PreferredCore = newCore;
AffinityMask = newAffinityMask;
if (oldAffinityMask != newAffinityMask)
{
int oldCore = ActiveCore;
if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0)
{
ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask);
if (PreferredCore < 0)
{
ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask);
}
else
{
ActiveCore = PreferredCore;
}
}
AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore);
}
}
KernelContext.CriticalSection.Leave();
bool targetThreadPinned = true;
while (targetThreadPinned)
{
KernelContext.CriticalSection.Enter();
if (TerminationRequested)
{
KernelContext.CriticalSection.Leave();
break;
}
targetThreadPinned = false;
int coreNumber = GetEffectiveRunningCore();
bool isPinnedThreadCurrentlyRunning = coreNumber >= 0;
if (isPinnedThreadCurrentlyRunning && ((1 << coreNumber) & AffinityMask) == 0)
{
if (IsPinned)
{
KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.TerminationRequested)
{
KernelContext.CriticalSection.Leave();
return KernelResult.ThreadTerminating;
}
_pinnedWaiters.AddLast(currentThread);
currentThread.Reschedule(ThreadSchedState.Paused);
}
else
{
ActiveCore = PreferredCore;
targetThreadPinned = true;
}
}
AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore);
KernelContext.CriticalSection.Leave();
}
return KernelResult.Success;
}
KernelContext.CriticalSection.Leave();
return KernelResult.Success;
}
private void CombineForcePauseFlags()
@ -638,7 +768,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
ThreadSchedState oldFlags = SchedFlags;
ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
SchedFlags = lowNibble | _forcePauseFlags;
SchedFlags = lowNibble | (_forcePauseFlags & _forcePausePermissionFlags);
AdjustScheduling(oldFlags);
}
@ -1106,7 +1236,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
foreach (KThread thread in _mutexWaiters)
{
thread.MutexOwner = null;
thread._preferredCoreOverride = 0;
thread._originalPreferredCore = 0;
thread.ObjSyncResult = KernelResult.InvalidState;
thread.ReleaseAndResume();
@ -1116,5 +1246,113 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
Owner?.DecrementThreadCountAndTerminateIfZero();
}
public void Pin()
{
IsPinned = true;
_coreMigrationDisableCount++;
int activeCore = ActiveCore;
_originalPreferredCore = PreferredCore;
_originalAffinityMask = AffinityMask;
ActiveCore = CurrentCore;
PreferredCore = CurrentCore;
AffinityMask = 1 << CurrentCore;
if (activeCore != CurrentCore || _originalAffinityMask != AffinityMask)
{
AdjustSchedulingForNewAffinity(_originalAffinityMask, activeCore);
}
_originalBasePriority = BasePriority;
BasePriority = Math.Min(_originalBasePriority, BitOperations.TrailingZeroCount(Owner.Capabilities.AllowedThreadPriosMask) - 1);
UpdatePriorityInheritance();
// Disallows thread pausing
_forcePausePermissionFlags &= ~ThreadSchedState.ThreadPauseFlag;
CombineForcePauseFlags();
// TODO: Assign reduced SVC permissions
}
public void Unpin()
{
IsPinned = false;
_coreMigrationDisableCount--;
long affinityMask = AffinityMask;
int activeCore = ActiveCore;
PreferredCore = _originalPreferredCore;
AffinityMask = _originalAffinityMask;
if (AffinityMask != affinityMask)
{
if ((AffinityMask & 1 << ActiveCore) != 0)
{
if (PreferredCore >= 0)
{
ActiveCore = PreferredCore;
}
else
{
ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask);
}
AdjustSchedulingForNewAffinity(affinityMask, activeCore);
}
}
BasePriority = _originalBasePriority;
UpdatePriorityInheritance();
if (!TerminationRequested)
{
// Allows thread pausing
_forcePausePermissionFlags |= ThreadSchedState.ThreadPauseFlag;
CombineForcePauseFlags();
// TODO: Restore SVC permissions
}
// Wake up waiters
foreach (KThread waiter in _pinnedWaiters)
{
waiter.ReleaseAndResume();
}
_pinnedWaiters.Clear();
}
public void SynchronizePreemptionState()
{
KernelContext.CriticalSection.Enter();
if (Owner != null && Owner.PinnedThreads[CurrentCore] == this)
{
ClearUserInterruptFlag();
Owner.UnpinThread(this);
}
KernelContext.CriticalSection.Leave();
}
public ushort GetUserDisableCount()
{
return Owner.CpuMemory.Read<ushort>(_tlsAddress + TlsUserDisableCountOffset);
}
public void SetUserInterruptFlag()
{
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 1);
}
public void ClearUserInterruptFlag()
{
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
}
}
}

View file

@ -4,11 +4,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
LowMask = 0xf,
HighMask = 0xfff0,
ForcePauseMask = 0x70,
ForcePauseMask = 0x1f0,
ProcessPauseFlag = 1 << 4,
ThreadPauseFlag = 1 << 5,
ProcessDebugPauseFlag = 1 << 6,
BacktracePauseFlag = 1 << 7,
KernelInitPauseFlag = 1 << 8,
None = 0,

View file

@ -330,6 +330,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success;
}
[CommandHipc(60)] // 2.0.0+
// SetMediaPlaybackStateForApplication(bool enabled)
public ResultCode SetMediaPlaybackStateForApplication(ServiceCtx context)
{
bool enabled = context.RequestData.ReadBoolean();
// NOTE: Service stores the "enabled" value in a private field, when enabled is false, it stores nn::os::GetSystemTick() too.
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { enabled });
return ResultCode.Success;
}
[CommandHipc(66)] // 3.0.0+
// InitializeGamePlayRecording(u64, handle<copy>)
public ResultCode InitializeGamePlayRecording(ServiceCtx context)

View file

@ -120,6 +120,45 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success;
}
[CommandHipc(10120)] // 10.0.0+
// nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool
public ResultCode IsFriendListCacheAvailable(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
if (userId.IsNull)
{
return ResultCode.InvalidArgument;
}
// TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise.
// NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check.
context.ResponseData.Write(true);
// TODO: Since we don't support friend features, it's fine to stub it for now.
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
return ResultCode.Success;
}
[CommandHipc(10121)] // 10.0.0+
// nn::friends::EnsureFriendListAvailable(nn::account::Uid userId)
public ResultCode EnsureFriendListAvailable(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
if (userId.IsNull)
{
return ResultCode.InvalidArgument;
}
// TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id.
// Since we don't support friend features, it's fine to stub it for now.
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
return ResultCode.Success;
}
[CommandHipc(10400)]
// nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>)
public ResultCode GetBlockedUserListIds(ServiceCtx context)
@ -311,4 +350,4 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success;
}
}
}
}

View file

@ -1,17 +1,17 @@
using Ryujinx.Common;
using Ryujinx.HLE.Exceptions;
using Ryujinx.Common.Configuration.Hid;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Hid
{

View file

@ -1,14 +1,13 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.Types;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Hid
{

View file

@ -5,9 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Hid.Types;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@ -642,9 +640,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array<NpadIdType, 9>)
public ResultCode SetSupportedNpadIdType(ServiceCtx context)
{
long appletResourceUserId = context.RequestData.ReadInt64();
ulong arrayPosition = context.Request.PtrBuff[0].Position;
ulong arraySize = context.Request.PtrBuff[0].Size;
long appletResourceUserId = context.RequestData.ReadInt64();
ulong arrayPosition = context.Request.PtrBuff[0].Position;
ulong arraySize = context.Request.PtrBuff[0].Size;
ReadOnlySpan<NpadIdType> supportedPlayerIds = MemoryMarshal.Cast<byte, NpadIdType>(context.Memory.GetSpan(arrayPosition, (int)arraySize));
@ -667,38 +665,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// ActivateNpad(nn::applet::AppletResourceUserId)
public ResultCode ActivateNpad(ServiceCtx context)
{
long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Npads.Active = true;
// Initialize entries to avoid issues with some games.
List<GamepadInput> emptyGamepadInputs = new List<GamepadInput>();
List<SixAxisInput> emptySixAxisInputs = new List<SixAxisInput>();
for (int player = 0; player < NpadDevices.MaxControllers; player++)
{
GamepadInput gamepadInput = new GamepadInput();
SixAxisInput sixaxisInput = new SixAxisInput();
gamepadInput.PlayerId = (PlayerIndex)player;
sixaxisInput.PlayerId = (PlayerIndex)player;
sixaxisInput.Orientation = new float[9];
emptyGamepadInputs.Add(gamepadInput);
emptySixAxisInputs.Add(sixaxisInput);
}
for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
{
context.Device.Hid.Npads.Update(emptyGamepadInputs);
context.Device.Hid.Npads.UpdateSixAxis(emptySixAxisInputs);
}
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success;
return ActiveNpadImpl(context);
}
[CommandHipc(104)]
@ -773,12 +740,20 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandHipc(109)] // 5.0.0+
// ActivateNpadWithRevision(nn::applet::AppletResourceUserId, int revision)
// ActivateNpadWithRevision(nn::applet::AppletResourceUserId, ulong revision)
public ResultCode ActivateNpadWithRevision(ServiceCtx context)
{
int revision = context.RequestData.ReadInt32();
ulong revision = context.RequestData.ReadUInt64();
return ActiveNpadImpl(context, revision);
}
private ResultCode ActiveNpadImpl(ServiceCtx context, ulong revision = 0)
{
long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Npads.Active = true;
// Initialize entries to avoid issues with some games.
List<GamepadInput> emptyGamepadInputs = new List<GamepadInput>();
@ -974,7 +949,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled
public ResultCode IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx context)
{
uint unknown0 = context.RequestData.ReadUInt32();
uint unknown0 = context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
context.ResponseData.Write(_unintendedHomeButtonInputProtectionEnabled);
@ -989,7 +964,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode EnableUnintendedHomeButtonInputProtection(ServiceCtx context)
{
_unintendedHomeButtonInputProtectionEnabled = context.RequestData.ReadBoolean();
uint unknown0 = context.RequestData.ReadUInt32();
uint unknown0 = context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled });
@ -1027,8 +1002,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
{
HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<HidVibrationDeviceHandle>();
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
{
@ -1092,9 +1067,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
{
DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(),
Position = context.RequestData.ReadByte(),
Reserved = context.RequestData.ReadByte()
PlayerId = context.RequestData.ReadByte(),
Position = context.RequestData.ReadByte(),
Reserved = context.RequestData.ReadByte()
};
HidVibrationValue vibrationValue = new HidVibrationValue
@ -1123,9 +1098,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
{
DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(),
Position = context.RequestData.ReadByte(),
Reserved = context.RequestData.ReadByte()
PlayerId = context.RequestData.ReadByte(),
Position = context.RequestData.ReadByte(),
Reserved = context.RequestData.ReadByte()
};
long appletResourceUserId = context.RequestData.ReadInt64();
@ -1185,8 +1160,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
Span<HidVibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, HidVibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<HidVibrationValue> vibrationValues = MemoryMarshal.Cast<byte, HidVibrationValue>(vibrationValueBuffer);
Span<HidVibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, HidVibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<HidVibrationValue> vibrationValues = MemoryMarshal.Cast<byte, HidVibrationValue>(vibrationValueBuffer);
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
{

View file

@ -2,12 +2,12 @@
{
public enum NpadStyleIndex : byte
{
FullKey = 3,
Handheld = 4,
JoyDual = 5,
JoyLeft = 6,
JoyRight = 7,
FullKey = 3,
Handheld = 4,
JoyDual = 5,
JoyLeft = 6,
JoyRight = 7,
SystemExt = 32,
System = 33
System = 33
}
}

View file

@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
[Service("bsd:u", false)]
class IClient : IpcService
{
private static Dictionary<WsaError, LinuxError> _errorMap = new Dictionary<WsaError, LinuxError>
private static readonly Dictionary<WsaError, LinuxError> _errorMap = new()
{
// WSAEINTR
{WsaError.WSAEINTR, LinuxError.EINTR},
@ -97,6 +97,50 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{0, 0}
};
private static readonly Dictionary<BsdSocketOption, SocketOptionName> _soSocketOptionMap = new()
{
{ BsdSocketOption.SoDebug, SocketOptionName.Debug },
{ BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress },
{ BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive },
{ BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute },
{ BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast },
{ BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback },
{ BsdSocketOption.SoLinger, SocketOptionName.Linger },
{ BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline },
{ BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress },
{ BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer },
{ BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer },
{ BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater },
{ BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater },
{ BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout },
{ BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout },
{ BsdSocketOption.SoError, SocketOptionName.Error },
{ BsdSocketOption.SoType, SocketOptionName.Type }
};
private static readonly Dictionary<BsdSocketOption, SocketOptionName> _ipSocketOptionMap = new()
{
{ BsdSocketOption.IpOptions, SocketOptionName.IPOptions },
{ BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded },
{ BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive },
{ BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface },
{ BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive },
{ BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback },
{ BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership },
{ BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership },
{ BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment },
{ BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership },
{ BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership }
};
private static readonly Dictionary<BsdSocketOption, SocketOptionName> _tcpSocketOptionMap = new()
{
{ BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay },
{ BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime },
{ BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval },
{ BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount }
};
private bool _isPrivileged;
private List<BsdSocket> _sockets = new List<BsdSocket>();
@ -118,13 +162,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags)
{
BsdSocketFlags SupportedFlags =
BsdSocketFlags.Oob |
BsdSocketFlags.Peek |
BsdSocketFlags.DontRoute |
BsdSocketFlags.Trunc |
BsdSocketFlags.CTrunc;
SocketFlags socketFlags = SocketFlags.None;
if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob))
@ -166,6 +203,25 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return socketFlags;
}
private static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name)
{
var table = level switch
{
SocketOptionLevel.Socket => _soSocketOptionMap,
SocketOptionLevel.IP => _ipSocketOptionMap,
SocketOptionLevel.Tcp => _tcpSocketOptionMap,
_ => null
};
if (table == null)
{
name = default;
return false;
}
return table.TryGetValue(option, out name);
}
private ResultCode WriteWinSock2Error(ServiceCtx context, WsaError errorCode)
{
return WriteBsdResult(context, -1, ConvertError(errorCode));
@ -820,9 +876,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
// GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>)
public ResultCode GetSockOpt(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
SocketOptionName optionName = (SocketOptionName)context.RequestData.ReadInt32();
int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22();
@ -831,7 +887,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
if (socket != null)
{
errno = HandleGetSocketOption(context, socket, optionName, level, bufferPosition, bufferSize);
errno = HandleGetSocketOption(context, socket, option, level, bufferPosition, bufferSize);
}
return WriteBsdResult(context, 0, errno);
@ -936,45 +992,26 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private static LinuxError HandleGetSocketOption(
ServiceCtx context,
BsdSocket socket,
SocketOptionName optionName,
BsdSocketOption option,
SocketOptionLevel level,
ulong optionValuePosition,
ulong optionValueSize)
{
try
{
if (!TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}");
return LinuxError.EOPNOTSUPP;
}
byte[] optionValue = new byte[optionValueSize];
switch (optionName)
{
case SocketOptionName.Broadcast:
case SocketOptionName.DontLinger:
case SocketOptionName.Debug:
case SocketOptionName.Error:
case SocketOptionName.KeepAlive:
case SocketOptionName.OutOfBandInline:
case SocketOptionName.ReceiveBuffer:
case SocketOptionName.ReceiveTimeout:
case SocketOptionName.SendBuffer:
case SocketOptionName.SendTimeout:
case SocketOptionName.Type:
case SocketOptionName.Linger:
socket.Handle.GetSocketOption(level, optionName, optionValue);
context.Memory.Write(optionValuePosition, optionValue);
socket.Handle.GetSocketOption(level, optionName, optionValue);
context.Memory.Write(optionValuePosition, optionValue);
return LinuxError.SUCCESS;
case (SocketOptionName)0x200:
socket.Handle.GetSocketOption(level, SocketOptionName.ReuseAddress, optionValue);
context.Memory.Write(optionValuePosition, optionValue);
return LinuxError.SUCCESS;
default:
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt OptionName: {optionName}");
return LinuxError.EOPNOTSUPP;
}
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
@ -985,47 +1022,34 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private static LinuxError HandleSetSocketOption(
ServiceCtx context,
BsdSocket socket,
SocketOptionName optionName,
BsdSocketOption option,
SocketOptionLevel level,
ulong optionValuePosition,
ulong optionValueSize)
{
try
{
switch (optionName)
if (!TryConvertSocketOption(option, level, out SocketOptionName optionName))
{
case SocketOptionName.Broadcast:
case SocketOptionName.DontLinger:
case SocketOptionName.Debug:
case SocketOptionName.Error:
case SocketOptionName.KeepAlive:
case SocketOptionName.OutOfBandInline:
case SocketOptionName.ReceiveBuffer:
case SocketOptionName.ReceiveTimeout:
case SocketOptionName.SendBuffer:
case SocketOptionName.SendTimeout:
case SocketOptionName.Type:
case SocketOptionName.ReuseAddress:
socket.Handle.SetSocketOption(level, optionName, context.Memory.Read<int>((ulong)optionValuePosition));
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
return LinuxError.SUCCESS;
case (SocketOptionName)0x200:
socket.Handle.SetSocketOption(level, SocketOptionName.ReuseAddress, context.Memory.Read<int>((ulong)optionValuePosition));
return LinuxError.SUCCESS;
case SocketOptionName.Linger:
socket.Handle.SetSocketOption(level, SocketOptionName.Linger,
new LingerOption(context.Memory.Read<int>((ulong)optionValuePosition) != 0, context.Memory.Read<int>((ulong)optionValuePosition + 4)));
return LinuxError.SUCCESS;
default:
Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {optionName}");
return LinuxError.EOPNOTSUPP;
return LinuxError.EOPNOTSUPP;
}
int value = context.Memory.Read<int>((ulong)optionValuePosition);
if (option == BsdSocketOption.SoLinger)
{
int value2 = context.Memory.Read<int>((ulong)optionValuePosition + 4);
socket.Handle.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2));
}
else
{
socket.Handle.SetSocketOption(level, optionName, value);
}
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
@ -1037,9 +1061,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
// SetSockOpt(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0> option_value) -> (i32 ret, u32 bsd_errno)
public ResultCode SetSockOpt(ServiceCtx context)
{
int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
SocketOptionName optionName = (SocketOptionName)context.RequestData.ReadInt32();
int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32();
(ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21();
@ -1048,7 +1072,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
if (socket != null)
{
errno = HandleSetSocketOption(context, socket, optionName, level, bufferPos, bufferSize);
errno = HandleSetSocketOption(context, socket, option, level, bufferPos, bufferSize);
}
return WriteBsdResult(context, 0, errno);

View file

@ -1,5 +1,3 @@
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
enum BsdSocketFlags

View file

@ -0,0 +1,119 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
enum BsdSocketOption
{
SoDebug = 0x1,
SoAcceptConn = 0x2,
SoReuseAddr = 0x4,
SoKeepAlive = 0x8,
SoDontRoute = 0x10,
SoBroadcast = 0x20,
SoUseLoopBack = 0x40,
SoLinger = 0x80,
SoOobInline = 0x100,
SoReusePort = 0x200,
SoTimestamp = 0x400,
SoNoSigpipe = 0x800,
SoAcceptFilter = 0x1000,
SoBinTime = 0x2000,
SoNoOffload = 0x4000,
SoNoDdp = 0x8000,
SoReusePortLb = 0x10000,
SoRError = 0x20000,
SoSndBuf = 0x1001,
SoRcvBuf = 0x1002,
SoSndLoWat = 0x1003,
SoRcvLoWat = 0x1004,
SoSndTimeo = 0x1005,
SoRcvTimeo = 0x1006,
SoError = 0x1007,
SoType = 0x1008,
SoLabel = 0x1009,
SoPeerLabel = 0x1010,
SoListenQLimit = 0x1011,
SoListenQLen = 0x1012,
SoListenIncQLen = 0x1013,
SoSetFib = 0x1014,
SoUserCookie = 0x1015,
SoProtocol = 0x1016,
SoTsClock = 0x1017,
SoMaxPacingRate = 0x1018,
SoDomain = 0x1019,
IpOptions = 1,
IpHdrIncl = 2,
IpTos = 3,
IpTtl = 4,
IpRecvOpts = 5,
IpRecvRetOpts = 6,
IpRecvDstAddr = 7,
IpRetOpts = 8,
IpMulticastIf = 9,
IpMulticastTtl = 10,
IpMulticastLoop = 11,
IpAddMembership = 12,
IpDropMembership = 13,
IpMulticastVif = 14,
IpRsvpOn = 15,
IpRsvpOff = 16,
IpRsvpVifOn = 17,
IpRsvpVifOff = 18,
IpPortRange = 19,
IpRecvIf = 20,
IpIpsecPolicy = 21,
IpOnesBcast = 23,
IpBindany = 24,
IpBindMulti = 25,
IpRssListenBucket = 26,
IpOrigDstAddr = 27,
IpFwTableAdd = 40,
IpFwTableDel = 41,
IpFwTableFlush = 42,
IpFwTableGetSize = 43,
IpFwTableList = 44,
IpFw3 = 48,
IpDummyNet3 = 49,
IpFwAdd = 50,
IpFwDel = 51,
IpFwFlush = 52,
IpFwZero = 53,
IpFwGet = 54,
IpFwResetLog = 55,
IpFwNatCfg = 56,
IpFwNatDel = 57,
IpFwNatGetConfig = 58,
IpFwNatGetLog = 59,
IpDummyNetConfigure = 60,
IpDummyNetDel = 61,
IpDummyNetFlush = 62,
IpDummyNetGet = 64,
IpRecvTtl = 65,
IpMinTtl = 66,
IpDontFrag = 67,
IpRecvTos = 68,
IpAddSourceMembership = 70,
IpDropSourceMembership = 71,
IpBlockSource = 72,
IpUnblockSource = 73,
TcpNoDelay = 1,
TcpMaxSeg = 2,
TcpNoPush = 4,
TcpNoOpt = 8,
TcpMd5Sig = 16,
TcpInfo = 32,
TcpCongestion = 64,
TcpKeepInit = 128,
TcpKeepIdle = 256,
TcpKeepIntvl = 512,
TcpKeepCnt = 1024
}
}