Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
43b4b34376 | ||
|
92ca1cb0cb | ||
|
50d7ecf76d | ||
|
42a2a80b87 | ||
|
54deded929 | ||
|
39bdf6d41e | ||
|
074190e03c | ||
|
256514c7c9 | ||
|
556be08c4e | ||
|
1cbca5eecb | ||
|
95017b8c66 | ||
|
4a892fbdc9 | ||
|
9eb5b7a10d | ||
|
d64594ec74 | ||
|
6a1a03566a | ||
|
13f5294aa3 | ||
|
9444b4a647 |
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -46,6 +46,7 @@ jobs:
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1.0"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-dotnet@v1
|
||||
@@ -59,24 +60,24 @@ jobs:
|
||||
- name: Clear
|
||||
run: dotnet clean && dotnet nuget locals all --clear
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.1.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
run: dotnet build -c "${{ matrix.configuration }}" /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
- name: Test
|
||||
run: dotnet test -c "${{ matrix.configuration }}"
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="${{ env.RYUJINX_BASE_VERSION }}" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-1.0.0+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ryujinx-headless-sdl2-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request'
|
||||
|
25
ARMeilleure/Decoders/OpCodeT32MemImm12.cs
Normal file
25
ARMeilleure/Decoders/OpCodeT32MemImm12.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem
|
||||
{
|
||||
public int Rt { get; }
|
||||
public int Rn { get; }
|
||||
public bool WBack => false;
|
||||
public bool IsLoad { get; }
|
||||
public bool Index => true;
|
||||
public bool Add => true;
|
||||
public int Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode);
|
||||
|
||||
public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rt = (opCode >> 12) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
|
||||
Immediate = opCode & 0xfff;
|
||||
|
||||
IsLoad = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
29
ARMeilleure/Decoders/OpCodeT32MemImm8.cs
Normal file
29
ARMeilleure/Decoders/OpCodeT32MemImm8.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem
|
||||
{
|
||||
public int Rt { get; }
|
||||
public int Rn { get; }
|
||||
public bool WBack { get; }
|
||||
public bool IsLoad { get; }
|
||||
public bool Index { get; }
|
||||
public bool Add { get; }
|
||||
public int Immediate { get; }
|
||||
|
||||
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode);
|
||||
|
||||
public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
|
||||
{
|
||||
Rt = (opCode >> 12) & 0xf;
|
||||
Rn = (opCode >> 16) & 0xf;
|
||||
|
||||
Index = ((opCode >> 10) & 1) != 0;
|
||||
Add = ((opCode >> 9) & 1) != 0;
|
||||
WBack = ((opCode >> 8) & 1) != 0;
|
||||
|
||||
Immediate = opCode & 0xff;
|
||||
|
||||
IsLoad = ((opCode >> 20) & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1065,6 +1065,26 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("11110x011011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluImm.Create);
|
||||
SetT32("111110000101xxxx<<<<10x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000101xxxx<<<<1100xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000101xxxx<<<<11x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001101xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000001xxxx<<<<10x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000001xxxx<<<<1100xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000001xxxx<<<<11x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000011xxxx<<<<10x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000011xxxx<<<<1100xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110000011xxxx<<<<11x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110010001xxxx<<<<10x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010001xxxx<<<<1100xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010001xxxx<<<<11x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110011001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110010011xxxx<<<<10x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010011xxxx<<<<1100xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110010011xxxx<<<<11x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110011011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm12.Create);
|
||||
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x00010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluImm.Create);
|
||||
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
|
||||
@@ -1077,6 +1097,12 @@ namespace ARMeilleure.Decoders
|
||||
SetT32("11110x01110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluImm.Create);
|
||||
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluImm.Create);
|
||||
SetT32("111110000100xxxxxxxx1<<>xxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001100xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000000xxxxxxxx1<<>xxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001000xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm12.Create);
|
||||
SetT32("111110000010xxxxxxxx1<<>xxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm8.Create);
|
||||
SetT32("111110001010xxxxxxxxxxxxxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm12.Create);
|
||||
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11110x01101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluImm.Create);
|
||||
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
|
||||
|
@@ -103,7 +103,7 @@ namespace ARMeilleure.Signal
|
||||
// Unix siginfo struct locations.
|
||||
// NOTE: These are incredibly likely to be different between kernel version and architectures.
|
||||
|
||||
config.StructAddressOffset = 16; // si_addr
|
||||
config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr
|
||||
config.StructWriteOffset = 8; // si_code
|
||||
|
||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
||||
|
@@ -21,6 +21,7 @@ namespace ARMeilleure.Signal
|
||||
static class UnixSignalHandlerRegistration
|
||||
{
|
||||
private const int SIGSEGV = 11;
|
||||
private const int SIGBUS = 10;
|
||||
private const int SA_SIGINFO = 0x00000004;
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
@@ -43,7 +44,17 @@ namespace ARMeilleure.Signal
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register sigaction. Error: {result}");
|
||||
throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
result = sigaction(SIGBUS, ref sig, out SigAction oldb);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
return old;
|
||||
@@ -51,7 +62,7 @@ namespace ARMeilleure.Signal
|
||||
|
||||
public static bool RestoreExceptionHandler(SigAction oldAction)
|
||||
{
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0;
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key</typeparam>
|
||||
/// <typeparam name="V">Value</typeparam>
|
||||
public class IntervalTree<K, V> where K : IComparable<K>
|
||||
class IntervalTree<K, V> where K : IComparable<K>
|
||||
{
|
||||
private const int ArrayGrowthSize = 32;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace ARMeilleure.Translation
|
||||
/// <returns>Number of intervals found</returns>
|
||||
public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0)
|
||||
{
|
||||
GetValues(_root, start, end, ref overlaps, ref overlapCount);
|
||||
GetKeys(_root, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
return overlapCount;
|
||||
}
|
||||
@@ -180,20 +180,20 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all values that overlap the given start and end keys.
|
||||
/// Retrieve all keys that overlap the given start and end keys.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range</param>
|
||||
/// <param name="end">End of the range</param>
|
||||
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||
/// <param name="overlapCount">Overlaps count to update</param>
|
||||
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
|
||||
private void GetKeys(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
|
||||
{
|
||||
if (node == null || start.CompareTo(node.Max) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
|
||||
GetKeys(node.Left, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
bool endsOnRight = end.CompareTo(node.Start) > 0;
|
||||
if (endsOnRight)
|
||||
@@ -208,7 +208,7 @@ namespace ARMeilleure.Translation
|
||||
overlaps[overlapCount++] = node.Start;
|
||||
}
|
||||
|
||||
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
|
||||
GetKeys(node.Right, start, end, ref overlaps, ref overlapCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,40 +717,40 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key type of the node</typeparam>
|
||||
/// <typeparam name="V">Value type of the node</typeparam>
|
||||
internal class IntervalTreeNode<K, V>
|
||||
class IntervalTreeNode<K, V>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal IntervalTreeNode<K, V> Left = null;
|
||||
internal IntervalTreeNode<K, V> Right = null;
|
||||
internal IntervalTreeNode<K, V> Parent = null;
|
||||
public bool Color = true;
|
||||
public IntervalTreeNode<K, V> Left = null;
|
||||
public IntervalTreeNode<K, V> Right = null;
|
||||
public IntervalTreeNode<K, V> Parent = null;
|
||||
|
||||
/// <summary>
|
||||
/// The start of the range.
|
||||
/// </summary>
|
||||
internal K Start;
|
||||
public K Start;
|
||||
|
||||
/// <summary>
|
||||
/// The end of the range.
|
||||
/// </summary>
|
||||
internal K End;
|
||||
public K End;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum end value of this node and all its children.
|
||||
/// </summary>
|
||||
internal K Max;
|
||||
public K Max;
|
||||
|
||||
/// <summary>
|
||||
/// Value stored on this node.
|
||||
/// </summary>
|
||||
internal V Value;
|
||||
public V Value;
|
||||
|
||||
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
this.Max = end;
|
||||
this.Value = value;
|
||||
this.Parent = parent;
|
||||
Start = start;
|
||||
End = end;
|
||||
Max = end;
|
||||
Value = value;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Collections
|
||||
@@ -779,37 +777,37 @@ namespace Ryujinx.Common.Collections
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key type of the node</typeparam>
|
||||
/// <typeparam name="V">Value type of the node</typeparam>
|
||||
internal class IntervalTreeNode<K, V>
|
||||
class IntervalTreeNode<K, V>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal IntervalTreeNode<K, V> Left = null;
|
||||
internal IntervalTreeNode<K, V> Right = null;
|
||||
internal IntervalTreeNode<K, V> Parent = null;
|
||||
public bool Color = true;
|
||||
public IntervalTreeNode<K, V> Left = null;
|
||||
public IntervalTreeNode<K, V> Right = null;
|
||||
public IntervalTreeNode<K, V> Parent = null;
|
||||
|
||||
/// <summary>
|
||||
/// The start of the range.
|
||||
/// </summary>
|
||||
internal K Start;
|
||||
public K Start;
|
||||
|
||||
/// <summary>
|
||||
/// The end of the range - maximum of all in the Values list.
|
||||
/// </summary>
|
||||
internal K End;
|
||||
public K End;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum end value of this node and all its children.
|
||||
/// </summary>
|
||||
internal K Max;
|
||||
public K Max;
|
||||
|
||||
internal List<RangeNode<K, V>> Values;
|
||||
public List<RangeNode<K, V>> Values;
|
||||
|
||||
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
this.Max = end;
|
||||
this.Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
|
||||
this.Parent = parent;
|
||||
Start = start;
|
||||
End = end;
|
||||
Max = end;
|
||||
Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -967,20 +967,20 @@ namespace Ryujinx.Common.Collections
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key of the node</typeparam>
|
||||
/// <typeparam name="V">Value of the node</typeparam>
|
||||
internal class Node<K, V>
|
||||
class Node<K, V>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal Node<K, V> Left = null;
|
||||
internal Node<K, V> Right = null;
|
||||
internal Node<K, V> Parent = null;
|
||||
internal K Key;
|
||||
internal V Value;
|
||||
public bool Color = true;
|
||||
public Node<K, V> Left = null;
|
||||
public Node<K, V> Right = null;
|
||||
public Node<K, V> Parent = null;
|
||||
public K Key;
|
||||
public V Value;
|
||||
|
||||
public Node(K key, V value, Node<K, V> parent)
|
||||
{
|
||||
this.Key = key;
|
||||
this.Value = value;
|
||||
this.Parent = parent;
|
||||
Key = key;
|
||||
Value = value;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ namespace Ryujinx.Cpu
|
||||
_baseAddress = (ulong)_addressSpace.Pointer;
|
||||
ulong endAddress = _baseAddress + addressSpace.Size;
|
||||
|
||||
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent);
|
||||
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEventEh);
|
||||
bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||
|
||||
if (!added)
|
||||
|
@@ -25,6 +25,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
private const int PointerTagBit = 62;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
/// <summary>
|
||||
@@ -50,10 +51,12 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Creates a new instance of the memory manager.
|
||||
/// </summary>
|
||||
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
|
||||
/// <param name="addressSpaceSize">Size of the address space</param>
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManager(ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
|
||||
public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
|
||||
ulong asSize = PageSize;
|
||||
@@ -73,18 +76,19 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, nuint hostAddress, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
ulong remainingSize = size;
|
||||
ulong oVa = va;
|
||||
ulong oPa = pa;
|
||||
while (remainingSize != 0)
|
||||
{
|
||||
_pageTable.Write((va / PageSize) * PteSize, hostAddress);
|
||||
_pageTable.Write((va / PageSize) * PteSize, PaToPte(pa));
|
||||
|
||||
va += PageSize;
|
||||
hostAddress += PageSize;
|
||||
pa += PageSize;
|
||||
remainingSize -= PageSize;
|
||||
}
|
||||
Tracking.Map(oVa, size);
|
||||
@@ -107,7 +111,7 @@ namespace Ryujinx.Cpu
|
||||
ulong remainingSize = size;
|
||||
while (remainingSize != 0)
|
||||
{
|
||||
_pageTable.Write((va / PageSize) * PteSize, (nuint)0);
|
||||
_pageTable.Write((va / PageSize) * PteSize, 0UL);
|
||||
|
||||
va += PageSize;
|
||||
remainingSize -= PageSize;
|
||||
@@ -122,9 +126,22 @@ namespace Ryujinx.Cpu
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T ReadTracked<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
try
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
|
||||
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||
|
||||
return Read<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -177,7 +194,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
data.CopyTo(GetHostSpanContiguous(va, data.Length));
|
||||
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -185,18 +202,22 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
|
||||
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
|
||||
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +245,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return GetHostSpanContiguous(va, size);
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -251,7 +272,7 @@ namespace Ryujinx.Cpu
|
||||
SignalMemoryTracking(va, (ulong)size, true);
|
||||
}
|
||||
|
||||
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
|
||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -264,7 +285,7 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
{
|
||||
@@ -273,7 +294,7 @@ namespace Ryujinx.Cpu
|
||||
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
||||
return ref *(T*)GetHostAddress(va);
|
||||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -293,7 +314,7 @@ namespace Ryujinx.Cpu
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||
@@ -315,7 +336,7 @@ namespace Ryujinx.Cpu
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -327,11 +348,11 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
@@ -341,9 +362,9 @@ namespace Ryujinx.Cpu
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<HostMemoryRange>();
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
nuint regionStart = GetHostAddress(va);
|
||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
@@ -353,12 +374,12 @@ namespace Ryujinx.Cpu
|
||||
return null;
|
||||
}
|
||||
|
||||
nuint newHostAddress = GetHostAddress(va + PageSize);
|
||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||
|
||||
if (GetHostAddress(va) + PageSize != newHostAddress)
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||
regionStart = newHostAddress;
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
@@ -366,7 +387,7 @@ namespace Ryujinx.Cpu
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
@@ -386,18 +407,22 @@ namespace Ryujinx.Cpu
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
@@ -446,7 +471,7 @@ namespace Ryujinx.Cpu
|
||||
return false;
|
||||
}
|
||||
|
||||
return _pageTable.Read<nuint>((va / PageSize) * PteSize) != 0;
|
||||
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
|
||||
}
|
||||
|
||||
private bool ValidateAddress(ulong va)
|
||||
@@ -480,37 +505,20 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a span representing the given virtual address and size range in host memory.
|
||||
/// This function assumes that the requested virtual memory region is contiguous.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>A span representing the given virtual range in host memory</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the base virtual address is not mapped</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
|
||||
private ulong GetPhysicalAddress(ulong va)
|
||||
{
|
||||
return new Span<byte>((void*)GetHostAddress(va), size);
|
||||
// We return -1L if the virtual address is invalid or unmapped.
|
||||
if (!ValidateAddress(va) || !IsMapped(va))
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the host address for a given virtual address, using the page table.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address</param>
|
||||
/// <returns>The corresponding host address for the given virtual address</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the virtual address is not mapped</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private nuint GetHostAddress(ulong va)
|
||||
{
|
||||
nuint pageBase = _pageTable.Read<nuint>((va / PageSize) * PteSize) & unchecked((nuint)0xffff_ffff_ffffUL);
|
||||
|
||||
if (pageBase == 0)
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
return pageBase + (nuint)(va & PageMask);
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -604,6 +612,16 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
private ulong PaToPte(ulong pa)
|
||||
{
|
||||
return (ulong)_backingMemory.GetPointer(pa, PageSize);
|
||||
}
|
||||
|
||||
private ulong PteToPa(ulong pte)
|
||||
{
|
||||
return (ulong)((long)pte - _backingMemory.Pointer.ToInt64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
|
@@ -5,7 +5,6 @@ using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@@ -14,7 +13,7 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked
|
||||
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
@@ -42,9 +41,12 @@ namespace Ryujinx.Cpu
|
||||
private readonly MemoryBlock _addressSpaceMirror;
|
||||
private readonly ulong _addressSpaceSize;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private ulong[] _pageTable;
|
||||
private readonly ulong[] _pageBitmap;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
@@ -59,11 +61,14 @@ namespace Ryujinx.Cpu
|
||||
/// <summary>
|
||||
/// Creates a new instance of the host mapped memory manager.
|
||||
/// </summary>
|
||||
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
|
||||
/// <param name="addressSpaceSize">Size of the address space</param>
|
||||
/// <param name="unsafeMode">True if unmanaged access should not be masked (unsafe), false otherwise.</param>
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManagerHostMapped(ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
_unsafeMode = unsafeMode;
|
||||
_addressSpaceSize = addressSpaceSize;
|
||||
@@ -79,9 +84,13 @@ namespace Ryujinx.Cpu
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
|
||||
_pageTable = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
_addressSpace = new MemoryBlock(asSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable);
|
||||
_addressSpaceMirror = _addressSpace.CreateMirror();
|
||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
|
||||
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||
|
||||
_addressSpace = new MemoryBlock(asSize, asFlags);
|
||||
_addressSpaceMirror = new MemoryBlock(asSize, asFlags | MemoryAllocationFlags.ForceWindows4KBViewMapping);
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(_addressSpace, Tracking);
|
||||
}
|
||||
@@ -136,12 +145,14 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, nuint hostAddress, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.Commit(va, size);
|
||||
_addressSpace.MapView(_backingMemory, pa, va, size);
|
||||
_addressSpaceMirror.MapView(_backingMemory, pa, va, size);
|
||||
AddMapping(va, size);
|
||||
PtMap(va, pa, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
@@ -155,7 +166,32 @@ namespace Ryujinx.Cpu
|
||||
Tracking.Unmap(va, size);
|
||||
|
||||
RemoveMapping(va, size);
|
||||
_addressSpace.Decommit(va, size);
|
||||
PtUnmap(va, size);
|
||||
_addressSpace.UnmapView(_backingMemory, va, size);
|
||||
_addressSpaceMirror.UnmapView(_backingMemory, va, size);
|
||||
}
|
||||
|
||||
private void PtMap(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Map(va, pa);
|
||||
|
||||
va += PageSize;
|
||||
pa += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void PtUnmap(ulong va, ulong size)
|
||||
{
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Unmap(va);
|
||||
|
||||
va += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -216,6 +252,7 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
{
|
||||
@@ -322,7 +359,7 @@ namespace Ryujinx.Cpu
|
||||
int bit = (int)((page & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(page >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageTable[pageIndex];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
@@ -373,7 +410,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
|
||||
pte |= pte >> 1;
|
||||
@@ -389,16 +426,53 @@ namespace Ryujinx.Cpu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
ulong regionStart = GetPhysicalAddressChecked(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
if (!ValidateAddress(va + PageSize))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
AssertMapped(va, size);
|
||||
ulong newPa = GetPhysicalAddressChecked(va + PageSize);
|
||||
|
||||
return new HostMemoryRange[] { new HostMemoryRange(_addressSpaceMirror.GetPointer(va, size), size) };
|
||||
if (GetPhysicalAddressChecked(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
va += PageSize;
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressChecked(ulong va)
|
||||
{
|
||||
if (!IsMapped(va))
|
||||
{
|
||||
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
|
||||
}
|
||||
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -427,7 +501,7 @@ namespace Ryujinx.Cpu
|
||||
int bit = (int)((pageStart & 31) << 1);
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageTable[pageIndex];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong state = ((pte >> bit) & 3);
|
||||
@@ -459,7 +533,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte = Volatile.Read(ref pageRef);
|
||||
ulong mappedMask = mask & BlockMappedMask;
|
||||
@@ -530,7 +604,7 @@ namespace Ryujinx.Cpu
|
||||
ulong tag = protTag << bit;
|
||||
|
||||
int pageIndex = (int)(pageStart >> PageToPteShift);
|
||||
ref ulong pageRef = ref _pageTable[pageIndex];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex];
|
||||
|
||||
ulong pte;
|
||||
|
||||
@@ -562,7 +636,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
@@ -632,7 +706,7 @@ namespace Ryujinx.Cpu
|
||||
mask &= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
|
||||
ulong pte;
|
||||
ulong mappedMask;
|
||||
@@ -677,7 +751,7 @@ namespace Ryujinx.Cpu
|
||||
mask |= endMask;
|
||||
}
|
||||
|
||||
ref ulong pageRef = ref _pageTable[pageIndex++];
|
||||
ref ulong pageRef = ref _pageBitmap[pageIndex++];
|
||||
ulong pte;
|
||||
|
||||
do
|
||||
@@ -695,11 +769,11 @@ namespace Ryujinx.Cpu
|
||||
/// </summary>
|
||||
protected override void Destroy()
|
||||
{
|
||||
_addressSpaceMirror.Dispose();
|
||||
_addressSpace.Dispose();
|
||||
_addressSpaceMirror.Dispose();
|
||||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
private void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
}
|
||||
}
|
||||
|
@@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
|
||||
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
|
||||
|
||||
void SetViewports(int first, ReadOnlySpan<Viewport> viewports);
|
||||
void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform);
|
||||
|
||||
void TextureBarrier();
|
||||
void TextureBarrierTiled();
|
||||
|
@@ -9,17 +9,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
public CommandType CommandType => CommandType.SetViewports;
|
||||
private int _first;
|
||||
private SpanRef<Viewport> _viewports;
|
||||
private bool _disableTransform;
|
||||
|
||||
public void Set(int first, SpanRef<Viewport> viewports)
|
||||
public void Set(int first, SpanRef<Viewport> viewports, bool disableTransform)
|
||||
{
|
||||
_first = first;
|
||||
_viewports = viewports;
|
||||
_disableTransform = disableTransform;
|
||||
}
|
||||
|
||||
public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded);
|
||||
renderer.Pipeline.SetViewports(command._first, viewports);
|
||||
renderer.Pipeline.SetViewports(command._first, viewports, command._disableTransform);
|
||||
command._viewports.Dispose(threaded);
|
||||
}
|
||||
}
|
||||
|
@@ -304,9 +304,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
|
||||
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
|
||||
{
|
||||
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports));
|
||||
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports), disableTransform);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,15 @@ namespace Ryujinx.Graphics.GAL
|
||||
DecrementAndClamp,
|
||||
Invert,
|
||||
IncrementAndWrap,
|
||||
DecrementAndWrap
|
||||
DecrementAndWrap,
|
||||
|
||||
ZeroGl = 0x0,
|
||||
InvertGl = 0x150a,
|
||||
KeepGl = 0x1e00,
|
||||
ReplaceGl = 0x1e01,
|
||||
IncrementAndClampGl = 0x1e02,
|
||||
DecrementAndClampGl = 0x1e03,
|
||||
IncrementAndWrapGl = 0x8507,
|
||||
DecrementAndWrapGl = 0x8508
|
||||
}
|
||||
}
|
@@ -72,6 +72,13 @@ namespace Ryujinx.Graphics.GAL
|
||||
UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count);
|
||||
}
|
||||
|
||||
public void UpdateViewportInverse(Vector4<float> data)
|
||||
{
|
||||
Data.ViewportInverse = data;
|
||||
|
||||
MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
if (_startOffset != -1)
|
||||
|
@@ -113,7 +113,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.DepthMode),
|
||||
nameof(ThreedClassState.ViewportTransform),
|
||||
nameof(ThreedClassState.ViewportExtents),
|
||||
nameof(ThreedClassState.YControl)),
|
||||
nameof(ThreedClassState.YControl),
|
||||
nameof(ThreedClassState.ViewportTransformEnable)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePolygonMode,
|
||||
nameof(ThreedClassState.PolygonModeFront),
|
||||
@@ -200,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null)
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
|
||||
{
|
||||
ForceShaderUpdate();
|
||||
}
|
||||
@@ -568,6 +569,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
var yControl = _state.State.YControl;
|
||||
var face = _state.State.FaceState;
|
||||
|
||||
bool disableTransform = _state.State.ViewportTransformEnable == 0;
|
||||
|
||||
UpdateFrontFace(yControl, face.FrontFace);
|
||||
UpdateDepthMode();
|
||||
|
||||
@@ -577,6 +580,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
for (int index = 0; index < Constants.TotalViewports; index++)
|
||||
{
|
||||
if (disableTransform)
|
||||
{
|
||||
ref var scissor = ref _state.State.ScreenScissorState;
|
||||
|
||||
float rScale = _channel.TextureManager.RenderTargetScale;
|
||||
var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
|
||||
|
||||
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var transform = ref _state.State.ViewportTransform[index];
|
||||
ref var extents = ref _state.State.ViewportExtents[index];
|
||||
|
||||
@@ -628,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetViewports(0, viewports);
|
||||
_context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -928,7 +942,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
size = endAddress.Pack() - address + 1;
|
||||
|
||||
if (stride > 0 && indexTypeSmall)
|
||||
if (stride > 0 && indexTypeSmall && _drawState.DrawIndexed && !instanced)
|
||||
{
|
||||
// If the index type is a small integer type, then we might be still able
|
||||
// to reduce the vertex buffer size based on the maximum possible index value.
|
||||
@@ -1194,7 +1208,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
return new GpuChannelGraphicsState(
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode);
|
||||
_state.State.TessMode,
|
||||
_state.State.ViewportTransformEnable == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1393,6 +1393,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="size">The size of the flushing memory access</param>
|
||||
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
|
||||
{
|
||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||
// In this case it will skip registering the action, but here we are already handling it,
|
||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||
|
||||
handle.ClearActionRegistered();
|
||||
|
||||
if (!handle.Modified)
|
||||
{
|
||||
return;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@@ -32,9 +33,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private ulong _modifiedSync;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a tracking action is currently registered or not.
|
||||
/// Whether a tracking action is currently registered or not. (0/1)
|
||||
/// </summary>
|
||||
private bool _actionRegistered;
|
||||
private int _actionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a sync action is currently registered or not.
|
||||
@@ -171,11 +172,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
if (!_actionRegistered)
|
||||
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
|
||||
_actionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +232,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="context">The GPU context used to wait for sync</param>
|
||||
public void Sync(GpuContext context)
|
||||
{
|
||||
_actionRegistered = false;
|
||||
|
||||
bool needsSync = !context.IsGpuThread();
|
||||
|
||||
if (needsSync)
|
||||
@@ -263,21 +260,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the action registered variable, indicating that the tracking action should be
|
||||
/// re-registered on the next modification.
|
||||
/// </summary>
|
||||
public void ClearActionRegistered()
|
||||
{
|
||||
Interlocked.Exchange(ref _actionRegistered, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to perform when a sync number is registered after modification.
|
||||
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
||||
/// </summary>
|
||||
private void SyncAction()
|
||||
{
|
||||
// The storage will need to signal modified again to update the sync number in future.
|
||||
_group.Storage.SignalModifiedDirty();
|
||||
|
||||
lock (Overlaps)
|
||||
{
|
||||
foreach (Texture texture in Overlaps)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Register region tracking for CPU? (again)
|
||||
_registeredSync = _modifiedSync;
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (!_actionRegistered)
|
||||
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
|
||||
_actionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -166,7 +166,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
||||
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
||||
topology,
|
||||
tessMode);
|
||||
tessMode,
|
||||
false);
|
||||
|
||||
TransformFeedbackDescriptor[] tfdNew = null;
|
||||
|
||||
|
@@ -185,6 +185,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
return _oldSpecState.GraphicsState.EarlyZForce;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryViewportTransformDisable()
|
||||
{
|
||||
return _oldSpecState.GraphicsState.ViewportTransformDisable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterTexture(int handle, int cbufSlot)
|
||||
{
|
||||
|
@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 1;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 0;
|
||||
private const uint CodeGenVersion = 1;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -217,6 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return _state.GraphicsState.EarlyZForce;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryViewportTransformDisable()
|
||||
{
|
||||
return _state.GraphicsState.ViewportTransformDisable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterTexture(int handle, int cbufSlot)
|
||||
{
|
||||
|
@@ -25,17 +25,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
public readonly TessMode TessellationMode;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the viewport transform is disabled.
|
||||
/// </summary>
|
||||
public readonly bool ViewportTransformDisable;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU graphics state.
|
||||
/// </summary>
|
||||
/// <param name="earlyZForce">Early Z force enable</param>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="tessellationMode">Tessellation mode</param>
|
||||
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode)
|
||||
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
|
||||
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
|
||||
{
|
||||
EarlyZForce = earlyZForce;
|
||||
Topology = topology;
|
||||
TessellationMode = tessellationMode;
|
||||
ViewportTransformDisable = viewportTransformDisable;
|
||||
}
|
||||
}
|
||||
}
|
@@ -249,12 +249,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
ShaderAddresses addresses)
|
||||
{
|
||||
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses))
|
||||
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, graphicsState, gpShaders, addresses))
|
||||
{
|
||||
return gpShaders;
|
||||
}
|
||||
|
||||
if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode))
|
||||
if (_graphicsShaderCache.TryFind(channel, poolState, graphicsState, addresses, out gpShaders, out var cachedGuestCode))
|
||||
{
|
||||
_gpPrograms[addresses] = gpShaders;
|
||||
return gpShaders;
|
||||
@@ -429,12 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel using the shader</param>
|
||||
/// <param name="poolState">GPU channel state to verify shader compatibility</param>
|
||||
/// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
|
||||
/// <param name="gpShaders">Cached graphics shaders</param>
|
||||
/// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
|
||||
/// <returns>True if the code is different, false otherwise</returns>
|
||||
private static bool IsShaderEqual(
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
CachedShaderProgram gpShaders,
|
||||
ShaderAddresses addresses)
|
||||
{
|
||||
@@ -452,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState);
|
||||
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -208,6 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </remarks>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">Graphics state</param>
|
||||
/// <param name="addresses">Guest addresses of the shaders to find</param>
|
||||
/// <param name="program">Cached host program for the given state, if found</param>
|
||||
/// <param name="guestCode">Cached guest code, if any found</param>
|
||||
@@ -215,6 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
public bool TryFind(
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
ShaderAddresses addresses,
|
||||
out CachedShaderProgram program,
|
||||
out CachedGraphicsGuestCode guestCode)
|
||||
@@ -234,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
|
||||
{
|
||||
return specList.TryFindForGraphics(channel, poolState, out program);
|
||||
return specList.TryFindForGraphics(channel, poolState, graphicsState, out program);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -24,13 +24,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">Graphics state</param>
|
||||
/// <param name="program">Cached program, if found</param>
|
||||
/// <returns>True if a compatible program is found, false otherwise</returns>
|
||||
public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
|
||||
public bool TryFindForGraphics(
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
out CachedShaderProgram program)
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState))
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
|
@@ -395,9 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">Graphics state</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState)
|
||||
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
|
||||
{
|
||||
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Matches(channel, poolState, isCompute: false);
|
||||
}
|
||||
|
||||
|
@@ -379,20 +379,28 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
switch (op)
|
||||
{
|
||||
case GAL.StencilOp.Keep:
|
||||
case GAL.StencilOp.KeepGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.Keep;
|
||||
case GAL.StencilOp.Zero:
|
||||
case GAL.StencilOp.ZeroGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.Zero;
|
||||
case GAL.StencilOp.Replace:
|
||||
case GAL.StencilOp.ReplaceGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.Replace;
|
||||
case GAL.StencilOp.IncrementAndClamp:
|
||||
case GAL.StencilOp.IncrementAndClampGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.Incr;
|
||||
case GAL.StencilOp.DecrementAndClamp:
|
||||
case GAL.StencilOp.DecrementAndClampGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.Decr;
|
||||
case GAL.StencilOp.Invert:
|
||||
case GAL.StencilOp.InvertGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.Invert;
|
||||
case GAL.StencilOp.IncrementAndWrap:
|
||||
case GAL.StencilOp.IncrementAndWrapGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap;
|
||||
case GAL.StencilOp.DecrementAndWrap:
|
||||
case GAL.StencilOp.DecrementAndWrapGl:
|
||||
return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap;
|
||||
}
|
||||
|
||||
|
@@ -1266,7 +1266,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
_vertexArray.SetVertexBuffers(vertexBuffers);
|
||||
}
|
||||
|
||||
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports)
|
||||
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
|
||||
{
|
||||
Array.Resize(ref _viewportArray, viewports.Length * 4);
|
||||
Array.Resize(ref _depthRangeArray, viewports.Length * 2);
|
||||
@@ -1305,6 +1305,19 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
GL.ViewportArray(first, viewports.Length, viewportArray);
|
||||
GL.DepthRangeArray(first, viewports.Length, depthRangeArray);
|
||||
|
||||
float disableTransformF = disableTransform ? 1.0f : 0.0f;
|
||||
if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform)
|
||||
{
|
||||
float scale = _renderScale[0].X;
|
||||
_supportBuffer.UpdateViewportInverse(new Vector4<float>
|
||||
{
|
||||
X = scale * 2f / viewports[first].Region.Width,
|
||||
Y = scale * 2f / viewports[first].Region.Height,
|
||||
Z = 1,
|
||||
W = disableTransformF
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void TextureBarrier()
|
||||
|
@@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
context.AppendLine();
|
||||
}
|
||||
}
|
||||
else if (isFragment)
|
||||
else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
DeclareSupportUniformBlock(context, context.Config.Stage, 0);
|
||||
}
|
||||
@@ -615,8 +615,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
|
||||
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
|
||||
{
|
||||
bool isFragment = stage == ShaderStage.Fragment;
|
||||
if (!isFragment && scaleElements == 0)
|
||||
bool needsSupportBlock = stage == ShaderStage.Fragment ||
|
||||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
|
||||
|
||||
if (!needsSupportBlock && scaleElements == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -630,6 +632,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
case ShaderStage.Vertex:
|
||||
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
|
||||
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
|
||||
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
|
||||
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
|
||||
break;
|
||||
case ShaderStage.Compute:
|
||||
|
@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
public const string SupportBlockName = "support_block";
|
||||
public const string SupportBlockAlphaTestName = "s_alpha_test";
|
||||
public const string SupportBlockIsBgraName = "s_is_bgra";
|
||||
public const string SupportBlockViewportInverse = "s_viewport_inverse";
|
||||
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
|
||||
public const string SupportBlockRenderScaleName = "s_render_scale";
|
||||
|
||||
|
@@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", VariableType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", VariableType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", VariableType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) }
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) },
|
||||
|
||||
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", VariableType.F32) },
|
||||
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", VariableType.F32) }
|
||||
};
|
||||
|
||||
private Dictionary<AstOperand, string> _locals;
|
||||
|
@@ -329,6 +329,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries if host state disables the viewport transform.
|
||||
/// </summary>
|
||||
/// <returns>True if the viewport transform is disabled</returns>
|
||||
bool QueryViewportTransformDisable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a texture used by the shader.
|
||||
/// </summary>
|
||||
|
@@ -206,7 +206,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
|
||||
if (emit)
|
||||
{
|
||||
if (context.Config.LastInVertexPipeline)
|
||||
{
|
||||
context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
|
||||
|
||||
context.EmitVertex();
|
||||
|
||||
// Restore output position value before transformation.
|
||||
|
||||
if (tempXLocal != null)
|
||||
{
|
||||
context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
|
||||
}
|
||||
|
||||
if (tempYLocal != null)
|
||||
{
|
||||
context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
|
||||
}
|
||||
|
||||
if (tempZLocal != null)
|
||||
{
|
||||
context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.EmitVertex();
|
||||
}
|
||||
}
|
||||
|
||||
if (cut)
|
||||
|
@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
public static int FragmentAlphaTestOffset;
|
||||
public static int FragmentIsBgraOffset;
|
||||
public static int ViewportInverseOffset;
|
||||
public static int FragmentRenderScaleCountOffset;
|
||||
public static int GraphicsRenderScaleOffset;
|
||||
public static int ComputeRenderScaleOffset;
|
||||
@@ -40,6 +41,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest);
|
||||
FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra);
|
||||
ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse);
|
||||
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
|
||||
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
|
||||
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
||||
@@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
public Vector4<int> FragmentAlphaTest;
|
||||
public Array8<Vector4<int>> FragmentIsBgra;
|
||||
public Vector4<float> ViewportInverse;
|
||||
public Vector4<int> FragmentRenderScaleCount;
|
||||
|
||||
// Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs.
|
||||
|
@@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public const int FragmentOutputIsBgraBase = 0x1000100;
|
||||
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
|
||||
|
||||
public const int SupportBlockViewInverseX = 0x1000200;
|
||||
public const int SupportBlockViewInverseY = 0x1000204;
|
||||
|
||||
public const int ThreadIdX = 0x2000000;
|
||||
public const int ThreadIdY = 0x2000004;
|
||||
public const int ThreadIdZ = 0x2000008;
|
||||
|
@@ -154,9 +154,56 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return label;
|
||||
}
|
||||
|
||||
public void PrepareForVertexReturn()
|
||||
{
|
||||
if (Config.GpuAccessor.QueryViewportTransformDisable())
|
||||
{
|
||||
Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask);
|
||||
Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask);
|
||||
Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX);
|
||||
Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY);
|
||||
Operand negativeOne = ConstF(-1.0f);
|
||||
|
||||
this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
|
||||
this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
|
||||
{
|
||||
if (Config.GpuAccessor.QueryViewportTransformDisable())
|
||||
{
|
||||
oldXLocal = Local();
|
||||
this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask));
|
||||
oldYLocal = Local();
|
||||
this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask));
|
||||
}
|
||||
else
|
||||
{
|
||||
oldXLocal = null;
|
||||
oldYLocal = null;
|
||||
}
|
||||
|
||||
// Will be used by Vulkan backend for depth mode emulation.
|
||||
oldZLocal = null;
|
||||
|
||||
PrepareForVertexReturn();
|
||||
}
|
||||
|
||||
public void PrepareForReturn()
|
||||
{
|
||||
if (!IsNonMain && Config.Stage == ShaderStage.Fragment)
|
||||
if (IsNonMain)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config.LastInVertexPipeline &&
|
||||
(Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
|
||||
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
|
||||
{
|
||||
PrepareForVertexReturn();
|
||||
}
|
||||
else if (Config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
if (Config.OmapDepth)
|
||||
{
|
||||
|
@@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public ShaderStage Stage { get; }
|
||||
|
||||
public bool GpPassthrough { get; }
|
||||
public bool LastInVertexPipeline { get; private set; }
|
||||
|
||||
public int ThreadsPerInputPrimitive { get; }
|
||||
|
||||
@@ -135,6 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
OmapSampleMask = header.OmapSampleMask;
|
||||
OmapDepth = header.OmapDepth;
|
||||
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
|
||||
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
|
||||
}
|
||||
|
||||
public int GetDepthRegister()
|
||||
@@ -274,6 +276,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
|
||||
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
|
||||
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
|
||||
|
||||
if (config.Stage != ShaderStage.Fragment)
|
||||
{
|
||||
LastInVertexPipeline = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void MergeOutputUserAttributes(int mask, int maskPerPatch)
|
||||
|
@@ -17,12 +17,14 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||
using static Ryujinx.HLE.HOS.ModLoader;
|
||||
@@ -307,6 +309,94 @@ namespace Ryujinx.HLE.HOS
|
||||
LoadNca(nca, null, null);
|
||||
}
|
||||
|
||||
public void LoadServiceNca(string ncaFile)
|
||||
{
|
||||
// Disable PPTC here as it does not support multiple processes running.
|
||||
// TODO: This should be eventually removed and it should stop using global state and
|
||||
// instead manage the cache per process.
|
||||
Ptc.Close();
|
||||
PtcProfiler.Stop();
|
||||
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = codeFs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false);
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs);
|
||||
|
||||
string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16");
|
||||
bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
|
||||
|
||||
string programName = Encoding.ASCII.GetString(npdm.Meta.Value.ProgramName).TrimEnd('\0');
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
||||
{
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
@@ -507,7 +597,7 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
||||
{
|
||||
metaData = null; //TODO: Check if we should retain old npdm
|
||||
metaData = null; // TODO: Check if we should retain old npdm.
|
||||
}
|
||||
|
||||
metaData ??= ReadNpdm(codeFs);
|
||||
@@ -520,7 +610,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // file doesn't exist, skip
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
@@ -532,13 +622,13 @@ namespace Ryujinx.HLE.HOS
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// ExeFs file replacements
|
||||
// ExeFs file replacements.
|
||||
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
|
||||
|
||||
// collect the nsos, ignoring ones that aren't used
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
// take the npdm from mods if present
|
||||
// Take the npdm from mods if present.
|
||||
if (modLoadResult.Npdm != null)
|
||||
{
|
||||
metaData = modLoadResult.Npdm;
|
||||
@@ -561,10 +651,21 @@ namespace Ryujinx.HLE.HOS
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, _device.Configuration.MemoryManagerMode);
|
||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode);
|
||||
|
||||
// We allow it for nx-hbloader because it can be used to launch homebrew.
|
||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, new ProgramInfo(in npdm), executables: programs);
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit);
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: programs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
|
||||
}
|
||||
@@ -573,7 +674,7 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
MetaLoader metaData = GetDefaultNpdm();
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm);
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: true);
|
||||
|
||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||
|
||||
@@ -586,7 +687,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
executable = obj;
|
||||
|
||||
// homebrew NRO can actually have some data after the actual NRO
|
||||
// Homebrew NRO can actually have some data after the actual NRO.
|
||||
if (input.Length > obj.FileSize)
|
||||
{
|
||||
input.Position = obj.FileSize;
|
||||
@@ -665,7 +766,7 @@ namespace Ryujinx.HLE.HOS
|
||||
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
// Explicitly null titleid to disable the shader cache
|
||||
// Explicitly null titleid to disable the shader cache.
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = null;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
|
@@ -21,15 +21,20 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
mode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MemoryManagerMode.SoftwarePageTable:
|
||||
return new ArmProcessContext<MemoryManager>(pid, _gpu, new MemoryManager(addressSpaceSize, invalidAccessHandler), for64Bit);
|
||||
return new ArmProcessContext<MemoryManager>(pid, _gpu, new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler), for64Bit);
|
||||
|
||||
case MemoryManagerMode.HostMapped:
|
||||
case MemoryManagerMode.HostMappedUnsafe:
|
||||
bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe;
|
||||
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _gpu, new MemoryManagerHostMapped(addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit);
|
||||
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _gpu, new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
@@ -72,6 +72,8 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
internal List<NfpDevice> NfpDevices { get; private set; }
|
||||
|
||||
internal SmRegistry SmRegistry { get; private set; }
|
||||
|
||||
internal ServerBase SmServer { get; private set; }
|
||||
internal ServerBase BsdServer { get; private set; }
|
||||
internal ServerBase AudRenServer { get; private set; }
|
||||
@@ -291,7 +293,8 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
private void InitializeServices()
|
||||
{
|
||||
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext));
|
||||
SmRegistry = new SmRegistry();
|
||||
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
|
||||
|
||||
// Wait until SM server thread is done with initialization,
|
||||
// only then doing connections to SM is safe.
|
||||
|
@@ -59,5 +59,15 @@ namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
return GetCurrentThread().Owner;
|
||||
}
|
||||
|
||||
internal static KProcess GetProcessByPid(ulong pid)
|
||||
{
|
||||
if (Context.Processes.TryGetValue(pid, out KProcess process))
|
||||
{
|
||||
return process;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
168
Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs
Normal file
168
Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KCodeMemory : KAutoObject
|
||||
{
|
||||
public KProcess Owner { get; private set; }
|
||||
private readonly KPageList _pageList;
|
||||
private readonly object _lock;
|
||||
private ulong _address;
|
||||
private bool _isOwnerMapped;
|
||||
private bool _isMapped;
|
||||
|
||||
public KCodeMemory(KernelContext context) : base(context)
|
||||
{
|
||||
_pageList = new KPageList();
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
public KernelResult Initialize(ulong address, ulong size)
|
||||
{
|
||||
Owner = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KernelResult result = Owner.MemoryManager.BorrowCodeMemory(_pageList, address, size);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Owner.CpuMemory.Fill(address, size, 0xff);
|
||||
Owner.IncrementReferenceCount();
|
||||
|
||||
_address = address;
|
||||
_isMapped = false;
|
||||
_isOwnerMapped = false;
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult Map(ulong address, ulong size, KMemoryPermission perm)
|
||||
{
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isMapped)
|
||||
{
|
||||
return KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
KProcess process = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KernelResult result = process.MemoryManager.MapPages(address, _pageList, MemoryState.CodeWritable, KMemoryPermission.ReadAndWrite);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
_isMapped = true;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult MapToOwner(ulong address, ulong size, KMemoryPermission permission)
|
||||
{
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isOwnerMapped)
|
||||
{
|
||||
return KernelResult.InvalidState;
|
||||
}
|
||||
|
||||
Debug.Assert(permission == KMemoryPermission.Read || permission == KMemoryPermission.ReadAndExecute);
|
||||
|
||||
KernelResult result = Owner.MemoryManager.MapPages(address, _pageList, MemoryState.CodeReadOnly, permission);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
_isOwnerMapped = true;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult Unmap(ulong address, ulong size)
|
||||
{
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
KProcess process = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KernelResult result = process.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeWritable);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Debug.Assert(_isMapped);
|
||||
|
||||
_isMapped = false;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult UnmapFromOwner(ulong address, ulong size)
|
||||
{
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
KernelResult result = Owner.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeReadOnly);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Debug.Assert(_isOwnerMapped);
|
||||
|
||||
_isOwnerMapped = false;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
if (!_isMapped && !_isOwnerMapped)
|
||||
{
|
||||
ulong size = _pageList.GetPagesCount() * KPageTableBase.PageSize;
|
||||
|
||||
if (Owner.MemoryManager.UnborrowCodeMemory(_address, size, _pageList) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes.");
|
||||
}
|
||||
}
|
||||
|
||||
Owner.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,10 +1,7 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
@@ -12,17 +9,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
private readonly IVirtualMemoryManager _cpuMemory;
|
||||
|
||||
public override bool SupportsMemoryAliasing => true;
|
||||
|
||||
public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
|
||||
{
|
||||
_cpuMemory = cpuMemory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList)
|
||||
{
|
||||
return _cpuMemory.GetPhysicalRegions(va, size);
|
||||
var ranges = _cpuMemory.GetPhysicalRegions(va, size);
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
pageList.AddRange(range.Address + DramMemoryMap.DramBase, range.Size / PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -34,7 +33,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission)
|
||||
{
|
||||
var srcRanges = GetPhysicalRegions(src, pagesCount * PageSize);
|
||||
KPageList pageList = new KPageList();
|
||||
GetPhysicalRegions(src, pagesCount * PageSize, pageList);
|
||||
|
||||
KernelResult result = Reprotect(src, pagesCount, KMemoryPermission.None);
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
return result;
|
||||
}
|
||||
|
||||
result = MapPages(dst, srcRanges, newDstPermission);
|
||||
result = MapPages(dst, pageList, newDstPermission, false, 0);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
@@ -59,10 +59,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
ulong size = pagesCount * PageSize;
|
||||
|
||||
var srcRanges = GetPhysicalRegions(src, size);
|
||||
var dstRanges = GetPhysicalRegions(dst, size);
|
||||
KPageList srcPageList = new KPageList();
|
||||
KPageList dstPageList = new KPageList();
|
||||
|
||||
if (!dstRanges.SequenceEqual(srcRanges))
|
||||
GetPhysicalRegions(src, size, srcPageList);
|
||||
GetPhysicalRegions(dst, size, dstPageList);
|
||||
|
||||
if (!dstPageList.IsEqual(srcPageList))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
@@ -78,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
KernelResult mapResult = MapPages(dst, dstRanges, oldDstPermission);
|
||||
KernelResult mapResult = MapPages(dst, dstPageList, oldDstPermission, false, 0);
|
||||
Debug.Assert(mapResult == KernelResult.Success);
|
||||
}
|
||||
|
||||
@@ -92,7 +95,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
Context.Memory.Commit(srcPa - DramMemoryMap.DramBase, size);
|
||||
|
||||
_cpuMemory.Map(dstVa, Context.Memory.GetPointer(srcPa - DramMemoryMap.DramBase, size), size);
|
||||
_cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size);
|
||||
|
||||
if (DramMemoryMap.IsHeapPhysicalAddress(srcPa))
|
||||
{
|
||||
@@ -121,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
Context.Memory.Commit(addr, size);
|
||||
|
||||
_cpuMemory.Map(currentVa, Context.Memory.GetPointer(addr, size), size);
|
||||
_cpuMemory.Map(currentVa, addr, size);
|
||||
|
||||
if (shouldFillPages)
|
||||
{
|
||||
@@ -136,33 +139,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult MapPages(ulong address, IEnumerable<HostMemoryRange> ranges, KMemoryPermission permission)
|
||||
{
|
||||
ulong currentVa = address;
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
ulong size = range.Size;
|
||||
|
||||
ulong pa = GetDramAddressFromHostAddress(range.Address);
|
||||
if (pa != ulong.MaxValue)
|
||||
{
|
||||
pa += DramMemoryMap.DramBase;
|
||||
if (DramMemoryMap.IsHeapPhysicalAddress(pa))
|
||||
{
|
||||
Context.MemoryManager.IncrementPagesReferenceCount(pa, size / PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
_cpuMemory.Map(currentVa, range.Address, size);
|
||||
|
||||
currentVa += size;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult Unmap(ulong address, ulong pagesCount)
|
||||
{
|
||||
@@ -172,13 +148,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
ulong pa = GetDramAddressFromHostAddress(region.Address);
|
||||
if (pa == ulong.MaxValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pa += DramMemoryMap.DramBase;
|
||||
ulong pa = region.Address + DramMemoryMap.DramBase;
|
||||
if (DramMemoryMap.IsHeapPhysicalAddress(pa))
|
||||
{
|
||||
pagesToClose.AddRange(pa, region.Size / PageSize);
|
||||
@@ -217,15 +187,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
_cpuMemory.Write(va, data);
|
||||
}
|
||||
|
||||
private ulong GetDramAddressFromHostAddress(nuint hostAddress)
|
||||
{
|
||||
if (hostAddress < (nuint)(ulong)Context.Memory.Pointer || hostAddress >= (nuint)((ulong)Context.Memory.Pointer + Context.Memory.Size))
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return hostAddress - (ulong)Context.Memory.Pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,9 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
@@ -73,8 +71,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
private MersenneTwister _randomNumberGenerator;
|
||||
|
||||
public abstract bool SupportsMemoryAliasing { get; }
|
||||
|
||||
private MemoryFillValue _heapFillValue;
|
||||
private MemoryFillValue _ipcFillValue;
|
||||
|
||||
@@ -380,8 +376,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public KernelResult UnmapPages(ulong address, ulong pagesCount, IEnumerable<HostMemoryRange> ranges, MemoryState stateExpected)
|
||||
public KernelResult UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected)
|
||||
{
|
||||
ulong pagesCount = pageList.GetPagesCount();
|
||||
ulong size = pagesCount * PageSize;
|
||||
|
||||
ulong endAddr = address + size;
|
||||
@@ -405,9 +402,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
lock (_blockManager)
|
||||
{
|
||||
var currentRanges = GetPhysicalRegions(address, size);
|
||||
KPageList currentPageList = new KPageList();
|
||||
|
||||
if (!currentRanges.SequenceEqual(ranges))
|
||||
GetPhysicalRegions(address, size, currentPageList);
|
||||
|
||||
if (!currentPageList.IsEqual(pageList))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
@@ -1096,6 +1095,77 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public KernelResult UnmapProcessMemory(ulong dst, ulong size, KPageTableBase srcPageTable, ulong src)
|
||||
{
|
||||
lock (_blockManager)
|
||||
{
|
||||
lock (srcPageTable._blockManager)
|
||||
{
|
||||
bool success = CheckRange(
|
||||
dst,
|
||||
size,
|
||||
MemoryState.Mask,
|
||||
MemoryState.ProcessMemory,
|
||||
KMemoryPermission.ReadAndWrite,
|
||||
KMemoryPermission.ReadAndWrite,
|
||||
MemoryAttribute.Mask,
|
||||
MemoryAttribute.None,
|
||||
MemoryAttribute.IpcAndDeviceMapped,
|
||||
out _,
|
||||
out _,
|
||||
out _);
|
||||
|
||||
success &= srcPageTable.CheckRange(
|
||||
src,
|
||||
size,
|
||||
MemoryState.MapProcessAllowed,
|
||||
MemoryState.MapProcessAllowed,
|
||||
KMemoryPermission.None,
|
||||
KMemoryPermission.None,
|
||||
MemoryAttribute.Mask,
|
||||
MemoryAttribute.None,
|
||||
MemoryAttribute.IpcAndDeviceMapped,
|
||||
out _,
|
||||
out _,
|
||||
out _);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
KPageList srcPageList = new KPageList();
|
||||
KPageList dstPageList = new KPageList();
|
||||
|
||||
srcPageTable.GetPhysicalRegions(src, size, srcPageList);
|
||||
GetPhysicalRegions(dst, size, dstPageList);
|
||||
|
||||
if (!dstPageList.IsEqual(srcPageList))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
|
||||
{
|
||||
return KernelResult.OutOfResource;
|
||||
}
|
||||
|
||||
ulong pagesCount = size / PageSize;
|
||||
|
||||
KernelResult result = Unmap(dst, pagesCount);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
_blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped);
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
|
||||
{
|
||||
lock (_blockManager)
|
||||
@@ -1690,11 +1760,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
bool send,
|
||||
out ulong dst)
|
||||
{
|
||||
if (!SupportsMemoryAliasing)
|
||||
{
|
||||
throw new NotSupportedException("Memory aliasing not supported, can't map IPC buffers.");
|
||||
}
|
||||
|
||||
dst = 0;
|
||||
|
||||
if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
|
||||
@@ -1828,7 +1893,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
ulong alignedSize = endAddrTruncated - addressRounded;
|
||||
|
||||
KernelResult result = MapPages(currentVa, srcPageTable.GetPhysicalRegions(addressRounded, alignedSize), permission);
|
||||
KPageList pageList = new KPageList();
|
||||
srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList);
|
||||
|
||||
KernelResult result = MapPages(currentVa, pageList, permission);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
@@ -2026,6 +2094,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
block.RestoreIpcMappingPermission();
|
||||
}
|
||||
|
||||
public KernelResult GetPagesIfStateEquals(
|
||||
ulong address,
|
||||
ulong size,
|
||||
MemoryState stateMask,
|
||||
MemoryState stateExpected,
|
||||
KMemoryPermission permissionMask,
|
||||
KMemoryPermission permissionExpected,
|
||||
MemoryAttribute attributeMask,
|
||||
MemoryAttribute attributeExpected,
|
||||
KPageList pageList)
|
||||
{
|
||||
if (!InsideAddrSpace(address, size))
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
lock (_blockManager)
|
||||
{
|
||||
if (CheckRange(
|
||||
address,
|
||||
size,
|
||||
stateMask | MemoryState.IsPoolAllocated,
|
||||
stateExpected | MemoryState.IsPoolAllocated,
|
||||
permissionMask,
|
||||
permissionExpected,
|
||||
attributeMask,
|
||||
attributeExpected,
|
||||
MemoryAttribute.IpcAndDeviceMapped,
|
||||
out _,
|
||||
out _,
|
||||
out _))
|
||||
{
|
||||
GetPhysicalRegions(address, size, pageList);
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KernelResult BorrowIpcBuffer(ulong address, ulong size)
|
||||
{
|
||||
return SetAttributesAndChangePermission(
|
||||
@@ -2041,7 +2152,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryAttribute.Borrowed);
|
||||
}
|
||||
|
||||
public KernelResult BorrowTransferMemory(List<HostMemoryRange> ranges, ulong address, ulong size, KMemoryPermission permission)
|
||||
public KernelResult BorrowTransferMemory(KPageList pageList, ulong address, ulong size, KMemoryPermission permission)
|
||||
{
|
||||
return SetAttributesAndChangePermission(
|
||||
address,
|
||||
@@ -2054,7 +2165,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryAttribute.None,
|
||||
permission,
|
||||
MemoryAttribute.Borrowed,
|
||||
ranges);
|
||||
pageList);
|
||||
}
|
||||
|
||||
public KernelResult BorrowCodeMemory(KPageList pageList, ulong address, ulong size)
|
||||
{
|
||||
return SetAttributesAndChangePermission(
|
||||
address,
|
||||
size,
|
||||
MemoryState.CodeMemoryAllowed,
|
||||
MemoryState.CodeMemoryAllowed,
|
||||
KMemoryPermission.Mask,
|
||||
KMemoryPermission.ReadAndWrite,
|
||||
MemoryAttribute.Mask,
|
||||
MemoryAttribute.None,
|
||||
KMemoryPermission.None,
|
||||
MemoryAttribute.Borrowed,
|
||||
pageList);
|
||||
}
|
||||
|
||||
private KernelResult SetAttributesAndChangePermission(
|
||||
@@ -2068,7 +2195,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryAttribute attributeExpected,
|
||||
KMemoryPermission newPermission,
|
||||
MemoryAttribute attributeSetMask,
|
||||
List<HostMemoryRange> ranges = null)
|
||||
KPageList pageList = null)
|
||||
{
|
||||
if (address + size <= address || !InsideAddrSpace(address, size))
|
||||
{
|
||||
@@ -2093,7 +2220,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
ulong pagesCount = size / PageSize;
|
||||
|
||||
ranges?.AddRange(GetPhysicalRegions(address, size));
|
||||
if (pageList != null)
|
||||
{
|
||||
GetPhysicalRegions(address, pagesCount * PageSize, pageList);
|
||||
}
|
||||
|
||||
if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
|
||||
{
|
||||
@@ -2143,7 +2273,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryAttribute.Borrowed);
|
||||
}
|
||||
|
||||
public KernelResult UnborrowTransferMemory(ulong address, ulong size, List<HostMemoryRange> ranges)
|
||||
public KernelResult UnborrowTransferMemory(ulong address, ulong size, KPageList pageList)
|
||||
{
|
||||
return ClearAttributesAndChangePermission(
|
||||
address,
|
||||
@@ -2156,7 +2286,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryAttribute.Borrowed,
|
||||
KMemoryPermission.ReadAndWrite,
|
||||
MemoryAttribute.Borrowed,
|
||||
ranges);
|
||||
pageList);
|
||||
}
|
||||
|
||||
public KernelResult UnborrowCodeMemory(ulong address, ulong size, KPageList pageList)
|
||||
{
|
||||
return ClearAttributesAndChangePermission(
|
||||
address,
|
||||
size,
|
||||
MemoryState.CodeMemoryAllowed,
|
||||
MemoryState.CodeMemoryAllowed,
|
||||
KMemoryPermission.None,
|
||||
KMemoryPermission.None,
|
||||
MemoryAttribute.Mask,
|
||||
MemoryAttribute.Borrowed,
|
||||
KMemoryPermission.ReadAndWrite,
|
||||
MemoryAttribute.Borrowed,
|
||||
pageList);
|
||||
}
|
||||
|
||||
private KernelResult ClearAttributesAndChangePermission(
|
||||
@@ -2170,7 +2316,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
MemoryAttribute attributeExpected,
|
||||
KMemoryPermission newPermission,
|
||||
MemoryAttribute attributeClearMask,
|
||||
List<HostMemoryRange> ranges = null)
|
||||
KPageList pageList = null)
|
||||
{
|
||||
if (address + size <= address || !InsideAddrSpace(address, size))
|
||||
{
|
||||
@@ -2195,11 +2341,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
ulong pagesCount = size / PageSize;
|
||||
|
||||
if (ranges != null)
|
||||
if (pageList != null)
|
||||
{
|
||||
var currentRanges = GetPhysicalRegions(address, size);
|
||||
KPageList currentPageList = new KPageList();
|
||||
|
||||
if (!currentRanges.SequenceEqual(ranges))
|
||||
GetPhysicalRegions(address, pagesCount * PageSize, currentPageList);
|
||||
|
||||
if (!currentPageList.IsEqual(pageList))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
@@ -2741,8 +2889,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <returns>Array of physical regions</returns>
|
||||
protected abstract IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size);
|
||||
/// <param name="pageList">Page list where the ranges will be added</param>
|
||||
protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only span of data from CPU mapped memory.
|
||||
@@ -2803,16 +2951,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
/// <returns>Result of the mapping operation</returns>
|
||||
protected abstract KernelResult MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages = false, byte fillValue = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a region of memory into the specified host memory ranges.
|
||||
/// </summary>
|
||||
/// <param name="address">Destination virtual address that should be mapped</param>
|
||||
/// <param name="ranges">Ranges of host memory that should be mapped</param>
|
||||
/// <param name="permission">Permission of the region to be mapped</param>
|
||||
/// <returns>Result of the mapping operation</returns>
|
||||
/// <exception cref="NotSupportedException">The implementation does not support memory aliasing</exception>
|
||||
protected abstract KernelResult MapPages(ulong address, IEnumerable<HostMemoryRange> ranges, KMemoryPermission permission);
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps a region of memory that was previously mapped with one of the page mapping methods.
|
||||
/// </summary>
|
||||
|
@@ -1,139 +0,0 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KPageTableHostMapped : KPageTableBase
|
||||
{
|
||||
private const int CopyChunckSize = 0x100000;
|
||||
|
||||
private readonly IVirtualMemoryManager _cpuMemory;
|
||||
|
||||
public override bool SupportsMemoryAliasing => false;
|
||||
|
||||
public KPageTableHostMapped(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
|
||||
{
|
||||
_cpuMemory = cpuMemory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
return _cpuMemory.GetPhysicalRegions(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ReadOnlySpan<byte> GetSpan(ulong va, int size)
|
||||
{
|
||||
return _cpuMemory.GetSpan(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission)
|
||||
{
|
||||
ulong size = pagesCount * PageSize;
|
||||
|
||||
_cpuMemory.Map(dst, 0, size);
|
||||
|
||||
ulong currentSize = size;
|
||||
while (currentSize > 0)
|
||||
{
|
||||
ulong copySize = Math.Min(currentSize, CopyChunckSize);
|
||||
_cpuMemory.Write(dst, _cpuMemory.GetSpan(src, (int)copySize));
|
||||
currentSize -= copySize;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission)
|
||||
{
|
||||
ulong size = pagesCount * PageSize;
|
||||
|
||||
// TODO: Validation.
|
||||
|
||||
ulong currentSize = size;
|
||||
while (currentSize > 0)
|
||||
{
|
||||
ulong copySize = Math.Min(currentSize, CopyChunckSize);
|
||||
_cpuMemory.Write(src, _cpuMemory.GetSpan(dst, (int)copySize));
|
||||
currentSize -= copySize;
|
||||
}
|
||||
|
||||
_cpuMemory.Unmap(dst, size);
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult MapPages(ulong dstVa, ulong pagesCount, ulong srcPa, KMemoryPermission permission, bool shouldFillPages, byte fillValue)
|
||||
{
|
||||
_cpuMemory.Map(dstVa, 0, pagesCount * PageSize);
|
||||
|
||||
if (shouldFillPages)
|
||||
{
|
||||
_cpuMemory.Fill(dstVa, pagesCount * PageSize, fillValue);
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages, byte fillValue)
|
||||
{
|
||||
ulong pagesCount = pageList.GetPagesCount();
|
||||
|
||||
_cpuMemory.Map(address, 0, pagesCount * PageSize);
|
||||
|
||||
if (shouldFillPages)
|
||||
{
|
||||
_cpuMemory.Fill(address, pagesCount * PageSize, fillValue);
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult MapPages(ulong address, IEnumerable<HostMemoryRange> ranges, KMemoryPermission permission)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult Unmap(ulong address, ulong pagesCount)
|
||||
{
|
||||
_cpuMemory.Unmap(address, pagesCount * PageSize);
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||
{
|
||||
// TODO.
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override KernelResult ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||
{
|
||||
// TODO.
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SignalMemoryTracking(ulong va, ulong size, bool write)
|
||||
{
|
||||
_cpuMemory.SignalMemoryTracking(va, size, write);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
_cpuMemory.Write(va, data);
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KSharedMemory : KAutoObject
|
||||
{
|
||||
private readonly SharedMemoryStorage _storage;
|
||||
private readonly KPageList _pageList;
|
||||
|
||||
private readonly ulong _ownerPid;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
KMemoryPermission ownerPermission,
|
||||
KMemoryPermission userPermission) : base(context)
|
||||
{
|
||||
_storage = storage;
|
||||
_pageList = storage.GetPageList();
|
||||
_ownerPid = ownerPid;
|
||||
_ownerPermission = ownerPermission;
|
||||
_userPermission = userPermission;
|
||||
@@ -33,10 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
KProcess process,
|
||||
KMemoryPermission permission)
|
||||
{
|
||||
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
|
||||
|
||||
var pageList = _storage.GetPageList();
|
||||
if (pageList.GetPagesCount() != pagesCountRounded)
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
@@ -50,35 +47,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
KernelResult result = memoryManager.MapPages(address, pageList, MemoryState.SharedMemory, permission);
|
||||
|
||||
if (result == KernelResult.Success && !memoryManager.SupportsMemoryAliasing)
|
||||
{
|
||||
_storage.Borrow(process, address);
|
||||
return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public KernelResult UnmapFromProcess(
|
||||
KPageTableBase memoryManager,
|
||||
ulong address,
|
||||
ulong size,
|
||||
KProcess process)
|
||||
public KernelResult UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process)
|
||||
{
|
||||
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
|
||||
|
||||
var pageList = _storage.GetPageList();
|
||||
ulong pagesCount = pageList.GetPagesCount();
|
||||
|
||||
if (pagesCount != pagesCountRounded)
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
var ranges = _storage.GetRanges();
|
||||
|
||||
return memoryManager.UnmapPages(address, pagesCount, ranges, MemoryState.SharedMemory);
|
||||
return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
@@ -14,9 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
// TODO: Remove when we no longer need to read it from the owner directly.
|
||||
public KProcess Creator => _creator;
|
||||
|
||||
private readonly List<HostMemoryRange> _ranges;
|
||||
|
||||
private readonly SharedMemoryStorage _storage;
|
||||
private readonly KPageList _pageList;
|
||||
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
@@ -28,12 +24,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
public KTransferMemory(KernelContext context) : base(context)
|
||||
{
|
||||
_ranges = new List<HostMemoryRange>();
|
||||
_pageList = new KPageList();
|
||||
}
|
||||
|
||||
public KTransferMemory(KernelContext context, SharedMemoryStorage storage) : base(context)
|
||||
{
|
||||
_storage = storage;
|
||||
_pageList = storage.GetPageList();
|
||||
Permission = KMemoryPermission.ReadAndWrite;
|
||||
|
||||
_hasBeenInitialized = true;
|
||||
@@ -46,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
_creator = creator;
|
||||
|
||||
KernelResult result = creator.MemoryManager.BorrowTransferMemory(_ranges, address, size, permission);
|
||||
KernelResult result = creator.MemoryManager.BorrowTransferMemory(_pageList, address, size, permission);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
@@ -71,15 +67,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
KProcess process,
|
||||
KMemoryPermission permission)
|
||||
{
|
||||
if (_storage == null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
|
||||
|
||||
var pageList = _storage.GetPageList();
|
||||
if (pageList.GetPagesCount() != pagesCountRounded)
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
@@ -91,16 +79,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory;
|
||||
|
||||
KernelResult result = memoryManager.MapPages(address, pageList, state, KMemoryPermission.ReadAndWrite);
|
||||
KernelResult result = memoryManager.MapPages(address, _pageList, state, KMemoryPermission.ReadAndWrite);
|
||||
|
||||
if (result == KernelResult.Success)
|
||||
{
|
||||
_isMapped = true;
|
||||
|
||||
if (!memoryManager.SupportsMemoryAliasing)
|
||||
{
|
||||
_storage.Borrow(process, address);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -112,26 +95,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
ulong size,
|
||||
KProcess process)
|
||||
{
|
||||
if (_storage == null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
ulong pagesCountRounded = BitUtils.DivRoundUp(size, KPageTableBase.PageSize);
|
||||
|
||||
var pageList = _storage.GetPageList();
|
||||
ulong pagesCount = pageList.GetPagesCount();
|
||||
|
||||
if (pagesCount != pagesCountRounded)
|
||||
if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize))
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
var ranges = _storage.GetRanges();
|
||||
|
||||
MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory;
|
||||
|
||||
KernelResult result = memoryManager.UnmapPages(address, pagesCount, ranges, state);
|
||||
KernelResult result = memoryManager.UnmapPages(address, _pageList, state);
|
||||
|
||||
if (result == KernelResult.Success)
|
||||
{
|
||||
@@ -145,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
if (_hasBeenInitialized)
|
||||
{
|
||||
if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _ranges) != KernelResult.Success)
|
||||
if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _pageList) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes.");
|
||||
}
|
||||
|
@@ -1,8 +1,4 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
@@ -12,9 +8,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
private readonly KPageList _pageList;
|
||||
private readonly ulong _size;
|
||||
|
||||
private IVirtualMemoryManager _borrowerMemory;
|
||||
private ulong _borrowerVa;
|
||||
|
||||
public SharedMemoryStorage(KernelContext context, KPageList pageList)
|
||||
{
|
||||
_context = context;
|
||||
@@ -29,24 +22,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public void Borrow(KProcess dstProcess, ulong va)
|
||||
{
|
||||
ulong currentOffset = 0;
|
||||
|
||||
foreach (KPageNode pageNode in _pageList)
|
||||
{
|
||||
ulong address = pageNode.Address - DramMemoryMap.DramBase;
|
||||
ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
|
||||
|
||||
dstProcess.CpuMemory.Write(va + currentOffset, _context.Memory.GetSpan(address + currentOffset, (int)size));
|
||||
|
||||
currentOffset += size;
|
||||
}
|
||||
|
||||
_borrowerMemory = dstProcess.CpuMemory;
|
||||
_borrowerVa = va;
|
||||
}
|
||||
|
||||
public void ZeroFill()
|
||||
{
|
||||
for (ulong offset = 0; offset < _size; offset += sizeof(ulong))
|
||||
@@ -56,8 +31,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
}
|
||||
|
||||
public ref T GetRef<T>(ulong offset) where T : unmanaged
|
||||
{
|
||||
if (_borrowerMemory == null)
|
||||
{
|
||||
if (_pageList.Nodes.Count == 1)
|
||||
{
|
||||
@@ -67,33 +40,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
throw new NotImplementedException("Non-contiguous shared memory is not yet supported.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ref _borrowerMemory.GetRef<T>(_borrowerVa + offset);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<HostMemoryRange> GetRanges()
|
||||
{
|
||||
if (_borrowerMemory == null)
|
||||
{
|
||||
var ranges = new List<HostMemoryRange>();
|
||||
|
||||
foreach (KPageNode pageNode in _pageList)
|
||||
{
|
||||
ulong address = pageNode.Address - DramMemoryMap.DramBase;
|
||||
ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
|
||||
|
||||
ranges.Add(new HostMemoryRange(_context.Memory.GetPointer(address, size), size));
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _borrowerMemory.GetPhysicalRegions(_borrowerVa, _size);
|
||||
}
|
||||
}
|
||||
|
||||
public KPageList GetPageList()
|
||||
{
|
||||
|
@@ -60,6 +60,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
public KProcessCapabilities Capabilities { get; private set; }
|
||||
|
||||
public bool AllowCodeMemoryForJit { get; private set; }
|
||||
|
||||
public ulong TitleId { get; private set; }
|
||||
public bool IsApplication { get; private set; }
|
||||
public ulong Pid { get; private set; }
|
||||
@@ -90,7 +92,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
public HleProcessDebugger Debugger { get; private set; }
|
||||
|
||||
public KProcess(KernelContext context) : base(context)
|
||||
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
|
||||
{
|
||||
_processLock = new object();
|
||||
_threadingLock = new object();
|
||||
@@ -102,6 +104,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
Capabilities = new KProcessCapabilities();
|
||||
|
||||
AllowCodeMemoryForJit = allowCodeMemoryForJit;
|
||||
|
||||
RandomEntropy = new ulong[KScheduler.CpuCoresCount];
|
||||
PinnedThreads = new KThread[KScheduler.CpuCoresCount];
|
||||
|
||||
@@ -1076,15 +1080,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
Context = _contextFactory.Create(KernelContext, Pid, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit);
|
||||
|
||||
if (Context.AddressSpace is MemoryManagerHostMapped)
|
||||
{
|
||||
MemoryManager = new KPageTableHostMapped(KernelContext, CpuMemory);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryManager = new KPageTable(KernelContext, CpuMemory);
|
||||
}
|
||||
}
|
||||
|
||||
private bool InvalidAccessHandler(ulong va)
|
||||
{
|
||||
|
@@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
|
||||
{
|
||||
return new ProcessContext(new AddressSpaceManager(addressSpaceSize));
|
||||
return new ProcessContext(new AddressSpaceManager(context.Memory, addressSpaceSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs
Normal file
10
Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{
|
||||
enum CodeMemoryOperation : uint
|
||||
{
|
||||
Map,
|
||||
MapToOwner,
|
||||
Unmap,
|
||||
UnmapFromOwner
|
||||
};
|
||||
}
|
@@ -7,7 +7,6 @@ using Ryujinx.HLE.HOS.Kernel.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
@@ -1317,6 +1316,248 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return process.MemoryManager.UnmapPhysicalMemory(address, size);
|
||||
}
|
||||
|
||||
public KernelResult CreateCodeMemory(ulong address, ulong size, out int handle)
|
||||
{
|
||||
handle = 0;
|
||||
|
||||
if (!PageAligned(address))
|
||||
{
|
||||
return KernelResult.InvalidAddress;
|
||||
}
|
||||
|
||||
if (!PageAligned(size) || size == 0)
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
if (size + address <= address)
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
KCodeMemory codeMemory = new KCodeMemory(_context);
|
||||
|
||||
using var _ = new OnScopeExit(codeMemory.DecrementReferenceCount);
|
||||
|
||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||
|
||||
if (!currentProcess.MemoryManager.InsideAddrSpace(address, size))
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
KernelResult result = codeMemory.Initialize(address, size);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle);
|
||||
}
|
||||
|
||||
public KernelResult ControlCodeMemory(int handle, CodeMemoryOperation op, ulong address, ulong size, KMemoryPermission permission)
|
||||
{
|
||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KCodeMemory codeMemory = currentProcess.HandleTable.GetObject<KCodeMemory>(handle);
|
||||
|
||||
// Newer versions of the kernel also returns an error here if the owner and process
|
||||
// where the operation will happen are the same. We do not return an error here
|
||||
// for homebrew because some of them requires this to be patched out to work (for JIT).
|
||||
if (codeMemory == null || (!currentProcess.AllowCodeMemoryForJit && codeMemory.Owner == currentProcess))
|
||||
{
|
||||
return KernelResult.InvalidHandle;
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case CodeMemoryOperation.Map:
|
||||
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
if (permission != KMemoryPermission.ReadAndWrite)
|
||||
{
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
return codeMemory.Map(address, size, permission);
|
||||
|
||||
case CodeMemoryOperation.MapToOwner:
|
||||
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
if (permission != KMemoryPermission.Read && permission != KMemoryPermission.ReadAndExecute)
|
||||
{
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
return codeMemory.MapToOwner(address, size, permission);
|
||||
|
||||
case CodeMemoryOperation.Unmap:
|
||||
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
if (permission != KMemoryPermission.None)
|
||||
{
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
return codeMemory.Unmap(address, size);
|
||||
|
||||
case CodeMemoryOperation.UnmapFromOwner:
|
||||
if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
if (permission != KMemoryPermission.None)
|
||||
{
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
return codeMemory.UnmapFromOwner(address, size);
|
||||
|
||||
default: return KernelResult.InvalidEnumValue;
|
||||
}
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission)
|
||||
{
|
||||
if (!PageAligned(src))
|
||||
{
|
||||
return KernelResult.InvalidAddress;
|
||||
}
|
||||
|
||||
if (!PageAligned(size) || size == 0)
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
if (permission != KMemoryPermission.None &&
|
||||
permission != KMemoryPermission.Read &&
|
||||
permission != KMemoryPermission.ReadAndWrite &&
|
||||
permission != KMemoryPermission.ReadAndExecute)
|
||||
{
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
|
||||
|
||||
if (targetProcess == null)
|
||||
{
|
||||
return KernelResult.InvalidHandle;
|
||||
}
|
||||
|
||||
if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult MapProcessMemory(ulong dst, int handle, ulong src, ulong size)
|
||||
{
|
||||
if (!PageAligned(src) || !PageAligned(dst))
|
||||
{
|
||||
return KernelResult.InvalidAddress;
|
||||
}
|
||||
|
||||
if (!PageAligned(size) || size == 0)
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
if (dst + size <= dst || src + size <= src)
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
KProcess dstProcess = KernelStatic.GetCurrentProcess();
|
||||
KProcess srcProcess = dstProcess.HandleTable.GetObject<KProcess>(handle);
|
||||
|
||||
if (srcProcess == null)
|
||||
{
|
||||
return KernelResult.InvalidHandle;
|
||||
}
|
||||
|
||||
if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) ||
|
||||
!dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
KPageList pageList = new KPageList();
|
||||
|
||||
KernelResult result = srcProcess.MemoryManager.GetPagesIfStateEquals(
|
||||
src,
|
||||
size,
|
||||
MemoryState.MapProcessAllowed,
|
||||
MemoryState.MapProcessAllowed,
|
||||
KMemoryPermission.None,
|
||||
KMemoryPermission.None,
|
||||
MemoryAttribute.Mask,
|
||||
MemoryAttribute.None,
|
||||
pageList);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite);
|
||||
}
|
||||
|
||||
public KernelResult UnmapProcessMemory(ulong dst, int handle, ulong src, ulong size)
|
||||
{
|
||||
if (!PageAligned(src) || !PageAligned(dst))
|
||||
{
|
||||
return KernelResult.InvalidAddress;
|
||||
}
|
||||
|
||||
if (!PageAligned(size) || size == 0)
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
if (dst + size <= dst || src + size <= src)
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
KProcess dstProcess = KernelStatic.GetCurrentProcess();
|
||||
KProcess srcProcess = dstProcess.HandleTable.GetObject<KProcess>(handle);
|
||||
|
||||
if (srcProcess == null)
|
||||
{
|
||||
return KernelResult.InvalidHandle;
|
||||
}
|
||||
|
||||
if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) ||
|
||||
!dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory))
|
||||
{
|
||||
return KernelResult.InvalidMemRange;
|
||||
}
|
||||
|
||||
KernelResult result = dstProcess.MemoryManager.UnmapProcessMemory(dst, size, srcProcess.MemoryManager, src);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
|
||||
{
|
||||
if (!PageAligned(dst) || !PageAligned(src))
|
||||
@@ -1391,43 +1632,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size);
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, KMemoryPermission permission)
|
||||
{
|
||||
if (!PageAligned(src))
|
||||
{
|
||||
return KernelResult.InvalidAddress;
|
||||
}
|
||||
|
||||
if (!PageAligned(size) || size == 0)
|
||||
{
|
||||
return KernelResult.InvalidSize;
|
||||
}
|
||||
|
||||
if (permission != KMemoryPermission.None &&
|
||||
permission != KMemoryPermission.Read &&
|
||||
permission != KMemoryPermission.ReadAndWrite &&
|
||||
permission != KMemoryPermission.ReadAndExecute)
|
||||
{
|
||||
return KernelResult.InvalidPermission;
|
||||
}
|
||||
|
||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||
|
||||
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
|
||||
|
||||
if (targetProcess == null)
|
||||
{
|
||||
return KernelResult.InvalidHandle;
|
||||
}
|
||||
|
||||
if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
|
||||
{
|
||||
return KernelResult.InvalidMemState;
|
||||
}
|
||||
|
||||
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
|
||||
}
|
||||
|
||||
private static bool PageAligned(ulong address)
|
||||
{
|
||||
return (address & (KPageTableBase.PageSize - 1)) == 0;
|
||||
|
@@ -143,6 +143,26 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return _syscall.CreateTransferMemory(out handle, address, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult CreateCodeMemory32([R(1)] uint address, [R(2)] uint size, [R(1)] out int handle)
|
||||
{
|
||||
return _syscall.CreateCodeMemory(address, size, out handle);
|
||||
}
|
||||
|
||||
public KernelResult ControlCodeMemory32(
|
||||
[R(0)] int handle,
|
||||
[R(1)] CodeMemoryOperation op,
|
||||
[R(2)] uint addressLow,
|
||||
[R(3)] uint addressHigh,
|
||||
[R(4)] uint sizeLow,
|
||||
[R(5)] uint sizeHigh,
|
||||
[R(6)] KMemoryPermission permission)
|
||||
{
|
||||
ulong address = addressLow | ((ulong)addressHigh << 32);
|
||||
ulong size = sizeLow | ((ulong)sizeHigh << 32);
|
||||
|
||||
return _syscall.ControlCodeMemory(handle, op, address, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult MapTransferMemory32([R(0)] int handle, [R(1)] uint address, [R(2)] uint size, [R(3)] KMemoryPermission permission)
|
||||
{
|
||||
return _syscall.MapTransferMemory(handle, address, size, permission);
|
||||
@@ -163,6 +183,34 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return _syscall.UnmapPhysicalMemory(address, size);
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission32(
|
||||
[R(0)] int handle,
|
||||
[R(1)] uint sizeLow,
|
||||
[R(2)] uint srcLow,
|
||||
[R(3)] uint srcHigh,
|
||||
[R(4)] uint sizeHigh,
|
||||
[R(5)] KMemoryPermission permission)
|
||||
{
|
||||
ulong src = srcLow | ((ulong)srcHigh << 32);
|
||||
ulong size = sizeLow | ((ulong)sizeHigh << 32);
|
||||
|
||||
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult MapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size)
|
||||
{
|
||||
ulong src = srcLow | ((ulong)srcHigh << 32);
|
||||
|
||||
return _syscall.MapProcessMemory(dst, handle, src, size);
|
||||
}
|
||||
|
||||
public KernelResult UnmapProcessMemory32([R(0)] uint dst, [R(1)] int handle, [R(2)] uint srcLow, [R(3)] uint srcHigh, [R(4)] uint size)
|
||||
{
|
||||
ulong src = srcLow | ((ulong)srcHigh << 32);
|
||||
|
||||
return _syscall.UnmapProcessMemory(dst, handle, src, size);
|
||||
}
|
||||
|
||||
public KernelResult MapProcessCodeMemory32([R(0)] int handle, [R(1)] uint srcLow, [R(2)] uint dstLow, [R(3)] uint dstHigh, [R(4)] uint srcHigh, [R(5)] uint sizeLow, [R(6)] uint sizeHigh)
|
||||
{
|
||||
ulong src = srcLow | ((ulong)srcHigh << 32);
|
||||
@@ -181,20 +229,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return _syscall.UnmapProcessCodeMemory(handle, dst, src, size);
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission32(
|
||||
[R(0)] int handle,
|
||||
[R(1)] uint sizeLow,
|
||||
[R(2)] uint srcLow,
|
||||
[R(3)] uint srcHigh,
|
||||
[R(4)] uint sizeHigh,
|
||||
[R(5)] KMemoryPermission permission)
|
||||
{
|
||||
ulong src = srcLow | ((ulong)srcHigh << 32);
|
||||
ulong size = sizeLow | ((ulong)sizeHigh << 32);
|
||||
|
||||
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
|
||||
}
|
||||
|
||||
// System
|
||||
|
||||
public void ExitProcess32()
|
||||
|
@@ -160,6 +160,16 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return _syscall.CreateTransferMemory(out handle, address, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult CreateCodeMemory64([R(1)] ulong address, [R(2)] ulong size, [R(1)] out int handle)
|
||||
{
|
||||
return _syscall.CreateCodeMemory(address, size, out handle);
|
||||
}
|
||||
|
||||
public KernelResult ControlCodeMemory64([R(0)] int handle, [R(1)] CodeMemoryOperation op, [R(2)] ulong address, [R(3)] ulong size, [R(4)] KMemoryPermission permission)
|
||||
{
|
||||
return _syscall.ControlCodeMemory(handle, op, address, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult MapTransferMemory64([R(0)] int handle, [R(1)] ulong address, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
|
||||
{
|
||||
return _syscall.MapTransferMemory(handle, address, size, permission);
|
||||
@@ -180,6 +190,21 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return _syscall.UnmapPhysicalMemory(address, size);
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
|
||||
{
|
||||
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
|
||||
}
|
||||
|
||||
public KernelResult MapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size)
|
||||
{
|
||||
return _syscall.MapProcessMemory(dst, handle, src, size);
|
||||
}
|
||||
|
||||
public KernelResult UnmapProcessMemory64([R(0)] ulong dst, [R(1)] int handle, [R(2)] ulong src, [R(3)] ulong size)
|
||||
{
|
||||
return _syscall.UnmapProcessMemory(dst, handle, src, size);
|
||||
}
|
||||
|
||||
public KernelResult MapProcessCodeMemory64([R(0)] int handle, [R(1)] ulong dst, [R(2)] ulong src, [R(3)] ulong size)
|
||||
{
|
||||
return _syscall.MapProcessCodeMemory(handle, dst, src, size);
|
||||
@@ -190,11 +215,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
return _syscall.UnmapProcessCodeMemory(handle, dst, src, size);
|
||||
}
|
||||
|
||||
public KernelResult SetProcessMemoryPermission64([R(0)] int handle, [R(1)] ulong src, [R(2)] ulong size, [R(3)] KMemoryPermission permission)
|
||||
{
|
||||
return _syscall.SetProcessMemoryPermission(handle, src, size, permission);
|
||||
}
|
||||
|
||||
// System
|
||||
|
||||
public void ExitProcess64()
|
||||
|
@@ -78,6 +78,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{ 0x43, nameof(Syscall64.ReplyAndReceive64) },
|
||||
{ 0x44, nameof(Syscall64.ReplyAndReceiveWithUserBuffer64) },
|
||||
{ 0x45, nameof(Syscall64.CreateEvent64) },
|
||||
{ 0x4b, nameof(Syscall64.CreateCodeMemory64) },
|
||||
{ 0x4c, nameof(Syscall64.ControlCodeMemory64) },
|
||||
{ 0x51, nameof(Syscall64.MapTransferMemory64) },
|
||||
{ 0x52, nameof(Syscall64.UnmapTransferMemory64) },
|
||||
{ 0x65, nameof(Syscall64.GetProcessList64) },
|
||||
@@ -86,6 +88,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{ 0x71, nameof(Syscall64.ManageNamedPort64) },
|
||||
{ 0x72, nameof(Syscall64.ConnectToPort64) },
|
||||
{ 0x73, nameof(Syscall64.SetProcessMemoryPermission64) },
|
||||
{ 0x74, nameof(Syscall64.MapProcessMemory64) },
|
||||
{ 0x75, nameof(Syscall64.UnmapProcessMemory64) },
|
||||
{ 0x77, nameof(Syscall64.MapProcessCodeMemory64) },
|
||||
{ 0x78, nameof(Syscall64.UnmapProcessCodeMemory64) },
|
||||
{ 0x7B, nameof(Syscall64.TerminateProcess64) },
|
||||
@@ -152,6 +156,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{ 0x41, nameof(Syscall32.AcceptSession32) },
|
||||
{ 0x43, nameof(Syscall32.ReplyAndReceive32) },
|
||||
{ 0x45, nameof(Syscall32.CreateEvent32) },
|
||||
{ 0x4b, nameof(Syscall32.CreateCodeMemory32) },
|
||||
{ 0x4c, nameof(Syscall32.ControlCodeMemory32) },
|
||||
{ 0x51, nameof(Syscall32.MapTransferMemory32) },
|
||||
{ 0x52, nameof(Syscall32.UnmapTransferMemory32) },
|
||||
{ 0x5F, nameof(Syscall32.FlushProcessDataCache32) },
|
||||
@@ -161,6 +167,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{ 0x71, nameof(Syscall32.ManageNamedPort32) },
|
||||
{ 0x72, nameof(Syscall32.ConnectToPort32) },
|
||||
{ 0x73, nameof(Syscall32.SetProcessMemoryPermission32) },
|
||||
{ 0x74, nameof(Syscall32.MapProcessMemory32) },
|
||||
{ 0x75, nameof(Syscall32.UnmapProcessMemory32) },
|
||||
{ 0x77, nameof(Syscall32.MapProcessCodeMemory32) },
|
||||
{ 0x78, nameof(Syscall32.UnmapProcessCodeMemory32) },
|
||||
{ 0x7B, nameof(Syscall32.TerminateProcess32) },
|
||||
|
@@ -20,11 +20,13 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
public string Name;
|
||||
public ulong ProgramId;
|
||||
public bool AllowCodeMemoryForJit;
|
||||
|
||||
public ProgramInfo(in Npdm npdm)
|
||||
public ProgramInfo(in Npdm npdm, bool allowCodeMemoryForJit)
|
||||
{
|
||||
Name = StringUtils.Utf8ZToString(npdm.Meta.Value.ProgramName);
|
||||
ProgramId = npdm.Aci.Value.ProgramId.Value;
|
||||
AllowCodeMemoryForJit = allowCodeMemoryForJit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +143,13 @@ namespace Ryujinx.HLE.HOS
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, MetaLoader metaData, ProgramInfo programInfo, byte[] arguments = null, params IExecutable[] executables)
|
||||
public static bool LoadNsos(
|
||||
KernelContext context,
|
||||
out ProcessTamperInfo tamperInfo,
|
||||
MetaLoader metaData,
|
||||
ProgramInfo programInfo,
|
||||
byte[] arguments = null,
|
||||
params IExecutable[] executables)
|
||||
{
|
||||
LibHac.Result rc = metaData.GetNpdm(out var npdm);
|
||||
|
||||
@@ -243,7 +251,7 @@ namespace Ryujinx.HLE.HOS
|
||||
return false;
|
||||
}
|
||||
|
||||
KProcess process = new KProcess(context);
|
||||
KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit);
|
||||
|
||||
MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Value.Flags >> 2) & 0xf);
|
||||
|
||||
|
@@ -2,9 +2,12 @@ using LibHac;
|
||||
using LibHac.Account;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
@@ -15,6 +18,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
|
||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||
using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
||||
@@ -37,6 +41,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
private int _notificationStorageChannelEventHandle;
|
||||
private int _healthWarningDisappearedSystemEventHandle;
|
||||
|
||||
private bool _gamePlayRecordingState;
|
||||
|
||||
private int _jitLoaded;
|
||||
|
||||
private HorizonClient _horizon;
|
||||
|
||||
public IApplicationFunctions(Horizon system)
|
||||
@@ -342,6 +350,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(65)] // 3.0.0+
|
||||
// IsGamePlayRecordingSupported() -> u8
|
||||
public ResultCode IsGamePlayRecordingSupported(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_gamePlayRecordingState);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(66)] // 3.0.0+
|
||||
// InitializeGamePlayRecording(u64, handle<copy>)
|
||||
public ResultCode InitializeGamePlayRecording(ServiceCtx context)
|
||||
@@ -355,9 +372,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
// SetGamePlayRecordingState(u32)
|
||||
public ResultCode SetGamePlayRecordingState(ServiceCtx context)
|
||||
{
|
||||
int state = context.RequestData.ReadInt32();
|
||||
_gamePlayRecordingState = context.RequestData.ReadInt32() != 0;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { state });
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -631,5 +648,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(1001)] // 10.0.0+
|
||||
// PrepareForJit()
|
||||
public ResultCode PrepareForJit(ServiceCtx context)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _jitLoaded, 1) == 0)
|
||||
{
|
||||
string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
|
||||
}
|
||||
|
||||
context.Device.Application.LoadServiceNca(filePath);
|
||||
|
||||
// FIXME: Most likely not how this should be done?
|
||||
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
|
||||
{
|
||||
context.Device.System.SmRegistry.WaitForServiceRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using Concentus.Structs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
class Decoder : IDecoder
|
||||
{
|
||||
private readonly OpusDecoder _decoder;
|
||||
|
||||
public int SampleRate => _decoder.SampleRate;
|
||||
public int ChannelsCount => _decoder.NumChannels;
|
||||
|
||||
public Decoder(int sampleRate, int channelsCount)
|
||||
{
|
||||
_decoder = new OpusDecoder(sampleRate, channelsCount);
|
||||
}
|
||||
|
||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
||||
{
|
||||
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_decoder.ResetState();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
using Concentus;
|
||||
using Concentus.Enums;
|
||||
using Concentus.Structs;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.Types;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
static class DecoderCommon
|
||||
{
|
||||
private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet)
|
||||
{
|
||||
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
|
||||
|
||||
numSamples = result;
|
||||
|
||||
if (result == OpusError.OPUS_INVALID_PACKET)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
else if (result == OpusError.OPUS_BAD_ARG)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public static ResultCode DecodeInterleaved(
|
||||
this IDecoder decoder,
|
||||
bool reset,
|
||||
ReadOnlySpan<byte> input,
|
||||
out short[] outPcmData,
|
||||
ulong outputSize,
|
||||
out uint outConsumed,
|
||||
out int outSamples)
|
||||
{
|
||||
outPcmData = null;
|
||||
outConsumed = 0;
|
||||
outSamples = 0;
|
||||
|
||||
int streamSize = input.Length;
|
||||
|
||||
if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
|
||||
int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
|
||||
uint totalSize = header.length + (uint)headerSize;
|
||||
|
||||
if (totalSize > streamSize)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray();
|
||||
|
||||
ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
outPcmData = new short[numSamples * decoder.ChannelsCount];
|
||||
|
||||
if (reset)
|
||||
{
|
||||
decoder.ResetState();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
|
||||
outConsumed = totalSize;
|
||||
}
|
||||
catch (OpusException)
|
||||
{
|
||||
// TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
interface IDecoder
|
||||
{
|
||||
int SampleRate { get; }
|
||||
int ChannelsCount { get; }
|
||||
|
||||
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
|
||||
void ResetState();
|
||||
}
|
||||
}
|
@@ -1,244 +1,112 @@
|
||||
using Concentus;
|
||||
using Concentus.Enums;
|
||||
using Concentus.Structs;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.Types;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
class IHardwareOpusDecoder : IpcService
|
||||
{
|
||||
private int _sampleRate;
|
||||
private int _channelsCount;
|
||||
private bool _reset;
|
||||
private readonly IDecoder _decoder;
|
||||
private readonly OpusDecoderFlags _flags;
|
||||
|
||||
private OpusDecoder _decoder;
|
||||
|
||||
public IHardwareOpusDecoder(int sampleRate, int channelsCount)
|
||||
public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags)
|
||||
{
|
||||
_sampleRate = sampleRate;
|
||||
_channelsCount = channelsCount;
|
||||
_reset = false;
|
||||
|
||||
_decoder = new OpusDecoder(sampleRate, channelsCount);
|
||||
_decoder = new Decoder(sampleRate, channelsCount);
|
||||
_flags = flags;
|
||||
}
|
||||
|
||||
private ResultCode GetPacketNumSamples(out int numSamples, byte[] packet)
|
||||
public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping)
|
||||
{
|
||||
int result = OpusPacketInfo.GetNumSamples(_decoder, packet, 0, packet.Length);
|
||||
|
||||
numSamples = result;
|
||||
|
||||
if (result == OpusError.OPUS_INVALID_PACKET)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
else if (result == OpusError.OPUS_BAD_ARG)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private ResultCode DecodeInterleavedInternal(BinaryReader input, out short[] outPcmData, long outputSize, out uint outConsumed, out int outSamples)
|
||||
{
|
||||
outPcmData = null;
|
||||
outConsumed = 0;
|
||||
outSamples = 0;
|
||||
|
||||
long streamSize = input.BaseStream.Length;
|
||||
|
||||
if (streamSize < Marshal.SizeOf<OpusPacketHeader>())
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
OpusPacketHeader header = OpusPacketHeader.FromStream(input);
|
||||
|
||||
uint totalSize = header.length + (uint)Marshal.SizeOf<OpusPacketHeader>();
|
||||
|
||||
if (totalSize > streamSize)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
byte[] opusData = input.ReadBytes((int)header.length);
|
||||
|
||||
ResultCode result = GetPacketNumSamples(out int numSamples, opusData);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
if ((uint)numSamples * (uint)_channelsCount * sizeof(short) > outputSize)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
outPcmData = new short[numSamples * _channelsCount];
|
||||
|
||||
if (_reset)
|
||||
{
|
||||
_reset = false;
|
||||
|
||||
_decoder.ResetState();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
outSamples = _decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / _channelsCount);
|
||||
outConsumed = totalSize;
|
||||
}
|
||||
catch (OpusException)
|
||||
{
|
||||
// TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
_decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||
_flags = flags;
|
||||
}
|
||||
|
||||
[CommandHipc(0)]
|
||||
// DecodeInterleaved(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
|
||||
public ResultCode DecodeInterleavedOriginal(ServiceCtx context)
|
||||
// DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
|
||||
public ResultCode DecodeInterleavedOld(ServiceCtx context)
|
||||
{
|
||||
ResultCode result;
|
||||
|
||||
ulong inPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inSize = context.Request.SendBuff[0].Size;
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] buffer = new byte[inSize];
|
||||
|
||||
context.Memory.Read(inPosition, buffer);
|
||||
|
||||
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
|
||||
{
|
||||
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
|
||||
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
|
||||
context.Memory.Write(outputPosition, pcmDataBytes);
|
||||
|
||||
context.ResponseData.Write(outConsumed);
|
||||
context.ResponseData.Write(outSamples);
|
||||
}
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
|
||||
}
|
||||
|
||||
return result;
|
||||
[CommandHipc(2)]
|
||||
// DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
|
||||
public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context)
|
||||
{
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
|
||||
}
|
||||
|
||||
[CommandHipc(4)] // 6.0.0+
|
||||
// DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
|
||||
{
|
||||
ResultCode result;
|
||||
|
||||
ulong inPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inSize = context.Request.SendBuff[0].Size;
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] buffer = new byte[inSize];
|
||||
|
||||
context.Memory.Read(inPosition, buffer);
|
||||
|
||||
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
|
||||
{
|
||||
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
|
||||
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
|
||||
context.Memory.Write(outputPosition, pcmDataBytes);
|
||||
|
||||
context.ResponseData.Write(outConsumed);
|
||||
context.ResponseData.Write(outSamples);
|
||||
|
||||
// This is the time the DSP took to process the request, TODO: fill this.
|
||||
context.ResponseData.Write(0);
|
||||
}
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
[CommandHipc(5)] // 6.0.0+
|
||||
// DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context)
|
||||
{
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandHipc(6)] // 6.0.0+
|
||||
// DecodeInterleavedOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedOld(ServiceCtx context)
|
||||
// DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context)
|
||||
{
|
||||
ResultCode result;
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
_reset = context.RequestData.ReadBoolean();
|
||||
|
||||
ulong inPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inSize = context.Request.SendBuff[0].Size;
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] buffer = new byte[inSize];
|
||||
|
||||
context.Memory.Read(inPosition, buffer);
|
||||
|
||||
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
|
||||
{
|
||||
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
|
||||
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
|
||||
context.Memory.Write(outputPosition, pcmDataBytes);
|
||||
|
||||
context.ResponseData.Write(outConsumed);
|
||||
context.ResponseData.Write(outSamples);
|
||||
|
||||
// This is the time the DSP took to process the request, TODO: fill this.
|
||||
context.ResponseData.Write(0);
|
||||
}
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
[CommandHipc(7)] // 6.0.0+
|
||||
// DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context)
|
||||
{
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandHipc(8)] // 7.0.0+
|
||||
// DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleaved(ServiceCtx context)
|
||||
{
|
||||
ResultCode result;
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
_reset = context.RequestData.ReadBoolean();
|
||||
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandHipc(9)] // 7.0.0+
|
||||
// DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context)
|
||||
{
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
|
||||
}
|
||||
|
||||
private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf)
|
||||
{
|
||||
ulong inPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inSize = context.Request.SendBuff[0].Size;
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] buffer = new byte[inSize];
|
||||
ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize);
|
||||
|
||||
context.Memory.Read(inPosition, buffer);
|
||||
|
||||
using (BinaryReader inputStream = new BinaryReader(new MemoryStream(buffer)))
|
||||
{
|
||||
result = DecodeInterleavedInternal(inputStream, out short[] outPcmData, (long)outputSize, out uint outConsumed, out int outSamples);
|
||||
ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
byte[] pcmDataBytes = new byte[outPcmData.Length * sizeof(short)];
|
||||
Buffer.BlockCopy(outPcmData, 0, pcmDataBytes, 0, pcmDataBytes.Length);
|
||||
context.Memory.Write(outputPosition, pcmDataBytes);
|
||||
context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan()));
|
||||
|
||||
context.ResponseData.Write(outConsumed);
|
||||
context.ResponseData.Write(outSamples);
|
||||
|
||||
if (withPerf)
|
||||
{
|
||||
// This is the time the DSP took to process the request, TODO: fill this.
|
||||
context.ResponseData.Write(0);
|
||||
context.ResponseData.Write(0UL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,28 @@
|
||||
using Concentus.Structs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
class MultiSampleDecoder : IDecoder
|
||||
{
|
||||
private readonly OpusMSDecoder _decoder;
|
||||
|
||||
public int SampleRate => _decoder.SampleRate;
|
||||
public int ChannelsCount { get; }
|
||||
|
||||
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
|
||||
{
|
||||
ChannelsCount = channelsCount;
|
||||
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||
}
|
||||
|
||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
||||
{
|
||||
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_decoder.ResetState();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
@@ -16,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
int sampleRate = context.RequestData.ReadInt32();
|
||||
int channelsCount = context.RequestData.ReadInt32();
|
||||
|
||||
MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount));
|
||||
MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
@@ -28,11 +29,50 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
// GetWorkBufferSize(bytes<8, 4>) -> u32
|
||||
public ResultCode GetWorkBufferSize(ServiceCtx context)
|
||||
{
|
||||
// NOTE: The sample rate is ignored because it is fixed to 48KHz.
|
||||
int sampleRate = context.RequestData.ReadInt32();
|
||||
int channelsCount = context.RequestData.ReadInt32();
|
||||
|
||||
context.ResponseData.Write(GetOpusDecoderSize(channelsCount));
|
||||
int opusDecoderSize = GetOpusDecoderSize(channelsCount);
|
||||
|
||||
int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64);
|
||||
int totalSize = opusDecoderSize + 1536 + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(2)] // 3.0.0+
|
||||
// InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
|
||||
public ResultCode InitializeForMultiStream(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
|
||||
|
||||
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(3)] // 3.0.0+
|
||||
// GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32
|
||||
public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
|
||||
|
||||
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
|
||||
|
||||
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
|
||||
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64);
|
||||
int totalSize = opusDecoderSize + streamSize + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -44,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
|
||||
|
||||
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
|
||||
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelCount));
|
||||
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
@@ -58,15 +98,84 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
|
||||
|
||||
// NOTE: The sample rate is ignored because it is fixed to 48KHz.
|
||||
context.ResponseData.Write(GetOpusDecoderSize(parameters.ChannelCount));
|
||||
int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount);
|
||||
|
||||
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
|
||||
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
|
||||
int totalSize = opusDecoderSize + 1536 + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(6)] // 12.0.0+
|
||||
// InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
|
||||
public ResultCode InitializeForMultiStreamEx(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
|
||||
|
||||
byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.ToSpan()).ToArray();
|
||||
|
||||
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
|
||||
MakeObject(context, new IHardwareOpusDecoder(
|
||||
parameters.SampleRate,
|
||||
parameters.ChannelsCount,
|
||||
parameters.NumberOfStreams,
|
||||
parameters.NumberOfStereoStreams,
|
||||
parameters.Flags,
|
||||
mappings));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(7)] // 12.0.0+
|
||||
// GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32
|
||||
public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
|
||||
|
||||
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
|
||||
|
||||
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
|
||||
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
|
||||
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
|
||||
int totalSize = opusDecoderSize + streamSize + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
|
||||
{
|
||||
if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int coupledSize = GetOpusDecoderSize(2);
|
||||
int monoSize = GetOpusDecoderSize(1);
|
||||
|
||||
return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
|
||||
Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c;
|
||||
}
|
||||
|
||||
private static int Align4(int value)
|
||||
{
|
||||
return BitUtils.AlignUp(value, 4);
|
||||
}
|
||||
|
||||
private static int GetOpusDecoderSize(int channelsCount)
|
||||
{
|
||||
const int silkDecoderSize = 0x2198;
|
||||
const int SilkDecoderSize = 0x2160;
|
||||
|
||||
if (channelsCount < 1 || channelsCount > 2)
|
||||
{
|
||||
@@ -74,24 +183,23 @@ namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
}
|
||||
|
||||
int celtDecoderSize = GetCeltDecoderSize(channelsCount);
|
||||
int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c;
|
||||
|
||||
int opusDecoderSize = (channelsCount * 0x800 + 0x4807) & -0x800 | 0x50;
|
||||
return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
|
||||
}
|
||||
|
||||
return opusDecoderSize + silkDecoderSize + celtDecoderSize;
|
||||
private static int GetOpusDecoderAllocSize(int channelsCount)
|
||||
{
|
||||
return (channelsCount * 0x800 + 0x4803) & -0x800;
|
||||
}
|
||||
|
||||
private static int GetCeltDecoderSize(int channelsCount)
|
||||
{
|
||||
const int decodeBufferSize = 0x2030;
|
||||
const int celtDecoderSize = 0x58;
|
||||
const int celtSigSize = 0x4;
|
||||
const int overlap = 120;
|
||||
const int eBandsCount = 21;
|
||||
const int DecodeBufferSize = 0x2030;
|
||||
const int Overlap = 120;
|
||||
const int EBandsCount = 21;
|
||||
|
||||
return (decodeBufferSize + overlap * 4) * channelsCount +
|
||||
eBandsCount * 16 +
|
||||
celtDecoderSize +
|
||||
celtSigSize;
|
||||
return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x110)]
|
||||
struct OpusMultiStreamParameters
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public int NumberOfStreams;
|
||||
public int NumberOfStereoStreams;
|
||||
public Array64<uint> ChannelMappings;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x118)]
|
||||
struct OpusMultiStreamParametersEx
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public int NumberOfStreams;
|
||||
public int NumberOfStereoStreams;
|
||||
public OpusDecoderFlags Flags;
|
||||
|
||||
Array4<byte> Padding1;
|
||||
|
||||
public Array64<uint> ChannelMappings;
|
||||
}
|
||||
}
|
@@ -12,9 +12,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
public uint length;
|
||||
public uint finalRange;
|
||||
|
||||
public static OpusPacketHeader FromStream(BinaryReader reader)
|
||||
public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
|
||||
{
|
||||
OpusPacketHeader header = reader.ReadStruct<OpusPacketHeader>();
|
||||
OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0];
|
||||
|
||||
header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
|
||||
header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;
|
||||
|
@@ -7,8 +7,8 @@ namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
struct OpusParametersEx
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelCount;
|
||||
public OpusDecoderFlags UseLargeFrameSize;
|
||||
public int ChannelsCount;
|
||||
public OpusDecoderFlags Flags;
|
||||
|
||||
Array4<byte> Padding1;
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
private ControllerType[] _configuredTypes;
|
||||
private KEvent[] _styleSetUpdateEvents;
|
||||
private bool[] _supportedPlayers;
|
||||
private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue
|
||||
private static VibrationValue _neutralVibrationValue = new VibrationValue
|
||||
{
|
||||
AmplitudeLow = 0f,
|
||||
FrequencyLow = 160f,
|
||||
@@ -33,8 +33,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
|
||||
internal ControllerType SupportedStyleSets { get; set; }
|
||||
|
||||
public Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>>();
|
||||
public Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)>();
|
||||
public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>>();
|
||||
public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (VibrationValue, VibrationValue)>();
|
||||
|
||||
public NpadDevices(Switch device, bool active = true) : base(device, active)
|
||||
{
|
||||
@@ -588,21 +588,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
|
||||
}
|
||||
|
||||
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, HidVibrationValue> dualVibrationValues)
|
||||
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, VibrationValue> dualVibrationValues)
|
||||
{
|
||||
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue))
|
||||
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue))
|
||||
{
|
||||
if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue))
|
||||
if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue))
|
||||
{
|
||||
leftVibrationValue = _neutralVibrationValue;
|
||||
}
|
||||
|
||||
if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue))
|
||||
if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue))
|
||||
{
|
||||
rightVibrationValue = _neutralVibrationValue;
|
||||
}
|
||||
|
||||
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
|
||||
if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
|
||||
{
|
||||
currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
|
||||
|
||||
@@ -611,9 +611,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
}
|
||||
|
||||
public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
|
||||
public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
|
||||
{
|
||||
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
|
||||
if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue))
|
||||
{
|
||||
return _neutralVibrationValue;
|
||||
}
|
||||
@@ -621,11 +621,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
|
||||
}
|
||||
|
||||
public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index)
|
||||
public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index)
|
||||
{
|
||||
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue))
|
||||
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue))
|
||||
{
|
||||
rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>();
|
||||
rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>();
|
||||
_device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
|
||||
}
|
||||
|
||||
|
@@ -35,5 +35,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
|
||||
PlayerIndex.Unknown => NpadIdType.Unknown,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index))
|
||||
};
|
||||
|
||||
public static bool IsValidNpadIdType(NpadIdType npadIdType)
|
||||
{
|
||||
return npadIdType <= NpadIdType.Player8 || npadIdType == NpadIdType.Handheld || npadIdType == NpadIdType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public enum HidNpadJoyAssignmentMode
|
||||
{
|
||||
Dual,
|
||||
Single
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public enum HidNpadHandheldActivationMode
|
||||
public enum NpadHandheldActivationMode
|
||||
{
|
||||
Dual,
|
||||
Single,
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public enum HidNpadJoyDeviceType
|
||||
public enum NpadJoyDeviceType
|
||||
{
|
||||
Left,
|
||||
Right
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public struct HidAccelerometerParameters
|
||||
public struct AccelerometerParameters
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public enum HidGyroscopeZeroDriftMode
|
||||
public enum GyroscopeZeroDriftMode
|
||||
{
|
||||
Loose,
|
||||
Standard,
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public struct HidSensorFusionParameters
|
||||
public struct SensorFusionParameters
|
||||
{
|
||||
public float RevisePower;
|
||||
public float ReviseRange;
|
@@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public struct HidVibrationDeviceValue
|
||||
{
|
||||
public HidVibrationDeviceType DeviceType;
|
||||
public HidVibrationDevicePosition Position;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public struct HidVibrationDeviceHandle
|
||||
public struct VibrationDeviceHandle
|
||||
{
|
||||
public byte DeviceType;
|
||||
public byte PlayerId;
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public enum HidVibrationDevicePosition
|
||||
public enum VibrationDevicePosition
|
||||
{
|
||||
None,
|
||||
Left,
|
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public enum HidVibrationDeviceType
|
||||
public enum VibrationDeviceType
|
||||
{
|
||||
None,
|
||||
LinearResonantActuator,
|
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public struct VibrationDeviceValue
|
||||
{
|
||||
public VibrationDeviceType DeviceType;
|
||||
public VibrationDevicePosition Position;
|
||||
}
|
||||
}
|
@@ -1,9 +1,8 @@
|
||||
using Ryujinx.HLE.HOS.Tamper;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
public struct HidVibrationValue
|
||||
public struct VibrationValue
|
||||
{
|
||||
public float AmplitudeLow;
|
||||
public float FrequencyLow;
|
||||
@@ -12,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is HidVibrationValue value &&
|
||||
return obj is VibrationValue value &&
|
||||
AmplitudeLow == value.AmplitudeLow &&
|
||||
AmplitudeHigh == value.AmplitudeHigh;
|
||||
}
|
@@ -5,6 +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.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -26,9 +27,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
private bool _isFirmwareUpdateAvailableForSixAxisSensor;
|
||||
private bool _isSixAxisSensorUnalteredPassthroughEnabled;
|
||||
|
||||
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
|
||||
private HidNpadHandheldActivationMode _npadHandheldActivationMode;
|
||||
private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
|
||||
private NpadHandheldActivationMode _npadHandheldActivationMode;
|
||||
private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
|
||||
|
||||
private long _npadCommunicationMode;
|
||||
private uint _accelerometerPlayMode;
|
||||
@@ -37,22 +37,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
#pragma warning restore CS0649
|
||||
private float _sevenSixAxisSensorFusionStrength;
|
||||
|
||||
private HidSensorFusionParameters _sensorFusionParams;
|
||||
private HidAccelerometerParameters _accelerometerParams;
|
||||
private SensorFusionParameters _sensorFusionParams;
|
||||
private AccelerometerParameters _accelerometerParams;
|
||||
|
||||
public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer)
|
||||
{
|
||||
_xpadIdEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
|
||||
_npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual;
|
||||
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
|
||||
_npadHandheldActivationMode = NpadHandheldActivationMode.Dual;
|
||||
_gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
|
||||
|
||||
_isFirmwareUpdateAvailableForSixAxisSensor = false;
|
||||
|
||||
_sensorFusionParams = new HidSensorFusionParameters();
|
||||
_accelerometerParams = new HidAccelerometerParameters();
|
||||
_sensorFusionParams = new SensorFusionParameters();
|
||||
_accelerometerParams = new AccelerometerParameters();
|
||||
|
||||
// TODO: signal event at right place
|
||||
_xpadIdEvent.ReadableEvent.Signal();
|
||||
@@ -393,7 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
|
||||
_sensorFusionParams = new HidSensorFusionParameters
|
||||
_sensorFusionParams = new SensorFusionParameters
|
||||
{
|
||||
RevisePower = context.RequestData.ReadInt32(),
|
||||
ReviseRange = context.RequestData.ReadInt32()
|
||||
@@ -445,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
|
||||
_accelerometerParams = new HidAccelerometerParameters
|
||||
_accelerometerParams = new AccelerometerParameters
|
||||
{
|
||||
X = context.RequestData.ReadInt32(),
|
||||
Y = context.RequestData.ReadInt32()
|
||||
@@ -539,7 +538,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
|
||||
_gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
|
||||
@@ -570,7 +569,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
|
||||
_gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
|
||||
|
||||
@@ -909,54 +908,63 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context)
|
||||
{
|
||||
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
|
||||
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode });
|
||||
if (HidUtils.IsValidNpadIdType(npadIdType))
|
||||
{
|
||||
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(123)]
|
||||
// SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType)
|
||||
// SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType)
|
||||
public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context)
|
||||
{
|
||||
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
|
||||
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
|
||||
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32();
|
||||
|
||||
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, hidNpadJoyDeviceType, _npadJoyAssignmentMode });
|
||||
if (HidUtils.IsValidNpadIdType(npadIdType))
|
||||
{
|
||||
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _);
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(124)]
|
||||
// SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId)
|
||||
// SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context)
|
||||
{
|
||||
PlayerIndex hidControllerId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32());
|
||||
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode });
|
||||
if (HidUtils.IsValidNpadIdType(npadIdType))
|
||||
{
|
||||
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(125)]
|
||||
// MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId)
|
||||
// MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId)
|
||||
public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context)
|
||||
{
|
||||
long singleJoyId0 = context.RequestData.ReadInt32();
|
||||
long singleJoyId1 = context.RequestData.ReadInt32();
|
||||
NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32();
|
||||
NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, singleJoyId0, singleJoyId1 });
|
||||
if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1))
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 });
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -988,7 +996,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
public ResultCode SetNpadHandheldActivationMode(ServiceCtx context)
|
||||
{
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
_npadHandheldActivationMode = (HidNpadHandheldActivationMode)context.RequestData.ReadInt64();
|
||||
_npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode });
|
||||
|
||||
@@ -1049,35 +1057,45 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
|
||||
[CommandHipc(133)] // 5.0.0+
|
||||
// SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1
|
||||
// SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet
|
||||
public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context)
|
||||
{
|
||||
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
|
||||
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
|
||||
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
|
||||
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
|
||||
if (HidUtils.IsValidNpadIdType(npadIdType))
|
||||
{
|
||||
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet);
|
||||
|
||||
context.ResponseData.Write(0); //Unknown0
|
||||
context.ResponseData.Write(0); //Unknown1
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
|
||||
appletResourceUserId,
|
||||
hidControllerId,
|
||||
hidNpadJoyDeviceType,
|
||||
_npadJoyAssignmentMode,
|
||||
Unknown0 = 0,
|
||||
Unknown1 = 0
|
||||
});
|
||||
if (npadIdTypeIsSet)
|
||||
{
|
||||
context.ResponseData.Write(npadIdTypeIsSet);
|
||||
context.ResponseData.Write((uint)npadIdTypeSet);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet)
|
||||
{
|
||||
npadIdTypeSet = default;
|
||||
npadIdTypeIsSet = false;
|
||||
|
||||
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
|
||||
|
||||
// TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual.
|
||||
// If one is found, it returns the npadIdType of the other Npad and a bool.
|
||||
// If not, it returns nothing.
|
||||
}
|
||||
|
||||
[CommandHipc(200)]
|
||||
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
|
||||
public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
|
||||
{
|
||||
HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<HidVibrationDeviceHandle>();
|
||||
VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<VibrationDeviceHandle>();
|
||||
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
|
||||
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
|
||||
|
||||
@@ -1093,28 +1111,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
return ResultCode.InvalidDeviceIndex;
|
||||
}
|
||||
|
||||
HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None;
|
||||
VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None;
|
||||
|
||||
if (Enum.IsDefined<NpadStyleIndex>(deviceType))
|
||||
if (Enum.IsDefined(deviceType))
|
||||
{
|
||||
vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator;
|
||||
vibrationDeviceType = VibrationDeviceType.LinearResonantActuator;
|
||||
}
|
||||
else if ((uint)deviceType == 8)
|
||||
{
|
||||
vibrationDeviceType = HidVibrationDeviceType.GcErm;
|
||||
vibrationDeviceType = VibrationDeviceType.GcErm;
|
||||
}
|
||||
|
||||
HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None;
|
||||
VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None;
|
||||
|
||||
if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator)
|
||||
if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator)
|
||||
{
|
||||
if (deviceHandle.Position == 0)
|
||||
{
|
||||
vibrationDevicePosition = HidVibrationDevicePosition.Left;
|
||||
vibrationDevicePosition = VibrationDevicePosition.Left;
|
||||
}
|
||||
else if (deviceHandle.Position == 1)
|
||||
{
|
||||
vibrationDevicePosition = HidVibrationDevicePosition.Right;
|
||||
vibrationDevicePosition = VibrationDevicePosition.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1122,7 +1140,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
}
|
||||
|
||||
HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue
|
||||
VibrationDeviceValue deviceInfo = new VibrationDeviceValue
|
||||
{
|
||||
DeviceType = vibrationDeviceType,
|
||||
Position = vibrationDevicePosition
|
||||
@@ -1140,7 +1158,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SendVibrationValue(ServiceCtx context)
|
||||
{
|
||||
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
|
||||
VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
|
||||
{
|
||||
DeviceType = context.RequestData.ReadByte(),
|
||||
PlayerId = context.RequestData.ReadByte(),
|
||||
@@ -1148,7 +1166,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
Reserved = context.RequestData.ReadByte()
|
||||
};
|
||||
|
||||
HidVibrationValue vibrationValue = new HidVibrationValue
|
||||
VibrationValue vibrationValue = new VibrationValue
|
||||
{
|
||||
AmplitudeLow = context.RequestData.ReadSingle(),
|
||||
FrequencyLow = context.RequestData.ReadSingle(),
|
||||
@@ -1158,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
|
||||
Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
|
||||
|
||||
dualVibrationValues[deviceHandle.Position] = vibrationValue;
|
||||
|
||||
@@ -1171,7 +1189,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue
|
||||
public ResultCode GetActualVibrationValue(ServiceCtx context)
|
||||
{
|
||||
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
|
||||
VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
|
||||
{
|
||||
DeviceType = context.RequestData.ReadByte(),
|
||||
PlayerId = context.RequestData.ReadByte(),
|
||||
@@ -1181,7 +1199,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
|
||||
VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
|
||||
|
||||
context.ResponseData.Write(vibrationValue.AmplitudeLow);
|
||||
context.ResponseData.Write(vibrationValue.FrequencyLow);
|
||||
@@ -1234,12 +1252,12 @@ 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<VibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, VibrationDeviceHandle>(vibrationDeviceHandleBuffer);
|
||||
Span<VibrationValue> vibrationValues = MemoryMarshal.Cast<byte, VibrationValue>(vibrationValueBuffer);
|
||||
|
||||
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
|
||||
{
|
||||
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
|
||||
Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
|
||||
PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId;
|
||||
|
||||
for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++)
|
||||
@@ -1250,7 +1268,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
if (index != currentIndex || dualVibrationValues.Count == 2)
|
||||
{
|
||||
context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
|
||||
dualVibrationValues = new Dictionary<byte, HidVibrationValue>();
|
||||
dualVibrationValues = new Dictionary<byte, VibrationValue>();
|
||||
}
|
||||
|
||||
dualVibrationValues[position] = vibrationValues[deviceCounter];
|
||||
|
@@ -1,8 +1,31 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Pm
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Pm
|
||||
{
|
||||
[Service("pm:dmnt")]
|
||||
class IDebugMonitorInterface : IpcService
|
||||
{
|
||||
public IDebugMonitorInterface(ServiceCtx context) { }
|
||||
|
||||
[CommandHipc(65000)]
|
||||
// AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status
|
||||
public ResultCode GetProcessInfo(ServiceCtx context)
|
||||
{
|
||||
ulong pid = context.RequestData.ReadUInt64();
|
||||
|
||||
KProcess process = KernelStatic.GetProcessByPid(pid);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(process, out int processHandle) != KernelResult.Success)
|
||||
{
|
||||
throw new System.Exception("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(processHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -29,6 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
|
||||
private List<NroInfo> _nroInfos;
|
||||
|
||||
private KProcess _owner;
|
||||
private IVirtualMemoryManager _ownerMm;
|
||||
|
||||
private static Random _random = new Random();
|
||||
|
||||
@@ -37,6 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
|
||||
_nrrInfos = new List<NrrInfo>(MaxNrr);
|
||||
_nroInfos = new List<NroInfo>(MaxNro);
|
||||
_owner = null;
|
||||
_ownerMm = null;
|
||||
}
|
||||
|
||||
private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, ulong nrrAddress, ulong nrrSize)
|
||||
@@ -563,12 +566,28 @@ namespace Ryujinx.HLE.HOS.Services.Ro
|
||||
return ResultCode.InvalidSession;
|
||||
}
|
||||
|
||||
_owner = context.Process.HandleTable.GetKProcess(context.Request.HandleDesc.ToCopy[0]);
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
int processHandle = context.Request.HandleDesc.ToCopy[0];
|
||||
_owner = context.Process.HandleTable.GetKProcess(processHandle);
|
||||
_ownerMm = _owner?.CpuMemory;
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(processHandle);
|
||||
|
||||
if (_ownerMm is IRefCounted rc)
|
||||
{
|
||||
rc.IncrementReferenceCount();
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(10)]
|
||||
// LoadNrr2(u64, u64, u64, pid)
|
||||
public ResultCode LoadNrr2(ServiceCtx context)
|
||||
{
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return LoadNrr(context);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
@@ -579,6 +598,11 @@ namespace Ryujinx.HLE.HOS.Services.Ro
|
||||
}
|
||||
|
||||
_nroInfos.Clear();
|
||||
|
||||
if (_ownerMm is IRefCounted rc)
|
||||
{
|
||||
rc.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Sm;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
|
@@ -234,6 +234,17 @@ namespace Ryujinx.HLE.HOS.Services.Settings
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(62)]
|
||||
// GetDebugModeFlag() -> bool
|
||||
public ResultCode GetDebugModeFlag(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(false);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceSet);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandHipc(77)]
|
||||
// GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16>
|
||||
public ResultCode GetDeviceNickName(ServiceCtx context)
|
||||
|
@@ -1,11 +1,9 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Ipc;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -17,21 +15,19 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
{
|
||||
private static Dictionary<string, Type> _services;
|
||||
|
||||
private static readonly ConcurrentDictionary<string, KPort> _registeredServices;
|
||||
|
||||
private readonly SmRegistry _registry;
|
||||
private readonly ServerBase _commonServer;
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
public IUserInterface(KernelContext context)
|
||||
public IUserInterface(KernelContext context, SmRegistry registry)
|
||||
{
|
||||
_commonServer = new ServerBase(context, "CommonServer");
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
static IUserInterface()
|
||||
{
|
||||
_registeredServices = new ConcurrentDictionary<string, KPort>();
|
||||
|
||||
_services = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true)
|
||||
.Select(service => (((ServiceAttribute)service).Name, type)))
|
||||
@@ -74,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
|
||||
KSession session = new KSession(context.Device.System.KernelContext);
|
||||
|
||||
if (_registeredServices.TryGetValue(name, out KPort port))
|
||||
if (_registry.TryGetService(name, out KPort port))
|
||||
{
|
||||
KernelResult result = port.EnqueueIncomingSession(session.ServerSession);
|
||||
|
||||
@@ -82,6 +78,15 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
{
|
||||
throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\".");
|
||||
}
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
session.ClientSession.DecrementReferenceCount();
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -107,7 +112,6 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
throw new NotImplementedException(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
|
||||
{
|
||||
@@ -118,6 +122,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
session.ClientSession.DecrementReferenceCount();
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@@ -179,7 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
|
||||
KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, 0);
|
||||
|
||||
if (!_registeredServices.TryAdd(name, port))
|
||||
if (!_registry.TryRegister(name, port))
|
||||
{
|
||||
return ResultCode.AlreadyRegistered;
|
||||
}
|
||||
@@ -219,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
return ResultCode.InvalidName;
|
||||
}
|
||||
|
||||
if (!_registeredServices.TryRemove(name, out _))
|
||||
if (!_registry.Unregister(name))
|
||||
{
|
||||
return ResultCode.NotRegistered;
|
||||
}
|
||||
|
49
Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs
Normal file
49
Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Ipc;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Sm
|
||||
{
|
||||
class SmRegistry
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, KPort> _registeredServices;
|
||||
private readonly AutoResetEvent _serviceRegistrationEvent;
|
||||
|
||||
public SmRegistry()
|
||||
{
|
||||
_registeredServices = new ConcurrentDictionary<string, KPort>();
|
||||
_serviceRegistrationEvent = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
public bool TryGetService(string name, out KPort port)
|
||||
{
|
||||
return _registeredServices.TryGetValue(name, out port);
|
||||
}
|
||||
|
||||
public bool TryRegister(string name, KPort port)
|
||||
{
|
||||
if (_registeredServices.TryAdd(name, port))
|
||||
{
|
||||
_serviceRegistrationEvent.Set();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Unregister(string name)
|
||||
{
|
||||
return _registeredServices.TryRemove(name, out _);
|
||||
}
|
||||
|
||||
public bool IsServiceRegistered(string name)
|
||||
{
|
||||
return _registeredServices.TryGetValue(name, out _);
|
||||
}
|
||||
|
||||
public void WaitForServiceRegistration()
|
||||
{
|
||||
_serviceRegistrationEvent.WaitOne();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Audio.Backends.CompatLayer;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
@@ -48,8 +49,12 @@ namespace Ryujinx.HLE
|
||||
FileSystem = Configuration.VirtualFileSystem;
|
||||
UiHandler = Configuration.HostUiHandler;
|
||||
|
||||
MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable
|
||||
? MemoryAllocationFlags.Reserve
|
||||
: MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable;
|
||||
|
||||
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
||||
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), MemoryAllocationFlags.Reserve);
|
||||
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
||||
Gpu = new GpuContext(Configuration.GpuRenderer);
|
||||
System = new Horizon(this);
|
||||
Statistics = new PerformanceStatistics();
|
||||
|
@@ -302,7 +302,7 @@ namespace Ryujinx.Input.HLE
|
||||
Vector3 gyroscope = _gamepad.GetMotionData(MotionInputId.Gyroscope);
|
||||
|
||||
accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
|
||||
gyroscope = new Vector3(gyroscope.X, gyroscope.Z, gyroscope.Y);
|
||||
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);
|
||||
|
||||
_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
|
||||
|
||||
@@ -543,14 +543,14 @@ namespace Ryujinx.Input.HLE
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue)
|
||||
public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue)
|
||||
{
|
||||
if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
|
||||
if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
|
||||
{
|
||||
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
|
||||
{
|
||||
HidVibrationValue leftVibrationValue = dualVibrationValue.Item1;
|
||||
HidVibrationValue rightVibrationValue = dualVibrationValue.Item2;
|
||||
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
|
||||
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
|
||||
|
||||
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
|
||||
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
|
||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.Memory.Tests
|
||||
{
|
||||
}
|
||||
|
||||
public void Map(ulong va, nuint hostAddress, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -59,9 +59,9 @@ namespace Ryujinx.Memory.Tests
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerable<HostMemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
|
||||
IEnumerable<MemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
return NoMappings ? new HostMemoryRange[0] : new HostMemoryRange[] { new HostMemoryRange((nuint)va, size) };
|
||||
return NoMappings ? new MemoryRange[0] : new MemoryRange[] { new MemoryRange(va, size) };
|
||||
}
|
||||
|
||||
public bool IsMapped(ulong va)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -38,5 +37,48 @@ namespace Ryujinx.Memory.Tests
|
||||
|
||||
Assert.AreEqual(Marshal.ReadInt32(_memoryBlock.Pointer, 0x2040), 0xbadc0de);
|
||||
}
|
||||
|
||||
[Test, Explicit]
|
||||
public void Test_Alias()
|
||||
{
|
||||
using MemoryBlock backing = new MemoryBlock(0x10000, MemoryAllocationFlags.Mirrorable);
|
||||
using MemoryBlock toAlias = new MemoryBlock(0x10000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
|
||||
|
||||
toAlias.MapView(backing, 0x1000, 0, 0x4000);
|
||||
toAlias.UnmapView(backing, 0x3000, 0x1000);
|
||||
|
||||
toAlias.Write(0, 0xbadc0de);
|
||||
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, 0x1000), 0xbadc0de);
|
||||
}
|
||||
|
||||
[Test, Explicit]
|
||||
public void Test_AliasRandom()
|
||||
{
|
||||
using MemoryBlock backing = new MemoryBlock(0x80000, MemoryAllocationFlags.Mirrorable);
|
||||
using MemoryBlock toAlias = new MemoryBlock(0x80000, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
|
||||
|
||||
Random rng = new Random(123);
|
||||
|
||||
for (int i = 0; i < 20000; i++)
|
||||
{
|
||||
int srcPage = rng.Next(0, 64);
|
||||
int dstPage = rng.Next(0, 64);
|
||||
int pages = rng.Next(1, 65);
|
||||
|
||||
if ((rng.Next() & 1) != 0)
|
||||
{
|
||||
toAlias.MapView(backing, (ulong)srcPage << 12, (ulong)dstPage << 12, (ulong)pages << 12);
|
||||
|
||||
int offset = rng.Next(0, 0x1000 - sizeof(int));
|
||||
|
||||
toAlias.Write((ulong)((dstPage << 12) + offset), 0xbadc0de);
|
||||
Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << 12) + offset), 0xbadc0de);
|
||||
}
|
||||
else
|
||||
{
|
||||
toAlias.UnmapView(backing, (ulong)dstPage << 12, (ulong)pages << 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,9 +13,9 @@ namespace Ryujinx.Memory
|
||||
/// </summary>
|
||||
public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock
|
||||
{
|
||||
public const int PageBits = PageTable<nuint>.PageBits;
|
||||
public const int PageSize = PageTable<nuint>.PageSize;
|
||||
public const int PageMask = PageTable<nuint>.PageMask;
|
||||
public const int PageBits = PageTable<ulong>.PageBits;
|
||||
public const int PageSize = PageTable<ulong>.PageSize;
|
||||
public const int PageMask = PageTable<ulong>.PageMask;
|
||||
|
||||
/// <summary>
|
||||
/// Address space width in bits.
|
||||
@@ -24,14 +24,15 @@ namespace Ryujinx.Memory
|
||||
|
||||
private readonly ulong _addressSpaceSize;
|
||||
|
||||
private readonly PageTable<nuint> _pageTable;
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the memory manager.
|
||||
/// </summary>
|
||||
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
|
||||
/// <param name="addressSpaceSize">Size of the address space</param>
|
||||
public AddressSpaceManager(ulong addressSpaceSize)
|
||||
public AddressSpaceManager(MemoryBlock backingMemory, ulong addressSpaceSize)
|
||||
{
|
||||
ulong asSize = PageSize;
|
||||
int asBits = PageBits;
|
||||
@@ -44,37 +45,26 @@ namespace Ryujinx.Memory
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
_addressSpaceSize = asSize;
|
||||
_pageTable = new PageTable<nuint>();
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a virtual memory range into a physical memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Addresses and size must be page aligned.
|
||||
/// </remarks>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="hostAddress">Physical memory address</param>
|
||||
/// <param name="size">Size to be mapped</param>
|
||||
public void Map(ulong va, nuint hostAddress, ulong size)
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
while (size != 0)
|
||||
{
|
||||
_pageTable.Map(va, hostAddress);
|
||||
_pageTable.Map(va, pa);
|
||||
|
||||
va += PageSize;
|
||||
hostAddress += PageSize;
|
||||
pa += PageSize;
|
||||
size -= PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps a previously mapped range of virtual memory.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range to be unmapped</param>
|
||||
/// <param name="size">Size of the range to be unmapped</param>
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
@@ -88,47 +78,25 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from mapped memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data being read</typeparam>
|
||||
/// <param name="va">Virtual address of the data in memory</param>
|
||||
/// <returns>The data</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||
/// <inheritdoc/>
|
||||
public T Read<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from mapped memory.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the data in memory</param>
|
||||
/// <param name="data">Span to store the data being read into</param>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||
/// <inheritdoc/>
|
||||
public void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
ReadImpl(va, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to mapped memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data being written</typeparam>
|
||||
/// <param name="va">Virtual address to write the data into</param>
|
||||
/// <param name="value">Data to be written</param>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||
/// <inheritdoc/>
|
||||
public void Write<T>(ulong va, T value) where T : unmanaged
|
||||
{
|
||||
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to mapped memory.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address to write the data into</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||
/// <inheritdoc/>
|
||||
public void Write(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
@@ -140,7 +108,7 @@ namespace Ryujinx.Memory
|
||||
|
||||
if (IsContiguousAndMapped(va, data.Length))
|
||||
{
|
||||
data.CopyTo(GetHostSpanContiguous(va, data.Length));
|
||||
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -148,34 +116,27 @@ namespace Ryujinx.Memory
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
|
||||
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
|
||||
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only span of data from mapped memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may perform a allocation if the data is not contiguous in memory.
|
||||
/// For this reason, the span is read-only, you can't modify the data.
|
||||
/// </remarks>
|
||||
/// <param name="va">Virtual address of the data</param>
|
||||
/// <param name="size">Size of the data</param>
|
||||
/// <param name="tracked">True if read tracking is triggered on the span</param>
|
||||
/// <returns>A read-only span of the data</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
@@ -185,7 +146,7 @@ namespace Ryujinx.Memory
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return GetHostSpanContiguous(va, size);
|
||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -197,19 +158,7 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a region of memory that can be written to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the requested region is not contiguous in physical memory,
|
||||
/// this will perform an allocation, and flush the data (writing it
|
||||
/// back to the backing memory) on disposal.
|
||||
/// </remarks>
|
||||
/// <param name="va">Virtual address of the data</param>
|
||||
/// <param name="size">Size of the data</param>
|
||||
/// <param name="tracked">True if write tracking is triggered on the span</param>
|
||||
/// <returns>A writable region of memory containing the data</returns>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||
/// <inheritdoc/>
|
||||
public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
if (size == 0)
|
||||
@@ -219,7 +168,7 @@ namespace Ryujinx.Memory
|
||||
|
||||
if (IsContiguousAndMapped(va, size))
|
||||
{
|
||||
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
|
||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -231,33 +180,18 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference for the given type at the specified virtual memory address.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The data must be located at a contiguous memory region.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Type of the data to get the reference</typeparam>
|
||||
/// <param name="va">Virtual address of the data</param>
|
||||
/// <returns>A reference to the data in memory</returns>
|
||||
/// <exception cref="MemoryNotContiguousException">Throw if the specified memory region is not contiguous in physical memory</exception>
|
||||
public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
/// <inheritdoc/>
|
||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
||||
{
|
||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||
{
|
||||
ThrowMemoryNotContiguous();
|
||||
}
|
||||
|
||||
return ref *(T*)GetHostAddress(va);
|
||||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the number of pages in a virtual address range.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <param name="startVa">The virtual address of the beginning of the first page</param>
|
||||
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetPagesCount(ulong va, uint size, out ulong startVa)
|
||||
{
|
||||
@@ -268,7 +202,7 @@ namespace Ryujinx.Memory
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||
@@ -290,7 +224,7 @@ namespace Ryujinx.Memory
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -301,18 +235,12 @@ namespace Ryujinx.Memory
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the physical regions that make up the given virtual address region.
|
||||
/// If any part of the virtual region is unmapped, null is returned.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <returns>Array of physical regions</returns>
|
||||
public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
@@ -322,9 +250,9 @@ namespace Ryujinx.Memory
|
||||
|
||||
int pages = GetPagesCount(va, (uint)size, out va);
|
||||
|
||||
var regions = new List<HostMemoryRange>();
|
||||
var regions = new List<MemoryRange>();
|
||||
|
||||
nuint regionStart = GetHostAddress(va);
|
||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
||||
ulong regionSize = PageSize;
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
@@ -334,12 +262,12 @@ namespace Ryujinx.Memory
|
||||
return null;
|
||||
}
|
||||
|
||||
nuint newHostAddress = GetHostAddress(va + PageSize);
|
||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
||||
|
||||
if (GetHostAddress(va) + PageSize != newHostAddress)
|
||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||
regionStart = newHostAddress;
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
regionSize = 0;
|
||||
}
|
||||
|
||||
@@ -347,7 +275,7 @@ namespace Ryujinx.Memory
|
||||
regionSize += PageSize;
|
||||
}
|
||||
|
||||
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
|
||||
return regions;
|
||||
}
|
||||
@@ -365,26 +293,26 @@ namespace Ryujinx.Memory
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given virtual address is mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address to check</param>
|
||||
/// <returns>True if the address is mapped, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsMapped(ulong va)
|
||||
{
|
||||
@@ -396,12 +324,7 @@ namespace Ryujinx.Memory
|
||||
return _pageTable.Read(va) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a memory range is mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>True if the entire range is mapped, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool IsRangeMapped(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0UL)
|
||||
@@ -460,14 +383,9 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return new Span<byte>((void*)GetHostAddress(va), size);
|
||||
}
|
||||
|
||||
private nuint GetHostAddress(ulong va)
|
||||
{
|
||||
return _pageTable.Read(va) + (nuint)(va & PageMask);
|
||||
return _pageTable.Read(va) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -13,9 +13,9 @@ namespace Ryujinx.Memory
|
||||
/// Addresses and size must be page aligned.
|
||||
/// </remarks>
|
||||
/// <param name="va">Virtual memory address</param>
|
||||
/// <param name="hostAddress">Pointer where the region should be mapped to</param>
|
||||
/// <param name="pa">Physical memory address where the region should be mapped to</param>
|
||||
/// <param name="size">Size to be mapped</param>
|
||||
void Map(ulong va, nuint hostAddress, ulong size);
|
||||
void Map(ulong va, ulong pa, ulong size);
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps a previously mapped range of virtual memory.
|
||||
@@ -111,7 +111,7 @@ namespace Ryujinx.Memory
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
/// <returns>Array of physical regions</returns>
|
||||
IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size);
|
||||
IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given CPU virtual address is mapped.
|
||||
|
@@ -29,6 +29,18 @@ namespace Ryujinx.Memory
|
||||
/// Enables mirroring of the memory block through aliasing of memory pages.
|
||||
/// When enabled, this allows creating more memory blocks sharing the same backing storage.
|
||||
/// </summary>
|
||||
Mirrorable = 1 << 2
|
||||
Mirrorable = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the memory block should support mapping views of a mirrorable memory block.
|
||||
/// The block that is to have their views mapped should be created with the <see cref="Mirrorable"/> flag.
|
||||
/// </summary>
|
||||
ViewCompatible = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Forces views to be mapped page by page on Windows. When partial unmaps are done, this avoids the need
|
||||
/// to unmap the full range and remap sub-ranges, which creates a time window with incorrectly unmapped memory.
|
||||
/// </summary>
|
||||
ForceWindows4KBViewMapping = 1 << 4
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@@ -11,8 +12,12 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
private readonly bool _usesSharedMemory;
|
||||
private readonly bool _isMirror;
|
||||
private readonly bool _viewCompatible;
|
||||
private readonly bool _forceWindows4KBView;
|
||||
private IntPtr _sharedMemory;
|
||||
private IntPtr _pointer;
|
||||
private ConcurrentDictionary<MemoryBlock, byte> _viewStorages;
|
||||
private int _viewCount;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the memory block data.
|
||||
@@ -36,12 +41,14 @@ namespace Ryujinx.Memory
|
||||
if (flags.HasFlag(MemoryAllocationFlags.Mirrorable))
|
||||
{
|
||||
_sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve));
|
||||
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory);
|
||||
_pointer = MemoryManagement.MapSharedMemory(_sharedMemory, size);
|
||||
_usesSharedMemory = true;
|
||||
}
|
||||
else if (flags.HasFlag(MemoryAllocationFlags.Reserve))
|
||||
{
|
||||
_pointer = MemoryManagement.Reserve(size);
|
||||
_viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible);
|
||||
_forceWindows4KBView = flags.HasFlag(MemoryAllocationFlags.ForceWindows4KBViewMapping);
|
||||
_pointer = MemoryManagement.Reserve(size, _viewCompatible, _forceWindows4KBView);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -49,6 +56,10 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
|
||||
Size = size;
|
||||
|
||||
_viewStorages = new ConcurrentDictionary<MemoryBlock, byte>();
|
||||
_viewStorages.TryAdd(this, 0);
|
||||
_viewCount = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +71,7 @@ namespace Ryujinx.Memory
|
||||
/// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception>
|
||||
private MemoryBlock(ulong size, IntPtr sharedMemory)
|
||||
{
|
||||
_pointer = MemoryManagement.MapSharedMemory(sharedMemory);
|
||||
_pointer = MemoryManagement.MapSharedMemory(sharedMemory, size);
|
||||
Size = size;
|
||||
_usesSharedMemory = true;
|
||||
_isMirror = true;
|
||||
@@ -112,6 +123,42 @@ namespace Ryujinx.Memory
|
||||
return MemoryManagement.Decommit(GetPointerInternal(offset, size), size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a view of memory from another memory block.
|
||||
/// </summary>
|
||||
/// <param name="srcBlock">Memory block from where the backing memory will be taken</param>
|
||||
/// <param name="srcOffset">Offset on <paramref name="srcBlock"/> of the region that should be mapped</param>
|
||||
/// <param name="dstOffset">Offset to map the view into on this block</param>
|
||||
/// <param name="size">Size of the range to be mapped</param>
|
||||
/// <exception cref="NotSupportedException">Throw when the source memory block does not support mirroring</exception>
|
||||
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
|
||||
public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size)
|
||||
{
|
||||
if (srcBlock._sharedMemory == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("The source memory block is not mirrorable, and thus cannot be mapped on the current block.");
|
||||
}
|
||||
|
||||
if (_viewStorages.TryAdd(srcBlock, 0))
|
||||
{
|
||||
srcBlock.IncrementViewCount();
|
||||
}
|
||||
|
||||
MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, _forceWindows4KBView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps a view of memory from another memory block.
|
||||
/// </summary>
|
||||
/// <param name="srcBlock">Memory block from where the backing memory was taken during map</param>
|
||||
/// <param name="offset">Offset of the view previously mapped with <see cref="MapView"/></param>
|
||||
/// <param name="size">Size of the range to be unmapped</param>
|
||||
public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size)
|
||||
{
|
||||
MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, _forceWindows4KBView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reprotects a region of memory.
|
||||
/// </summary>
|
||||
@@ -124,21 +171,7 @@ namespace Ryujinx.Memory
|
||||
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
|
||||
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true)
|
||||
{
|
||||
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, throwOnFail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remaps a region of memory into this memory block.
|
||||
/// </summary>
|
||||
/// <param name="offset">Starting offset of the range to be remapped into</param>
|
||||
/// <param name="sourceAddress">Starting offset of the range to be remapped from</param>
|
||||
/// <param name="size">Size of the range to be remapped</param>
|
||||
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
|
||||
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
|
||||
public void Remap(ulong offset, IntPtr sourceAddress, ulong size)
|
||||
{
|
||||
MemoryManagement.Remap(GetPointerInternal(offset, size), sourceAddress, size);
|
||||
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, _forceWindows4KBView, throwOnFail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -274,7 +307,7 @@ namespace Ryujinx.Memory
|
||||
/// <exception cref="ObjectDisposedException">Throw when the memory block has already been disposed</exception>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when either <paramref name="offset"/> or <paramref name="size"/> are out of range</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public nuint GetPointer(ulong offset, ulong size) => (nuint)(ulong)GetPointerInternal(offset, size);
|
||||
public IntPtr GetPointer(ulong offset, ulong size) => GetPointerInternal(offset, size);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private IntPtr GetPointerInternal(ulong offset, ulong size)
|
||||
@@ -367,22 +400,63 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
if (_usesSharedMemory)
|
||||
{
|
||||
MemoryManagement.UnmapSharedMemory(ptr);
|
||||
MemoryManagement.UnmapSharedMemory(ptr, Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryManagement.Free(ptr, Size, _forceWindows4KBView);
|
||||
}
|
||||
|
||||
if (_sharedMemory != IntPtr.Zero && !_isMirror)
|
||||
foreach (MemoryBlock viewStorage in _viewStorages.Keys)
|
||||
{
|
||||
viewStorage.DecrementViewCount();
|
||||
}
|
||||
|
||||
_viewStorages.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the number of views that uses this memory block as storage.
|
||||
/// </summary>
|
||||
private void IncrementViewCount()
|
||||
{
|
||||
Interlocked.Increment(ref _viewCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the number of views that uses this memory block as storage.
|
||||
/// </summary>
|
||||
private void DecrementViewCount()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _viewCount) == 0 && _sharedMemory != IntPtr.Zero && !_isMirror)
|
||||
{
|
||||
MemoryManagement.DestroySharedMemory(_sharedMemory);
|
||||
_sharedMemory = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified memory allocation flags are supported on the current platform.
|
||||
/// </summary>
|
||||
/// <param name="flags">Flags to be checked</param>
|
||||
/// <returns>True if the platform supports all the flags, false otherwise</returns>
|
||||
public static bool SupportsFlags(MemoryAllocationFlags flags)
|
||||
{
|
||||
MemoryManagement.Free(ptr);
|
||||
}
|
||||
}
|
||||
if (flags.HasFlag(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134);
|
||||
}
|
||||
|
||||
private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
|
||||
private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
|
||||
return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
|
||||
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
|
||||
}
|
||||
}
|
||||
|
@@ -8,12 +8,9 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr sizeNint = new IntPtr((long)size);
|
||||
|
||||
return MemoryManagementWindows.Allocate(sizeNint);
|
||||
return MemoryManagementWindows.Allocate((IntPtr)size);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.Allocate(size);
|
||||
}
|
||||
@@ -23,16 +20,13 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr Reserve(ulong size)
|
||||
public static IntPtr Reserve(ulong size, bool viewCompatible, bool force4KBMap)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr sizeNint = new IntPtr((long)size);
|
||||
|
||||
return MemoryManagementWindows.Reserve(sizeNint);
|
||||
return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible, force4KBMap);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.Reserve(size);
|
||||
}
|
||||
@@ -46,12 +40,9 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr sizeNint = new IntPtr((long)size);
|
||||
|
||||
return MemoryManagementWindows.Commit(address, sizeNint);
|
||||
return MemoryManagementWindows.Commit(address, (IntPtr)size);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.Commit(address, size);
|
||||
}
|
||||
@@ -65,12 +56,9 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr sizeNint = new IntPtr((long)size);
|
||||
|
||||
return MemoryManagementWindows.Decommit(address, sizeNint);
|
||||
return MemoryManagementWindows.Decommit(address, (IntPtr)size);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.Decommit(address, size);
|
||||
}
|
||||
@@ -80,18 +68,68 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool throwOnFail)
|
||||
public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr address, ulong size, bool force4KBMap)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (force4KBMap)
|
||||
{
|
||||
MemoryManagementWindows.MapView4KB(sharedMemory, srcOffset, address, (IntPtr)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size);
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
MemoryManagementUnix.MapView(sharedMemory, srcOffset, address, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnmapView(IntPtr sharedMemory, IntPtr address, ulong size, bool force4KBMap)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (force4KBMap)
|
||||
{
|
||||
MemoryManagementWindows.UnmapView4KB(address, (IntPtr)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size);
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
MemoryManagementUnix.UnmapView(address, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool forView, bool force4KBMap, bool throwOnFail)
|
||||
{
|
||||
bool result;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr sizeNint = new IntPtr((long)size);
|
||||
|
||||
result = MemoryManagementWindows.Reprotect(address, sizeNint, permission);
|
||||
if (forView && force4KBMap)
|
||||
{
|
||||
result = MemoryManagementWindows.Reprotect4KB(address, (IntPtr)size, permission, forView);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else
|
||||
{
|
||||
result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView);
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
result = MemoryManagementUnix.Reprotect(address, size, permission);
|
||||
}
|
||||
@@ -106,14 +144,13 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Free(IntPtr address)
|
||||
public static bool Free(IntPtr address, ulong size, bool force4KBMap)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return MemoryManagementWindows.Free(address);
|
||||
return MemoryManagementWindows.Free(address, (IntPtr)size, force4KBMap);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.Free(address);
|
||||
}
|
||||
@@ -127,12 +164,9 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
IntPtr sizeNint = new IntPtr((long)size);
|
||||
|
||||
return MemoryManagementWindows.CreateSharedMemory(sizeNint, reserve);
|
||||
return MemoryManagementWindows.CreateSharedMemory((IntPtr)size, reserve);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.CreateSharedMemory(size, reserve);
|
||||
}
|
||||
@@ -148,8 +182,7 @@ namespace Ryujinx.Memory
|
||||
{
|
||||
MemoryManagementWindows.DestroySharedMemory(handle);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
MemoryManagementUnix.DestroySharedMemory(handle);
|
||||
}
|
||||
@@ -159,16 +192,15 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr MapSharedMemory(IntPtr handle)
|
||||
public static IntPtr MapSharedMemory(IntPtr handle, ulong size)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return MemoryManagementWindows.MapSharedMemory(handle);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.MapSharedMemory(handle);
|
||||
return MemoryManagementUnix.MapSharedMemory(handle, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,29 +208,15 @@ namespace Ryujinx.Memory
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnmapSharedMemory(IntPtr address)
|
||||
public static void UnmapSharedMemory(IntPtr address, ulong size)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
MemoryManagementWindows.UnmapSharedMemory(address);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
MemoryManagementUnix.UnmapSharedMemory(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr Remap(IntPtr target, IntPtr source, ulong size)
|
||||
{
|
||||
if (OperatingSystem.IsLinux() ||
|
||||
OperatingSystem.IsMacOS())
|
||||
{
|
||||
return MemoryManagementUnix.Remap(target, source, size);
|
||||
MemoryManagementUnix.UnmapSharedMemory(address, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user