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

View file

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

View file

@ -58,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ 0x253b0, 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) }, { 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) }, { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) },
{ 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) }, { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) },
{ 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) }, { 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: // Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards. // - ASTC is usually not supported on desktop cards.
// - BC4/BC5 is not supported on 3D textures. // - 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( if (!AstcDecoder.TryDecodeToRgba8P(
data.ToArray(), data.ToArray(),
@ -805,11 +805,15 @@ namespace Ryujinx.Graphics.Gpu.Image
data = decoded; 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); 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); 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) if (info.Target == Target.Texture3D)
{ {
// The host API does not support 3D BC4/BC5 compressed formats. // 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.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.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.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.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.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)); 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() (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 // 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. // 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() public Capabilities GetCapabilities()
{ {
return new Capabilities( return new Capabilities(
HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows, hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows, hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
HwCapabilities.SupportsAstcCompression, supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
HwCapabilities.SupportsFragmentShaderInterlock, supportsR4G4Format: false,
HwCapabilities.SupportsFragmentShaderOrdering, supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
HwCapabilities.SupportsImageLoadFormatted, supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
HwCapabilities.SupportsMismatchingViewFormat, supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
HwCapabilities.SupportsNonConstantTextureOffset, supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
HwCapabilities.SupportsShaderBallot, supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
HwCapabilities.SupportsTextureShadowLod, supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
HwCapabilities.SupportsViewportSwizzle, supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
HwCapabilities.SupportsIndirectParameters, supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
HwCapabilities.MaximumComputeSharedMemorySize, supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
HwCapabilities.MaximumSupportedAnisotropy, maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
HwCapabilities.StorageBufferOffsetAlignment); maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment);
} }
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data) 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 KAddressArbiter AddressArbiter { get; private set; }
public long[] RandomEntropy { get; private set; } public long[] RandomEntropy { get; private set; }
public KThread[] PinnedThreads { get; private set; }
private bool _signaled; private bool _signaled;
@ -102,6 +103,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Capabilities = new KProcessCapabilities(); Capabilities = new KProcessCapabilities();
RandomEntropy = new long[KScheduler.CpuCoresCount]; RandomEntropy = new long[KScheduler.CpuCoresCount];
PinnedThreads = new KThread[KScheduler.CpuCoresCount];
// TODO: Remove once we no longer need to initialize it externally. // TODO: Remove once we no longer need to initialize it externally.
HandleTable = new KHandleTable(context); HandleTable = new KHandleTable(context);
@ -749,7 +751,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); 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(); KernelContext.Schedulers[currentThread.CurrentCore].Schedule();
} }
@ -952,6 +971,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
if (currentThread != null && PinnedThreads[currentThread.CurrentCore] == currentThread)
{
UnpinThread(currentThread);
}
foreach (KThread thread in _threads) foreach (KThread thread in _threads)
{ {
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
@ -1139,5 +1163,35 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.InvalidState; 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) private bool IsPointingInsideKernel(ulong address)
{ {
return (address + 0x1000000000) < 0xffffff000; return (address + 0x1000000000) < 0xffffff000;

View file

@ -491,5 +491,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ {
return _syscall.SignalToAddress(address, type, value, count); 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); 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) 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) if (context.IsAarch32)
{ {
@ -44,13 +59,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
svcFunc(_syscall64, context); svcFunc(_syscall64, context);
} }
PostSvcHandler();
}
private void PostSvcHandler()
{
KThread currentThread = KernelStatic.GetCurrentThread();
currentThread.HandlePostSyscall(); currentThread.HandlePostSyscall();
} }
} }

View file

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

View file

@ -36,6 +36,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly KThread _idleThread; private readonly KThread _idleThread;
public KThread PreviousThread => _previousThread; public KThread PreviousThread => _previousThread;
public KThread CurrentThread => _currentThread;
public long LastContextSwitchTime { get; private set; } public long LastContextSwitchTime { get; private set; }
public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning; public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
@ -87,6 +88,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); 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); scheduledCoresMask |= context.Schedulers[core].SelectThread(thread);
} }

View file

