Code:
// Comment this out if you want to use this class out of process.
//#define IN_PROC
// Comment this out if you want boolean values to be read as a byte, instead of a 4 byte integer.
#define BOOL_AS_INT
// Comment this out if you want 'char' to be treated as a double byte character.
#define CHAR_AS_SHORT
// Comment this out if you want to treat strings as ANSI strings. Otherwise, it will use Unicode. (MBCS)
#define UNICODE_STRINGS
using System;
using System.Runtime.InteropServices;
using System.Text;
#if !IN_PROC
using System.Collections.Generic;
#endif
namespace MemoryLib
{
public static class Memory
{
public static T Read<T>(IntPtr address)
{
object ret;
#if IN_PROC
unsafe
{
if (typeof (T) == typeof (IntPtr))
ret = *(IntPtr*) address;
else
switch (Type.GetTypeCode(typeof (T)))
{
case TypeCode.Boolean:
#if BOOL_AS_INT
ret = *(int*) address != 0;
#else
ret = *(byte*) address != 0;
#endif
break;
case TypeCode.Char:
#if CHAR_AS_SHORT
ret = (char) *(short*) address;
#else
ret = (char) *(byte*) address;
#endif
break;
case TypeCode.SByte:
ret = *(sbyte*) address;
break;
case TypeCode.Byte:
ret = *(byte*) address;
break;
case TypeCode.Int16:
ret = *(short*) address;
break;
case TypeCode.UInt16:
ret = *(ushort*) address;
break;
case TypeCode.Int32:
ret = *(int*) address;
break;
case TypeCode.UInt32:
ret = *(uint*) address;
break;
case TypeCode.Int64:
ret = *(long*) address;
break;
case TypeCode.UInt64:
ret = *(ulong*) address;
break;
case TypeCode.Single:
ret = *(float*) address;
break;
case TypeCode.Double:
ret = *(double*) address;
break;
case TypeCode.Decimal:
ret = *(decimal*) address;
break;
case TypeCode.String:
#if UNICODE_STRINGS
ret = Marshal.PtrToStringUni(address);
#else
ret = Marshal.PtrToStringAnsi(address);
#endif
break;
default:
// Probably shouldn't do this, but whatever. :)
// This should let us use classes instead of structs.
// However, structs will be more accurate, and less volitile.
ret = Marshal.PtrToStructure(address, typeof (T));
break;
}
}
#else // OUT OF PROCESS
if (typeof(T) == typeof(string))
{
var bytes = new List<byte>();
int offset = 0;
byte lastByte;
while ((lastByte = Read<byte>((IntPtr)(address.ToInt32() + offset))) != 0)
{
offset++;
bytes.Add(lastByte);
}
#if UNICODE_STRINGS
ret = Encoding.Unicode.GetString(bytes.ToArray());
#else
ret = Encoding.ASCII.GetString(bytes.ToArray());
#endif
return (T) ret;
}
int numBytes = Marshal.SizeOf(typeof (T));
if (typeof(T) == typeof(IntPtr))
{
// Note: This will work on x86/x64, due to how IntPtr changes
// its size depending on platform. (4 bytes for x86, 8 bytes for x64)
// It's valid to set an IntPtr as a long. (It will auto-convert properly)
ret = (IntPtr) BitConverter.ToInt64(Win32.ReadBytes(address, numBytes), 0);
}
else
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Boolean:
#if BOOL_AS_INT
ret = BitConverter.ToInt32(Win32.ReadBytes(address, 4), 0) != 0;
#else
ret = Win32.ReadBytes(address, 1)[0] != 0;
#endif
break;
case TypeCode.Char:
#if CHAR_AS_SHORT
ret = BitConverter.ToChar(Win32.ReadBytes(address, 2), 0);
#else
ret = (char) Win32.ReadBytes(address, 1)[0];
#endif
break;
case TypeCode.SByte:
ret = (sbyte)Win32.ReadBytes(address, numBytes)[0];
break;
case TypeCode.Byte:
ret = Win32.ReadBytes(address, numBytes)[0];
break;
case TypeCode.Int16:
ret = BitConverter.ToInt16(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.UInt16:
ret = BitConverter.ToUInt16(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.Int32:
ret = BitConverter.ToInt32(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.UInt32:
ret = BitConverter.ToUInt32(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.Int64:
ret = BitConverter.ToInt64(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.UInt64:
ret = BitConverter.ToUInt64(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.Single:
ret = BitConverter.ToSingle(Win32.ReadBytes(address, numBytes), 0);
break;
case TypeCode.Double:
ret = BitConverter.ToDouble(Win32.ReadBytes(address, numBytes), 0);
break;
default:
// Yes, sadly, this is the easiest (and somewhat fastest)
// way to do this.
// Allocate the required amount of memory to store the struct.
IntPtr dataStore = Marshal.AllocHGlobal(numBytes);
// Read the data from the process.
byte[] data = Win32.ReadBytes(address, numBytes);
// Copy the data to our own memory, so we can make the Marshaler read
// from there (thus; doing all the byte packing, alignment, etc, for us.)
Marshal.Copy(data, 0, dataStore, numBytes);
ret = Marshal.PtrToStructure(dataStore, typeof(T));
// Make sure we release the memory we allocated, to avoid memory leaks.
Marshal.FreeHGlobal(dataStore);
break;
}
#endif
return (T) ret;
}
public static T ReadStruct<T>(IntPtr address) where T : struct
{
#if IN_PROC
return (T) Marshal.PtrToStructure(address, typeof (T));
#else
unsafe
{
int numBytes = Marshal.SizeOf(typeof (T));
byte* dataStore = stackalloc byte[numBytes];
byte[] data = Win32.ReadBytes(address, numBytes);
Marshal.Copy(data, 0, (IntPtr)dataStore, numBytes);
var ret = (T)Marshal.PtrToStructure((IntPtr)dataStore, typeof(T));
return ret;
}
#endif
}
public static void Write<T>(IntPtr address, T value)
{
if (value is string)
{
// Laziness ftw.
#if !UNICODE_STRINGS
Win32.WriteBytes(address, Encoding.ASCII.GetBytes(value as string));
#else
Win32.WriteBytes(address, Encoding.Unicode.GetBytes(value as string));
#endif
}
else
{
#if IN_PROC
Marshal.StructureToPtr(value, address, true);
#else
int numBytes = Marshal.SizeOf(value);
unsafe
{
byte* bytes = stackalloc byte[numBytes];
Marshal.StructureToPtr(value, (IntPtr) bytes, true);
byte[] writeBytes = new byte[numBytes];
Marshal.Copy((IntPtr) bytes, writeBytes, 0, numBytes);
Win32.WriteBytes(address, writeBytes);
}
#endif
}
}
}
}