Memory Modification Tutorial & Template
Useful snippets for creation game bots, trainers and other programs with ability to read and write memory of selected process.
Code Features Focused On For Using Memory Template:
ToolHelp Win32 API Function:
- - Arguments to retrieve the PID of your game
- Variables for Calling the Function
Write Memory Procedures
- - Arguments to write to the memory
- Variable to Pass the ProcessID to WriteMemoryProcess
- Calling the the procedure for WriteMemoryProcess:
- -1 byte (byte)
-2 bytes(word)
-4 bytes(Cardinal)
-Byte Array(Array of Bytes)
Timer Procedures
- -Setting Timer Interval Speed
-Using Virtual Key Codes to Activate Hotkey
Instructions for operating this memory modification template.
and
Functional Delphi Memory Modification Trainer Source Attached.
Lets get right to it, I'm working with Delphi 2005, Delphi 7 shouldn't be
much different, so Open Delphi Go to >
File > New > VCL Forms Application -Delphi for Win32
You will be presented with a blank GUI form and this skeleton code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
end.
Now before you can use the API Function we have to tell Delphi we will be utilizing the ToolHelp 32 API so add it to the USES clause
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, tlhelp32;
Lets begin by taking a look at the Toolhelp API function arguements we will be using.
function GetProcessID(Const ExeFileName: string; var ProcessId: integer): boolean;
First we will be adding the variables we need to use the toolhelp32 function that retreives
the ProcessID by looping through your running processes looking for the target process " SOME_GAME.exe".
We will need 1 Const String ( Some_Game.exe) and 1 integer variable (for holding the process ID integer) to
call this function, Boolean just means its either true or false ( SOME_GAME.exe is running or not)
So add them.
var
Form1: TForm1;
PidID : integer;
Const
ProgramName = ' SOME_GAME.exe';
Lets take a look at the whole function though you just have to focus on the arguements for now.
function GetProcessID(Const ExeFileName: string; var ProcessId: integer): boolean;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
result := false;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := Sizeof(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while integer(ContinueLoop) <> 0 do begin
if (StrIComp(PChar(ExtractFileName(FProcessEntry32.sz ExeFile)), PChar(ExeFileName)) = 0)
or (StrIComp(FProcessEntry32.szExeFile, PChar(ExeFileName)) = 0) then begin
ProcessId:= FProcessEntry32.th32ProcessID;
result := true;
break;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
Now lets make procedures you can easily call that writes to the games memory.
Remember:
byte = 1 byte
word = 2 bytes
cardinal = 4 bytes
So we will use those data lenghts types to make quick pokes
This procedure will write 1 byte to the games memory
procedure poke1(Address: Cardinal; Data: Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(?, Pointer(Address), @Data, SizeOf(Data), Written);
end;
Lets take a look at the function argument:
procedure poke1(Address: Cardinal; Data: Byte);
So we will be calling it with the address(offset) and the Data(opcodes) to modify it to.
The data type, in this case "Byte" dictates the size.
Lets take a look at the WriteProcessMemory part of the function.
WriteProcessMemory(?, Pointer(Address), @Data, SizeOf(Data), Written);
The compiler won't like that question mark there, I just put it there so you would notice it.
We need a process ID to write to, we don't know the process ID until our GetProcessID API function has it, so we will need a global variable there. Add your Global variable, mine is PidHandle
var
Form1: TForm1;
PidHandle: integer;
PidID : integer;
So then we will have
procedure poke1(Address: Cardinal; Data: Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
Data "Word" for 2 bytes
procedure poke2(Address: Cardinal; Data: Word);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
Data "Cardinal" for 4 bytes
procedure poke4(Address: Cardinal; Data: Cardinal);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
So the only difference in these procedures is length of data being written, 1, 2 and 4 bytes.
Great for a few NOPs or small pokes but writing a string of opcodes using small data types would be rather annoying, so to handle many opcodes we will specify the data as being an "array of byte".
procedure pokeX(Address: Cardinal; Data: array of Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
Now we have are functions setup, we have to call them.
Switch to Form View, look at your Tool Palette > Categories > Standard > TButton.
Put a button on your forum, double click the form button to enter Code view inside the
button procedure.
It automatically writes the needed types that have to add here:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
And the button click start procedure is generated in the code section.
procedure TForm1.Button1Click(Sender: TObject);
begin
end;
Now our "ProgramName" Const String equals ' SOME_GAME.exe' and we declared an Integer Variable 'PidId' to hold ProcessID when the function is called.
We will call it with an If statement so only if the ' SOME_GAME.exe' Process is found the code inside the if statement continue to execute
if GetProcessID(ProgramName, PidId) then begin
Now remember that PidHandle Integer variable we declared and use for WriteMemoryProcess? The PidId can now pass the true processID.
So now we can prepare the SOME_GAME.exe Process memory to be modified
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
And we can call our poke procedures now with the address and
opcodes your writing, then close PidHandle.
Poke1($401000, $90);
poke2($401001, $9090);
poke4($401003, $90909090);
closehandle(PidHandle);
Lets take a look at the whole Button Procedure
procedure TForm1.Button1Click(Sender: TObject);
begin
if GetProcessID(ProgramName, PidId) then
begin
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
poke1($401000, $90);
poke2($401001, $9090);
poke4($401003, $90909090);
closehandle(PidHandle);
end;
end;
Great, so lets take a look at everything we have so far.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, tlhelp32, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
PidHandle: integer;
PidID : integer;
Const
ProgramName = ' SOME_GAME.exe';
implementation
{$R *.dfm}
function GetProcessID(Const ExeFileName: string; var ProcessId: integer): boolean;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
result := false;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := Sizeof(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while integer(ContinueLoop) <> 0 do begin
if (StrIComp(PChar(ExtractFileName(FProcessEntry32.sz ExeFile)), PChar(ExeFileName)) = 0)
or (StrIComp(FProcessEntry32.szExeFile, PChar(ExeFileName)) = 0) then begin
ProcessId:= FProcessEntry32.th32ProcessID;
result := true;
break;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
//Write 1 byte to memory
procedure poke1(Address: Cardinal; Data: Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Write 2 bytes to memory
procedure poke2(Address: Cardinal; Data: Word);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Write 4 bytes to memory
procedure poke4(Address: Cardinal; Data: Cardinal);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Write an Array of bytes to memory
procedure pokeX(Address: Cardinal; Data: Array of Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Example Function Call 1
procedure TForm1.Button1Click(Sender: TObject);
begin
if GetProcessID(ProgramName, PidId) then
begin
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
poke1($401000, $90);
poke2($401001, $9090);
poke4($401003, $90909090);
closehandle(PidHandle);
end;
end;
end.
If you need to write in a long string of opcodes Declare the Array of byte
Var
Form1: TForm1;
PidHandle: integer;
PidID : integer;
byteArr : Array of byte;
And this is an example of a Byte Array in a new button that was added.
procedure TForm1.Button2Click(Sender: TObject);
begin
if GetProcessID(ProgramName, PidId) then
begin
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
SetLength(byteArr, 16);
byteArr[0] := $8B;
byteArr[1] := $71;
byteArr[2] := $10;
byteArr[3] := $0F;
byteArr[4] := $85;
byteArr[5] := $6A;
byteArr[6] := $9D;
byteArr[7] := $FD;
byteArr[8] := $FF;
byteArr[9] := $83;
byteArr[10] := $7E;
byteArr[11] := $0C;
byteArr[12] := $00;
byteArr[13] := $0F;
byteArr[14] := $85;
byteArr[15] := $60;
pokeX($401007, byteArr);
SetLength(byteArr, 15)
closehandle(PidHandle);
end;
end;
On to the Timer and Hotkeys, Toggle View to see the form, Tool Palette> Categories again,
this time choose System and pick Ttimer, drop it on your form.
Select the Timer1 on your form, go to Object Inspector > Properties, set the timer intervals to
200 ms. Double click on the form to jump within the timer procedure.
Pick your virtual key
I am going to use F1 (VK_F1)
We are just going to check GetAsyncKeyState, if your hotkey is pressed then click the button.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if
(GetAsyncKeyState(VK_F1) <> 0)
then Button1.Click;
end;
One last thing, like this if the SOME_GAME.exe process is not found, it will do nothing
since all the code is inside the if statement, lets create a message if the process is not
located.
procedure TForm1.Button1Click(Sender: TObject);
begin
if GetProcessID(ProgramName, PidId) then
begin
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
poke1($401000, $90);
poke2($401001, $9090);
poke4($401003, $90909090);
closehandle(PidHandle);
end else
begin
MessageDlg('Start SOME_GAME First.', mtwarning, [mbOK],0);
end;
end;
Thats it, lets see the whole source code .
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, tlhelp32, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Timer1: TTimer;
Label1: TLabel;
Label18: TLabel;
Label2: TLabel;
procedure Timer1Timer(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
PidHandle: integer;
PidID : integer;
byteArr : Array of byte;
Const
ProgramName = ' SOME_GAME.exe';
implementation
{$R *.dfm}
// tlhelp32 function to Loop through processes and locate your target
function GetProcessID(Const ExeFileName: string; var ProcessId: integer): boolean;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
result := false;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := Sizeof(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while integer(ContinueLoop) <> 0 do begin
if (StrIComp(PChar(ExtractFileName(FProcessEntry32.sz ExeFile)), PChar(ExeFileName)) = 0)
or (StrIComp(FProcessEntry32.szExeFile, PChar(ExeFileName)) = 0) then begin
ProcessId:= FProcessEntry32.th32ProcessID;
result := true;
break;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
//Write 1 byte to memory
procedure poke1(Address: Cardinal; Data: Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Write 2 bytes to memory
procedure poke2(Address: Cardinal; Data: Word);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Write 4 bytes to memory
procedure poke4(Address: Cardinal; Data: Cardinal);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Write an Array of bytes to memory
procedure pokeX(Address: Cardinal; Data: Array of Byte);
var
Written: Cardinal;
begin
WriteProcessMemory(PidHandle, Pointer(Address), @Data, SizeOf(Data), Written);
end;
//Example Function Call 1
procedure TForm1.Button1Click(Sender: TObject);
begin
if GetProcessID(ProgramName, PidId) then
begin
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
poke1($401000, $90);
poke2($401001, $9090);
poke4($401003, $90909090);
closehandle(PidHandle);
end else
begin
MessageDlg('Start SOME_GAME First.', mtwarning, [mbOK],0);
end;
end;
//Example Function Call 2
procedure TForm1.Button2Click(Sender: TObject);
begin
if GetProcessID(ProgramName, PidId) then
begin
PidHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PidId);
SetLength(byteArr, 16);
byteArr[0] := $8B;
byteArr[1] := $71;
byteArr[2] := $10;
byteArr[3] := $0F;
byteArr[4] := $85;
byteArr[5] := $6A;
byteArr[6] := $9D;
byteArr[7] := $FD;
byteArr[8] := $FF;
byteArr[9] := $83;
byteArr[10] := $7E;
byteArr[11] := $0C;
byteArr[12] := $00;
byteArr[13] := $0F;
byteArr[14] := $85;
byteArr[15] := $60;
pokeX($401007, byteArr);
SetLength(byteArr, 15);
closehandle(PidHandle);
end else
begin
MessageDlg('Start SOME_GAME First.', mtwarning, [mbOK],0);
end;
end;
// Timer to Detect Hotkey and Execute your Buttons Code
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if
(GetAsyncKeyState(VK_F1) <> 0)
then Button1.Click;
end;
end.
Created by Dubbls