@ -11,6 +11,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
class KThread : KSynchronizationObject, IKFutureSchedulerObject class KThread : KSynchronizationObject, IKFutureSchedulerObject
{ {
private const int TlsUserDisableCountOffset = 0x100;
private const int TlsUserInterruptFlagOffset = 0x102;
public const int MaxWaitSyncObjects = 64; public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent; private ManualResetEvent _schedulerWaitEvent;
@ -43,6 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable; public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable;
public ulong MutexAddress { get; set; } public ulong MutexAddress { get; set; }
public int KernelWaitersCount { get; private set; }
public KProcess Owner { get; private set; } public KProcess Owner { get; private set; }
@ -65,11 +69,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private LinkedList<KThread> _mutexWaiters; private LinkedList<KThread> _mutexWaiters;
private LinkedListNode<KThread> _mutexWaiterNode; private LinkedListNode<KThread> _mutexWaiterNode;
private LinkedList<KThread> _pinnedWaiters;
public KThread MutexOwner { get; private set; } public KThread MutexOwner { get; private set; }
public int ThreadHandleForUserMutex { get; set; } public int ThreadHandleForUserMutex { get; set; }
private ThreadSchedState _forcePauseFlags; private ThreadSchedState _forcePauseFlags;
private ThreadSchedState _forcePausePermissionFlags;
public KernelResult ObjSyncResult { get; set; } public KernelResult ObjSyncResult { get; set; }
@ -79,11 +86,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public int CurrentCore { get; set; } public int CurrentCore { get; set; }
public int ActiveCore { get; set; } public int ActiveCore { get; set; }
private long _affinityMaskOverride; public bool IsPinned { get; private set; }
private int _preferredCoreOverride;
#pragma warning disable CS0649 private long _originalAffinityMask;
private int _affinityOverrideCount; private int _originalPreferredCore;
#pragma warning restore CS0649 private int _originalBasePriority;
private int _coreMigrationDisableCount;
public ThreadSchedState SchedFlags { get; private set; } public ThreadSchedState SchedFlags { get; private set; }
@ -108,6 +116,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public long LastPc { get; set; } public long LastPc { get; set; }
private object ActivityOperationLock = new object();
public KThread(KernelContext context) : base(context) public KThread(KernelContext context) : base(context)
{ {
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@ -116,6 +126,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount]; SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
_mutexWaiters = new LinkedList<KThread>(); _mutexWaiters = new LinkedList<KThread>();
_pinnedWaiters = new LinkedList<KThread>();
} }
public KernelResult Initialize( public KernelResult Initialize(
@ -147,6 +158,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
DynamicPriority = priority; DynamicPriority = priority;
BasePriority = priority; BasePriority = priority;
CurrentCore = cpuCore; CurrentCore = cpuCore;
IsPinned = false;
_entrypoint = entrypoint; _entrypoint = entrypoint;
_customThreadStart = customThreadStart; _customThreadStart = customThreadStart;
@ -204,6 +216,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_hasBeenInitialized = true; _hasBeenInitialized = true;
_forcePausePermissionFlags = ThreadSchedState.ForcePauseMask;
if (owner != null) if (owner != null)
{ {
owner.SubscribeThreadEventHandlers(Context); owner.SubscribeThreadEventHandlers(Context);
@ -301,6 +315,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
{
Owner.UnpinThread(this);
}
ThreadSchedState result; ThreadSchedState result;
if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0) if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0)
@ -405,6 +424,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
_forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
_forcePausePermissionFlags = 0;
bool decRef = ExitImpl(); bool decRef = ExitImpl();
@ -433,6 +453,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return decRef; 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) public KernelResult Sleep(long timeout)
{ {
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
@ -465,7 +498,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
BasePriority = priority; if (IsPinned)
{
_originalBasePriority = priority;
}
else
{
BasePriority = priority;
}
UpdatePriorityInheritance(); UpdatePriorityInheritance();
@ -497,53 +537,96 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KernelResult SetActivity(bool pause) public KernelResult SetActivity(bool pause)
{ {
KernelResult result = KernelResult.Success; lock (ActivityOperationLock)
KernelContext.CriticalSection.Enter();
ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running)
{ {
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(); KernelContext.CriticalSection.Leave();
return KernelResult.InvalidState; if (result == KernelResult.Success && pause)
}
KernelContext.CriticalSection.Enter();
if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
{
if (pause)
{ {
// Pause, the force pause flag should be clear (thread is NOT paused). bool isThreadRunning = true;
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
while (isThreadRunning)
{ {
Suspend(ThreadSchedState.ThreadPauseFlag); KernelContext.CriticalSection.Enter();
}
else if (TerminationRequested)
{ {
result = KernelResult.InvalidState; KernelContext.CriticalSection.Leave();
}
} break;
else }
{
// Unpause, the force pause flag should be set (thread is paused). isThreadRunning = false;
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
{ if (IsPinned)
Resume(ThreadSchedState.ThreadPauseFlag); {
} KThread currentThread = KernelStatic.GetCurrentThread();
else
{ if (currentThread.TerminationRequested)
result = KernelResult.InvalidState; {
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() public void CancelSynchronization()
@ -579,58 +662,105 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask) public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask)
{ {
KernelContext.CriticalSection.Enter(); lock (ActivityOperationLock)
bool useOverride = _affinityOverrideCount != 0;
// The value -3 is "do not change the preferred core".
if (newCore == -3)
{ {
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 ((newAffinityMask & (1 << newCore)) == 0)
}
}
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 (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 else
{ {
ActiveCore = PreferredCore; targetThreadPinned = true;
} }
} }
AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); KernelContext.CriticalSection.Leave();
} }
return KernelResult.Success;
} }
KernelContext.CriticalSection.Leave();
return KernelResult.Success;
} }
private void CombineForcePauseFlags() private void CombineForcePauseFlags()
@ -638,7 +768,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
ThreadSchedState oldFlags = SchedFlags; ThreadSchedState oldFlags = SchedFlags;
ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
SchedFlags = lowNibble | _forcePauseFlags; SchedFlags = lowNibble | (_forcePauseFlags & _forcePausePermissionFlags);
AdjustScheduling(oldFlags); AdjustScheduling(oldFlags);
} }
@ -1106,7 +1236,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
foreach (KThread thread in _mutexWaiters) foreach (KThread thread in _mutexWaiters)
{ {
thread.MutexOwner = null; thread.MutexOwner = null;
thread._preferredCoreOverride = 0; thread._originalPreferredCore = 0;
thread.ObjSyncResult = KernelResult.InvalidState; thread.ObjSyncResult = KernelResult.InvalidState;
thread.ReleaseAndResume(); thread.ReleaseAndResume();
@ -1116,5 +1246,113 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
Owner?.DecrementThreadCountAndTerminateIfZero(); 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, LowMask = 0xf,
HighMask = 0xfff0, HighMask = 0xfff0,
ForcePauseMask = 0x70, ForcePauseMask = 0x1f0,
ProcessPauseFlag = 1 << 4, ProcessPauseFlag = 1 << 4,
ThreadPauseFlag = 1 << 5, ThreadPauseFlag = 1 << 5,
ProcessDebugPauseFlag = 1 << 6, ProcessDebugPauseFlag = 1 << 6,
BacktracePauseFlag = 1 << 7,
KernelInitPauseFlag = 1 << 8, KernelInitPauseFlag = 1 << 8,
None = 0, None = 0,

View file

@ -330,6 +330,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success; 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+ [CommandHipc(66)] // 3.0.0+
// InitializeGamePlayRecording(u64, handle<copy>) // InitializeGamePlayRecording(u64, handle<copy>)
public ResultCode InitializeGamePlayRecording(ServiceCtx context) public ResultCode InitializeGamePlayRecording(ServiceCtx context)

View file

@ -120,6 +120,45 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success; 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)] [CommandHipc(10400)]
// nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>) // nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>)
public ResultCode GetBlockedUserListIds(ServiceCtx context) public ResultCode GetBlockedUserListIds(ServiceCtx context)
@ -311,4 +350,4 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success; return ResultCode.Success;
} }
} }
} }

