239 lines
7.9 KiB
C#
239 lines
7.9 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.IO;
|
|
|
|
namespace BizHawk.BizInvoke
|
|
{
|
|
public sealed class MemoryBlock : MemoryBlockBase
|
|
{
|
|
/// <summary>
|
|
/// handle returned by CreateFileMapping
|
|
/// </summary>
|
|
private IntPtr _handle;
|
|
|
|
/// <inheritdoc cref="MemoryBlockBase(ulong,ulong)"/>
|
|
/// <exception cref="InvalidOperationException">failed to create file mapping</exception>
|
|
public MemoryBlock(ulong start, ulong size) : base(start, size)
|
|
{
|
|
_handle = Kernel32.CreateFileMapping(
|
|
Kernel32.INVALID_HANDLE_VALUE,
|
|
IntPtr.Zero,
|
|
Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit,
|
|
(uint)(Size >> 32),
|
|
(uint)Size,
|
|
null
|
|
);
|
|
if (_handle == IntPtr.Zero) throw new InvalidOperationException($"{nameof(Kernel32.CreateFileMapping)}() returned NULL");
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="true"/> or failed to map file view</exception>
|
|
public override void Activate()
|
|
{
|
|
if (Active)
|
|
throw new InvalidOperationException("Already active");
|
|
if (Kernel32.MapViewOfFileEx(
|
|
_handle,
|
|
Kernel32.FileMapAccessType.Read | Kernel32.FileMapAccessType.Write | Kernel32.FileMapAccessType.Execute,
|
|
0,
|
|
0,
|
|
Z.UU(Size),
|
|
Z.US(AddressRange.Start)
|
|
) != Z.US(AddressRange.Start))
|
|
{
|
|
throw new InvalidOperationException($"{nameof(Kernel32.MapViewOfFileEx)}() returned NULL");
|
|
}
|
|
ProtectAll();
|
|
Active = true;
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="false"/> or failed to unmap file view</exception>
|
|
public override void Deactivate()
|
|
{
|
|
if (!Active)
|
|
throw new InvalidOperationException("Not active");
|
|
if (!Kernel32.UnmapViewOfFile(Z.US(AddressRange.Start)))
|
|
throw new InvalidOperationException($"{nameof(Kernel32.UnmapViewOfFile)}() returned NULL");
|
|
Active = false;
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException">snapshot already taken, <see cref="MemoryBlockBase.Active"/> is <see langword="false"/>, or failed to make memory read-only</exception>
|
|
public override void SaveXorSnapshot()
|
|
{
|
|
if (_snapshot != null)
|
|
throw new InvalidOperationException("Snapshot already taken");
|
|
if (!Active)
|
|
throw new InvalidOperationException("Not active");
|
|
|
|
// temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want
|
|
// that to complicate things
|
|
Kernel32.MemoryProtection old;
|
|
if (!Kernel32.VirtualProtect(Z.UU(AddressRange.Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
|
|
throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!");
|
|
|
|
_snapshot = new byte[Size];
|
|
var ds = new MemoryStream(_snapshot, true);
|
|
var ss = GetStream(AddressRange.Start, Size, false);
|
|
ss.CopyTo(ds);
|
|
XorHash = WaterboxUtils.Hash(_snapshot);
|
|
|
|
ProtectAll();
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="false"/> or failed to make memory read-only</exception>
|
|
public override byte[] FullHash()
|
|
{
|
|
if (!Active)
|
|
throw new InvalidOperationException("Not active");
|
|
// temporarily switch the entire block to `R`
|
|
Kernel32.MemoryProtection old;
|
|
if (!Kernel32.VirtualProtect(Z.UU(AddressRange.Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
|
|
throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!");
|
|
var ret = WaterboxUtils.Hash(GetStream(AddressRange.Start, Size, false));
|
|
ProtectAll();
|
|
return ret;
|
|
}
|
|
|
|
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
|
|
{
|
|
Kernel32.MemoryProtection p;
|
|
switch (prot)
|
|
{
|
|
case Protection.None: p = Kernel32.MemoryProtection.NOACCESS; break;
|
|
case Protection.R: p = Kernel32.MemoryProtection.READONLY; break;
|
|
case Protection.RW: p = Kernel32.MemoryProtection.READWRITE; break;
|
|
case Protection.RX: p = Kernel32.MemoryProtection.EXECUTE_READ; break;
|
|
default: throw new ArgumentOutOfRangeException(nameof(prot));
|
|
}
|
|
return p;
|
|
}
|
|
|
|
protected override void ProtectAll()
|
|
{
|
|
int ps = 0;
|
|
for (int i = 0; i < _pageData.Length; i++)
|
|
{
|
|
if (i == _pageData.Length - 1 || _pageData[i] != _pageData[i + 1])
|
|
{
|
|
var p = GetKernelMemoryProtectionValue(_pageData[i]);
|
|
ulong zstart = GetStartAddr(ps);
|
|
ulong zend = GetStartAddr(i + 1);
|
|
Kernel32.MemoryProtection old;
|
|
if (!Kernel32.VirtualProtect(Z.UU(zstart), Z.UU(zend - zstart), p, out old))
|
|
throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!");
|
|
ps = i + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <exception cref="InvalidOperationException">failed to protect memory</exception>
|
|
public override void Protect(ulong start, ulong length, Protection prot)
|
|
{
|
|
if (length == 0)
|
|
return;
|
|
int pstart = GetPage(start);
|
|
int pend = GetPage(start + length - 1);
|
|
|
|
var p = GetKernelMemoryProtectionValue(prot);
|
|
for (int i = pstart; i <= pend; i++)
|
|
_pageData[i] = prot; // also store the value for later use
|
|
|
|
if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation
|
|
{
|
|
var computedStart = WaterboxUtils.AlignDown(start);
|
|
var computedEnd = WaterboxUtils.AlignUp(start + length);
|
|
var computedLength = computedEnd - computedStart;
|
|
|
|
Kernel32.MemoryProtection old;
|
|
if (!Kernel32.VirtualProtect(Z.UU(computedStart),
|
|
Z.UU(computedLength), p, out old))
|
|
throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!");
|
|
}
|
|
}
|
|
|
|
public override void Dispose(bool disposing)
|
|
{
|
|
if (_handle != IntPtr.Zero)
|
|
{
|
|
if (Active)
|
|
Deactivate();
|
|
Kernel32.CloseHandle(_handle);
|
|
_handle = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
~MemoryBlock()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
private static class Kernel32
|
|
{
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool VirtualProtect(UIntPtr lpAddress, UIntPtr dwSize,
|
|
MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
|
|
|
|
[Flags]
|
|
public enum MemoryProtection : uint
|
|
{
|
|
EXECUTE = 0x10,
|
|
EXECUTE_READ = 0x20,
|
|
EXECUTE_READWRITE = 0x40,
|
|
EXECUTE_WRITECOPY = 0x80,
|
|
NOACCESS = 0x01,
|
|
READONLY = 0x02,
|
|
READWRITE = 0x04,
|
|
WRITECOPY = 0x08,
|
|
GUARD_Modifierflag = 0x100,
|
|
NOCACHE_Modifierflag = 0x200,
|
|
WRITECOMBINE_Modifierflag = 0x400
|
|
}
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern IntPtr CreateFileMapping(
|
|
IntPtr hFile,
|
|
IntPtr lpFileMappingAttributes,
|
|
FileMapProtection flProtect,
|
|
uint dwMaximumSizeHigh,
|
|
uint dwMaximumSizeLow,
|
|
string lpName);
|
|
|
|
[Flags]
|
|
public enum FileMapProtection : uint
|
|
{
|
|
PageReadonly = 0x02,
|
|
PageReadWrite = 0x04,
|
|
PageWriteCopy = 0x08,
|
|
PageExecuteRead = 0x20,
|
|
PageExecuteReadWrite = 0x40,
|
|
SectionCommit = 0x8000000,
|
|
SectionImage = 0x1000000,
|
|
SectionNoCache = 0x10000000,
|
|
SectionReserve = 0x4000000,
|
|
}
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
public static extern IntPtr MapViewOfFileEx(IntPtr hFileMappingObject,
|
|
FileMapAccessType dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
|
|
UIntPtr dwNumberOfBytesToMap, IntPtr lpBaseAddress);
|
|
|
|
[Flags]
|
|
public enum FileMapAccessType : uint
|
|
{
|
|
Copy = 0x01,
|
|
Write = 0x02,
|
|
Read = 0x04,
|
|
AllAccess = 0x08,
|
|
Execute = 0x20,
|
|
}
|
|
|
|
public static readonly IntPtr INVALID_HANDLE_VALUE = Z.US(0xffffffffffffffff);
|
|
}
|
|
}
|
|
}
|