Implement a new physical memory manager and replace DeviceMemory
This commit is contained in:
parent
87bfe681ef
commit
4f5b582df5
19 changed files with 887 additions and 71 deletions
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
{
|
{
|
||||||
private Switch _device;
|
private Switch _device;
|
||||||
|
|
||||||
private long _physicalAddress;
|
private ulong _physicalAddress;
|
||||||
|
|
||||||
private string _fontsPath;
|
private string _fontsPath;
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
|
|
||||||
private Dictionary<SharedFontType, FontInfo> _fontData;
|
private Dictionary<SharedFontType, FontInfo> _fontData;
|
||||||
|
|
||||||
public SharedFontManager(Switch device, long physicalAddress)
|
public SharedFontManager(Switch device, ulong physicalAddress)
|
||||||
{
|
{
|
||||||
_physicalAddress = physicalAddress;
|
_physicalAddress = physicalAddress;
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
{
|
{
|
||||||
if (_fontData == null)
|
if (_fontData == null)
|
||||||
{
|
{
|
||||||
_device.Memory.FillWithZeros(_physicalAddress, Horizon.FontSize);
|
_device.Memory.ZeroFill(_physicalAddress, Horizon.FontSize);
|
||||||
|
|
||||||
uint fontOffset = 0;
|
uint fontOffset = 0;
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
|
|
||||||
for (; fontOffset - start < data.Length; fontOffset++)
|
for (; fontOffset - start < data.Length; fontOffset++)
|
||||||
{
|
{
|
||||||
_device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]);
|
_device.Memory.Write(_physicalAddress + fontOffset, data[fontOffset - start]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
@ -107,7 +107,7 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
|
|
||||||
for (; fontOffset - start < data.Length; fontOffset++)
|
for (; fontOffset - start < data.Length; fontOffset++)
|
||||||
{
|
{
|
||||||
_device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]);
|
_device.Memory.Write(_physicalAddress + fontOffset, data[fontOffset - start]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
@ -138,15 +138,15 @@ namespace Ryujinx.HLE.HOS.Font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteMagicAndSize(long position, int size)
|
private void WriteMagicAndSize(ulong address, int size)
|
||||||
{
|
{
|
||||||
const int decMagic = 0x18029a7f;
|
const int decMagic = 0x18029a7f;
|
||||||
const int key = 0x49621806;
|
const int key = 0x49621806;
|
||||||
|
|
||||||
int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ key);
|
int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ key);
|
||||||
|
|
||||||
_device.Memory.WriteInt32(position + 0, decMagic);
|
_device.Memory.Write(address + 0, decMagic);
|
||||||
_device.Memory.WriteInt32(position + 4, encryptedSize);
|
_device.Memory.Write(address + 4, encryptedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetFontSize(SharedFontType fontType)
|
public int GetFontSize(SharedFontType fontType)
|
||||||
|
|
|
@ -190,13 +190,13 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
|
KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
|
||||||
|
|
||||||
TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, (long)(timePa - DramMemoryMap.DramBase), TimeSize);
|
TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timePa - DramMemoryMap.DramBase, TimeSize);
|
||||||
|
|
||||||
AppletState = new AppletStateMgr(this);
|
AppletState = new AppletStateMgr(this);
|
||||||
|
|
||||||
AppletState.SetFocus(true);
|
AppletState.SetFocus(true);
|
||||||
|
|
||||||
Font = new SharedFontManager(device, (long)(fontPa - DramMemoryMap.DramBase));
|
Font = new SharedFontManager(device, fontPa - DramMemoryMap.DramBase);
|
||||||
|
|
||||||
IUserInterface.InitializePort(this);
|
IUserInterface.InitializePort(this);
|
||||||
|
|
||||||
|
|
|
@ -352,7 +352,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
{
|
{
|
||||||
int newHandle = 0;
|
int newHandle = 0;
|
||||||
|
|
||||||
int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4);
|
int handle = System.Device.Memory.Read<int>(clientMsg.DramAddress + offset * 4);
|
||||||
|
|
||||||
if (clientResult == KernelResult.Success && handle != 0)
|
if (clientResult == KernelResult.Success && handle != 0)
|
||||||
{
|
{
|
||||||
|
@ -368,7 +368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
{
|
{
|
||||||
int newHandle = 0;
|
int newHandle = 0;
|
||||||
|
|
||||||
int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4);
|
int handle = System.Device.Memory.Read<int>(clientMsg.DramAddress + offset * 4);
|
||||||
|
|
||||||
if (handle != 0)
|
if (handle != 0)
|
||||||
{
|
{
|
||||||
|
@ -404,7 +404,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
|
|
||||||
for (int index = 0; index < clientHeader.PointerBuffersCount; index++)
|
for (int index = 0; index < clientHeader.PointerBuffersCount; index++)
|
||||||
{
|
{
|
||||||
ulong pointerDesc = System.Device.Memory.ReadUInt64((long)clientMsg.DramAddress + offset * 4);
|
ulong pointerDesc = System.Device.Memory.Read<ulong>(clientMsg.DramAddress + offset * 4);
|
||||||
|
|
||||||
PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
|
PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
|
||||||
|
|
||||||
|
@ -463,11 +463,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
|
|
||||||
for (int index = 0; index < totalBuffersCount; index++)
|
for (int index = 0; index < totalBuffersCount; index++)
|
||||||
{
|
{
|
||||||
long clientDescAddress = (long)clientMsg.DramAddress + offset * 4;
|
ulong clientDescAddress = clientMsg.DramAddress + offset * 4;
|
||||||
|
|
||||||
uint descWord0 = System.Device.Memory.ReadUInt32(clientDescAddress + 0);
|
uint descWord0 = System.Device.Memory.Read<uint>(clientDescAddress + 0);
|
||||||
uint descWord1 = System.Device.Memory.ReadUInt32(clientDescAddress + 4);
|
uint descWord1 = System.Device.Memory.Read<uint>(clientDescAddress + 4);
|
||||||
uint descWord2 = System.Device.Memory.ReadUInt32(clientDescAddress + 8);
|
uint descWord2 = System.Device.Memory.Read<uint>(clientDescAddress + 8);
|
||||||
|
|
||||||
bool isSendDesc = index < clientHeader.SendBuffersCount;
|
bool isSendDesc = index < clientHeader.SendBuffersCount;
|
||||||
bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount;
|
bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount;
|
||||||
|
@ -700,8 +700,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy header.
|
// Copy header.
|
||||||
System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 0, serverHeader.Word0);
|
System.Device.Memory.Write(clientMsg.DramAddress + 0, serverHeader.Word0);
|
||||||
System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 4, serverHeader.Word1);
|
System.Device.Memory.Write(clientMsg.DramAddress + 4, serverHeader.Word1);
|
||||||
|
|
||||||
// Copy handles.
|
// Copy handles.
|
||||||
uint offset;
|
uint offset;
|
||||||
|
@ -710,11 +710,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
{
|
{
|
||||||
offset = 3;
|
offset = 3;
|
||||||
|
|
||||||
System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 8, serverHeader.Word2);
|
System.Device.Memory.Write(clientMsg.DramAddress + 8, serverHeader.Word2);
|
||||||
|
|
||||||
if (serverHeader.HasPid)
|
if (serverHeader.HasPid)
|
||||||
{
|
{
|
||||||
System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + offset * 4, serverProcess.Pid);
|
System.Device.Memory.Write(clientMsg.DramAddress + offset * 4, serverProcess.Pid);
|
||||||
|
|
||||||
offset += 2;
|
offset += 2;
|
||||||
}
|
}
|
||||||
|
@ -730,7 +730,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle);
|
GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle);
|
System.Device.Memory.Write(clientMsg.DramAddress + offset * 4, newHandle);
|
||||||
|
|
||||||
offset++;
|
offset++;
|
||||||
}
|
}
|
||||||
|
@ -753,7 +753,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle);
|
System.Device.Memory.Write(clientMsg.DramAddress + offset * 4, newHandle);
|
||||||
|
|
||||||
offset++;
|
offset++;
|
||||||
}
|
}
|
||||||
|
@ -819,11 +819,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
|
|
||||||
for (int index = 0; index < totalBuffersCount; index++)
|
for (int index = 0; index < totalBuffersCount; index++)
|
||||||
{
|
{
|
||||||
long dstDescAddress = (long)clientMsg.DramAddress + offset * 4;
|
ulong dstDescAddress = clientMsg.DramAddress + offset * 4;
|
||||||
|
|
||||||
System.Device.Memory.WriteUInt32(dstDescAddress + 0, 0);
|
System.Device.Memory.Write(dstDescAddress + 0, 0);
|
||||||
System.Device.Memory.WriteUInt32(dstDescAddress + 4, 0);
|
System.Device.Memory.Write(dstDescAddress + 4, 0);
|
||||||
System.Device.Memory.WriteUInt32(dstDescAddress + 8, 0);
|
System.Device.Memory.Write(dstDescAddress + 8, 0);
|
||||||
|
|
||||||
offset += 3;
|
offset += 3;
|
||||||
}
|
}
|
||||||
|
@ -878,9 +878,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
|
|
||||||
private MessageHeader GetClientMessageHeader(Message clientMsg)
|
private MessageHeader GetClientMessageHeader(Message clientMsg)
|
||||||
{
|
{
|
||||||
uint word0 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 0);
|
uint word0 = System.Device.Memory.Read<uint>(clientMsg.DramAddress + 0);
|
||||||
uint word1 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 4);
|
uint word1 = System.Device.Memory.Read<uint>(clientMsg.DramAddress + 4);
|
||||||
uint word2 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 8);
|
uint word2 = System.Device.Memory.Read<uint>(clientMsg.DramAddress + 8);
|
||||||
|
|
||||||
return new MessageHeader(word0, word1, word2);
|
return new MessageHeader(word0, word1, word2);
|
||||||
}
|
}
|
||||||
|
@ -970,11 +970,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
|
|
||||||
ulong[] receiveList = new ulong[recvListSize];
|
ulong[] receiveList = new ulong[recvListSize];
|
||||||
|
|
||||||
long recvListAddress = (long)message.DramAddress + recvListOffset;
|
ulong recvListAddress = message.DramAddress + recvListOffset;
|
||||||
|
|
||||||
for (int index = 0; index < recvListSize; index++)
|
for (int index = 0; index < recvListSize; index++)
|
||||||
{
|
{
|
||||||
receiveList[index] = System.Device.Memory.ReadUInt64(recvListAddress + index * 8);
|
receiveList[index] = System.Device.Memory.Read<ulong>(recvListAddress + (ulong)index * 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
return receiveList;
|
return receiveList;
|
||||||
|
@ -1225,8 +1225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
|
||||||
|
|
||||||
ulong address = clientProcess.MemoryManager.GetDramAddressFromVa(request.CustomCmdBuffAddr);
|
ulong address = clientProcess.MemoryManager.GetDramAddressFromVa(request.CustomCmdBuffAddr);
|
||||||
|
|
||||||
System.Device.Memory.WriteInt64((long)address + 0, 0);
|
System.Device.Memory.Write<ulong>(address, 0);
|
||||||
System.Device.Memory.WriteInt32((long)address + 8, (int)result);
|
System.Device.Memory.Write(address + 8, (int)result);
|
||||||
|
|
||||||
clientProcess.MemoryManager.UnborrowIpcBuffer(
|
clientProcess.MemoryManager.UnborrowIpcBuffer(
|
||||||
request.CustomCmdBuffAddr,
|
request.CustomCmdBuffAddr,
|
||||||
|
|
|
@ -1840,7 +1840,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
{
|
{
|
||||||
ulong unusedSizeBefore = address - addressTruncated;
|
ulong unusedSizeBefore = address - addressTruncated;
|
||||||
|
|
||||||
_system.Device.Memory.Set(dstFirstPagePa, 0, unusedSizeBefore);
|
_system.Device.Memory.ZeroFill(dstFirstPagePa, unusedSizeBefore);
|
||||||
|
|
||||||
ulong copySize = addressRounded <= endAddr ? addressRounded - address : size;
|
ulong copySize = addressRounded <= endAddr ? addressRounded - address : size;
|
||||||
|
|
||||||
|
@ -1859,7 +1859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
|
|
||||||
if (unusedSizeAfter != 0)
|
if (unusedSizeAfter != 0)
|
||||||
{
|
{
|
||||||
_system.Device.Memory.Set(firstPageFillAddress, 0, unusedSizeAfter);
|
_system.Device.Memory.ZeroFill(firstPageFillAddress, unusedSizeAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
KPageList pages = new KPageList();
|
KPageList pages = new KPageList();
|
||||||
|
@ -1919,7 +1919,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
unusedSizeAfter = PageSize;
|
unusedSizeAfter = PageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
_system.Device.Memory.Set(lastPageFillAddr, 0, unusedSizeAfter);
|
_system.Device.Memory.ZeroFill(lastPageFillAddr, unusedSizeAfter);
|
||||||
|
|
||||||
if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success)
|
if (pages.AddRange(dstFirstPagePa, 1) != KernelResult.Success)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1022,7 +1022,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
|
||||||
bool useFlatPageTable = memRegion == MemoryRegion.Application;
|
bool useFlatPageTable = memRegion == MemoryRegion.Application;
|
||||||
|
|
||||||
CpuMemory = new MemoryManager(_system.Device.Memory.RamPointer, addrSpaceBits, useFlatPageTable);
|
CpuMemory = new MemoryManager(_system.Device.Memory.Pointer, addrSpaceBits, useFlatPageTable);
|
||||||
|
|
||||||
Translator = new Translator(CpuMemory);
|
Translator = new Translator(CpuMemory);
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter();
|
EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
|
public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, ulong timeSharedMemoryAddress, int timeSharedMemorySize)
|
||||||
{
|
{
|
||||||
SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryAddress, timeSharedMemorySize);
|
SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryAddress, timeSharedMemorySize);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
|
@ -13,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
{
|
{
|
||||||
private Switch _device;
|
private Switch _device;
|
||||||
private KSharedMemory _sharedMemory;
|
private KSharedMemory _sharedMemory;
|
||||||
private long _timeSharedMemoryAddress;
|
private ulong _timeSharedMemoryAddress;
|
||||||
private int _timeSharedMemorySize;
|
private int _timeSharedMemorySize;
|
||||||
|
|
||||||
private const uint SteadyClockContextOffset = 0x00;
|
private const uint SteadyClockContextOffset = 0x00;
|
||||||
|
@ -21,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
private const uint NetworkSystemClockContextOffset = 0x80;
|
private const uint NetworkSystemClockContextOffset = 0x80;
|
||||||
private const uint AutomaticCorrectionEnabledOffset = 0xC8;
|
private const uint AutomaticCorrectionEnabledOffset = 0xC8;
|
||||||
|
|
||||||
public void Initialize(Switch device, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
|
public void Initialize(Switch device, KSharedMemory sharedMemory, ulong timeSharedMemoryAddress, int timeSharedMemorySize)
|
||||||
{
|
{
|
||||||
_device = device;
|
_device = device;
|
||||||
_sharedMemory = sharedMemory;
|
_sharedMemory = sharedMemory;
|
||||||
|
@ -29,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
_timeSharedMemorySize = timeSharedMemorySize;
|
_timeSharedMemorySize = timeSharedMemorySize;
|
||||||
|
|
||||||
// Clean the shared memory
|
// Clean the shared memory
|
||||||
_device.Memory.FillWithZeros(_timeSharedMemoryAddress, _timeSharedMemorySize);
|
_device.Memory.ZeroFill(_timeSharedMemoryAddress, (ulong)_timeSharedMemorySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KSharedMemory GetSharedMemory()
|
public KSharedMemory GetSharedMemory()
|
||||||
|
@ -86,9 +87,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context);
|
WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private T ReadObjectFromSharedMemory<T>(long offset, long padding)
|
private T ReadObjectFromSharedMemory<T>(ulong offset, ulong padding)
|
||||||
{
|
{
|
||||||
long indexOffset = _timeSharedMemoryAddress + offset;
|
ulong indexOffset = _timeSharedMemoryAddress + offset;
|
||||||
|
|
||||||
T result;
|
T result;
|
||||||
uint index;
|
uint index;
|
||||||
|
@ -96,31 +97,31 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
index = _device.Memory.ReadUInt32(indexOffset);
|
index = _device.Memory.Read<uint>(indexOffset);
|
||||||
|
|
||||||
long objectOffset = indexOffset + 4 + padding + (index & 1) * Marshal.SizeOf<T>();
|
ulong objectOffset = indexOffset + 4 + padding + (ulong)((index & 1) * Unsafe.SizeOf<T>());
|
||||||
|
|
||||||
result = _device.Memory.ReadStruct<T>(objectOffset);
|
result = _device.Memory.Read<T>(objectOffset);
|
||||||
|
|
||||||
Thread.MemoryBarrier();
|
Thread.MemoryBarrier();
|
||||||
|
|
||||||
possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset);
|
possiblyNewIndex = _device.Memory.Read<uint>(indexOffset);
|
||||||
} while (index != possiblyNewIndex);
|
} while (index != possiblyNewIndex);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteObjectToSharedMemory<T>(long offset, long padding, T value)
|
private void WriteObjectToSharedMemory<T>(ulong offset, ulong padding, T value)
|
||||||
{
|
{
|
||||||
long indexOffset = _timeSharedMemoryAddress + offset;
|
ulong indexOffset = _timeSharedMemoryAddress + offset;
|
||||||
uint newIndex = _device.Memory.ReadUInt32(indexOffset) + 1;
|
uint newIndex = _device.Memory.Read<uint>(indexOffset) + 1;
|
||||||
long objectOffset = indexOffset + 4 + padding + (newIndex & 1) * Marshal.SizeOf<T>();
|
ulong objectOffset = indexOffset + 4 + padding + (ulong)((newIndex & 1) * Unsafe.SizeOf<T>());
|
||||||
|
|
||||||
_device.Memory.WriteStruct(objectOffset, value);
|
_device.Memory.Write(objectOffset, value);
|
||||||
|
|
||||||
Thread.MemoryBarrier();
|
Thread.MemoryBarrier();
|
||||||
|
|
||||||
_device.Memory.WriteUInt32(indexOffset, newIndex);
|
_device.Memory.Write(indexOffset, newIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,9 +88,9 @@ namespace Ryujinx.HLE.Input
|
||||||
_mainLayoutOffset = Offset + HidControllerHeaderSize
|
_mainLayoutOffset = Offset + HidControllerHeaderSize
|
||||||
+ ((int)ControllerLayouts.Main * HidControllerLayoutsSize);
|
+ ((int)ControllerLayouts.Main * HidControllerLayoutsSize);
|
||||||
|
|
||||||
Device.Memory.FillWithZeros(Offset, 0x5000);
|
Device.Memory.ZeroFill((ulong)Offset, 0x5000);
|
||||||
Device.Memory.WriteStruct(Offset, Header);
|
Device.Memory.Write((ulong)Offset, Header);
|
||||||
Device.Memory.WriteStruct(DeviceStateOffset, DeviceState);
|
Device.Memory.Write((ulong)DeviceStateOffset, DeviceState);
|
||||||
|
|
||||||
Connected = true;
|
Connected = true;
|
||||||
}
|
}
|
||||||
|
@ -126,14 +126,14 @@ namespace Ryujinx.HLE.Input
|
||||||
Timestamp = GetTimestamp(),
|
Timestamp = GetTimestamp(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Device.Memory.WriteStruct(_currentLayoutOffset, newInputStateHeader);
|
Device.Memory.Write((ulong)_currentLayoutOffset, newInputStateHeader);
|
||||||
Device.Memory.WriteStruct(_mainLayoutOffset, newInputStateHeader);
|
Device.Memory.Write((ulong)_mainLayoutOffset, newInputStateHeader);
|
||||||
|
|
||||||
long currentInputStateOffset = HidControllersLayoutHeaderSize
|
long currentInputStateOffset = HidControllersLayoutHeaderSize
|
||||||
+ newInputStateHeader.CurrentEntryIndex * HidControllersInputEntrySize;
|
+ newInputStateHeader.CurrentEntryIndex * HidControllersInputEntrySize;
|
||||||
|
|
||||||
Device.Memory.WriteStruct(_currentLayoutOffset + currentInputStateOffset, currentInput);
|
Device.Memory.Write((ulong)(_currentLayoutOffset + currentInputStateOffset), currentInput);
|
||||||
Device.Memory.WriteStruct(_mainLayoutOffset + currentInputStateOffset, currentInput);
|
Device.Memory.Write((ulong)(_mainLayoutOffset + currentInputStateOffset), currentInput);
|
||||||
|
|
||||||
LastInputState = currentInput;
|
LastInputState = currentInput;
|
||||||
CurrentStateHeader = newInputStateHeader;
|
CurrentStateHeader = newInputStateHeader;
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace Ryujinx.HLE.Input
|
||||||
_device = device;
|
_device = device;
|
||||||
HidPosition = hidPosition;
|
HidPosition = hidPosition;
|
||||||
|
|
||||||
device.Memory.FillWithZeros(hidPosition, Horizon.HidSize);
|
device.Memory.ZeroFill((ulong)hidPosition, Horizon.HidSize);
|
||||||
|
|
||||||
_currentTouchHeader = new TouchHeader()
|
_currentTouchHeader = new TouchHeader()
|
||||||
{
|
{
|
||||||
|
@ -152,7 +152,7 @@ namespace Ryujinx.HLE.Input
|
||||||
TouchCount = points.Length
|
TouchCount = points.Length
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.Memory.WriteStruct(currentTouchEntryOffset, touchEntry);
|
_device.Memory.Write((ulong)currentTouchEntryOffset, touchEntry);
|
||||||
|
|
||||||
currentTouchEntryOffset += HidTouchEntryHeaderSize;
|
currentTouchEntryOffset += HidTouchEntryHeaderSize;
|
||||||
|
|
||||||
|
@ -169,12 +169,12 @@ namespace Ryujinx.HLE.Input
|
||||||
Y = points[i].Y
|
Y = points[i].Y
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.Memory.WriteStruct(currentTouchEntryOffset, touch);
|
_device.Memory.Write((ulong)currentTouchEntryOffset, touch);
|
||||||
|
|
||||||
currentTouchEntryOffset += HidTouchEntryTouchSize;
|
currentTouchEntryOffset += HidTouchEntryTouchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.Memory.WriteStruct(_touchScreenOffset, newTouchHeader);
|
_device.Memory.Write((ulong)_touchScreenOffset, newTouchHeader);
|
||||||
|
|
||||||
_currentTouchHeader = newTouchHeader;
|
_currentTouchHeader = newTouchHeader;
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ namespace Ryujinx.HLE.Input
|
||||||
Timestamp = timestamp,
|
Timestamp = timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.Memory.WriteStruct(_keyboardOffset, newKeyboardHeader);
|
_device.Memory.Write((ulong)_keyboardOffset, newKeyboardHeader);
|
||||||
|
|
||||||
long keyboardEntryOffset = _keyboardOffset + HidKeyboardHeaderSize;
|
long keyboardEntryOffset = _keyboardOffset + HidKeyboardHeaderSize;
|
||||||
keyboardEntryOffset += newKeyboardHeader.CurrentEntryIndex * HidKeyboardEntrySize;
|
keyboardEntryOffset += newKeyboardHeader.CurrentEntryIndex * HidKeyboardEntrySize;
|
||||||
|
@ -204,7 +204,7 @@ namespace Ryujinx.HLE.Input
|
||||||
Modifier = keyboard.Modifier,
|
Modifier = keyboard.Modifier,
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.Memory.WriteStruct(keyboardEntryOffset, newkeyboardEntry);
|
_device.Memory.Write((ulong)keyboardEntryOffset, newkeyboardEntry);
|
||||||
|
|
||||||
_currentKeyboardEntry = newkeyboardEntry;
|
_currentKeyboardEntry = newkeyboardEntry;
|
||||||
_currentKeyboardHeader = newKeyboardHeader;
|
_currentKeyboardHeader = newKeyboardHeader;
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics\Ryujinx.Graphics.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics\Ryujinx.Graphics.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
|
<ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
|
||||||
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services;
|
using Ryujinx.HLE.HOS.Services;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.Input;
|
using Ryujinx.HLE.Input;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ namespace Ryujinx.HLE
|
||||||
{
|
{
|
||||||
internal IAalOutput AudioOut { get; private set; }
|
internal IAalOutput AudioOut { get; private set; }
|
||||||
|
|
||||||
internal DeviceMemory Memory { get; private set; }
|
internal MemoryBlock Memory { get; private set; }
|
||||||
|
|
||||||
internal NvGpu Gpu { get; private set; }
|
internal NvGpu Gpu { get; private set; }
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ namespace Ryujinx.HLE
|
||||||
|
|
||||||
AudioOut = audioOut;
|
AudioOut = audioOut;
|
||||||
|
|
||||||
Memory = new DeviceMemory();
|
Memory = new MemoryBlock(1UL << 32);
|
||||||
|
|
||||||
Gpu = new NvGpu(renderer);
|
Gpu = new NvGpu(renderer);
|
||||||
|
|
||||||
|
|
274
Ryujinx.Memory/MemoryBlock.cs
Normal file
274
Ryujinx.Memory/MemoryBlock.cs
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using static Ryujinx.Memory.MemoryConstants;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a block of contiguous physical guest memory.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MemoryBlock : IDisposable
|
||||||
|
{
|
||||||
|
private IntPtr _pointer;
|
||||||
|
|
||||||
|
private readonly HashSet<MemoryRange>[] _pages;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pointer to the memory block data.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr Pointer => _pointer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of the memory block.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the memory block class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size of the memory block</param>
|
||||||
|
public MemoryBlock(ulong size)
|
||||||
|
{
|
||||||
|
_pointer = MemoryManagement.Allocate(size);
|
||||||
|
|
||||||
|
Size = size;
|
||||||
|
|
||||||
|
_pages = new HashSet<MemoryRange>[size >> PageBits];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads bytes from the memory block.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Starting address of the range being read</param>
|
||||||
|
/// <param name="data">Span where the bytes being read will be copied to</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Read(ulong address, Span<byte> data)
|
||||||
|
{
|
||||||
|
GetSpan(address, data.Length).CopyTo(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads data from the memory block.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the data</typeparam>
|
||||||
|
/// <param name="address">Address where the data is located</param>
|
||||||
|
/// <returns>Data at the specified address</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public T Read<T>(ulong address)
|
||||||
|
{
|
||||||
|
return GetRef<T>(address, Unsafe.SizeOf<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes bytes to the memory block.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Starting address of the range being written</param>
|
||||||
|
/// <param name="data">Span where the bytes being written will be copied from</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Write(ulong address, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
data.CopyTo(GetSpan(address, data.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes data to the memory block.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the data being written</typeparam>
|
||||||
|
/// <param name="address">Address to write the data into</param>
|
||||||
|
/// <param name="data">Data to be written</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Write<T>(ulong address, T data)
|
||||||
|
{
|
||||||
|
GetRef<T>(address, Unsafe.SizeOf<T>()) = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies data from one memory location to another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="srcAddress">Source address to read the data from</param>
|
||||||
|
/// <param name="dstAddress">Destination address to write the data into</param>
|
||||||
|
/// <param name="size">Size of the copy in bytes</param>
|
||||||
|
public void Copy(ulong srcAddress, ulong dstAddress, ulong size)
|
||||||
|
{
|
||||||
|
Write(dstAddress, GetSpan(srcAddress, (int)size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills a region of memory with zeros.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address of the region to fill with zeros</param>
|
||||||
|
/// <param name="size">Size in bytes of the region to fill</param>
|
||||||
|
public void ZeroFill(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
GetSpan(address, (int)size).Fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a range of memory that is tracked for memory modification.
|
||||||
|
/// The range can be checked for modification of any sub-range inside the range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Starting address of the range being tracked</param>
|
||||||
|
/// <param name="size">Size in bytes of the range being tracked</param>
|
||||||
|
/// <returns>The new write tracked memory range</returns>
|
||||||
|
public MemoryRange CreateMemoryRange(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
return new MemoryRange(this, address, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference of the data at a given memory block region.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Data type</typeparam>
|
||||||
|
/// <param name="address">Address of the memory region</param>
|
||||||
|
/// <param name="size">Size in bytes of the memory region</param>
|
||||||
|
/// <returns>A reference to the given memory region data</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe ref T GetRef<T>(ulong address, int size)
|
||||||
|
{
|
||||||
|
IntPtr ptr = _pointer;
|
||||||
|
|
||||||
|
if (ptr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MemoryBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endAddress = address + (ulong)size;
|
||||||
|
|
||||||
|
if (endAddress > Size || endAddress < address)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref Unsafe.AsRef<T>((void*)PtrAddr(ptr, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the span of a given memory block region.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Start address of the memory region</param>
|
||||||
|
/// <param name="size">Size in bytes of the region</param>
|
||||||
|
/// <returns>Span of the memory region</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe Span<byte> GetSpan(ulong address, int size)
|
||||||
|
{
|
||||||
|
IntPtr ptr = _pointer;
|
||||||
|
|
||||||
|
if (ptr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MemoryBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endAddress = address + (ulong)size;
|
||||||
|
|
||||||
|
if (endAddress > Size || endAddress < address)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Span<byte>((void*)PtrAddr(ptr, address), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a range of pages was modified, since the last call to this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">Page number of the first page to be checked</param>
|
||||||
|
/// <param name="count">Number of pages to check</param>
|
||||||
|
/// <param name="range">Memory range being checked</param>
|
||||||
|
/// <returns>True if any of the pages were modified, false otherwise</returns>
|
||||||
|
internal bool QueryModified(int page, int count, MemoryRange range)
|
||||||
|
{
|
||||||
|
return QueryModifiedImpl(page, count, range, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a range of pages was modified, since the last call to this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">Page number of the first page to be checked</param>
|
||||||
|
/// <param name="count">Number of pages to check</param>
|
||||||
|
/// <param name="range">Memory range being checked</param>
|
||||||
|
/// <param name="outBuffer">Optional output buffer to write the new data</param>
|
||||||
|
/// <returns>True if any of the pages were modified, false otherwise</returns>
|
||||||
|
internal bool QueryModified(int page, int count, MemoryRange range, Span<byte> outBuffer)
|
||||||
|
{
|
||||||
|
return QueryModifiedImpl(page, count, range, outBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a range of pages was modified, since the last call to this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">Page number of the first page to be checked</param>
|
||||||
|
/// <param name="count">Number of pages to check</param>
|
||||||
|
/// <param name="range">Memory range being checked</param>
|
||||||
|
/// <param name="outBuffer">Optional output buffer to write the new data</param>
|
||||||
|
/// <returns>True if any of the pages were modified, false otherwise</returns>
|
||||||
|
private bool QueryModifiedImpl(int page, int count, MemoryRange range, Span<byte> outBuffer)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<IntPtr> addresses = stackalloc IntPtr[count];
|
||||||
|
|
||||||
|
IntPtr pagePtr = PtrAddr(_pointer, (ulong)page << PageBits);
|
||||||
|
|
||||||
|
MemoryManagement.QueryModifiedPages(pagePtr, (IntPtr)((ulong)count << PageBits), addresses, out ulong pc);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)pc; i++)
|
||||||
|
{
|
||||||
|
int modifiedPage = (int)((ulong)(addresses[i].ToInt64() - _pointer.ToInt64()) >> PageBits);
|
||||||
|
|
||||||
|
if (outBuffer != null)
|
||||||
|
{
|
||||||
|
Read((ulong)modifiedPage << PageBits, outBuffer.Slice((modifiedPage - page) << PageBits, PageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
_pages[modifiedPage]?.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool modified = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (_pages[page + i] == null)
|
||||||
|
{
|
||||||
|
_pages[page + i] = new HashSet<MemoryRange>();
|
||||||
|
}
|
||||||
|
|
||||||
|
modified |= _pages[page + i].Add(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a 64-bits offset to a native pointer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pointer">Native pointer</param>
|
||||||
|
/// <param name="offset">Offset to add</param>
|
||||||
|
/// <returns>Native pointer with the added offset</returns>
|
||||||
|
private IntPtr PtrAddr(IntPtr pointer, ulong offset)
|
||||||
|
{
|
||||||
|
return (IntPtr)(pointer.ToInt64() + (long)offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frees the memory allocated for this memory block.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It's an error to use the memory block after disposal.
|
||||||
|
/// </remarks>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero);
|
||||||
|
|
||||||
|
if (ptr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
MemoryManagement.Free(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Ryujinx.Memory/MemoryConstants.cs
Normal file
9
Ryujinx.Memory/MemoryConstants.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
static class MemoryConstants
|
||||||
|
{
|
||||||
|
public const int PageBits = 12;
|
||||||
|
public const int PageSize = 1 << PageBits;
|
||||||
|
public const int PageMask = PageSize - 1;
|
||||||
|
}
|
||||||
|
}
|
63
Ryujinx.Memory/MemoryManagement.cs
Normal file
63
Ryujinx.Memory/MemoryManagement.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
public static class MemoryManagement
|
||||||
|
{
|
||||||
|
public static IntPtr Allocate(ulong size)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
IntPtr sizeNint = new IntPtr((long)size);
|
||||||
|
|
||||||
|
return MemoryManagementWin.Allocate(sizeNint);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
|
||||||
|
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
return MemoryManagementUnix.Allocate(size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Free(IntPtr address)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
return MemoryManagementWin.Free(address);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
|
||||||
|
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
return MemoryManagementUnix.Free(address);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool QueryModifiedPages(IntPtr address, IntPtr size, Span<IntPtr> addresses, out ulong count)
|
||||||
|
{
|
||||||
|
// This is only supported on windows, but returning
|
||||||
|
// false (failed) is also valid for platforms without
|
||||||
|
// write tracking support on the OS.
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
return MemoryManagementWin.QueryModifiedPages(address, size, addresses, out count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Ryujinx.Memory/MemoryManagementUnix.cs
Normal file
49
Ryujinx.Memory/MemoryManagementUnix.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using Mono.Unix.Native;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
static class MemoryManagementUnix
|
||||||
|
{
|
||||||
|
public static IntPtr Allocate(ulong size)
|
||||||
|
{
|
||||||
|
ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE);
|
||||||
|
|
||||||
|
const MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE;
|
||||||
|
|
||||||
|
const MmapFlags flags = MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS;
|
||||||
|
|
||||||
|
IntPtr ptr = Syscall.mmap(IntPtr.Zero, size + pageSize, prot, flags, -1, 0);
|
||||||
|
|
||||||
|
if (ptr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
ptr = new IntPtr(ptr.ToInt64() + (long)pageSize);
|
||||||
|
|
||||||
|
*((ulong*)ptr - 1) = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Free(IntPtr address)
|
||||||
|
{
|
||||||
|
ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE);
|
||||||
|
|
||||||
|
ulong size;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
size = *((ulong*)address - 1);
|
||||||
|
|
||||||
|
address = new IntPtr(address.ToInt64() - (long)pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Syscall.munmap(address, size + pageSize) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
Ryujinx.Memory/MemoryManagementWin.cs
Normal file
112
Ryujinx.Memory/MemoryManagementWin.cs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
static class MemoryManagementWin
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
private enum AllocationType : uint
|
||||||
|
{
|
||||||
|
Commit = 0x1000,
|
||||||
|
Reserve = 0x2000,
|
||||||
|
Decommit = 0x4000,
|
||||||
|
Release = 0x8000,
|
||||||
|
Reset = 0x80000,
|
||||||
|
Physical = 0x400000,
|
||||||
|
TopDown = 0x100000,
|
||||||
|
WriteWatch = 0x200000,
|
||||||
|
LargePages = 0x20000000
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum MemoryProtection : uint
|
||||||
|
{
|
||||||
|
NoAccess = 0x01,
|
||||||
|
ReadOnly = 0x02,
|
||||||
|
ReadWrite = 0x04,
|
||||||
|
WriteCopy = 0x08,
|
||||||
|
Execute = 0x10,
|
||||||
|
ExecuteRead = 0x20,
|
||||||
|
ExecuteReadWrite = 0x40,
|
||||||
|
ExecuteWriteCopy = 0x80,
|
||||||
|
GuardModifierflag = 0x100,
|
||||||
|
NoCacheModifierflag = 0x200,
|
||||||
|
WriteCombineModifierflag = 0x400
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum WriteWatchFlags : uint
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Reset = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern IntPtr VirtualAlloc(
|
||||||
|
IntPtr lpAddress,
|
||||||
|
IntPtr dwSize,
|
||||||
|
AllocationType flAllocationType,
|
||||||
|
MemoryProtection flProtect);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern bool VirtualFree(
|
||||||
|
IntPtr lpAddress,
|
||||||
|
IntPtr dwSize,
|
||||||
|
AllocationType dwFreeType);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private unsafe static extern int GetWriteWatch(
|
||||||
|
WriteWatchFlags dwFlags,
|
||||||
|
IntPtr lpBaseAddress,
|
||||||
|
IntPtr dwRegionSize,
|
||||||
|
IntPtr* lpAddresses,
|
||||||
|
ref ulong lpdwCount,
|
||||||
|
out uint lpdwGranularity);
|
||||||
|
|
||||||
|
public static IntPtr Allocate(IntPtr size)
|
||||||
|
{
|
||||||
|
const AllocationType flags =
|
||||||
|
AllocationType.Reserve |
|
||||||
|
AllocationType.Commit |
|
||||||
|
AllocationType.WriteWatch;
|
||||||
|
|
||||||
|
IntPtr ptr = VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite);
|
||||||
|
|
||||||
|
if (ptr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Free(IntPtr address)
|
||||||
|
{
|
||||||
|
return VirtualFree(address, IntPtr.Zero, AllocationType.Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe static bool QueryModifiedPages(IntPtr address, IntPtr size, Span<IntPtr> addresses, out ulong count)
|
||||||
|
{
|
||||||
|
ulong pagesCount = (ulong)addresses.Length;
|
||||||
|
|
||||||
|
int result;
|
||||||
|
|
||||||
|
fixed (IntPtr* addressesPtr = addresses)
|
||||||
|
{
|
||||||
|
result = GetWriteWatch(
|
||||||
|
WriteWatchFlags.Reset,
|
||||||
|
address,
|
||||||
|
size,
|
||||||
|
addressesPtr,
|
||||||
|
ref pagesCount,
|
||||||
|
out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = pagesCount;
|
||||||
|
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
277
Ryujinx.Memory/MemoryRange.cs
Normal file
277
Ryujinx.Memory/MemoryRange.cs
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using static Ryujinx.Memory.MemoryConstants;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a write tracked range of memory.
|
||||||
|
/// </summary>
|
||||||
|
public class MemoryRange
|
||||||
|
{
|
||||||
|
private readonly MemoryBlock _memoryBlock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Address of the memory range.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size in bytes of the memory range.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
private readonly ulong _lastPageAddress;
|
||||||
|
|
||||||
|
private readonly int _firstPage;
|
||||||
|
private readonly int _firstPageOffset;
|
||||||
|
private readonly int _lastPage;
|
||||||
|
private readonly int _lastPageEndOffset;
|
||||||
|
|
||||||
|
private readonly byte[] _firstPageData;
|
||||||
|
private readonly byte[] _lastPageData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the memory range class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memoryBlock">Parent memory block that the range belongs to</param>
|
||||||
|
/// <param name="address">Start address of the range</param>
|
||||||
|
/// <param name="size">Size in bytes of the range</param>
|
||||||
|
internal MemoryRange(MemoryBlock memoryBlock, ulong address, ulong size)
|
||||||
|
{
|
||||||
|
_memoryBlock = memoryBlock;
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
|
||||||
|
int firstPage = (int)(address / PageSize);
|
||||||
|
int firstPageOffset = (int)(address & PageMask);
|
||||||
|
|
||||||
|
if (firstPageOffset != 0 || size < PageSize)
|
||||||
|
{
|
||||||
|
int dataSize = PageSize - firstPageOffset;
|
||||||
|
|
||||||
|
if ((ulong)dataSize > size)
|
||||||
|
{
|
||||||
|
dataSize = (int)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
_firstPageData = new byte[dataSize];
|
||||||
|
|
||||||
|
_memoryBlock.Read(address, _firstPageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endAddress = address + size;
|
||||||
|
|
||||||
|
int lastPage = (int)(BitUtils.DivRoundUp(endAddress, PageSize) - 1);
|
||||||
|
|
||||||
|
int lastPageEndOffset = (int)(endAddress & PageMask);
|
||||||
|
|
||||||
|
ulong lastPageAddress = endAddress - (ulong)lastPageEndOffset;
|
||||||
|
|
||||||
|
if (lastPage > firstPage && lastPageEndOffset != 0)
|
||||||
|
{
|
||||||
|
_lastPageData = new byte[lastPageEndOffset];
|
||||||
|
|
||||||
|
_memoryBlock.Read(lastPageAddress, _lastPageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong addressAligned = BitUtils.AlignUp(address, PageSize);
|
||||||
|
|
||||||
|
int alignedPagesCount = (int)((BitUtils.AlignDown(endAddress, PageSize) - addressAligned) >> PageBits);
|
||||||
|
|
||||||
|
memoryBlock.QueryModified((int)(addressAligned >> PageBits), alignedPagesCount, this);
|
||||||
|
|
||||||
|
_lastPageAddress = lastPageAddress;
|
||||||
|
_firstPage = firstPage;
|
||||||
|
_firstPageOffset = firstPageOffset;
|
||||||
|
_lastPage = lastPage;
|
||||||
|
_lastPageEndOffset = lastPageEndOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the memory range was written since the last call to this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the range data was modified since the last call, false otherwise</returns>
|
||||||
|
public bool QueryModified()
|
||||||
|
{
|
||||||
|
bool modified = false;
|
||||||
|
|
||||||
|
int page = _firstPage;
|
||||||
|
|
||||||
|
if (_firstPageOffset != 0 || Size < PageSize)
|
||||||
|
{
|
||||||
|
int dataSize = _firstPageData.Length;
|
||||||
|
|
||||||
|
if ((ulong)dataSize > Size)
|
||||||
|
{
|
||||||
|
dataSize = (int)Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> oldData = new Span<byte>(_firstPageData);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(Address, dataSize);
|
||||||
|
|
||||||
|
if (!oldData.SequenceEqual(newData))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
|
||||||
|
newData.CopyTo(oldData);
|
||||||
|
}
|
||||||
|
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endPage = _lastPageEndOffset != 0 ? _lastPage : _lastPage + 1;
|
||||||
|
|
||||||
|
int alignedPagesCount = endPage - page;
|
||||||
|
|
||||||
|
if (alignedPagesCount > 0 && _memoryBlock.QueryModified(page, alignedPagesCount, this))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastPage > _firstPage && _lastPageEndOffset != 0)
|
||||||
|
{
|
||||||
|
Span<byte> oldData = new Span<byte>(_lastPageData);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(_lastPageAddress, _lastPageData.Length);
|
||||||
|
|
||||||
|
if (!oldData.SequenceEqual(newData))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
|
||||||
|
newData.CopyTo(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a span of data inside this memory range.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use this method after checking for data modification inside the memory range.
|
||||||
|
/// This ensures that you're working with the same data being used internally
|
||||||
|
/// for modification checks with memory comparison.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The data span</returns>
|
||||||
|
public Span<byte> GetSpan()
|
||||||
|
{
|
||||||
|
if (_firstPageData == null && _lastPageData == null)
|
||||||
|
{
|
||||||
|
return _memoryBlock.GetSpan(Address, (int)Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> data = new byte[Size];
|
||||||
|
|
||||||
|
int middleOffset = 0;
|
||||||
|
int unalignedSize = 0;
|
||||||
|
|
||||||
|
if (_firstPageData != null)
|
||||||
|
{
|
||||||
|
middleOffset = _firstPageData.Length;
|
||||||
|
unalignedSize = _firstPageData.Length;
|
||||||
|
|
||||||
|
_firstPageData.CopyTo(data.Slice(0, _firstPageData.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastPageData != null)
|
||||||
|
{
|
||||||
|
unalignedSize += _lastPageData.Length;
|
||||||
|
|
||||||
|
_lastPageData.CopyTo(data.Slice(data.Length - _lastPageData.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
int middleSize = data.Length - unalignedSize;
|
||||||
|
|
||||||
|
_memoryBlock.Read(Address + (ulong)middleOffset, data.Slice(middleOffset, middleSize));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a given sub-range inside the memory range was written since the last
|
||||||
|
/// call to this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset of the sub-range being checked</param>
|
||||||
|
/// <param name="size">Size in bytes of the sub-range being checked</param>
|
||||||
|
/// <param name="data">Buffer used for memory comparison, and to hold the updated data</param>
|
||||||
|
/// <returns>True if the sub-range was modified, false otherwise</returns>
|
||||||
|
public bool QueryModified(int offset, int size, Span<byte> data)
|
||||||
|
{
|
||||||
|
ulong address = Address + (ulong)offset;
|
||||||
|
|
||||||
|
int firstPage = (int)(address / PageSize);
|
||||||
|
int firstPageOffset = (int)(address & PageMask);
|
||||||
|
|
||||||
|
ulong endAddress = address + (ulong)size;
|
||||||
|
|
||||||
|
int lastPage = (int)(BitUtils.DivRoundUp(endAddress, PageSize) - 1);
|
||||||
|
|
||||||
|
int lastPageEndOffset = (int)(endAddress & PageMask);
|
||||||
|
|
||||||
|
bool modified = false;
|
||||||
|
|
||||||
|
int page = firstPage;
|
||||||
|
|
||||||
|
int alignedOffset = offset;
|
||||||
|
|
||||||
|
if (firstPageOffset != 0 || size < PageSize)
|
||||||
|
{
|
||||||
|
int dataSize = PageSize - firstPageOffset;
|
||||||
|
|
||||||
|
if (dataSize > size)
|
||||||
|
{
|
||||||
|
dataSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
alignedOffset += dataSize;
|
||||||
|
|
||||||
|
Span<byte> oldData = data.Slice(offset, dataSize);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(address, dataSize);
|
||||||
|
|
||||||
|
if (!oldData.SequenceEqual(newData))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
|
||||||
|
newData.CopyTo(oldData);
|
||||||
|
}
|
||||||
|
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endPage = lastPageEndOffset != 0 ? lastPage : lastPage + 1;
|
||||||
|
|
||||||
|
int alignedPagesCount = endPage - page;
|
||||||
|
|
||||||
|
if (alignedPagesCount > 0)
|
||||||
|
{
|
||||||
|
Span<byte> outBuffer = data.Slice(alignedOffset, alignedPagesCount << PageBits);
|
||||||
|
|
||||||
|
if (_memoryBlock.QueryModified(page, alignedPagesCount, this, outBuffer))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPage > firstPage && lastPageEndOffset != 0)
|
||||||
|
{
|
||||||
|
Span<byte> oldData = data.Slice(offset + size - lastPageEndOffset, lastPageEndOffset);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> newData = _memoryBlock.GetSpan(endAddress - (ulong)lastPageEndOffset, lastPageEndOffset);
|
||||||
|
|
||||||
|
if (!oldData.SequenceEqual(newData))
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
|
||||||
|
newData.CopyTo(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
Ryujinx.Memory/Ryujinx.Memory.csproj
Normal file
19
Ryujinx.Memory/Ryujinx.Memory.csproj
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
14
Ryujinx.sln
14
Ryujinx.sln
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 15.0.26730.8
|
VisualStudioVersion = 16.0.29613.14
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -124,6 +126,14 @@ Global
|
||||||
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.Build.0 = Release|Any CPU
|
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Profile Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue