// To setup the project: // - Setup the project to be a "Windows Application" - you don't need anything other than this .cs file in it. // - Needs System.Drawing.Common and System.Drawing.Primitives packages from NuGet. // - Make sure that System.Windows.Forms is added as a reference in the project. // - Needs to "allow unsafe" in the project configuration. // - Remember to compile for the right architecture (x86/x64) otherwise you might get the wrong pointer size. // TODO - make all commandline parameters optional / put the process name and number in a textbox / automatically // invalidate the textbox when we detect the process is not valid anymore... // TODO - mouseover = grab pixel value... // Version 1 using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace Peek { class UnmanagedMemWrapper // should we GC.AddMemoryPressure? { public UnmanagedMemWrapper(uint size) { this.ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal((int)size); this.size = size; } ~UnmanagedMemWrapper() { System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr); } public IntPtr ptr; public uint size = 0; } enum ImgOP { NONE, F16_TO_U8, F32_TO_U8, TWO_TO_THREE_U8 } enum ImgBaseFormats { argb8, rgb8, argb16, rgb16, r8, rg8, r16, argb32f, rgb32f, argb16f, rbg16f, r32f } class ImgBaseFormatsHelper { public static System.Drawing.Imaging.PixelFormat GetPixelFormat(ImgBaseFormats format) { switch (format) { case ImgBaseFormats.argb8: return System.Drawing.Imaging.PixelFormat.Format32bppArgb; case ImgBaseFormats.rgb8: return System.Drawing.Imaging.PixelFormat.Format24bppRgb; case ImgBaseFormats.argb16: return System.Drawing.Imaging.PixelFormat.Format64bppArgb; case ImgBaseFormats.rgb16: return System.Drawing.Imaging.PixelFormat.Format48bppRgb; case ImgBaseFormats.r8: return System.Drawing.Imaging.PixelFormat.Format8bppIndexed; case ImgBaseFormats.rg8: return System.Drawing.Imaging.PixelFormat.Format24bppRgb; case ImgBaseFormats.r16: return System.Drawing.Imaging.PixelFormat.Format16bppGrayScale; case ImgBaseFormats.argb32f: return System.Drawing.Imaging.PixelFormat.Format32bppArgb; case ImgBaseFormats.rgb32f: return System.Drawing.Imaging.PixelFormat.Format24bppRgb; case ImgBaseFormats.argb16f: return System.Drawing.Imaging.PixelFormat.Format32bppArgb; case ImgBaseFormats.rbg16f: return System.Drawing.Imaging.PixelFormat.Format24bppRgb; case ImgBaseFormats.r32f: return System.Drawing.Imaging.PixelFormat.Format8bppIndexed; default: Debug.Assert(false); return System.Drawing.Imaging.PixelFormat.Undefined; } } public static ImgOP GetImgOp(ImgBaseFormats format) { switch (format) { case ImgBaseFormats.argb8: case ImgBaseFormats.rgb8: case ImgBaseFormats.argb16: case ImgBaseFormats.rgb16: case ImgBaseFormats.r8: case ImgBaseFormats.r16: return ImgOP.NONE; case ImgBaseFormats.rg8: return ImgOP.TWO_TO_THREE_U8; case ImgBaseFormats.argb32f: case ImgBaseFormats.rgb32f: case ImgBaseFormats.r32f: return ImgOP.F32_TO_U8; case ImgBaseFormats.argb16f: case ImgBaseFormats.rbg16f: return ImgOP.F16_TO_U8; default: Debug.Assert(false); return ImgOP.NONE; } } } class PeekImgForm : System.Windows.Forms.Form { [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);*/ [System.Runtime.InteropServices.DllImport("kernel32.dll")] static extern uint GetExitCodeProcess(uint hProcess, UIntPtr lpExitCode); // 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); enum Backgrounds { Checkers, White, Black, Magenta } enum ChannelOptions { NoTransform, Red, Green, Blue, Alpha, SwapR_B, SwapAll, SwapA } static UInt32 GetBytesForImageFormat(System.Drawing.Imaging.PixelFormat format) { switch (format) { case System.Drawing.Imaging.PixelFormat.Format32bppArgb: return 4; case System.Drawing.Imaging.PixelFormat.Format24bppRgb: return 3; case System.Drawing.Imaging.PixelFormat.Format64bppArgb: return 8; case System.Drawing.Imaging.PixelFormat.Format48bppRgb: return 6; case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale: return 2; case System.Drawing.Imaging.PixelFormat.Format8bppIndexed: return 1; default: Debug.Assert(false); return 0; } } void UpdateImageTransform() { if (ctrlChannelOps.SelectedIndex != 0 || ctrlUseAlpha.Checked == false) { float[][] colorMatrixElements; switch ((ChannelOptions)ctrlChannelOps.SelectedIndex) { case ChannelOptions.NoTransform: colorMatrixElements = new float[][]{ 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, 0, 1 }}; // translation break; case ChannelOptions.Red: colorMatrixElements = new float[][]{ new float[] { 1, 0, 0, 0, 0 }, // red scale new float[] { 0, 0, 0, 0, 0 }, // green scale new float[] { 0, 0, 0, 0, 0 }, // blue scale new float[] { 0, 0, 0, 0, 0 }, // alpha scale new float[] { 0, 0, 0, 1, 1 }}; // translation break; case ChannelOptions.Green: colorMatrixElements = new float[][]{ new float[] { 0, 0, 0, 0, 0 }, // red scale new float[] { 0, 1, 0, 0, 0 }, // green scale new float[] { 0, 0, 0, 0, 0 }, // blue scale new float[] { 0, 0, 0, 0, 0 }, // alpha scale new float[] { 0, 0, 0, 1, 1 }}; // translation break; case ChannelOptions.Blue: colorMatrixElements = new float[][]{ new float[] { 0, 0, 0, 0, 0 }, // red scale new float[] { 0, 0, 0, 0, 0 }, // green scale new float[] { 0, 0, 1, 0, 0 }, // blue scale new float[] { 0, 0, 0, 0, 0 }, // alpha scale new float[] { 0, 0, 0, 1, 1 }}; // translation break; case ChannelOptions.Alpha: colorMatrixElements = new float[][]{ new float[] { 0, 0, 0, 1, 0 }, // red scale new float[] { 0, 0, 0, 1, 0 }, // green scale new float[] { 0, 0, 0, 1, 0 }, // blue scale new float[] { 0, 0, 0, 0, 0 }, // alpha scale new float[] { 0, 0, 0, 1, 1 }}; // translation break; case ChannelOptions.SwapR_B: colorMatrixElements = new float[][]{ new float[] { 0, 0, 1, 0, 0 }, // red scale new float[] { 0, 1, 0, 0, 0 }, // green scale new float[] { 1, 0, 0, 0, 0 }, // blue scale new float[] { 0, 0, 0, 1, 0 }, // alpha scale new float[] { 0, 0, 0, 0, 1 }}; // translation break; case ChannelOptions.SwapAll: // i.e. ARGB->BGRA colorMatrixElements = new float[][]{ new float[] { 0, 0, 0, 1, 0 }, // red scale new float[] { 0, 0, 1, 0, 0 }, // green scale new float[] { 0, 1, 0, 0, 0 }, // blue scale new float[] { 1, 0, 0, 0, 0 }, // alpha scale new float[] { 0, 0, 0, 0, 1 }}; // translation break; case ChannelOptions.SwapA: // i.e. ARGB->RGBA colorMatrixElements = new float[][]{ new float[] { 0, 0, 0, 1, 0 }, // red scale new float[] { 1, 0, 0, 0, 0 }, // gree scale new float[] { 0, 1, 0, 0, 0 }, // blue scale new float[] { 0, 0, 1, 0, 0 }, // alpha scale new float[] { 0, 0, 0, 0, 1 }}; // translation break; default: Debug.Assert(false); return; } if (!ctrlUseAlpha.Checked && (ChannelOptions)ctrlChannelOps.SelectedIndex != ChannelOptions.Alpha) { colorMatrixElements[3][3] = 0; colorMatrixElements[4][3] = 1; } imageTransform.SetColorMatrix(new System.Drawing.Imaging.ColorMatrix(colorMatrixElements), System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap); } } bool ReadMemoryPtrAndProcessToBuffer(UIntPtr ptr, UnmanagedMemWrapper buffer) { if (imageSize == 0) return false; var format = ImgBaseFormatsHelper.GetPixelFormat((ImgBaseFormats)ctrlFormat.SelectedIndex); var imgOp = ImgBaseFormatsHelper.GetImgOp((ImgBaseFormats)ctrlFormat.SelectedIndex); bool readSuccess; if (ctrlSwizzle4x4.Checked) { readSuccess = ReadProcessMemory(procHandle, ptr, unmanagedMemoryTemp.ptr, readSize, 0); if (readSuccess) { unsafe { int readChannels = imgOp == ImgOP.TWO_TO_THREE_U8 ? 2 : (int)GetBytesForImageFormat(format); byte* bytePtr = (byte*)buffer.ptr; byte* bytePtrTemp = (byte*)unmanagedMemoryTemp.ptr; for (int y = 0; y < ctrlHeight.Value; y += 4) // apparently I don't know how to swizzle by moving bits around for (int x = 0; x < ctrlWidth.Value; x += 4) for (int yy = 0; yy < 4; yy++) { int row = (y + yy) * (int)ctrlWidth.Value; for (int xx = x * readChannels; xx < (x + 4) * readChannels; xx++) { bytePtr[row * readChannels + xx] = *bytePtrTemp; bytePtrTemp++; } } } } } else readSuccess = ReadProcessMemory(procHandle, ptr, buffer.ptr, readSize, 0); if (!readSuccess) { unsafe { uint exitCode = 0; uint ok = GetExitCodeProcess(procHandle, new UIntPtr(&exitCode)); if (ok == 0 || exitCode != 259) { Text = "Process not found - launch a new window?"; return false; } } if (ReadProcessMemory(procHandle, ptr, buffer.ptr, 1, 0)) Text = "Unable to Peek (image requested too big: " + readSize.ToString() + " bytes)"; else Text = "Unable to Peek (wrong pointer address!)"; return false; } else Text = "Image Peeker (" + readSize.ToString() + " bytes)"; if ((format == System.Drawing.Imaging.PixelFormat.Format64bppArgb) || (format == System.Drawing.Imaging.PixelFormat.Format48bppRgb)) // these are not 16bpp, but 13, really { unsafe { ushort* ushortPtr = (ushort*)buffer.ptr; for (int i = 0; i < imageSize / 2; i++) ushortPtr[i] >>= 3; } } if ((imgOp == ImgOP.F16_TO_U8) || (imgOp == ImgOP.F32_TO_U8)) { float scale = (float)ctrlHdrMult.Value * 255.0f; // bake here the *255 we need to convert to 8-bit if (imgOp == ImgOP.F16_TO_U8) // first expand f16 to f32, then treat as f32 D3DXFloat16To32Array(tempHalfToFloatMemory, buffer.ptr, (uint)tempHalfToFloatMemory.Length); unsafe { byte* bytePtr = (byte*)buffer.ptr; fixed (float* floatPtrT = tempHalfToFloatMemory) { float* floatPtr = (imgOp == ImgOP.F16_TO_U8) ? floatPtrT : (float*)buffer.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); } } } } else if (imgOp == ImgOP.TWO_TO_THREE_U8) { unsafe { byte* bytePtr = (byte*)buffer.ptr; uint ii = imageSize - 1; for (int i = (int)readSize - 1; i >= 0; i -= 2, ii -= 3) { bytePtr[ii] = bytePtr[i]; bytePtr[ii + 1] = bytePtr[i + 1]; bytePtr[ii + 2] = 0; } } } if (needsStrideFix) // at this point we should have the image in the right format, but we might need to move row data around to conform to stride requirements { int current_stride = (int)ctrlWidth.Value * (int)GetBytesForImageFormat(format); int target_stride = current_stride + (4 - (current_stride % 4)); unsafe { byte* bytePtr = (byte*)buffer.ptr; byte* targetPtr = bytePtr; bytePtr += imageSize - current_stride; // point to the current last row targetPtr += imageSizeStrideFixed - target_stride; // where the row should be copied to for (int i = (int)ctrlHeight.Value - 1; i >= 0; i--) { for (int j = current_stride - 1; j >= 0; j--) targetPtr[j] = bytePtr[j]; bytePtr -= current_stride; targetPtr -= target_stride; } } } return true; } void PaintCallback(object sender, System.Windows.Forms.PaintEventArgs e) // Main code here - get called on Refresh() { // This either has to be done here (i.e. at every repaint), or if we were to call it only when the controls that require // re-allocation of resources change, we'd have to make sure we don't directly access these control values here, as a redraw // can happen before a control delegate event is called! CheckAndAllocateResources(); if (!ReadMemoryPtrAndProcessToBuffer(pointer, unmanagedMemory)) { e.Graphics.FillRectangle(System.Drawing.Brushes.Red, 0, 80, Bounds.Width, Bounds.Height - 80); return; } if (pointerDiff.ToUInt64() != 0) { if (!ReadMemoryPtrAndProcessToBuffer(pointerDiff, unmanagedMemoryDiff)) { e.Graphics.FillRectangle(System.Drawing.Brushes.OrangeRed, 0, 80, Bounds.Width, Bounds.Height - 80); return; } else { unsafe { byte* bytePtr = (byte*)unmanagedMemory.ptr; byte* bytePtrDiff = (byte*)unmanagedMemoryDiff.ptr; for (int i = 0; i < imageSize; i++) bytePtr[i] = (byte)(bytePtr[i] ^ bytePtrDiff[i]); } } } /*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);*/ int drawHeight = bitmap.Height * (int)ctrlDisplayScale.Value; int drawWidth = bitmap.Width * (int)ctrlDisplayScale.Value; switch ((Backgrounds)ctrlBackground.SelectedIndex) { case Backgrounds.Checkers: var background = new System.Drawing.Drawing2D.HatchBrush( System.Drawing.Drawing2D.HatchStyle.LargeCheckerBoard, System.Drawing.Color.Black, System.Drawing.Color.White); e.Graphics.FillRectangle(background, 0, 80, drawWidth, drawHeight); // Draw a pattern to be able to "see" alpha... break; case Backgrounds.White: e.Graphics.FillRectangle(System.Drawing.Brushes.White, 0, 80, drawWidth, drawHeight); break; case Backgrounds.Black: e.Graphics.FillRectangle(System.Drawing.Brushes.Black, 0, 80, drawWidth, drawHeight); break; case Backgrounds.Magenta: e.Graphics.FillRectangle(System.Drawing.Brushes.Magenta, 0, 80, drawWidth, drawHeight); break; } // Scale the image to the DPI of the screen - we want to match 1 pixel to 1 pixel on the screen, regardless of the DPI settings. float dpiX = e.Graphics.DpiX; float dpiY = e.Graphics.DpiY; e.Graphics.ResetTransform(); e.Graphics.ScaleTransform(96f / dpiX, 96f / dpiY); e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; bool imageTransformEnabled = ctrlChannelOps.SelectedIndex != 0 || ctrlUseAlpha.Checked == false; if (imageTransformEnabled || (int)ctrlDisplayScale.Value != 1) e.Graphics.DrawImage(bitmap, new System.Drawing.Rectangle(0, 80, drawWidth, drawHeight), 0, 0, bitmap.Width, bitmap.Height, System.Drawing.GraphicsUnit.Pixel, imageTransformEnabled ? imageTransform : null); else e.Graphics.DrawImageUnscaled(bitmap, 0, 80); } public void CheckAndAllocateResources() { var format = ImgBaseFormatsHelper.GetPixelFormat((ImgBaseFormats)ctrlFormat.SelectedIndex); var imgOp = ImgBaseFormatsHelper.GetImgOp((ImgBaseFormats)ctrlFormat.SelectedIndex); UInt32 bytesPP = GetBytesForImageFormat(format); int stride = (int)ctrlWidth.Value * (int)bytesPP; imageSize = readSize = (uint)stride * (uint)ctrlHeight.Value; if (imgOp == ImgOP.F16_TO_U8) { if (tempHalfToFloatMemory.Length != imageSize) tempHalfToFloatMemory = new float[imageSize]; readSize *= 2; } else if (imgOp == ImgOP.F32_TO_U8) readSize *= 4; else if (imgOp == ImgOP.TWO_TO_THREE_U8) readSize = (imageSize * 2) / 3; if (stride % 4 != 0) // Annoying - the bitmap class has to have a row-stride that is multiple of four { needsStrideFix = true; stride += 4 - (stride % 4); } else needsStrideFix = false; imageSizeStrideFixed = (uint)stride * (uint)ctrlHeight.Value; uint unmanagedMemoryBufferSize = Math.Max(imageSizeStrideFixed, readSize); // the actual bitmap will always be imageSize bytes, but for processing (TWO_TO_THREE_U8 specifically) we might need more. bool needsNewBitmap = false; if (unmanagedMemory == null || unmanagedMemory.size != unmanagedMemoryBufferSize) { needsNewBitmap = true; unmanagedMemory = new UnmanagedMemWrapper(unmanagedMemoryBufferSize); } if (pointerDiff.ToUInt64() != 0) if (unmanagedMemoryDiff == null || unmanagedMemoryDiff.size != unmanagedMemoryBufferSize) unmanagedMemoryDiff = new UnmanagedMemWrapper(unmanagedMemoryBufferSize); if (ctrlSwizzle4x4.Checked) if (unmanagedMemoryTemp == null || unmanagedMemoryTemp.size != readSize) unmanagedMemoryTemp = new UnmanagedMemWrapper(readSize); if (needsNewBitmap || bitmap == null || bitmap.Width != (int)ctrlWidth.Value || bitmap.Height != (int)ctrlHeight.Value || bitmap.PixelFormat != format) { bitmap = new System.Drawing.Bitmap( (int)ctrlWidth.Value, (int)ctrlHeight.Value, stride, 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... } } } void ControlsCheckEnableSwizzle4x4() { if ((ctrlWidth.Value % 4 == 0) && (ctrlHeight.Value % 4 == 0) && Program.imgBaseFormatStrings[ctrlFormat.SelectedIndex].EndsWith("8")) ctrlSwizzle4x4.Enabled = true; else { ctrlSwizzle4x4.Checked = false; ctrlSwizzle4x4.Enabled = false; } } public void Initialize(uint procHandle, string ptrString, ImgBaseFormats imgFormat, uint xsize, uint ysize, bool blockSwizzle) { this.procHandle = procHandle; if (blockSwizzle) { xsize = (xsize / 4) * 4; ysize = (ysize / 4) * 4; } ctrlSwizzle4x4.Enabled = (xsize % 4 == 0) && (ysize % 4 == 0); ctrlSwizzle4x4.Checked = blockSwizzle; ctrlWidth.Value = xsize; ctrlHeight.Value = ysize; ctrlChannelOps.SelectedIndex = 0; ctrlBackground.SelectedIndex = 0; ctrlUseAlpha.Checked = true; ctrlFormat.SelectedIndexChanged += delegate (object sender, System.EventArgs e) { var imgOp = ImgBaseFormatsHelper.GetImgOp((ImgBaseFormats)ctrlFormat.SelectedIndex); ctrlHdrMult.Enabled = (imgOp == ImgOP.F16_TO_U8 || imgOp == ImgOP.F32_TO_U8); ControlsCheckEnableSwizzle4x4(); //CheckAndAllocateResources(); Refresh(); }; ctrlFormat.SelectedIndex = (int)imgFormat; ctrlSwizzle4x4.CheckedChanged += delegate (object sender, System.EventArgs e) { Refresh(); }; ctrlPointer.TextChanged += delegate (object sender, System.EventArgs e) { UInt64 pointerInt = 0; if (ctrlPointer.Text.StartsWith("0x")) { try { pointerInt = Convert.ToUInt64(ctrlPointer.Text.Substring(2), 16); } catch (System.Exception) { ctrlPointer.Text = "Can't parse ptr"; } } else if (!UInt64.TryParse(ctrlPointer.Text, out pointerInt)) { ctrlPointer.Text = "Can't parse ptr"; } pointer = new UIntPtr(pointerInt); Refresh(); }; ctrlPointer.Text = ptrString; ctrlDiffPointer.TextChanged += delegate (object sender, System.EventArgs e) { UInt64 pointerInt = 0; if (ctrlDiffPointer.Text.StartsWith("0x")) { try { pointerInt = Convert.ToUInt64(ctrlDiffPointer.Text.Substring(2), 16); } catch (System.Exception) { ctrlDiffPointer.Text = "Can't parse ptr"; } } else if (!UInt64.TryParse(ctrlDiffPointer.Text, out pointerInt)) { ctrlDiffPointer.Text = "Can't parse ptr"; } pointerDiff = new UIntPtr(pointerInt); //CheckAndAllocateResources(); Refresh(); }; ctrlWidth.ValueChanged += delegate (object sender, System.EventArgs e) { ControlsCheckEnableSwizzle4x4(); Refresh(); }; ctrlHeight.ValueChanged += delegate (object sender, System.EventArgs e) { ControlsCheckEnableSwizzle4x4(); Refresh(); }; ctrlChannelOps.SelectedIndexChanged += delegate (object sender, System.EventArgs e) { UpdateImageTransform(); Refresh(); }; ctrlUseAlpha.CheckedChanged += delegate (object sender, System.EventArgs e) { UpdateImageTransform(); Refresh(); }; ctrlDisplayScale.ValueChanged += delegate (object sender, System.EventArgs e) { Refresh(); }; ctrlRefresh.Click += delegate (object sender, System.EventArgs e) { Refresh(); }; timer.Tick += delegate (object sender, EventArgs e) { Refresh(); }; ctrlLiveUpdate.CheckedChanged += delegate (object sender, System.EventArgs e) { timer.Enabled = ctrlLiveUpdate.Checked; }; ctrlHdrMult.ValueChanged += delegate (object sender, System.EventArgs e) { Refresh(); }; ctrlBackground.SelectedIndexChanged += delegate (object sender, System.EventArgs e) { Refresh(); }; ctrlNewWindow.Click += delegate (object sender, System.EventArgs e) { Program.Main(Program.savedArgs); }; Paint += PaintCallback; //CheckAndAllocateResources(); Refresh(); } public PeekImgForm() { DoubleBuffered = true; ctrlChannelOps.Items.AddRange(Enum.GetNames(typeof(ChannelOptions))); ctrlFormat.Items.AddRange(Program.imgBaseFormatStrings); ctrlBackground.Items.AddRange(Enum.GetNames(typeof(Backgrounds))); // Add all controls that are fields of this class to the form foreach (var field in this.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)) { if (typeof(System.Windows.Forms.Control).IsAssignableFrom(field.FieldType)) { var control = (System.Windows.Forms.Control)field.GetValue(this); if (control != null) { Controls.Add(control); } } } } uint imageSize = 0; // byte size of the final image (i.e. after format conversions) uint imageSizeStrideFixed = 0; // if needsStrideFix - this will be bigger imageSize due to stride alignment rules uint readSize = 0; // readSize (how much process memory we'll need to read) can be different than imageSize, if we need to convert from float to byte, for example bool needsStrideFix = false; float[] tempHalfToFloatMemory = null; // Temporary data needed for half-float to float conversion UnmanagedMemWrapper unmanagedMemory = null; // This chunk of memory will be used to copy over the process data and for any in-place modification needed to display the image. UnmanagedMemWrapper unmanagedMemoryTemp = null; // Optional temporary memory needed if in-place modification is not possible. UnmanagedMemWrapper unmanagedMemoryDiff = null; System.Drawing.Bitmap bitmap = null; // This uses the unmanagedMemory as the backing store. System.Drawing.Imaging.ImageAttributes imageTransform = new System.Drawing.Imaging.ImageAttributes(); uint procHandle; UIntPtr pointer = new UIntPtr(0); // Pointer in the process memory space to read from. UIntPtr pointerDiff = new UIntPtr(0); // Optional, second memory location to diff against. System.Windows.Forms.Button ctrlRefresh = new System.Windows.Forms.Button() { Text = "⟳", Left = 0, Width = 20 }; System.Windows.Forms.CheckBox ctrlLiveUpdate = new System.Windows.Forms.CheckBox() { Text = "Live", Left = 25, Width = 45 }; System.Windows.Forms.ComboBox ctrlChannelOps = new System.Windows.Forms.ComboBox() { Left = 70, Width = 80 }; System.Windows.Forms.CheckBox ctrlUseAlpha = new System.Windows.Forms.CheckBox() { Text = "Alpha x:", Left = 150, Width = 70 }; System.Windows.Forms.NumericUpDown ctrlDisplayScale = new System.Windows.Forms.NumericUpDown() { Minimum = 1, Maximum = 8, Left = 220, Width = 40 }; System.Windows.Forms.NumericUpDown ctrlHdrMult = new System.Windows.Forms.NumericUpDown() { DecimalPlaces = 2, Minimum = -999999, Maximum = 999999, Increment = 0.25m, Value = 1, Left = 270, Width = 50 }; System.Windows.Forms.ComboBox ctrlBackground = new System.Windows.Forms.ComboBox() { Left = 330, Width = 80 }; System.Windows.Forms.NumericUpDown ctrlWidth = new System.Windows.Forms.NumericUpDown() { Minimum = 1, Maximum = 9999, Top = 35, Left = 0, Width = 70 }; System.Windows.Forms.NumericUpDown ctrlHeight = new System.Windows.Forms.NumericUpDown() { Minimum = 1, Maximum = 9999, Top = 35, Left = 70, Width = 70 }; System.Windows.Forms.TextBox ctrlPointer = new System.Windows.Forms.TextBox() { Left = 150, Width = 120, Top = 25 }; System.Windows.Forms.TextBox ctrlDiffPointer = new System.Windows.Forms.TextBox() { Text = "xor diff ptr", Left = 150, Width = 120, Top = 45 }; System.Windows.Forms.ComboBox ctrlFormat = new System.Windows.Forms.ComboBox() { Left = 280, Width = 80, Top = 35 }; System.Windows.Forms.CheckBox ctrlSwizzle4x4 = new System.Windows.Forms.CheckBox() { Text = "4x4", Left = 360, Width = 50, Top = 35 }; System.Windows.Forms.Button ctrlNewWindow = new System.Windows.Forms.Button() { Text = "New window", Left = 420, Width = 50, Height = 50 }; System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer() { Interval = 33, Enabled = false }; } class Program { // 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_QUERY_LIMITED_INFORMATION = 0x1000; //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); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool AttachConsole(int dwProcessId); public static string[] imgBaseFormatStrings; public static string[] savedArgs; static void PrintUsageInstructions(string error) { System.Console.WriteLine(); System.Console.WriteLine("ImgPeek"); System.Console.WriteLine("----"); System.Console.WriteLine(); System.Console.WriteLine("Arguments: process name, instance number, [pointer address, format, xsize, ysize] "); System.Console.WriteLine("- Process name and instance number are mandatory, the other arguments are optional (but must all be specified, or none)."); System.Console.WriteLine("- Note that multiple processes can have the same name, that's why we need an instance number."); System.Console.WriteLine("- Supported formats:" + string.Join(", ", imgBaseFormatStrings)); System.Console.WriteLine("- Appending _4x4 to any of the 8-bit formats implies the image is swizzled in 4x4 blocks."); System.Console.WriteLine(); if (error.Length != 0) { System.Console.WriteLine("Error!"); System.Console.WriteLine(error); } } [STAThread] static public void Main(string[] args) { imgBaseFormatStrings = Enum.GetNames(typeof(ImgBaseFormats)); if (args.Length == 0) { AttachConsole(-1); PrintUsageInstructions(""); return; } bool isFirstLaunch = args[args.Length - 1] != "@@@"; int validArgs = args.Length; if (!isFirstLaunch) validArgs--; // The idea is that this program will be compiled as a windows application, but we expect to be able to launch it from the commandline // and get error messages there. To do this we have to manually attach the console, as a windows application doesn't have one. AttachConsole(-1); if (validArgs != 2 && validArgs != 6) { PrintUsageInstructions("Wrong number of arguments."); return; } savedArgs = new string[validArgs]; for (int i = 0; i < validArgs; i++) savedArgs[i] = String.Copy(args[i]); var procs = System.Diagnostics.Process.GetProcessesByName(args[0]); UInt32 procNumber = 0; if (!UInt32.TryParse(args[1], out procNumber)) { PrintUsageInstructions("Can't parse process number."); return; } if (procs.Length <= procNumber) { PrintUsageInstructions("Process instance not found."); return; } var proc = procs[procNumber]; uint procHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION, false, proc.Id); if (procHandle == 0) { PrintUsageInstructions("Failed to open process: " + args[0]); return; } UInt32 xsize = 1, ysize = 1; String format = imgBaseFormatStrings[0]; bool blockSwizzle = false; if (validArgs == 6) { if ((!UInt32.TryParse(args[4], out xsize)) || (!UInt32.TryParse(args[5], out ysize))) { PrintUsageInstructions("Can't parse img size."); return; } format = String.Copy(args[3]); if (format.EndsWith("_4x4")) // Block swizzle format { blockSwizzle = true; format = format.Substring(0, format.Length - 4); if (xsize % 4 != 0 || ysize % 4 != 0) { PrintUsageInstructions("Block swizzle format requires dimensions to be multiples of 4."); return; } if (!format.EndsWith("8")) { PrintUsageInstructions("Block swizzle format 8-bit formats."); return; } } } int formatIndex = Array.IndexOf(imgBaseFormatStrings, format); if (formatIndex == -1) { PrintUsageInstructions("Unsupported format"); return; } // This is a little "trick" to allow the app to immediately return when launched from the commandline, while the WinForms app will continue running. // This is why the program has to be compiled as a Windows Application - otherwise this second launch would also create a console window... // Note: to debug, directly add @@@ at the end of the debug command line. if (isFirstLaunch) { // Start a new process for the WinForms application. ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = System.Reflection.Assembly.GetExecutingAssembly().Location; // Use the same executable. startInfo.Arguments = String.Join(" ", args) + " @@@"; // Pass an argument to identify the WinForms process. startInfo.UseShellExecute = true; // Crucial for immediate return. startInfo.CreateNoWindow = true; // Optional: Run the new process without a console window. Process.Start(startInfo); return; } ImgBaseFormats imgBaseFormat = (ImgBaseFormats)formatIndex; using (var form = new PeekImgForm()) { form.SetBounds(0, 0, xsize > 600 ? (int)xsize : 600, ysize > 400 ? (int)ysize + 100 : 400); form.Initialize(procHandle, validArgs == 6 ? args[2] : "0x0", imgBaseFormat, xsize, ysize, blockSwizzle); System.Windows.Forms.Application.EnableVisualStyles(); form.Show(); form.Focus(); System.Windows.Forms.Application.Run(form); } } } }