2019-10-19 05:20:31 +00:00
using System ;
using System.IO ;
using System.Runtime.InteropServices ;
2020-01-22 22:55:29 +00:00
using BizHawk.Common ;
namespace BizHawk.BizInvoke
2019-10-19 05:20:31 +00:00
{
public abstract class MemoryBlockBase : IDisposable
{
2020-01-03 13:35:07 +00:00
/// <summary>allocate <paramref name="size"/> bytes starting at a particular address <paramref name="start"/></summary>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> is not aligned or <paramref name="size"/> is <c>0</c></exception>
2019-10-19 05:20:31 +00:00
protected MemoryBlockBase ( ulong start , ulong size )
{
2020-01-03 13:35:07 +00:00
if ( ! WaterboxUtils . Aligned ( start ) ) throw new ArgumentOutOfRangeException ( nameof ( start ) , start , "start address must be aligned" ) ;
if ( size = = 0 ) throw new ArgumentOutOfRangeException ( nameof ( size ) , size , "cannot create 0-length block" ) ;
2019-10-19 05:20:31 +00:00
Size = WaterboxUtils . AlignUp ( size ) ;
2020-01-03 13:35:07 +00:00
AddressRange = start . RangeToExclusive ( start + Size ) ;
_pageData = new Protection [ 1 + GetPage ( AddressRange . EndInclusive ) ] ;
2019-10-19 05:20:31 +00:00
}
/// <summary>stores last set memory protection value for each page</summary>
protected readonly Protection [ ] _pageData ;
2020-01-03 13:35:07 +00:00
/// <summary>valid address range of the memory block</summary>
public readonly Range < ulong > AddressRange ;
2019-10-19 05:20:31 +00:00
/// <summary>total size of the memory block</summary>
public readonly ulong Size ;
/// <summary>snapshot for XOR buffer</summary>
protected byte [ ] _snapshot ;
/// <summary>true if this is currently swapped in</summary>
public bool Active { get ; protected set ; }
public byte [ ] XorHash { get ; protected set ; }
/// <summary>get a page index within the block</summary>
2020-01-03 13:35:07 +00:00
protected int GetPage ( ulong addr ) = > AddressRange . Contains ( addr )
? ( int ) ( ( addr - AddressRange . Start ) > > WaterboxUtils . PageShift )
: throw new ArgumentOutOfRangeException ( nameof ( addr ) , addr , "invalid address" ) ;
2019-10-19 05:20:31 +00:00
/// <summary>get a start address for a page index within the block</summary>
2020-01-03 13:35:07 +00:00
protected ulong GetStartAddr ( int page ) = > AddressRange . Start + ( ( ulong ) page < < WaterboxUtils . PageShift ) ;
2019-10-19 05:20:31 +00:00
/// <summary>Get a stream that can be used to read or write from part of the block. Does not check for or change <see cref="Protect"/>!</summary>
2020-01-03 13:35:07 +00:00
/// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> or end (= <paramref name="start"/> + <paramref name="length"/>) are outside <see cref="AddressRange"/></exception>
2019-10-19 05:20:31 +00:00
public Stream GetStream ( ulong start , ulong length , bool writer )
{
2020-01-03 13:35:07 +00:00
if ( start < AddressRange . Start ) throw new ArgumentOutOfRangeException ( nameof ( start ) , start , "invalid address" ) ;
if ( AddressRange . EndInclusive < start + length - 1 ) throw new ArgumentOutOfRangeException ( nameof ( length ) , length , "requested length implies invalid end address" ) ;
2019-10-19 05:20:31 +00:00
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>
2020-01-03 13:35:07 +00:00
/// <exception cref="ArgumentOutOfRangeException"><paramref name="start"/> or end (= <paramref name="start"/> + <paramref name="length"/>) are outside <see cref="AddressRange"/></exception>
2019-12-27 19:41:54 +00:00
/// <exception cref="InvalidOperationException">no snapshot taken (haven't called <see cref="SaveXorSnapshot"/>)</exception>
2019-10-19 05:20:31 +00:00
public Stream GetXorStream ( ulong start , ulong length , bool writer )
{
2020-01-03 13:35:07 +00:00
if ( start < AddressRange . Start ) throw new ArgumentOutOfRangeException ( nameof ( start ) , start , "invalid address" ) ;
if ( AddressRange . EndInclusive < start + length - 1 ) throw new ArgumentOutOfRangeException ( nameof ( length ) , length , "requested length implies invalid end address" ) ;
2019-10-19 05:20:31 +00:00
if ( _snapshot = = null ) throw new InvalidOperationException ( "No snapshot taken!" ) ;
2020-01-03 13:35:07 +00:00
return new MemoryViewXorStream ( ! writer , writer , ( long ) start , ( long ) length , this , _snapshot , ( long ) ( start - AddressRange . Start ) ) ;
2019-10-19 05:20:31 +00:00
}
/// <summary>activate the memory block, swapping it in at the pre-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>take a hash of the current full contents of the block, including unreadable areas</summary>
public abstract byte [ ] FullHash ( ) ;
/// <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 ) ;
/// <summary>restore all recorded protections</summary>
protected abstract void ProtectAll ( ) ;
/// <summary>take a snapshot of the entire memory block's contents, for use in <see cref="GetXorStream"/></summary>
public abstract void SaveXorSnapshot ( ) ;
public abstract void Dispose ( bool disposing ) ;
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
~ MemoryBlockBase ( )
{
Dispose ( false ) ;
}
/// <summary>allocate <paramref name="size"/> bytes starting at a particular address <paramref name="start"/></summary>
2019-11-04 04:30:05 +00:00
public static MemoryBlockBase CallPlatformCtor ( ulong start , ulong size ) = > OSTailoredCode . IsUnixHost
? ( MemoryBlockBase ) new MemoryBlockUnix ( start , size )
: new MemoryBlock ( start , size ) ;
2019-10-19 05:20:31 +00:00
/// <summary>allocate <paramref name="size"/> bytes at any address</summary>
public static MemoryBlockBase CallPlatformCtor ( ulong size ) = > CallPlatformCtor ( 0 , size ) ;
/// <summary>Memory protection constant</summary>
public enum Protection : byte { None , R , RW , RX }
private class MemoryViewStream : Stream
{
public MemoryViewStream ( bool readable , bool writable , long ptr , long length , MemoryBlockBase owner )
{
_readable = readable ;
_writable = writable ;
_ptr = ptr ;
_length = length ;
_owner = owner ;
_pos = 0 ;
}
private readonly long _length ;
private readonly MemoryBlockBase _owner ;
private readonly long _ptr ;
private readonly bool _readable ;
private readonly bool _writable ;
private long _pos ;
public override bool CanRead = > _readable ;
public override bool CanSeek = > true ;
public override bool CanWrite = > _writable ;
public override long Length = > _length ;
public override long Position
{
2020-01-25 04:53:45 +00:00
get = > _pos ;
2019-10-19 05:20:31 +00:00
set
{
if ( value < 0 | | _length < value ) throw new ArgumentOutOfRangeException ( ) ;
_pos = value ;
}
}
private void EnsureNotDisposed ( )
{
2020-01-03 13:35:07 +00:00
if ( _owner . AddressRange . Start = = 0 ) throw new ObjectDisposedException ( nameof ( MemoryBlockBase ) ) ; //TODO bug?
2019-10-19 05:20:31 +00:00
}
public override void Flush ( ) { }
public override int Read ( byte [ ] buffer , int offset , int count )
{
if ( ! _readable ) throw new InvalidOperationException ( ) ;
if ( count < 0 | | buffer . Length < count + offset ) 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 | | _length - _pos < count | | buffer . Length < count + offset ) 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 , MemoryBlockBase 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 )
{
var 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 )
{
var pos = ( int ) Position ;
if ( count < 0 | | Length - pos < count | | buffer . Length < count + offset ) throw new ArgumentOutOfRangeException ( ) ;
// is mutating the buffer passed to Stream.Write kosher?
XorTransform ( _initial , _offset + pos , buffer , offset , count ) ;
base . Write ( buffer , offset , count ) ;
}
/// <remarks>bounds check already done by calling method i.e. in <see cref="MemoryViewStream.Read">base.Read</see> (for <see cref="Read"/>) or in <see cref="Write"/></remarks>
private static unsafe void XorTransform ( byte [ ] source , int sourceOffset , byte [ ] dest , int destOffset , int length )
{
// 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 + + ;
}
}
}
}
}