View file

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

View file

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

View file

@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
[Service("bsd:u", false)] [Service("bsd:u", false)]
class IClient : IpcService class IClient : IpcService
{ {
private static Dictionary<WsaError, LinuxError> _errorMap = new Dictionary<WsaError, LinuxError> private static readonly Dictionary<WsaError, LinuxError> _errorMap = new()
{ {
// WSAEINTR // WSAEINTR
{WsaError.WSAEINTR, LinuxError.EINTR}, {WsaError.WSAEINTR, LinuxError.EINTR},
@ -97,6 +97,50 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{0, 0} {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 bool _isPrivileged;
private List<BsdSocket> _sockets = new List<BsdSocket>(); private List<BsdSocket> _sockets = new List<BsdSocket>();
@ -118,13 +162,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags)
{ {
BsdSocketFlags SupportedFlags =
BsdSocketFlags.Oob |
BsdSocketFlags.Peek |
BsdSocketFlags.DontRoute |
BsdSocketFlags.Trunc |
BsdSocketFlags.CTrunc;
SocketFlags socketFlags = SocketFlags.None; SocketFlags socketFlags = SocketFlags.None;
if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob))
@ -166,6 +203,25 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return socketFlags; 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) private ResultCode WriteWinSock2Error(ServiceCtx context, WsaError errorCode)
{ {
return WriteBsdResult(context, -1, ConvertError(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>) // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>)
public ResultCode GetSockOpt(ServiceCtx context) public ResultCode GetSockOpt(ServiceCtx context)
{ {
int socketFd = context.RequestData.ReadInt32(); int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
SocketOptionName optionName = (SocketOptionName)context.RequestData.ReadInt32(); BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32();
(ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22();
@ -831,7 +887,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
if (socket != null) if (socket != null)
{ {
errno = HandleGetSocketOption(context, socket, optionName, level, bufferPosition, bufferSize); errno = HandleGetSocketOption(context, socket, option, level, bufferPosition, bufferSize);
} }
return WriteBsdResult(context, 0, errno); return WriteBsdResult(context, 0, errno);
@ -936,45 +992,26 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private static LinuxError HandleGetSocketOption( private static LinuxError HandleGetSocketOption(
ServiceCtx context, ServiceCtx context,
BsdSocket socket, BsdSocket socket,
SocketOptionName optionName, BsdSocketOption option,
SocketOptionLevel level, SocketOptionLevel level,
ulong optionValuePosition, ulong optionValuePosition,
ulong optionValueSize) ulong optionValueSize)
{ {
try 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]; byte[] optionValue = new byte[optionValueSize];
switch (optionName) socket.Handle.GetSocketOption(level, optionName, optionValue);
{ context.Memory.Write(optionValuePosition, optionValue);
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);
return LinuxError.SUCCESS; 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;
}
} }
catch (SocketException exception) catch (SocketException exception)
{ {
@ -985,47 +1022,34 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
private static LinuxError HandleSetSocketOption( private static LinuxError HandleSetSocketOption(
ServiceCtx context, ServiceCtx context,
BsdSocket socket, BsdSocket socket,
SocketOptionName optionName, BsdSocketOption option,
SocketOptionLevel level, SocketOptionLevel level,
ulong optionValuePosition, ulong optionValuePosition,
ulong optionValueSize) ulong optionValueSize)
{ {
try try
{ {
switch (optionName) if (!TryConvertSocketOption(option, level, out SocketOptionName optionName))
{ {
case SocketOptionName.Broadcast: Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}");
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));
return LinuxError.SUCCESS; return LinuxError.EOPNOTSUPP;
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;
} }
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) 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) // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0> option_value) -> (i32 ret, u32 bsd_errno)
public ResultCode SetSockOpt(ServiceCtx context) public ResultCode SetSockOpt(ServiceCtx context)
{ {
int socketFd = context.RequestData.ReadInt32(); int socketFd = context.RequestData.ReadInt32();
SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32();
SocketOptionName optionName = (SocketOptionName)context.RequestData.ReadInt32(); BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32();
(ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21();
@ -1048,7 +1072,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
if (socket != null) if (socket != null)
{ {
errno = HandleSetSocketOption(context, socket, optionName, level, bufferPos, bufferSize); errno = HandleSetSocketOption(context, socket, option, level, bufferPos, bufferSize);
} }
return WriteBsdResult(context, 0, errno); return WriteBsdResult(context, 0, errno);

View file

@ -1,5 +1,3 @@
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
enum BsdSocketFlags 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
}
}