2015-06-24 01:35:34 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Runtime.InteropServices ;
2016-03-22 01:43:56 +00:00
using System.IO ;
2015-06-24 01:35:34 +00:00
namespace BizHawk.Emulation.Cores
{
2016-03-11 03:00:52 +00:00
public static class Z
{
public static IntPtr US ( ulong l )
{
if ( IntPtr . Size = = 8 )
return ( IntPtr ) ( long ) l ;
else
return ( IntPtr ) ( int ) l ;
}
public static UIntPtr UU ( ulong l )
{
if ( UIntPtr . Size = = 8 )
return ( UIntPtr ) l ;
else
return ( UIntPtr ) ( uint ) l ;
}
public static IntPtr SS ( long l )
{
if ( IntPtr . Size = = 8 )
return ( IntPtr ) l ;
else
return ( IntPtr ) ( int ) l ;
}
public static UIntPtr SU ( long l )
{
if ( UIntPtr . Size = = 8 )
return ( UIntPtr ) ( ulong ) l ;
else
return ( UIntPtr ) ( uint ) l ;
}
}
2015-06-24 01:35:34 +00:00
public sealed class MemoryBlock : IDisposable
{
2016-03-25 23:06:35 +00:00
/// <summary>
/// system page size
/// </summary>
public static int PageSize { get ; private set ; }
/// <summary>
/// bitshift corresponding to PageSize
/// </summary>
private static readonly int PageShift ;
/// <summary>
/// bitmask corresponding to PageSize
/// </summary>
private static readonly ulong PageMask ;
static MemoryBlock ( )
{
int p = PageSize = Environment . SystemPageSize ;
while ( p ! = 0 )
{
p > > = 1 ;
PageShift + + ;
}
PageMask = ( ulong ) ( PageSize - 1 ) ;
}
/// <summary>
/// true if addr is aligned
/// </summary>
private static bool Aligned ( ulong addr )
{
return ( addr & PageMask ) = = 0 ;
}
/// <summary>
/// align address down to previous page boundary
/// </summary>
private static ulong AlignDown ( ulong addr )
{
return addr & ~ PageMask ;
}
/// <summary>
/// align address up to next page boundary
/// </summary>
private static ulong AlignUp ( ulong addr )
{
return ( ( addr - 1 ) | PageMask ) + 1 ;
}
2016-03-11 03:00:52 +00:00
/// <summary>
/// starting address of the memory block
/// </summary>
public ulong Start { get ; private set ; }
/// <summary>
/// total size of the memory block
/// </summary>
public ulong Size { get ; private set ; }
/// <summary>
/// ending address of the memory block; equal to start + size
/// </summary>
public ulong End { get ; private set ; }
2016-03-25 23:06:35 +00:00
/// <summary>
/// handle returned by CreateFileMapping
/// </summary>
private IntPtr _handle ;
/// <summary>
/// true if this is currently swapped in
/// </summary>
public bool Active { get ; private set ; }
/// <summary>
/// stores last set memory protection value for each page
/// </summary>
private readonly Protection [ ] _pageData ;
/// <summary>
/// get a page index within the block
/// </summary>
private int GetPage ( ulong addr )
{
if ( addr < Start | | addr > = End )
throw new ArgumentOutOfRangeException ( ) ;
return ( int ) ( ( addr - Start ) > > PageShift ) ;
}
/// <summary>
/// get a start address for a page index within the block
/// </summary>
private ulong GetStartAddr ( int page )
{
return ( ( ulong ) page < < PageShift ) + Start ;
}
2015-06-24 01:35:34 +00:00
2016-03-11 03:00:52 +00:00
/// <summary>
/// allocate size bytes at any address
/// </summary>
/// <param name="size"></param>
public MemoryBlock ( ulong size )
: this ( 0 , size )
2015-06-24 01:35:34 +00:00
{
}
2016-03-11 03:00:52 +00:00
/// <summary>
2016-03-26 06:50:19 +00:00
/// Allocates a memory block as a memory-mapped file with the given size (created immediately) which can be Activated and Deactivated only at the given address.
2016-03-11 03:00:52 +00:00
/// </summary>
2016-03-26 06:50:19 +00:00
/// <param name="start">Location where this block can be Activated</param>
/// <param name="size">Size of the block</param>
public MemoryBlock ( ulong start , ulong size )
{
size = 8 * 1024 * 1024 * 1024L ;
if ( ! Aligned ( start ) )
throw new ArgumentOutOfRangeException ( ) ;
if ( size = = 0 )
throw new ArgumentOutOfRangeException ( ) ;
size = AlignUp ( size ) ;
//create a temporary file--the alternative is using the system pagefile(s)
//some systems have disabled or too-small files and many systems have a few GBs pagefile and difficulty growing it.
//creating a tempfile subverts the user's choice of pagefile locations, but %temp%'s location should be similarly chosen
//Could use this as a fallback in case creation on the pagefile fails?
var fname = BizHawk . Common . TempFileCleaner . GetTempFilename ( "MemoryBlock" , null , false ) ;
IntPtr fhTemp = Kernel32 . CreateFile (
fname ,
unchecked ( ( int ) ( 0x80000000 | 0x40000000 | 0x20000000 ) ) , //+RWX (protections can later be added with finer granularity)
3 , //share RW
IntPtr . Zero , //security stuff
2 , //create always
0x04000000 , //delete on close!
IntPtr . Zero //template
) ;
//create FileMapping around the tempfile
//(again, protections can later be added with finer granularity)
_handle = Kernel32 . CreateFileMapping ( fhTemp , IntPtr . Zero ,
Kernel32 . FileMapProtection . PageExecuteReadWrite | Kernel32 . FileMapProtection . SectionCommit , ( uint ) ( size > > 32 ) , ( uint ) size , null ) ;
//releasing our tempfile handle, essentially yielding the handle to the FileMapping as sole owner
new Microsoft . Win32 . SafeHandles . SafeFileHandle ( fhTemp , true ) . Dispose ( ) ;
if ( _handle = = IntPtr . Zero )
throw new InvalidOperationException ( "CreateFileMapping() returned NULL" ) ;
Start = start ;
End = start + size ;
Size = size ;
_pageData = new Protection [ GetPage ( End - 1 ) + 1 ] ;
2016-03-25 23:06:35 +00:00
}
2016-03-26 06:50:19 +00:00
2016-03-25 23:06:35 +00:00
/// <summary>
2016-03-26 06:50:19 +00:00
/// activate the memory block, swapping it in at the predefined address
2016-03-25 23:06:35 +00:00
/// </summary>
public void Activate ( )
{
if ( Active )
2016-03-26 06:50:19 +00:00
throw new InvalidOperationException ( "Already active" ) ;
//(again, protections can later be added with finer granularity)
IntPtr ptr = Kernel32 . MapViewOfFileEx ( _handle , Kernel32 . FileMapAccessType . Read | Kernel32 . FileMapAccessType . Write | Kernel32 . FileMapAccessType . Execute ,
0 , 0 , Z . UU ( Size ) , Z . US ( Start ) ) ;
if ( ptr ! = Z . US ( Start ) )
2015-06-24 23:34:38 +00:00
{
2016-03-26 06:50:19 +00:00
throw new InvalidOperationException ( "MapViewOfFileEx() returned incorrect starting address" ) ;
2015-06-24 23:34:38 +00:00
}
2016-03-25 23:06:35 +00:00
ProtectAll ( ) ;
Active = true ;
}
/// <summary>
/// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in
/// </summary>
public void Deactivate ( )
{
if ( ! Active )
throw new InvalidOperationException ( "Not active" ) ;
if ( ! Kernel32 . UnmapViewOfFile ( Z . US ( Start ) ) )
2016-03-26 06:50:19 +00:00
throw new InvalidOperationException ( "UnmapViewOfFile() returned false" ) ;
2015-06-24 01:35:34 +00:00
}
2016-03-25 23:06:35 +00:00
/// <summary>
/// Memory protection constant
/// </summary>
public enum Protection : byte
2015-06-24 01:35:34 +00:00
{
2016-03-25 23:06:35 +00:00
None , R , RW , RX
2015-06-24 01:35:34 +00:00
}
2016-03-25 23:06:35 +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 Protect()!
/// </summary>
2016-03-22 01:43:56 +00:00
public Stream GetStream ( ulong start , ulong length , bool writer )
{
if ( start < Start )
throw new ArgumentOutOfRangeException ( "start" ) ;
if ( start + length > End )
throw new ArgumentOutOfRangeException ( "length" ) ;
return new MemoryViewStream ( ! writer , writer , ( long ) start , ( long ) length , this ) ;
}
2016-03-25 23:06:35 +00:00
private static Kernel32 . MemoryProtection GetKernelMemoryProtectionValue ( Protection prot )
2015-06-24 01:35:34 +00:00
{
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 ( "prot" ) ;
}
2016-03-25 23:06:35 +00:00
return p ;
}
/// <summary>
/// restore all recorded protections
/// </summary>
private 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 ( "VirtualProtect() returned FALSE!" ) ;
}
}
}
/// <summary>
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
/// </summary>
public 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
2015-06-24 01:35:34 +00:00
Kernel32 . MemoryProtection old ;
2016-03-11 03:00:52 +00:00
if ( ! Kernel32 . VirtualProtect ( Z . UU ( start ) , Z . UU ( length ) , p , out old ) )
2015-06-24 01:35:34 +00:00
throw new InvalidOperationException ( "VirtualProtect() returned FALSE!" ) ;
}
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
private void Dispose ( bool disposing )
{
2016-03-25 23:06:35 +00:00
if ( _handle ! = IntPtr . Zero )
2015-06-24 01:35:34 +00:00
{
2016-03-25 23:06:35 +00:00
if ( Active )
Deactivate ( ) ;
Kernel32 . CloseHandle ( _handle ) ;
_handle = IntPtr . Zero ;
2015-06-24 01:35:34 +00:00
}
}
~ MemoryBlock ( )
{
Dispose ( false ) ;
}
2016-03-22 01:43:56 +00:00
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 bool _readable ;
private bool _writable ;
private long _length ;
private long _pos ;
private long _ptr ;
public override bool CanRead { get { return _readable ; } }
public override bool CanSeek { get { return true ; } }
public override bool CanWrite { get { return _writable ; } }
public override void Flush ( ) { }
public override long Length { get { return _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 > buffer . Length )
throw new ArgumentOutOfRangeException ( ) ;
EnsureNotDisposed ( ) ;
count = ( int ) Math . Min ( count , _length - _pos ) ;
Marshal . Copy ( Z . SS ( _ptr + _pos ) , buffer , 0 , count ) ;
_pos + = count ;
return count ;
}
public override long Seek ( long offset , SeekOrigin origin )
{
long newpos ;
switch ( origin )
{
default :
case SeekOrigin . Begin :
newpos = 0 ;
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 > buffer . Length )
throw new ArgumentOutOfRangeException ( ) ;
EnsureNotDisposed ( ) ;
count = ( int ) Math . Min ( count , _length - _pos ) ;
Marshal . Copy ( buffer , 0 , Z . SS ( _ptr + _pos ) , count ) ;
_pos + = count ;
}
}
2015-06-24 01:35:34 +00:00
private static class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UIntPtr VirtualAlloc ( UIntPtr lpAddress , UIntPtr dwSize ,
AllocationType flAllocationType , MemoryProtection flProtect ) ;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualFree ( UIntPtr lpAddress , UIntPtr dwSize ,
FreeType dwFreeType ) ;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualProtect ( UIntPtr lpAddress , UIntPtr dwSize ,
MemoryProtection flNewProtect , out MemoryProtection lpflOldProtect ) ;
public enum FreeType : uint
{
DECOMMIT = 0x4000 ,
RELEASE = 0x8000
}
[Flags]
public enum AllocationType : uint
{
COMMIT = 0x1000 ,
RESERVE = 0x2000 ,
RESET = 0x80000 ,
RESET_UNDO = 0x1000000 ,
LARGE_PAGES = 0x20000000 ,
PHYSICAL = 0x400000 ,
TOP_DOWN = 0x100000 ,
WRITE_WATCH = 0x200000
}
[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
2016-03-26 06:50:19 +00:00
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile (
string fileName ,
int desiredAccess ,
int shareMode ,
IntPtr securityAttributes ,
int creationDisposition ,
int flagsAndAttributes ,
IntPtr templateFile ) ;
2016-03-25 23:06:35 +00:00
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping (
IntPtr hFile ,
IntPtr lpFileMappingAttributes ,
FileMapProtection flProtect ,
uint dwMaximumSizeHigh ,
uint dwMaximumSizeLow ,
string lpName ) ;
2015-06-24 23:34:38 +00:00
2015-06-24 01:35:34 +00:00
[Flags]
2016-03-25 23:06:35 +00:00
public enum FileMapProtection : uint
2015-06-24 01:35:34 +00:00
{
2016-03-25 23:06:35 +00:00
PageReadonly = 0x02 ,
PageReadWrite = 0x04 ,
PageWriteCopy = 0x08 ,
PageExecuteRead = 0x20 ,
PageExecuteReadWrite = 0x40 ,
SectionCommit = 0x8000000 ,
SectionImage = 0x1000000 ,
SectionNoCache = 0x10000000 ,
SectionReserve = 0x4000000 ,
2015-06-24 23:34:38 +00:00
}
2015-06-24 01:35:34 +00:00
2016-03-25 23:06:35 +00:00
[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 ) ;
2015-06-24 23:34:38 +00:00
[Flags]
2016-03-25 23:06:35 +00:00
public enum FileMapAccessType : uint
2015-06-24 23:34:38 +00:00
{
2016-03-25 23:06:35 +00:00
Copy = 0x01 ,
Write = 0x02 ,
Read = 0x04 ,
AllAccess = 0x08 ,
Execute = 0x20 ,
2015-06-24 23:34:38 +00:00
}
2016-03-25 23:06:35 +00:00
public static readonly IntPtr INVALID_HANDLE_VALUE = Z . US ( 0xffffffffffffffff ) ;
2015-06-24 01:35:34 +00:00
}
}
}