Update: Now with floating point images support and endian swaps...
// See http://blackandodd.blogspot.ca/2012/12/c-read-and-write-process-memory-in.html
// and http://www.mpgh.net/forum/250-c-programming/298510-c-writeprocessmemory-readprocessmemory.html
using System;
namespace Peek
{
class Program
{
#region Kernel Imports
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
const uint ACL_DELETE = 0x00010000;
const uint ACL_READ_CONTROL = 0x00020000;
const uint ACL_WRITE_DAC = 0x00040000;
const uint ACL_WRITE_OWNER = 0x00080000;
const uint ACL_SYNCHRONIZE = 0x00100000;
const uint ACL_END = 0xFFF; //if you have Windows XP or Windows Server 2003 you must change this to 0xFFFF
const uint PROCESS_VM_READ = 0x0010;
const uint PROCESS_VM_WRITE = 0x0020;
const uint PROCESS_VM_OPERATION = 0x0008;
const uint PROCESS_ALL_ACCESS = (ACL_DELETE | ACL_READ_CONTROL | ACL_WRITE_DAC | ACL_WRITE_OWNER | ACL_SYNCHRONIZE | ACL_END);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern uint OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern bool ReadProcessMemory(uint hProcess, UIntPtr lpBaseAddress, IntPtr buffer, uint size, uint lpNumberOfBytesRead);
/*[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(uint hProcess, UIntPtr lpBaseAddress, byte[] buffer, uint size, uint lpNumberOfBytesWritten);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(uint hProcess, UIntPtr lpBaseAddress, IntPtr buffer, uint size, uint lpNumberOfBytesWritten);*/
class UnmanagedMemWrapper // should we GC.AddMemoryPressure?
{
public UnmanagedMemWrapper(uint size)
{
this.ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal((int)size);
}
~UnmanagedMemWrapper()
{
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
}
public IntPtr ptr;
}
#endregion // Kernel Imports
// Utility, half2float, could use DirectXMath DirectX::PackedVector functions instead...
[System.Runtime.InteropServices.DllImport("d3dx9_35.dll")]
public static extern void D3DXFloat16To32Array(float[] output, IntPtr input, uint nfloats);
static void PrintUsageAndErrors(string error)
{
System.Console.WriteLine("Peek");
System.Console.WriteLine("----");
System.Console.WriteLine();
System.Console.WriteLine("Arguments: process name, instance number, pointer address, [peek mode]");
System.Console.WriteLine("Note that multiple processes can have the same name...");
System.Console.WriteLine();
System.Console.WriteLine("Peek mode:");
System.Console.WriteLine(" img [format] xsize ysize -- draws a 2d image");
System.Console.WriteLine(" Supported formats: argb8 argb16 rgb8 rgb16 r8 r16 argb32f argb16f rgb32f rgb16f r32f r16f");
System.Console.WriteLine();
if(error.Length!=0)
{
System.Console.WriteLine("Error!");
System.Console.WriteLine(error);
}
}
[STAThread] static void Main(string[] args)
{
if (args.Length < 5)
{
PrintUsageAndErrors("Not enough arguments"); return;
}
var procs = System.Diagnostics.Process.GetProcessesByName(args[0]);
UInt32 procNumber = 0;
if (!UInt32.TryParse(args[1], out procNumber))
{
PrintUsageAndErrors("Can't parse process number"); return;
}
if (procs.Length <= procNumber)
{
PrintUsageAndErrors("Process instance not found"); return;
}
var proc = procs[procNumber];
uint procHandle = OpenProcess(PROCESS_VM_READ, false, proc.Id);
if (procHandle == 0)
{
PrintUsageAndErrors("Failed to open process"); return;
}
switch (args[3])
{
case "img":
{
UInt32 xsize, ysize;
if ((!UInt32.TryParse(args[5], out xsize)) || (!UInt32.TryParse(args[6], out ysize)))
{
PrintUsageAndErrors("Can't parse img size"); return;
}
switch (args[4])
{
case "argb8":
PeekImg(procHandle, args[2], xsize, ysize, 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ImgOP.NONE);
break;
case "rgb8":
PeekImg(procHandle, args[2], xsize, ysize, 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ImgOP.NONE);
break;
case "argb16":
PeekImg(procHandle, args[2], xsize, ysize, 8, System.Drawing.Imaging.PixelFormat.Format64bppArgb, ImgOP.NONE);
break;
case "rgb16":
PeekImg(procHandle, args[2], xsize, ysize, 6, System.Drawing.Imaging.PixelFormat.Format48bppRgb, ImgOP.NONE);
break;
case "r8":
PeekImg(procHandle, args[2], xsize, ysize, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, ImgOP.NONE);
break;
case "r16":
PeekImg(procHandle, args[2], xsize, ysize, 2, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale, ImgOP.NONE);
break;
case "argb32f":
PeekImg(procHandle, args[2], xsize, ysize, 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ImgOP.F32_TO_I8);
break;
case "rgb32f":
PeekImg(procHandle, args[2], xsize, ysize, 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ImgOP.F32_TO_I8);
break;
case "argb16f":
PeekImg(procHandle, args[2], xsize, ysize, 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ImgOP.F16_TO_I8);
break;
case "rgb16f":
PeekImg(procHandle, args[2], xsize, ysize, 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ImgOP.F16_TO_I8);
break;
case "r32f":
PeekImg(procHandle, args[2], xsize, ysize, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, ImgOP.F32_TO_I8);
break;
case "r16f":
PeekImg(procHandle, args[2], xsize, ysize, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, ImgOP.F16_TO_I8);
break;
default:
PrintUsageAndErrors("Unknown image format");
return;
}
break;
}
default:
PrintUsageAndErrors("Unknown peek options");
return;
}
}
enum ImgOP { NONE, F16_TO_I8, F32_TO_I8 }
class PeekImgForm : System.Windows.Forms.Form
{
public PeekImgForm()
{
DoubleBuffered = true;
Text = "Peeker";
Controls.Add(memControl);
Controls.Add(hdrScale);
Controls.Add(noAlphaButton);
Controls.Add(endianSwapButton);
Controls.Add(fillBlackButton);
Controls.Add(RBSwapButton);
Controls.Add(xresControl);
Controls.Add(yresControl);
Controls.Add(resetButton);
resetButton.Click += delegate(object sender, System.EventArgs e)
{
CreateBuffers();
};
var background = new System.Drawing.Drawing2D.HatchBrush(
System.Drawing.Drawing2D.HatchStyle.LargeCheckerBoard, System.Drawing.Color.Black, System.Drawing.Color.White);
Paint += delegate(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (!ReadProcessMemory(procHandle, pointer, unmanagedMemory.ptr, readSize, 0))
{
e.Graphics.FillRectangle(System.Drawing.Brushes.Red, 0, 0, Bounds.Width, Bounds.Height);
return;
}
float scale = (float)hdrScale.Value * 255.0f;
if ((format == System.Drawing.Imaging.PixelFormat.Format64bppArgb) ||
(format == System.Drawing.Imaging.PixelFormat.Format48bppRgb)) // these are not 16bpp, but 13, really
{
unsafe
{
ushort* ushortPtr = (ushort*)unmanagedMemory.ptr;
for (int i = 0; i < imageSize / 2; i++)
ushortPtr[i] >>= 3;
}
}
if (imgOp == ImgOP.F16_TO_I8)
{
D3DXFloat16To32Array(tempHalfToFloatMemory, unmanagedMemory.ptr, (uint)tempHalfToFloatMemory.Length);
unsafe
{
fixed (float* floatPtr = tempHalfToFloatMemory)
{
byte* bytePtr = (byte*)unmanagedMemory.ptr;
for (int i = 0; i < imageSize; i++)
{
float scaledVal = floatPtr[i] * scale;
bytePtr[i] = (byte)(scaledVal > 255.0f ? 255.0f : scaledVal);
}
}
}
}
else if (imgOp == ImgOP.F32_TO_I8)
{
unsafe
{
byte* bytePtr = (byte*)unmanagedMemory.ptr;
float* floatPtr = (float*)unmanagedMemory.ptr;
/*for (int i = 0; i < imageSize; i += 4)
{
floatPtr[i] /= floatPtr[i + 3];
floatPtr[i+1] /= floatPtr[i + 3];
floatPtr[i+2] /= floatPtr[i + 3];
}*/
for (int i = 0; i < imageSize; i++)
{
float scaledVal = floatPtr[i] * scale;
bytePtr[i] = (byte)(scaledVal > 255.0f ? 255.0f : scaledVal);
}
}
}
if (endianSwapButton.Checked)
{
unsafe
{
byte* bytePtr = (byte*)unmanagedMemory.ptr;
for (int i = 0; i < imageSize; i += 4)
{
byte temp = bytePtr[i + 3];
bytePtr[i + 3] = bytePtr[i];
bytePtr[i] = temp;
temp = bytePtr[i + 2];
bytePtr[i + 2] = bytePtr[i + 1];
bytePtr[i + 1] = temp;
}
}
}
if (RBSwapButton.Checked) // Loop again, I don't want to code the variants...
{
unsafe
{
byte* bytePtr = (byte*)unmanagedMemory.ptr;
for (int i = 0; i < imageSize; i += 4)
{
byte temp = bytePtr[i + 2];
bytePtr[i + 2] = bytePtr[i];
bytePtr[i] = temp;
}
}
}
/*var data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height)
, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat);
System.Diagnostics.Debug.Assert(data.Scan0 == unmanagedMemory.ptr);
bitmap.UnlockBits(data);*/
if (fillBlackButton.Checked)
e.Graphics.FillRectangle(System.Drawing.Brushes.Black, 0, 0, Bounds.Width, Bounds.Height);
else
e.Graphics.FillRectangle(background, 0, 0, Bounds.Width, Bounds.Height); // Draw a pattern to be able to "see" alpha...
if (noAlphaButton.Checked) // TODO: add scaling options...
e.Graphics.DrawImage(bitmap, new System.Drawing.Rectangle(0, 60, bitmap.Width, bitmap.Height), 0, 0, bitmap.Width, bitmap.Height, System.Drawing.GraphicsUnit.Pixel, imageAttributesKillAlpha );
else
e.Graphics.DrawImageUnscaled(bitmap, 0, 60);
};
}
public void SetParams(uint procHandle, string ptrString, System.Drawing.Imaging.PixelFormat format, uint xsize, uint ysize, uint bytesPP, ImgOP imgOp, bool enableImgButtons, bool enableHDRButtons)
{
memControl.Text = ptrString;
xresControl.Value = xsize;
yresControl.Value = ysize;
this.bytesPP = bytesPP;
this.imgOp = imgOp;
this.procHandle = procHandle;
this.format = format;
if (!enableImgButtons)
{
endianSwapButton.Enabled = false;
RBSwapButton.Enabled = false;
}
if (!enableHDRButtons)
{
hdrScale.Enabled = false;
}
Refresh();
}
public void CreateBuffers()
{
imageSize = (uint)xresControl.Value * bytesPP * (uint)yresControl.Value;
readSize = imageSize;
if (imgOp == ImgOP.F16_TO_I8)
{
tempHalfToFloatMemory = new float[imageSize];
readSize *= 2;
}
else if (imgOp == ImgOP.F32_TO_I8)
{
readSize *= 4;
}
unmanagedMemory = new UnmanagedMemWrapper(readSize);
bitmap = new System.Drawing.Bitmap(
(int)xresControl.Value, (int)yresControl.Value, (int)(xresControl.Value * bytesPP), format, unmanagedMemory.ptr
);
System.Drawing.Imaging.ColorPalette palette = bitmap.Palette;
if (palette.Entries.Length != 0)
{
for (int i = 0; i < palette.Entries.Length; i++)
palette.Entries.SetValue(System.Drawing.Color.FromArgb(255, i, i, i), i);
bitmap.Palette = palette; // weird dance...
}
imageAttributesKillAlpha = new System.Drawing.Imaging.ImageAttributes();
float[][] colorMatrixElements = {
new float[] {1, 0, 0, 0, 0}, // red scale
new float[] {0, 1, 0, 0, 0}, // green scale
new float[] {0, 0, 1, 0, 0}, // blue scale
new float[] {0, 0, 0, 1, 0}, // alpha scale
new float[] {0, 0, 0, 1, 1}}; // translation
imageAttributesKillAlpha.SetColorMatrix(
new System.Drawing.Imaging.ColorMatrix(colorMatrixElements), System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap
); // TODO: RGB swaps and R-G-B channel selections and so on can/should be done with a matrix instead of the way they are currently implemented (i.e. endianSwapButton...)
UInt64 pointerInt = 0;
if (memControl.Text.StartsWith("0x"))
{
try
{
pointerInt = Convert.ToUInt64(memControl.Text.Substring(2), 16);
}
catch (System.Exception) { memControl.Text = "Can't parse ptr"; }
}
else if (!UInt64.TryParse(memControl.Text, out pointerInt))
{
memControl.Text = "Can't parse ptr";
}
pointer = new UIntPtr(pointerInt);
Refresh();
}
uint imageSize = 0;
uint readSize = 0;
float[] tempHalfToFloatMemory = null;
UnmanagedMemWrapper unmanagedMemory = null;
UIntPtr pointer = new UIntPtr(0);
System.Drawing.Bitmap bitmap = null;
uint bytesPP;
ImgOP imgOp;
uint procHandle;
System.Drawing.Imaging.PixelFormat format;
System.Drawing.Imaging.ImageAttributes imageAttributesKillAlpha;
// Meh, there was no reason to do all this by hand really...
System.Windows.Forms.CheckBox noAlphaButton = new System.Windows.Forms.CheckBox() { Text = "NoAlpha", Left = 0, Width = 70 };
System.Windows.Forms.CheckBox endianSwapButton = new System.Windows.Forms.CheckBox() { Text = "Endian", Left = 70, Width = 70 };
System.Windows.Forms.CheckBox RBSwapButton = new System.Windows.Forms.CheckBox() { Text = "RB Swap", Left = 140, Width = 70 };
System.Windows.Forms.NumericUpDown hdrScale = new System.Windows.Forms.NumericUpDown() { DecimalPlaces = 2, Minimum = -999999, Maximum = 999999, Increment = 0.25m, Value = 1, Left = 330, Width = 50 };
System.Windows.Forms.CheckBox fillBlackButton = new System.Windows.Forms.CheckBox() { Text = "Black Backgr.", Left = 380, Width = 70 };
System.Windows.Forms.NumericUpDown xresControl = new System.Windows.Forms.NumericUpDown() { Minimum = 0, Maximum = 9999, Top = 25, Left = 0, Width = 105 };
System.Windows.Forms.NumericUpDown yresControl = new System.Windows.Forms.NumericUpDown() { Minimum = 0, Maximum = 9999, Top = 25, Left = 105, Width = 105 };
System.Windows.Forms.TextBox memControl = new System.Windows.Forms.TextBox() { Left = 210, Width = 170, Top = 25 };
System.Windows.Forms.Button resetButton = new System.Windows.Forms.Button() { Text = "Region Update", Left = 380, Top = 25, Width = 100 };
}
static void PeekImg(
uint procHandle, string ptrString, UInt32 xsize, UInt32 ysize, UInt32 bytesPP,
System.Drawing.Imaging.PixelFormat format, ImgOP imgOp = ImgOP.NONE
) // TODO: move the format params into a drop-down of the form, instead of having to specify by hand in the commandline...
{
using (var form = new PeekImgForm())
{
var timer = new System.Windows.Forms.Timer() { Interval = 33, Enabled = true };
timer.Tick += delegate(object sender, EventArgs e)
{
//form.Refresh(); // TODO: Enable-Disable auto refresh switch, via a command-line switch or a checkbox
};
form.SetBounds(0, 0, xsize > 600 ? (int)xsize : 600, (int)ysize + 100);
form.SetParams(procHandle, ptrString, format, xsize, ysize, bytesPP, imgOp,
format == System.Drawing.Imaging.PixelFormat.Format32bppArgb,
imgOp != ImgOP.NONE
);
form.CreateBuffers();
// Run...
System.Windows.Forms.Application.EnableVisualStyles();
form.Show(); form.Focus(); timer.Start();
System.Windows.Forms.Application.Run(form);
}
}
}
}