BizHawk/BizHawk.Common/BizInvoke/MemoryBlock.cs

301 lines
8.7 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.IO;
namespace BizHawk.Common.BizInvoke
{
public abstract class MemoryBlock : IDisposable
{
/// <summary>
/// starting address of the memory block
/// </summary>
public ulong Start { get; protected set; }
/// <summary>
/// total size of the memory block
/// </summary>
public ulong Size { get; protected set; }
/// <summary>
/// ending address of the memory block; equal to start + size
/// </summary>
public ulong End { get; protected set; }
/// <summary>
/// true if this is currently swapped in
/// </summary>
public bool Active { get; protected set; }
/// <summary>
/// stores last set memory protection value for each page
/// </summary>
protected Protection[] _pageData;
/// <summary>
/// snapshot for XOR buffer
/// </summary>
protected byte[] _snapshot;
public byte[] XorHash { get; protected set; }
/// <summary>
/// get a page index within the block
/// </summary>
protected int GetPage(ulong addr)
{
if (addr < Start || addr >= End) throw new ArgumentOutOfRangeException();
return (int) ((addr - Start) >> WaterboxUtils.PageShift);
}
/// <summary>
/// get a start address for a page index within the block
/// </summary>
protected ulong GetStartAddr(int page)
{
return ((ulong) page << WaterboxUtils.PageShift) + Start;
}
/// <summary>
/// activate the memory block, swapping it in at the specified address
/// </summary>
public abstract void Activate();
/// <summary>
/// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in
/// </summary>
public abstract void Deactivate();
/// <summary>
/// Memory protection constant
/// </summary>
public enum Protection : byte
{
None, R, RW, RX
}
/// <summary>
/// Get a stream that can be used to read or write from part of the block. Does not check for or change Protect()!
/// </summary>
public Stream GetStream(ulong start, ulong length, bool writer)
{
if (start < Start) throw new ArgumentOutOfRangeException(nameof(start));
if (start + length > End) throw new ArgumentOutOfRangeException(nameof(length));
return new MemoryViewStream(!writer, writer, (long) start, (long) length, this);
}
/// <summary>
/// get a stream that can be used to read or write from part of the block.
/// both reads and writes will be XORed against an earlier recorded snapshot
/// </summary>
public Stream GetXorStream(ulong start, ulong length, bool writer)
{
if (start < Start) throw new ArgumentOutOfRangeException(nameof(start));
if (start + length > End) throw new ArgumentOutOfRangeException(nameof(length));
if (_snapshot == null) throw new InvalidOperationException("No snapshot taken!");
return new MemoryViewXorStream(!writer, writer, (long) start, (long) length, this, _snapshot, (long) (start - Start));
}
/// <summary>
/// take a snapshot of the entire memory block's contents, for use in GetXorStream
/// </summary>
public abstract void SaveXorSnapshot();
/// <summary>
/// take a hash of the current full contents of the block, including unreadable areas
/// </summary>
public abstract byte[] FullHash();
/// <summary>
/// restore all recorded protections
/// </summary>
protected abstract void ProtectAll();
/// <summary>
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
/// </summary>
public abstract void Protect(ulong start, ulong length, Protection prot);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected abstract void Dispose(bool disposing);
~MemoryBlock()
{
Dispose(false);
}
/// <summary>
/// allocate size bytes at any address
/// </summary>
public static MemoryBlock PlatformConstructor(ulong size)
{
return PlatformConstructor(0, size);
}
/// <summary>
/// allocate size bytes starting at a particular address
/// </summary>
public static MemoryBlock PlatformConstructor(ulong start, ulong size)
{
switch (PlatformLinkedLibSingleton.CurrentOS)
{
case PlatformLinkedLibSingleton.DistinctOS.Linux:
case PlatformLinkedLibSingleton.DistinctOS.macOS:
// return new MemoryBlockUnix(start, size);
throw new InvalidOperationException("ctor of nonfunctional MemoryBlockUnix class");
case PlatformLinkedLibSingleton.DistinctOS.Windows:
return new MemoryBlockWin32(start, size);
default:
throw new ArgumentOutOfRangeException();
}
}
private class MemoryViewStream : Stream
{
public MemoryViewStream(bool readable, bool writable, long ptr, long length, MemoryBlock owner)
{
_readable = readable;
_writable = writable;
_ptr = ptr;
_length = length;
_owner = owner;
_pos = 0;
}
private void EnsureNotDisposed()
{
if (_owner.Start == 0) throw new ObjectDisposedException("MemoryBlock");
}
private MemoryBlock _owner;
private readonly bool _readable;
private readonly bool _writable;
private long _length;
private long _pos;
private readonly long _ptr;
public override bool CanRead => _readable;
public override bool CanSeek => true;
public override bool CanWrite => _writable;
public override void Flush() { }
public override long Length => _length;
public override long Position
{
get { return _pos; }
set
{
if (value < 0 || value > _length) throw new ArgumentOutOfRangeException();
_pos = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
if (!_readable) throw new InvalidOperationException();
if (count < 0 || count + offset > buffer.Length) throw new ArgumentOutOfRangeException();
EnsureNotDisposed();
count = (int) Math.Min(count, _length - _pos);
Marshal.Copy(Z.SS(_ptr + _pos), buffer, offset, count);
_pos += count;
return count;
}
public override long Seek(long offset, SeekOrigin origin)
{
long newpos;
switch (origin)
{
default:
case SeekOrigin.Begin:
newpos = offset;
break;
case SeekOrigin.Current:
newpos = _pos + offset;
break;
case SeekOrigin.End:
newpos = _length + offset;
break;
}
Position = newpos;
return newpos;
}
public override void SetLength(long value)
{
throw new InvalidOperationException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_writable) throw new InvalidOperationException();
if (count < 0 || count + offset > buffer.Length || count > _length - _pos)
throw new ArgumentOutOfRangeException();
EnsureNotDisposed();
Marshal.Copy(buffer, offset, Z.SS(_ptr + _pos), count);
_pos += count;
}
}
private class MemoryViewXorStream : MemoryViewStream
{
public MemoryViewXorStream(bool readable, bool writable, long ptr, long length, MemoryBlock owner,
byte[] initial, long offset)
: base(readable, writable, ptr, length, owner)
{
_initial = initial;
_offset = (int) offset;
}
/// <summary>
/// the initial data to XOR against for both reading and writing
/// </summary>
private readonly byte[] _initial;
/// <summary>
/// offset into the XOR data that this stream is representing
/// </summary>
private readonly int _offset;
public override int Read(byte[] buffer, int offset, int count)
{
int pos = (int) Position;
count = base.Read(buffer, offset, count);
XorTransform(_initial, _offset + pos, buffer, offset, count);
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
int pos = (int) Position;
if (count < 0 || count + offset > buffer.Length || count > Length - pos)
throw new ArgumentOutOfRangeException();
// is mutating the buffer passed to Stream.Write kosher?
XorTransform(_initial, _offset + pos, buffer, offset, count);
base.Write(buffer, offset, count);
}
private static unsafe void XorTransform(byte[] source, int sourceOffset, byte[] dest, int destOffset, int length)
{
// we don't do any bounds check because MemoryViewStream.Read and MemoryViewXorStream.Write already did it
// TODO: C compilers can make this pretty snappy, but can the C# jitter? Or do we need intrinsics
fixed (byte* _s = source, _d = dest)
{
byte* s = _s + sourceOffset;
byte* d = _d + destOffset;
byte* sEnd = s + length;
while (s < sEnd)
{
*d++ ^= *s++;
}
}
}
}
}
}