Update: Now with floating point images support and endian swaps...
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, byte[] buffer, uint size, uint lpNumberOfBytesRead);
[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);
static byte[] ReadMemory(UIntPtr address, uint size, uint processHandle)
{
byte[] buffer = new byte[size];
ReadProcessMemory(processHandle, address, buffer, size, 0);
return buffer;
}
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 < 7)
{
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;
}
}
class DBForm : System.Windows.Forms.Form
{
public DBForm()
{
DoubleBuffered = true;
}
}
enum ImgOP { NONE, F16_TO_I8, F32_TO_I8 }
static void PeekImg(
uint procHandle, string ptrString, UInt32 xsize, UInt32 ysize, UInt32 bytesPP,
System.Drawing.Imaging.PixelFormat format, ImgOP imgOp = ImgOP.NONE
)
{
uint imageSize = 0;
uint readSize = 0;
float[] tempHalfToFloatMemory = null;
UnmanagedMemWrapper unmanagedMemory = null;
UIntPtr pointer = new UIntPtr(0);
using (var form = new DBForm() { Text = "Peeker" })
{
form.SetBounds(0, 0, xsize > 400 ? (int)xsize : 400, (int)ysize + 100);
// Meh, there was no reason to do all this by hand really, also, I could have added everything to the DBForm class...
var timer = new System.Windows.Forms.Timer() { Interval = 33, Enabled = true };
var background = new System.Drawing.Drawing2D.HatchBrush(
System.Drawing.Drawing2D.HatchStyle.LargeCheckerBoard, System.Drawing.Color.Black, System.Drawing.Color.White);
System.Drawing.Bitmap bitmap = null;
var noAlphaButton = new System.Windows.Forms.CheckBox() { Text = "NoAlpha", Left = 0, Width = 70 };
var endianSwapButton = new System.Windows.Forms.CheckBox() { Text = "Endian", Left = 70, Width = 70 };
var RBSwapButton = new System.Windows.Forms.CheckBox() { Text = "RB Swap", Left = 140, Width = 70 };
var memControl = new System.Windows.Forms.TextBox() { Text = ptrString, Left = 210, Width = 210 };
var xresControl = new System.Windows.Forms.NumericUpDown() { Minimum = 0, Maximum = 9999, Value = xsize, Top = 25, Left = 0, Width = 105 };
var yresControl = new System.Windows.Forms.NumericUpDown() { Minimum = 0, Maximum = 9999, Value = ysize, Top = 25, Left = 105, Width = 105 };
var resetButton = new System.Windows.Forms.Button() { Text = "Reinit", Top = 25, Left = 260 };
form.Controls.Add(memControl);
form.Controls.Add(noAlphaButton); form.Controls.Add(endianSwapButton); form.Controls.Add(RBSwapButton);
form.Controls.Add(xresControl); form.Controls.Add(yresControl); form.Controls.Add(resetButton);
if (format != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{ // For now these conversions are coded only for argb8 and formats that decode into argb8
noAlphaButton.Enabled = false;
endianSwapButton.Enabled = false;
RBSwapButton.Enabled = false;
}
resetButton.Click += delegate(object sender, System.EventArgs e)
{ // This is and ugly hack just because the delegate/event system in C# is a bit restrictive...
InitBuffers(bytesPP, format, imgOp, ref imageSize, ref readSize, ref tempHalfToFloatMemory,
ref unmanagedMemory, ref bitmap, ref pointer, ref memControl, xresControl, yresControl);
};
InitBuffers(bytesPP, format, imgOp, ref imageSize, ref readSize, ref tempHalfToFloatMemory,
ref unmanagedMemory, ref bitmap, ref pointer, ref memControl, xresControl, yresControl);
form.Paint += delegate(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (unmanagedMemory == null)
return;
ReadProcessMemory(procHandle, pointer, unmanagedMemory.ptr, readSize, 0);
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] * 255.0f;
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++)
{
float scaledVal = floatPtr[i] * 255.0f;
bytePtr[i] = (byte)(scaledVal > 255.0f ? 255.0f : scaledVal);
}
}
}
if (noAlphaButton.Checked && endianSwapButton.Checked)
{
unsafe
{
byte* bytePtr = (byte*)unmanagedMemory.ptr;
for (int i = 0; i < imageSize; i += 4)
{
bytePtr[i] = bytePtr[i + 3];
bytePtr[i + 3] = 255;
byte temp = bytePtr[i + 2];
bytePtr[i + 2] = bytePtr[i + 1];
bytePtr[i + 1] = temp;
}
}
}
else if (noAlphaButton.Checked)
{
unsafe
{
byte* bytePtr = (byte*)unmanagedMemory.ptr;
for (int i = 0; i < imageSize; i += 4)
bytePtr[i+3] = 255;
}
}
else 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 all 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(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat);
//System.Diagnostics.Debug.Assert(data.Scan0 == unmanagedPtr.ptr);
//bitmap.UnlockBits(data);
// Draw a pattern to be able to "see" alpha...
e.Graphics.FillRectangle(background, 0, 0, form.Bounds.Width, form.Bounds.Height);
e.Graphics.DrawImage(bitmap, new System.Drawing.Point(0, 60));
};
timer.Tick += delegate(object sender, EventArgs e)
{
form.Refresh();
};
// Run...
System.Windows.Forms.Application.EnableVisualStyles();
form.Show(); form.Focus(); timer.Start();
System.Windows.Forms.Application.Run(form);
}
}
private static void InitBuffers(UInt32 bytesPP, System.Drawing.Imaging.PixelFormat format, ImgOP imgOp,
ref uint imageSize, ref uint readSize, ref float[] tempHalfToFloatMemory, ref UnmanagedMemWrapper unmanagedMemory,
ref System.Drawing.Bitmap bitmap, ref UIntPtr pointer, ref System.Windows.Forms.TextBox memControl,
System.Windows.Forms.NumericUpDown xresControl, System.Windows.Forms.NumericUpDown yresControl)
{
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
);
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);
}
}
}