The Goal
To verify that our MemoryManager code can, in fact, read another process's memory.
We will be using Cheat Engine (a memory scanner) to find addresses and to verify the data returned from our functions is correct.
Basically Cheat Engine scans all memory being used by a program. and can search it for a value. So using CE we can actually learn
the address where pinball stores our score! (note: this address can change each time you run pinball, we'll worry about that later if need be)
(crude demo on how to find memory address with Cheat Engine)
-Open pinball. Play some. Make youre score go above 0
-Pause the game (F3) -- your score value needs to not change while CE scans
-Open Cheat Engine
-Attach CE to pinball
-Leave all options to default (4 bytes,range, etc)
-Do a first scan for your score
On my system, CE only finds the score stored at 1 location. If your score was, for example, 100, it probably would have found that value at a LOT more locations. Try changing your score and scanning again (new scan). If that doesn't work, read a tutorial on 'how to find addresses with Cheat Engine' ( 1) find all possible locs, 2)change your score, 3)see if those possible locs still == your score. CE will help you do this.) For pinball, there should only be 1 address. Either way, we're just trying to make sure our MemoryManager can actually read data. Any address could do: I chose reading pinball's score because it gives some "oh, I see it" satisfaction.


So, now we have an address to read! In my case 0x00AC4E4C (yours may/should be different, that's ok)
Back to the code. We haven't actually made a function in the MemoryManager class which can read data yet. We have a decision to make. There are actually two major design strategies we can use here: we can have lots of functions, one named for each data type we will read from ram. So our class will have ReadInt32(), ReadUint32(), ReadInt64(), ReadUint64 etc. Or, the other approach is create one function (or, prototype??) which lets you pass in what data type you want to read. For example, MemManager.ReadValue(0x123456,Int32) (This can only work with POD(?) types, which have a constant size in memory) You would want to do this, maybe, to read a custom structure from ram instead of just a plain old built-in type. It's part of
some new language features(function prototyping?) put into VB.Net. To be honest, I'm not really sure, and it's not something I've read about yet. I chose to instead, explicitly create a bunch of functions, each one made to read a specific data type. This means a LOT or duplicate code. Which isn't good. Keep that in mind.
MemoryManager class. The first Read() function. ReadInt32()
Code:
Public Function ReadInt32(Byval addr as IntPtr) As Int32
'Int32 data type-- is 32 bits long, 4 bytes.
Dim _dataBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
Return BitConverter.ToInt32(_bytes, 0)
End Function
A couple things to note about the function
No error checking
lpBytesRead = vbNull ? This is used to know if RPM was actually able to read all data. If RPM only
read some of the memory locations, _bytesRead will be < iSize (in previous example, iSize=4)
If we pass in vbNull,because the parameter is ByRef, basically it's saying, I don't care, I won't use this value in my code.\
BitConverter A handy .Net class which converts an array of bytes to many core data types
error checking
Code:
Public Function ReadInt32(Byval addr as IntPtr) As Int32
Dim _dataBytes(3) As Byte
Dim _actualReadLength as Int32 = 0
If ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, _actualReadLength) Then
'success
If _actualReadLength = 4 Then
'all bytes read
Return BitConverter.ToInt32(_bytes, 0)
Else
'ReadProcessMemory only able to read *some* of the memory location.
Return 0 'or handle byte array at own risk? [should have been zero filled on creation?] + [some valid data?]
Else
'ReadProcessMemory returned FAIL. Possibly ram is inaccessable.
Return 0
End Function
The second Read() function. ReadInt64()
Code:
Public Function ReadInt32(Byval addr as IntPtr) As Int32
Dim _dataBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
Return BitConverter.ToInt32(_bytes, 0)
End Function
Public Function ReadInt64(Byval addr as IntPtr) As Int64
Dim _dataBytes(7) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 8, vbNull)
Return BitConverter.ToInt64(_bytes, 0)
End Function
Side Note
Int32 and Uint32 are both 32 bits, 4 bytes, in length. You need to understand how signed and unsigned works! That's too much to explain inside another tutorial. I HIGHLY recomend physically writing on paper numbers in binary,decimal,hex..lining them up in different ways, and try to feel comfortable looking at numbers in different formats. This helps especially when debugging. For example, I kept reading a pointer and outputting it using .ToString(). Well I was getting a number like 110347892 instead of the hex address Cheat Engine was showing me. It was because .ToString() was displaying it in base 10, which is the default, we all use grew up using base 10. Fortunately, .ToString() can take a formatting parameter where it will convert it to hex for us! (and there are other .Net functions for converting bases). The point is that you need to understand numbers.
Tip. In vb.net hex numbers start with &H <-- get it..H, pretty obvious. In other languages the prefix is 0x. Sometimes you'll see me refer to a memory loc as 0x123456. In vb it would be &H123456 and in decimal it would be..you tell me. 
Another Read() Function. This time UnSigned (if that should really matter..) Hooray BitCoverter class.
Code:
Public Function ReadInt32(Byval addr as IntPtr) As Int32
Dim _dataBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
Return BitConverter.ToInt32(_bytes, 0)
End Function
Public Function ReadInt64(Byval addr as IntPtr) As Int64
Dim _dataBytes(7) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 8, vbNull)
Return BitConverter.ToInt64(_bytes, 0)
End Function
Public Function ReadUInt32(Byval addr as IntPtr) As UInt32
Dim _dataBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
Return BitConverter.ToUInt32(_bytes, 0)
End Function
Until you have one for each data type you might read
Code:
Public Function ReadInt16(ByVal addr As IntPtr) As Int16
Dim _rtnBytes(1) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 2, vbNull)
Return BitConverter.ToInt16(_rtnBytes, 0)
End Function
Public Function ReadInt32(ByVal addr As IntPtr) As Int32
Dim _rtnBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 4, vbNull)
Return BitConverter.ToInt32(_rtnBytes, 0)
End Function
Public Function ReadInt64(ByVal addr As IntPtr) As Int64
Dim _rtnBytes(7) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 8, vbNull)
Return BitConverter.ToInt64(_rtnBytes, 0)
End Function
Public Function ReadUInt16(ByVal addr As IntPtr) As UInt16
Dim _rtnBytes(1) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 2, vbNull)
Return BitConverter.ToUInt16(_rtnBytes, 0)
End Function
Public Function ReadUInt32(ByVal addr As IntPtr) As UInt32
Dim _rtnBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 4, vbNull)
Return BitConverter.ToUInt32(_rtnBytes, 0)
End Function
Public Function ReadUInt64(ByVal addr As IntPtr) As UInt64
Dim _rtnBytes(7) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 8, vbNull)
Return BitConverter.ToUInt64(_rtnBytes, 0)
End Function
Public Function ReadFloat(ByVal addr As IntPtr) As Single
Dim _rtnBytes(3) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 4, vbNull)
Return BitConverter.ToSingle(_rtnBytes, 0)
End Function
Public Function ReadDouble(ByVal addr As IntPtr) As Double
Dim _rtnBytes(7) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 8, vbNull)
Return BitConverter.ToDouble(_rtnBytes, 0)
End Function
Public Function ReadIntPtr(ByVal addr As IntPtr) As IntPtr
Dim _rtnBytes(IntPtr.Size - 1) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, IntPtr.Size, Nothing)
If IntPtr.Size = 4 Then
Return New IntPtr(BitConverter.ToUInt32(_rtnBytes, 0))
Else
Return New IntPtr(BitConverter.ToInt64(_rtnBytes, 0))
End If
End Function
Public Function ReadBytes(ByVal addr As IntPtr, ByVal size As Int32) As Byte()
Dim _rtnBytes(size - 1) As Byte
ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, size, vbNull)
Return _rtnBytes
End Function
I also added one for IntPtr (which uses IntPtr.Size -- 32/64 problems ahead..) and one for ReadBytes. And forgot Boolean
I hope they make sense. We'll be using ReadBytes() much later to scan the entire memory range because it's more efficient to read a large chunk of ram vs. 4 bytes at a time. It's the difference between calling ReadProcessMemory 10,000 times and 10,000,000 times...
I guess I forgot ReadByte(), singular. I don't see myself using it. If so desired, you should* be able to create it your self.
Back to actually testing if any of this works. I assume you've created a new project, and the MemoryManager class. And added all our new read functions.
Create a variable of the MemoryManager class. (Top of your form class? For demo purposes. I won't go into variable scoping) and use it!
Code:
Public Class Form1
Private _memManager as New MemoryManager 'New word is important! -I'm not explaining this.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If _memManager.TryAttachToProcess("3D Pinball") Then
Dim _myScore as Int32 = _memManager.ReadInt32(&HAC4E4C)
' Done using MemoryManager for reading. Close it.
_memManager.DetachFromProcess()
MessageBox.Show("Your current 3D Pinball score is: " & _myScore.ToString())
Else
MessageBox.Show("MemoryManager unable to attach to 3D Pinball for ram reading")
End If
End Sub
End Class
That's it. You can now go poking around, trying to read the memory of different programs. But please don't. 
-Andrew