Read / Write memory (beginner level)

Get help with using AutoHotkey and its commands and hotkeys
r3dp1ll

Read / Write memory (beginner level)

25 Aug 2018, 14:02

Hello everyone,


Context:
That's it, I'm a beginner and I have a project: to create a trainer with AHK! For that, I need to access the memory of the game.
And I do not know how to do it despite the fact that I read a lot of threads on it.

I chose the AssaultCube game because it is not protected by an anticheat. With the help of cheat engine, I could find the pointers. Sometimes there were several levels!
I have the addresses of the health, the armor, the ammunition (the stock and in the magazine, grenades, the activation of the double gun etc ....)
All these values ​​are in solo mode (not online, so I can learn and it's not for ez kill guys who did not ask anything).

This is what I call the "player object pointer": ac_client.exe + 10F4F4

And here are some other addresses like this:
Health: ac_client.exe + 10F4F4 + offset F8
Armor: ac_client.exe + 10F4F4 + offset FC
Rifle ammo in mag: ac_client.exe + 10F4F4 + offset 150

I did some research and apparently I need to use :
OpenProcess ()
ReadProcessMemory ()
WriteProcessMemory ()



The objective:
Only there is not a single tutorial for beginners and I do not understand the code that I find here and there .... I need people to help me step by step.
I do not go out on my own. I tried a lot of things, besides it seems that some things only work in 32bit other 64bits,
and that according to the version of the OS some things do not work either.
I am under windows 10 64bits. The game is 32bits.
Do not think I'm lazy, I really spent a lot of time searching, and I did my homework look:


Let's go:
Good the first step open the process:
I think I can get the PID and the hwnd


here is my code:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

ReadMemory(MADDRESS=0x15B29DD0,PROGRAM="AssaultCube",BYTES=4)
{
   Static OLDPROC, ProcessHandle
   VarSetCapacity(MVALUE, BYTES,0)
   If PROGRAM != %OLDPROC%
   {
      WinGet, pid, pid, % OLDPROC := PROGRAM
      ProcessHandle := ( ProcessHandle ? 0*(closed:=DllCall("CloseHandle"
      ,"UInt",ProcessHandle)) : 0 )+(pid ? DllCall("OpenProcess"
      ,"Int",16,"Int",0,"UInt",pid) : 0)
   }
   If (ProcessHandle) && DllCall("ReadProcessMemory","UInt",ProcessHandle,"UInt",MADDRESS,"Str",MVALUE,"UInt",BYTES,"UInt *",0)
   { 
       Loop % BYTES
           Result += *(&MVALUE + A_Index-1) << 8*(A_Index-1)
       Return Result
   }
   return !ProcessHandle ? "Handle Closed:" closed : "Fail"
}

BaseAdress := 0x400000
offsettoPlayerObjectPointer := 0x10F4F4
PlayerObjectPointer := 0
RifleAmmoLoad := 0

f1::
WinGet, AssaultCubePID, PID, AssaultCube
MsgBox %AssaultCubePID%
return

f2::
AssaultCubehWnd := WinExist("ahk_exe ac_client.exe")
MsgBox %AssaultCubehWnd%
return

f3::
winwait AssaultCube
StartTime := A_TickCount

loop 1000
value:=ReadMemory(BaseAdress,"AssaultCube")

ElapsedTime := A_TickCount - StartTime
msgbox, Memory address 0x400000 = %value%`nTake %ElapsedTime% ms to loop 1000 times
return

f4::
winwait AssaultCube
StartTime := A_TickCount

loop 1000
value:=ReadMemory(BaseAdress,"AssaultCube")

ElapsedTime := A_TickCount - StartTime
MsgBox %value%
PlayerObjectPointer = %value%
MsgBox %PlayerObjectPointer%
PlayerObjectPointer += %offsettoPlayerObjectPointer%
MsgBox PlayerObjectPointer = %PlayerObjectPointer%

winwait AssaultCube
StartTime := A_TickCount

loop 1000
value:=ReadMemory(PlayerObjectPointer,"AssaultCube")

ElapsedTime := A_TickCount - StartTime
msgbox, Memory address PlayerObjectPointer = %value%`nTake %ElapsedTime% ms to loop 1000 times
return
Step 1: OpenProcess ()! Done?

With F1, I can see that I have a value for the PID that changes constantly. Ex: 12988.
Does it look like a PID ? ^^

With F2, I can see that I have a value for the hwnd which changes constantly. Ex: 0x4f0dd6.
It looks like a Hwnd ? ^^




Step 2: ReadProcessMemory ()! Arf, it makes me crazy!
From this post: https://autohotkey.com/boards/viewtopic.php?t=44241
My game has a static address! Who is: 0x00400000

With f3, I get the same value for address 0x400000, from cheat engine and my script! Oh yeah, that's it! I'm in memory! :superhappy:

With f4: Ok, I tried everything and anything on this F4. And it's anything now lol

For now I am stuck here.
1) What link can I make between my base address, and my cheat engine pointers
2) How to add offsets in my script?
=> I would like for the moment, by pressing f4, to display the same value of Player object pointer as cheat engine.
Then, with f6, add an offset to find lhealth for example.




Step 3: WriteProcessMemory ()! I should complete the step 2 -.- We'll see after.

If some can help me, for the moment with the step 2. Obviously, I do not really understand how to interact with the memory of the game.
Thank you for your help !
User avatar
CerpinTaxt
Posts: 13
Joined: 23 Dec 2017, 12:16

Re: Read / Write memory (beginner level)

26 Aug 2018, 00:53

I doubt this'll work on the first run, but let's try it out. I tried to rewrite a lot of what you had and make it least a lot clearer because it does seem like you're trying to learn and seeing code approached differently always helped me.

Code: Select all

#NoEnv
SetWorkingDir %A_ScriptDir%

BaseAdress := 0x400000
offsetToPlayerObjectPointer := 0x10F4F4

f3::
	If WinExist("ahk_exe ac_client.exe")
	{
		result := ReadCubeMem(baseAddress)
		MsgBox, % "Memory address 0x400000 = " . result
	}
	return


f4::
	If WinExist("ahk_exe ac_client.exe")
	{
		PlayerObjectPointer := ( ReadCubeMem(baseAddress) + offsetToPlayerObjectPointer )
		result := ReadCubeMem(PlayerObjectPointer)
		MsgBox, % "Memory address PlayerObjectPointer = " . result
	}
	return


f5::
	ReadCubeMem(-1) ; call function and pass -1 to close any previously open handles
	return
	
	
	


ReadCubeMem(baseAddress := 0x15B29DD0)
{
	static AcPID, AcHwnd, hProcess, remoteMemAddress, bytes := 4

	If (baseAddress == -1)
	{
		DllCall("CloseHandle", "Ptr", hProcess)
		return
	}

	; Waits up to 1 second to get the PID of the program.
	; In reality, it will be near instant if the exe is running
	Process, Wait, ac_client.exe, 1
	AcPID := ErrorLevel

	; this means we failed already, i.e. the process doesn't exist
	If (AcPID == 0)
		return

	If (AcHwnd := WinExist("ahk_exe ac_client.exe"))
	{
		MsgBox, % "We've made it at least as far as line " . A_LineNumber . "`n"
				. "AssaultCube's PID: " . AcPID		. "`n"
				. "AssaultCube's hwnd: " . AcHwnd
	}

	; Retrieve a handle to the AssaultCube process so we can operate within its memory space
	; HANDLE OpenProcess(
	  ; DWORD dwDesiredAccess,	; we want ( 0x8 | 0x10 | 0x20)
	  ; BOOL  bInheritHandle,	; we don't need this
	  ; DWORD dwProcessId		; the PID of the process we're opening the handle for
	; );
	hProcess := DllCall("OpenProcess"
						, "UInt", ( PROCESS_VM_OPERATION := 0x8 | PROCESS_VM_READ := 0x10 | PROCESS_VM_WRITE := 0x20 )
						, "Int" , false
						, "UInt", AcPID
						, "Ptr") ; <- this 4th type with no parameter indicates we want the return value to be a pointer

	; We shouldn't go further if the above didn't work
	If !(hProcess)
		return

	; Allocate a *local* buffer, which means clear out enough space in AHK's memory space for this variable
	; 4 bytes in this case
	VarSetCapacity(memVal, bytes, 0)

	; BOOL WINAPI ReadProcessMemory(
	  ; _In_  HANDLE  hProcess,
	  ; _In_  LPCVOID lpBaseAddress,
	  ; _Out_ LPVOID  lpBuffer,
	  ; _In_  SIZE_T  nSize,
	  ; _Out_ SIZE_T  *lpNumberOfBytesRead
	; );

	; &memVal means we're passing a pointer to memVal's memory address, not the variable's contents itself - it's still empty before this
	DllCall("ReadProcessMemory"
			, "Ptr", hProcess
			, "Ptr", startAddr
			, "Ptr", &memVal	
			, "UInt", bytes
			, "UInt", 0)

	; Frankly I'm not sure what's going on with this loop here, maybe somebody else can chime in
	; Is this something to do with 64 <-> 32 communication?
	; I'll leave it here in case it's important lol, but normally memVal should be readable by now.
	; So let's try it out. You can uncomment the loop and remove this line if it fails.
	
	; Loop % BYTES
	   ; Result += *(&memVal + (A_Index-1)) << 8*(A_Index-1)
	; Return Result
	
	return memVal
}
Last edited by CerpinTaxt on 26 Aug 2018, 03:27, edited 1 time in total.
Image
and with just one faint glance back into the sea
the mollusk lingers with its wandering eye
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

26 Aug 2018, 02:13

Why do you need to allocate memory in the other process? It's necessary if you call some code in that process that can only work with local memory. But ReadProcessMemory works between processes.
r3dp1ll

Re: Read / Write memory (beginner level)

26 Aug 2018, 03:21

Hi, @VismundCygnus

Your code seems really interesting. I study it. Unfortunately, it does not work. So I give you some info to fix it.

1) Without the end loop activated :

F3 gives me:
Msgbox with:
"We've made .... line 62
AssaultCube's PID: 4208
AssaultCube's hwnd: 0x2911fa"

and after another msgbox with:
"Memory Addresses 0x400000 ="

F4 gives me:
2x the Msgbox with:
"We've made .... line 62
AssaultCube's PID: 4208
AssaultCube's hwnd: 0x2911fa"

and after another msgbox with:
"Memory Addresses PlayerObjectPointer ="


2) With the end loop activated :
If I get uncomment the loop to activate it, now I have the same as above with a result this time, BUT it is still 0
ex: Memory addresses 0x400000 = 0



PS, i dont want if u need it but :
Console for the f3 (with uncommented loop):
Spoiler
console for f4 (with uncommented loop):
Spoiler
Hi, @YMP2

You, again ! You will become my private teacher, I will have to pay you! :D again thanks for the help.

Hmm, I do not know but my next step is to write code in this local memory no?
Using the values that I have: health, armor, etc ... And later, I also want to work on the recoil (understand how it works and remove it, or at worst compensate automatically) but also try to make a ESP, that would be a technical feat for me. Do you think that all this can be done without calling some code in that process that can only work with local memory. ?
User avatar
CerpinTaxt
Posts: 13
Joined: 23 Dec 2017, 12:16

Re: Read / Write memory (beginner level)

26 Aug 2018, 03:31

YMP2 wrote:Why do you need to allocate memory in the other process?
You don't! Honestly I was a bit tired when I was making that and not thinking it through all the way. Also I've only ever used OpenProcess and ReadProcessMemory in situations where I did need to have the remote process fill that buffer. But that is not the case here, so I've removed those extraneous bits. Derp. Thanks lol.

(And here is the code with all the comments removed because I know numerous big blocks of comments can muddle things up at times.)

Code: Select all

#NoEnv
SetWorkingDir %A_ScriptDir%

BaseAdress := 0x400000

offsetToPlayerObjectPointer := 0x10F4F4

f3::
	If WinExist("ahk_exe ac_client.exe")
	{
		result := ReadCubeMem(baseAddress)
		MsgBox, % "Memory address 0x400000 = " . result
	}
	return

f4::
	If WinExist("ahk_exe ac_client.exe")
	{
		PlayerObjectPointer := ( ReadCubeMem(baseAddress) + offsetToPlayerObjectPointer )
		result := ReadCubeMem(PlayerObjectPointer)
		MsgBox, % "Memory address PlayerObjectPointer = " . result
	}
	return

f5::
	ReadCubeMem(-1)
	return

ReadCubeMem(baseAddress := 0x15B29DD0)
{
	static AcPID, AcHwnd, hProcess, remoteMemAddress, bytes := 4

	If (baseAddress == -1)
	{
		DllCall("CloseHandle", "Ptr", hProcess)
		return
	}

	Process, Wait, ac_client.exe, 1
	AcPID := ErrorLevel

	If (AcPID == 0)
		return

	If (AcHwnd := WinExist("ahk_exe ac_client.exe"))
	{
		MsgBox, % "We've made it at least as far as line " . A_LineNumber . "`n"
				. "AssaultCube's PID: " . AcPID		. "`n"
				. "AssaultCube's hwnd: " . AcHwnd
	}

	hProcess := DllCall("OpenProcess", "UInt", ( PROCESS_VM_OPERATION := 0x8 | PROCESS_VM_READ := 0x10 | PROCESS_VM_WRITE := 0x20 )
						, "Int" , false, "UInt", AcPID, "Ptr")

	If !(hProcess)
		return
		
	VarSetCapacity(memVal, bytes, 0)

	DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", startAddr, "Ptr", &memVal, "UInt", bytes, "UInt", 0)
	
	return memVal
}
Image
and with just one faint glance back into the sea
the mollusk lingers with its wandering eye
r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

26 Aug 2018, 05:37

Ok,

Always the same thing, I can only have the PID and the hwnd, as I had them at the beginning.
Other functions does not return nothing.

I'm here with my pointers and I do not know how to use them from the base address. I'm still searching... If u have any idea...
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

26 Aug 2018, 07:23

Try if this will work for you. I divided the code among several functions with self-explanatory names. Does it give you the right value for PlayerObjectPointer?
If you want to read numbers of types other than "ptr", use the types that the NumGet() function uses: "short", "int", "uint", etc.

Code: Select all

SetFormat, Integer, Hex
BaseAddress := 0x400000
OffsetToPlayerObjectPointer := 0x10F4F4

F1::
    PlayerObjectPointer := ""
    Pid := PidFromWindow("AssaultCube")
    If (Pid) {
        hProcess := HandleFromPid(Pid)
        If (hProcess) {
            Address := BaseAddress + OffsetToPlayerObjectPointer
            PlayerObjectPointer := NumberFromProcess(hProcess, Address, "ptr")
            CloseHandle(hProcess)
        }
    }
    MsgBox, %PlayerObjectPointer%
    Return

; ============================ Functions =======================================

PidFromWindow(Window)
{
    WinGet, PID, PID, %Window%
    Return PID
}

HandleFromPid(Pid)
{
    static PROCESS_VM_OPERATION := 0x8, PROCESS_VM_READ := 0x10, PROCESS_VM_WRITE := 0x20
    Return DllCall("OpenProcess", "uint", PROCESS_VM_READ
                                        | PROCESS_VM_WRITE
                                        | PROCESS_VM_OPERATION
                                , "int", False, "uint", Pid, "ptr")
}

NumberFromProcess(hProcess, Address, NumType)
{
    VarSetCapacity(buf, 8, 0)
    If (NumType = "ptr") {
        NumType := ProcessBitness(hProcess) = 32 ? "uint" : "uint64"
    }
    If DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", Address
                                  , "ptr", &buf, "ptr", 8, "ptr", 0) {
        Return NumGet(buf, 0, NumType)
    }
    Return ""
}

ProcessBitness(hProcess)
{
    If (!A_Is64bitOS) {
        Return 32
    }
    IsWow64 := False
    DllCall("IsWow64Process", "ptr", hProcess, "uint *", IsWow64)
    Return IsWow64 ? 32 : 64
}

CloseHandle(hProcess)
{
    DllCall("CloseHandle", "ptr", hProcess)
}

; ==============================================================================
r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

26 Aug 2018, 10:53

In fact, I did other researchs, other tests in the meantime and I ended up finding:

Code: Select all

#NoEnv
SetWorkingDir %A_ScriptDir%

ReadMemory(MADDRESS, pOffset = 0, PROGRAM = "ac_client.exe")
{
         Process, wait, %PROGRAM%, 0.5
         pid = %ErrorLevel%
         if pid = 0
         {
            return
         }
         VarSetCapacity(MVALUE,4)
         ProcessHandle := DllCall("OpenProcess", "Int", 24, "Char", 0, "UInt", pid, "UInt")
         DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "Str", MVALUE, "Uint",4, "Uint *",0)
         DllCall("CloseHandle", "int", ProcessHandle)
         Loop 4
            result += *(&MVALUE + A_Index-1) << 8*(A_Index-1)
            
         return result
}

DecToHex(Value)
{
    SetFormat IntegerFast, Hex
    Value += 0
    Value .= "" ;required due to 'fast' mode
    SetFormat IntegerFast, D
    Return Value
}

ReadMemory_Str(MADDRESS=0, pOffset = 0, PROGRAM = "AssaultCube", length = 0 , terminator = "")
{
   Static OLDPROC, ProcessHandle
   VarSetCapacity(MVALUE,4,0)
   If PROGRAM != %OLDPROC%
   {
      WinGet, pid, pid, % OLDPROC := PROGRAM
      ProcessHandle := ( ProcessHandle ? 0*(closed:=DllCall("CloseHandle"
      ,"UInt",ProcessHandle)) : 0 )+(pid ? DllCall("OpenProcess"
      ,"Int",16,"Int",0,"UInt",pid) : 0) ;PID is stored in value pid
   }
    If (MADDRESS = 0) ; the above expression/syntax does my head, hence easy close handle
        closed:=DllCall("CloseHandle","UInt",ProcessHandle)
    If ( length = 0) ; read until terminator found
    {
        teststr =
        Loop
        {
            Output := "x"  ; Put exactly one character in as a placeholder. used to break loop on null
            tempVar := DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "str", Output, "Uint", 1, "Uint *", 0)
            if (ErrorLevel or !tempVar)
               return teststr
            if (Output = terminator)
              break
            teststr .= Output
            MADDRESS++
        }
        return, teststr  
        }        
    Else ; will read until X length
    {
         teststr =
         Loop % length
         {
            Output := "x"  ; Put exactly one character in as a memory placeholder.
            tempVar := DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "str", Output, "Uint", 1, "Uint *", 0)
            if (ErrorLevel or !tempVar)
              return teststr
            teststr .= Output
            MADDRESS++
         }
          return, teststr  
    }
        
        
}

getProcessBaseAddress(WindowTitle, Hex=1, MatchMode=3)    ;WindowTitle can be anything ahk_exe ahk_class etc
{
    SetTitleMatchMode, %MatchMode%    ;mode 3 is an exact match
    WinGet, hWnd, ID, %WindowTitle%
    ; AHK32Bit A_PtrSize = 4 | AHK64Bit - 8 bytes
    BaseAddress := DllCall(A_PtrSize = 4
        ? "GetWindowLong"
        : "GetWindowLongPtr", "Uint", hWnd, "Uint", -6)
    if Hex
        return dectohex(BaseAddress)
    else return BaseAddress
}

F5::
Assualthwnd := getProcessBaseAddress("AssaultCube")
MsgBox "Hwnd of the game %Assualthwnd%"
baseAdressDec := Assualthwnd + 0x10f4f4 ;base address
MsgBox "base adress of the game in dec %baseAdressDec%"
baseAdressHex := DecToHex(baseAdressDec)
MsgBox "base adress of the game in hex %baseAdressHex%"
valuePlayerObjectPointer := ReadMemory(baseAdressHex)
MsgBox "Value of valuePlayerObjectPointer in dec %valuePlayerObjectPointer%"
valuePlayerObjectPointerHex := DecToHex(valuePlayerObjectPointer)
MsgBox "Value of valuePlayerObjectPointer in hex %valuePlayerObjectPointerHex%"
HealthAdress := valuePlayerObjectPointerHex + 0xf8
MsgBox "Health adress in dec %HealthAdress%"
HealthAdressHex := DecToHex(HealthAdress)
MsgBox "Health adress in Hex %HealthAdressHex%"
valueHealthAdressDec := ReadMemory(HealthAdressHex)
MsgBox "Health value in Dec %valueHealthAdressDec%"
valueHealthAdressHex := DecToHex(valueHealthAdressDec)
MsgBox "Health value in Hex %valueHealthAdressHex%"
return
It's working. it's gave me the health's value. I think my problem was that I had to convert the values received in Hex simply, to addtion and search!

Now I'm going to look at how to write in memory. If i have some other troubles i came back !

Than you for your help @YMP2 :)

PS : I just tested your code! it also works : it gives me the value of the pointer's address to object player !
r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

27 Aug 2018, 04:35

Ok, Can someone explain to me how to use a writeprocessmemory? I use several functions found here, and nothing works. I think I'm going wrong with the parameters or so ... I do not know. Please a little help would be welcome!
I want to try to change the amount of health points, 100 to 200.

Code: Select all

#NoEnv
SetWorkingDir %A_ScriptDir%

;                       ///// READ MEMORY /////

ReadMemory(MADDRESS, pOffset = 0, PROGRAM = "ac_client.exe")
{
         Process, wait, %PROGRAM%, 0.5
         pid = %ErrorLevel%
         if pid = 0
         {
            return
         }
         VarSetCapacity(MVALUE,4)
         ProcessHandle := DllCall("OpenProcess", "Int", 24, "Char", 0, "UInt", pid, "UInt")
         DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "Str", MVALUE, "Uint",4, "Uint *",0)
         DllCall("CloseHandle", "int", ProcessHandle)
         Loop 4
            result += *(&MVALUE + A_Index-1) << 8*(A_Index-1)
            
         return result
}

DecToHex(Value)
{
    SetFormat IntegerFast, Hex
    Value += 0
    Value .= "" ;required due to 'fast' mode
    SetFormat IntegerFast, D
    Return Value
}

ReadMemory_Str(MADDRESS=0, pOffset = 0, PROGRAM = "AssaultCube", length = 0 , terminator = "")
{
   Static OLDPROC, ProcessHandle
   VarSetCapacity(MVALUE,4,0)
   If PROGRAM != %OLDPROC%
   {
      WinGet, pid, pid, % OLDPROC := PROGRAM
      ProcessHandle := ( ProcessHandle ? 0*(closed:=DllCall("CloseHandle"
      ,"UInt",ProcessHandle)) : 0 )+(pid ? DllCall("OpenProcess"
      ,"Int",16,"Int",0,"UInt",pid) : 0) ;PID is stored in value pid
   }
    If (MADDRESS = 0) ; the above expression/syntax does my head, hence easy close handle
        closed:=DllCall("CloseHandle","UInt",ProcessHandle)
    If ( length = 0) ; read until terminator found
    {
        teststr =
        Loop
        {
            Output := "x"  ; Put exactly one character in as a placeholder. used to break loop on null
            tempVar := DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "str", Output, "Uint", 1, "Uint *", 0)
            if (ErrorLevel or !tempVar)
               return teststr
            if (Output = terminator)
              break
            teststr .= Output
            MADDRESS++
        }
        return, teststr  
        }        
    Else ; will read until X length
    {
         teststr =
         Loop % length
         {
            Output := "x"  ; Put exactly one character in as a memory placeholder.
            tempVar := DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "str", Output, "Uint", 1, "Uint *", 0)
            if (ErrorLevel or !tempVar)
              return teststr
            teststr .= Output
            MADDRESS++
         }
          return, teststr  
    }
        
        
}

getProcessBaseAddress(WindowTitle, Hex=1, MatchMode=3)    ;WindowTitle can be anything ahk_exe ahk_class etc
{
    SetTitleMatchMode, %MatchMode%    ;mode 3 is an exact match
    WinGet, hWnd, ID, %WindowTitle%
    ; AHK32Bit A_PtrSize = 4 | AHK64Bit - 8 bytes
    BaseAddress := DllCall(A_PtrSize = 4
        ? "GetWindowLong"
        : "GetWindowLongPtr", "Uint", hWnd, "Uint", -6)
    if Hex
        return dectohex(BaseAddress)
    else return BaseAddress
}






;                                   /////// WRITE FUNCTIONS //////////


    WriteProcessMemory(win_id, addr, addr_value, addr_offset = 0, value_size = 4)
    {
        if (win_id > 0)
        {
            addr += addr_offset

            WinGet, proc_id, PID, ahk_id %win_id%
            ProcessHandle := DllCall("OpenProcess", "UInt", 0x28, "char", 0, "UInt", proc_id, "UInt")

            write_success := DllCall("WriteProcessMemory", "UInt", ProcessHandle, "UInt", addr, "UInt *", addr_value, "Uint", value_size, "Uint *", BytesWritten)

            DllCall("CloseHandle", "int", ProcessHandle)
        }

        return, % write_success
    }

f1::
WinGet, AssaultCubePID, PID, AssaultCube
MsgBox %AssaultCubePID%
return

F5::
Assualthwnd := getProcessBaseAddress("AssaultCube")
MsgBox "Hwnd of the game %Assualthwnd%"
baseAdressDec := Assualthwnd + 0x10f4f4 ;base address
MsgBox "base adress of the game in dec %baseAdressDec%"
baseAdressHex := DecToHex(baseAdressDec)
MsgBox "base adress of the game in hex %baseAdressHex%"
valuePlayerObjectPointer := ReadMemory(baseAdressHex)
MsgBox "Value of valuePlayerObjectPointer in dec %valuePlayerObjectPointer%"
valuePlayerObjectPointerHex := DecToHex(valuePlayerObjectPointer)
MsgBox "Value of valuePlayerObjectPointer in hex %valuePlayerObjectPointerHex%"
HealthAdress := valuePlayerObjectPointerHex + 0xf8
MsgBox "Health adress in dec %HealthAdress%"
HealthAdressHex := DecToHex(HealthAdress)
MsgBox "Health adress in Hex %HealthAdressHex%"
valueHealthAdressDec := ReadMemory(HealthAdressHex)
MsgBox "Health value in Dec %valueHealthAdressDec%"
valueHealthAdressHex := DecToHex(valueHealthAdressDec)
MsgBox "Health value in Hex %valueHealthAdressHex%"
return

f6::
WinGet, win_id, ID, AssaultCube
WriteProcessMemory(win_id, HealthAdressHex, 200, addr_offset = F8, value_size = 4)
MsgBox "Health value in Hex %HealthAdressHex%"
return

f7::
WinGet, win_id, ID, AssaultCube
MsgBox "%win_id%"
return
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

27 Aug 2018, 06:08

You use the 0xF8 offset twice. Since you've already added it to the address, you shouldn't pass it again to WriteMemory.
r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

27 Aug 2018, 18:26

Thank you for your answer :)

Yep, but before i tried :
WriteProcessMemory(win_id, valueHealthAdressHex, 200, addr_offset = 0x0, value_size = 4)
don't work, i tried that too :
WriteProcessMemory(win_id, valuePlayerObjectPointerHex, 200, addr_offset = 0xF8, value_size = 4)
don't work too.
so i'm totally lost and i tried what u seen.

Each time it returns me :
Spoiler
i had a "write_success" so function seems to work without issues.. but nothing really happens in the values...
i don't understand :(

edit: of course, for each try, i press F5 before, to set up the variables.
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

27 Aug 2018, 23:04

If you can read the right value of health, you can read it again after the write operation and make sure the value has changed. If the game doesn't seem to see the change, it's another matter. Is the health of 200 even possible in it? Maybe try a lower value, like 80 or 50.
r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

28 Aug 2018, 02:36

When i use the key F6, (the one that allows me to change the value), I check with the msgbox if the value has changed, but it always displays the same as before (0x64). I just did it on purpose to make sure that the value had not changed without the game refreshing this data. Knowing that it would be unfortunate since when I change the value with cheat engine it instantly changes the value on the screen.

I also thought that the game was not protected in reading but can be in writing! But in fact with cheat engine I could do both without problem then ... Unless cheat engine automatically fall protection that my script, actually, is unable to do...

I did not think it wasn't possible to put more than 100, since with cheat engine I can put 200 and it appears and it works. Well, in doubt, I just tried to put an 80 ... No success! The value returns (0x64) after calling my function! :(

Thank you for giving me directions to follow! It could have come from there! Other ideas?

Ps : the download game link (54mb) for those who would be interested :
Spoiler
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

28 Aug 2018, 05:14

OK, I have installed the game and tried to write to it with my set of functions. It worked and the game saw the change. Try my code below. If it works for you, then you should probably make sure you calculate the address correctly in your code. Press F3 to see the current health, then F4 to change it to 200, then again F3 to see the new health, then look in the game itself.

Code: Select all

#SingleInstance, Force
ProcessBase := ProcessBaseFromWindow("AssaultCube")
PlayerObjectPointer := NumberFromWindowProcess("AssaultCube", ProcessBase + 0x10F4F4, "ptr")
HealthAddress := PlayerObjectPointer + 0xF8

F3::
    Health := NumberFromWindowProcess("AssaultCube", HealthAddress, "uint")
    MsgBox, %Health%
    Return

F4:: NumberToWindowProcess(200, "AssaultCube", HealthAddress, "uint")


; ======================= Functions ============================================

NumberFromWindowProcess(Window, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromWindow(Window))
        Number := NumberFromProcess(hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
    CloseHandle(hProcess)
    Return Number
}

NumberToWindowProcess(Number, Window, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromWindow(Window))
        NumberToProcess(Number, hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
    CloseHandle(hProcess)
}

NumberFromExeProcess(Exe, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromExe(Exe))
        Number := NumberFromProcess(hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
    CloseHandle(hProcess)
    Return Number
}

NumberToExeProcess(Number, Exe, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromExe(Exe))
        NumberToProcess(Number, hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
    CloseHandle(hProcess)
}

PidFromWindow(Window)
{
    If !WinExist(Window) {
        Throw Exception("Window not found: " . Window)
    }
    WinGet, PID, PID
    Return PID
}

PidFromExe(Exe)
{
    Process, Exist, %Exe%
    If (!ErrorLevel) {
        Throw Exception("Process not found: " . Exe)
    }
    Return ErrorLevel
}

HandleFromPid(Pid)
{
    static PROCESS_VM_OPERATION := 0x8, PROCESS_VM_READ := 0x10, PROCESS_VM_WRITE := 0x20
    hProcess := DllCall("OpenProcess", "uint", PROCESS_VM_READ
                                        | PROCESS_VM_WRITE
                                        | PROCESS_VM_OPERATION
                                , "int", False, "uint", Pid, "ptr")
    If (!hProcess) {
        Throw Exception("OpenProcess failed: " . A_LastError)
    }
    Return hProcess
}

NumberFromProcess(hProcess, Address, NumType)
{
    VarSetCapacity(buf, 8, 0)
    If (NumType = "ptr") {
        NumType := ProcessBitness(hProcess) = 32 ? "uint" : "uint64"
    }
    If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", Address
                                   , "ptr", &buf, "ptr", 8, "ptr", 0) {
        Throw Exception("ReadProcessMemory failed: " . A_LastError)
    }
    Return NumGet(buf, 0, NumType)
}

NumberToProcess(Number, hProcess, Address, NumType)
{
    VarSetCapacity(buf, 8, 0)
    If (NumType = "ptr") {
        NumType := ProcessBitness(hProcess) = 32 ? "uint" : "uint64"
    }
    BytesToWrite := NumPut(Number, buf, 0, NumType) - &buf
    If !DllCall("WriteProcessMemory", "ptr", hProcess, "ptr", Address
                                    , "ptr", &buf, "ptr", BytesToWrite, "ptr", 0) {
        Throw Exception("WriteProcessMemory failed: " . A_LastError)
    }
}

ProcessBitness(hProcess)
{
    If (!A_Is64bitOS) {
        Return 32
    }
    IsWow64 := False
    DllCall("IsWow64Process", "ptr", hProcess, "uint *", IsWow64)
    Return IsWow64 ? 32 : 64
}

ProcessBaseFromWindow(Window)
{
    static GWL_HINSTANCE := -6
    Try {
        hWnd := WinExist(Window)
        If (!hWnd) {
            Throw Exception("Window not found: " . Window)
        }
        FuncName := A_PtrSize = 4 ? "GetWindowLong" : "GetWindowLongPtr"
        Base := DllCall(FuncName, "ptr", hWnd, "int", GWL_HINSTANCE, "ptr")
        If (!Base) {
            Throw Exception(FuncName . " failed: " . A_LastError)
        }
    } Catch e {
        MsgBox,, % e.What, % e.Message
        Exit
    }
    Return Base
}

CloseHandle(hProcess)
{
    DllCall("CloseHandle", "ptr", hProcess)
}
r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

28 Aug 2018, 14:44

Hey, your code works perfectly.

Is it reusable? As an object in C ++? Basically, if I call your functions and I just update the parameters normally it should work no ? On all games or so it may not be compatible with others?

Ok, it will be useful, I will keep them in my personal library, but there I learn nothing, except that my code is wrong and for now I do not know what I screwed! I'll try to understand your code it will explain to me surely what I did wrong.

There, I look and analyze the code. I understand a lot of things, I understand the idea but the syntax of times is hard for me. Too technical! So I will continue to analyze it and try to learn. If I block some passage of your code you think you have time and the desire to explain myself? Of course I will have researched BEFORE to try to understand alone to avoid you having to explain me 80% of your code ahhaha But you're going to have a job with me lol I'm really sorry lol Otherwise it's okay , you have helped me a lot and on many threads! :) I'm grateful !
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

29 Aug 2018, 01:28

Yes, you can use it with other processes. I can explain if you don't understand something in it. Don't feel sorry, I come to the forum when I have time and am willing to come, so it's my own decision. I had long wanted to write my own functions to read/write process memory, so that was a chance to do it at last.

It also works with the functions you used above. But they should be updated because they use the "UInt" type for handles and pointers, which is OK for a 32-bit process only.

Code: Select all

BaseAddress := getProcessBaseAddress("AssaultCube")
PlayerObjectPointer := ReadMemory(BaseAddress, 0x10F4F4, "ac_client.exe")

F3::
    Health := ReadMemory(PlayerObjectPointer, 0xF8, "ac_client.exe")
    MsgBox, %Health%
    Return

F4::
    hWnd := WinExist("AssaultCube")
    WriteProcessMemory(hWnd, PlayerObjectPointer, 200, 0xF8, 4)
    Return

; ==============================================================================

;                       ///// READ MEMORY /////

ReadMemory(MADDRESS, pOffset = 0, PROGRAM = "ac_client.exe")
{
         Process, wait, %PROGRAM%, 0.5
         pid = %ErrorLevel%
         if pid = 0
         {
            return
         }
         VarSetCapacity(MVALUE,4)
         ProcessHandle := DllCall("OpenProcess", "Int", 24, "Char", 0, "UInt", pid, "UInt")
         DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "Str", MVALUE, "Uint",4, "Uint *",0)
         DllCall("CloseHandle", "int", ProcessHandle)
         Loop 4
            result += *(&MVALUE + A_Index-1) << 8*(A_Index-1)
            
         return result
}

DecToHex(Value)
{
    SetFormat IntegerFast, Hex
    Value += 0
    Value .= "" ;required due to 'fast' mode
    SetFormat IntegerFast, D
    Return Value
}

ReadMemory_Str(MADDRESS=0, pOffset = 0, PROGRAM = "AssaultCube", length = 0 , terminator = "")
{
   Static OLDPROC, ProcessHandle
   VarSetCapacity(MVALUE,4,0)
   If PROGRAM != %OLDPROC%
   {
      WinGet, pid, pid, % OLDPROC := PROGRAM
      ProcessHandle := ( ProcessHandle ? 0*(closed:=DllCall("CloseHandle"
      ,"UInt",ProcessHandle)) : 0 )+(pid ? DllCall("OpenProcess"
      ,"Int",16,"Int",0,"UInt",pid) : 0) ;PID is stored in value pid
   }
    If (MADDRESS = 0) ; the above expression/syntax does my head, hence easy close handle
        closed:=DllCall("CloseHandle","UInt",ProcessHandle)
    If ( length = 0) ; read until terminator found
    {
        teststr =
        Loop
        {
            Output := "x"  ; Put exactly one character in as a placeholder. used to break loop on null
            tempVar := DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "str", Output, "Uint", 1, "Uint *", 0)
            if (ErrorLevel or !tempVar)
               return teststr
            if (Output = terminator)
              break
            teststr .= Output
            MADDRESS++
        }
        return, teststr  
        }        
    Else ; will read until X length
    {
         teststr =
         Loop % length
         {
            Output := "x"  ; Put exactly one character in as a memory placeholder.
            tempVar := DllCall("ReadProcessMemory", "UInt", ProcessHandle, "UInt", MADDRESS+pOffset, "str", Output, "Uint", 1, "Uint *", 0)
            if (ErrorLevel or !tempVar)
              return teststr
            teststr .= Output
            MADDRESS++
         }
          return, teststr  
    }
        
        
}

getProcessBaseAddress(WindowTitle, Hex=1, MatchMode=3)    ;WindowTitle can be anything ahk_exe ahk_class etc
{
    SetTitleMatchMode, %MatchMode%    ;mode 3 is an exact match
    WinGet, hWnd, ID, %WindowTitle%
    ; AHK32Bit A_PtrSize = 4 | AHK64Bit - 8 bytes
    BaseAddress := DllCall(A_PtrSize = 4
        ? "GetWindowLong"
        : "GetWindowLongPtr", "Uint", hWnd, "Uint", -6)
    if Hex
        return dectohex(BaseAddress)
    else return BaseAddress
}






;                                   /////// WRITE FUNCTIONS //////////


    WriteProcessMemory(win_id, addr, addr_value, addr_offset = 0, value_size = 4)
    {
        if (win_id > 0)
        {
            addr += addr_offset

            WinGet, proc_id, PID, ahk_id %win_id%
            ProcessHandle := DllCall("OpenProcess", "UInt", 0x28, "char", 0, "UInt", proc_id, "UInt")

            write_success := DllCall("WriteProcessMemory", "UInt", ProcessHandle, "UInt", addr, "UInt *", addr_value, "Uint", value_size, "Uint *", BytesWritten)

            DllCall("CloseHandle", "int", ProcessHandle)
        }

        return, % write_success
    }

r3dp1ll
Posts: 7
Joined: 26 Aug 2018, 03:27

Re: Read / Write memory (beginner level)

29 Aug 2018, 13:35

Ok, here I am again. My head is going to explode, your code is so complex for a newbie like me lol But I learned a lot. It took me a lot of time to look for the functions and dll you use and to try to understand their jobs. I did my best but there are still things that I do not understand and that I did not find on google. This is the first part of my translation work. It does not look like that, but it took a lot of time, but I learned a lot thanks to you!

I realize that I have many issues on the understanding of the operation of a program and of windows. I started ahk (and programming upside down). I was trying to do simple things: hotkey, mouse move, and I was only looking for these functions etc ...

I learn the basics of C ++, slowly, and I start from 0 and I learn much faster, because it is in variable order> function > Classes etc .... And it's in my language! On ahk, I have to look for function by function to understand. I do not have a global vision of programming. Unfortunately I do not have lessons on ahk in my language and in English is a little too specific language and it takes me a lot of time to understand and translate the English terms.

PS: I'm feel sorry because you help me a lot. Create script/program is something that fascinates me and your help is precious to me. But I cannot give you anything in exchange...

Anyway, my translation:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

#SingleInstance, Force
ProcessBase := ProcessBaseFromWindow("AssaultCube")
PlayerObjectPointer := NumberFromWindowProcess("AssaultCube", ProcessBase + 0x10F4F4, "ptr")
HealthAddress := PlayerObjectPointer + 0xF8

F3::
    Health := NumberFromWindowProcess("AssaultCube", HealthAddress, "uint")
    MsgBox, %Health%
    Return

F4:: NumberToWindowProcess(200, "AssaultCube", HealthAddress, "uint")


; ======================= Functions ============================================

NumberFromWindowProcess(Window, Address, NumType) ;2) Here it will be the second action of the script, we will try to know the PlayerObjectPointer
{
    Try { ; We try the following code if it does not work we jump to catch e
        hProcess := HandleFromPid(PidFromWindow(Window)) ; we create a variable called hProcess which will have the value of handleFromPid: then we jump to the function: PidFromWindow (Window) then to HandleFromPid (PidFromWindow (Window) Finally we will put as value the unique key obtained (handle)?
        Return NumberFromProcess(hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
}

NumberToWindowProcess(Number, Window, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromWindow(Window))
        NumberToProcess(Number, hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
}

NumberFromExeProcess(Exe, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromExe(Exe))
        Return NumberFromProcess(hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
}

NumberToExeProcess(Number, Exe, Address, NumType)
{
    Try {
        hProcess := HandleFromPid(PidFromExe(Exe))
        NumberToProcess(Number, hProcess, Address, NumType)
    } Catch e {
        If (hProcess) {
            CloseHandle(hProcess)
        }
        MsgBox,, % e.What, % e.Message
        Exit
    }
}

PidFromWindow(Window) ; 2)bis ! You get back the Process ID just by knowing the name of the window!
{
    If !WinExist(Window) { ; If no window exists named "assaultCube"
        Throw Exception("Window not found: " . Window) ; Show up a pop'up saying "Window not found: assaultcube".
    }
    WinGet, PID, PID ; Recover the PID from windows name and store it in a variable called PID. But how does winget know that it's the window of assaultcube? Normally it should not be: WinGet, PID, PID, assaultcube?
     Return PID ;return the value (the PID number) of this variable.
}

PidFromExe(Exe)
{
    Process, Exist, %Exe%
    If (!ErrorLevel) {
        Throw Exception("Process not found: " . Exe)
    }
    Return ErrorLevel
}

HandleFromPid(Pid) ; 2)ter You are trying to get the HWND (the unique id of the window that is displayed), by the process id!
{
    static PROCESS_VM_OPERATION := 0x8, PROCESS_VM_READ := 0x10, PROCESS_VM_WRITE := 0x20 ; You give yourself the rights to read, write and manipulate the processus by the window unique ID ? But where did you find the 0x10 0x8 etc ... Are these numbers giving you administrator access? Is it possible for a program to protect itself and prevent you from accessing the processus by create a handle despite these rights?
    hProcess := DllCall("OpenProcess", "uint", PROCESS_VM_READ
                                        | PROCESS_VM_WRITE
                                        | PROCESS_VM_OPERATION
                                , "int", False, "uint", Pid, "ptr") ;I already have trouble understanding ahk scripts, so the dll ... I understand that you are looking at the part of the cube assault process (the window process part) through the rights and through the process id you found, and you format this answer in a pointer format. Are you asking for permission to access and modify the process, and maybe this authorization (from windows) generates a sort of unique and temporary key? That's a handle? Is a handle a sort of door, you can use to go in a process ?
    If (!hProcess) { ; if hProcess could not be created then:
        Throw Exception("OpenProcess failed: " . A_LastError) ; Create a pop-up saying: OpenProcess failed: "with the error number
    }
    Return hProcess ; you return the value of hProcess (the key?)
}

NumberFromProcess(hProcess, Address, NumType) ;2) quater: are you trying to get the value of a specific variable from the handle?
{
    VarSetCapacity(buf, 8, 0) ; increase the capacity of the variables that will be created. Because DLLs give big numbers. 
    If (NumType = "ptr") {
        NumType := ProcessBitness(hProcess) = 32 ? "uint" : "uint64"
    }
    If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", Address
                                   , "ptr", &buf, "ptr", 8, "ptr", 0) {
        Throw Exception("ReadProcessMemory failed: " . A_LastError)
    }
    Return NumGet(buf, 0, NumType)
}

NumberToProcess(Number, hProcess, Address, NumType)
{
    VarSetCapacity(buf, 8, 0)
    If (NumType = "ptr") {
        NumType := ProcessBitness(hProcess) = 32 ? "uint" : "uint64"
    }
    BytesToWrite := NumPut(Number, buf, 0, NumType) - &buf
    If !DllCall("WriteProcessMemory", "ptr", hProcess, "ptr", Address
                                    , "ptr", &buf, "ptr", BytesToWrite, "ptr", 0) {
        Throw Exception("WriteProcessMemory failed: " . A_LastError)
    }
}

ProcessBitness(hProcess)
{
    If (!A_Is64bitOS) {
        Return 32
    }
    IsWow64 := False
    DllCall("IsWow64Process", "ptr", hProcess, "uint *", IsWow64)
    Return IsWow64 ? 32 : 64
}

ProcessBaseFromWindow(Window) ; 1) This is the first thing that happens in your code.
{
    static GWL_HINSTANCE := -6 ; You create a variable GWL_HINSTANCE with a value of -6, and you configure this variable so that it is only visible in this function. Why ? GWL_HINSTANCE is a critical value that should not be manipulated?
    Try { ; Try is a tricks that lets you tell the script that it have to read what's between {} and that if something does not work, it have to jump right to the Catch e! That's it ? Why use this? Because if there is an error the script would crash (it would stop) while that allows it to continue and display a return error, is that?
        hWnd := WinExist(Window) ; check if the windows (assaulCube) exist, if so, you get the unique ID (HWND) of the AssaultCube window and the stock in your variable hWnd!
        If (!hWnd) { ; if the variable hWnd was not created
            Throw Exception("Window not found: " . Window) ; Show up a pop'up saying "Window not found: assaultcube". What is the difference with a msgbox? Maybe it's mostly to tell the script to jump to Catch e?
        }
        FuncName := A_PtrSize = 4 ? "GetWindowLong" : "GetWindowLongPtr" ; What what what ? You say that in your variable FuncName you want to receive: = you configures the answer to be the size of a 32-bit pointer, the "?" What is it ? The GetWindowLongA / Ptr function retrieves information from a window. Do you give the names of the apis you want to use with a ":" in the middle, why? I do not know, surely a trick to switch the 32b or 64b depending on what the application is. I'm lost.
        Base := DllCall(FuncName, "ptr", hWnd, "int", GWL_HINSTANCE) ; You create a BASE variable that will host a value returned by the dllcall function. the Dllcall function will call the GetWindowLongA API, I have this syntax: LONG GetWindowLongA (HWND, hWnd, int, nIndex) on msdn.microsoft.com, so I guess you tell him you want the HWND (assautCube ) in a format of pointer, and with the index -6 you tell him that you want the handle of this window precisely. But the hwnd we already had it no? so that does not have to be that, what does GetWindowLongA give you?
        If (!Base) { ;If the Base variable could not be created
            Throw Exception(FuncName . " failed: " . A_LastError) ;Show up a pop'up saying "Base failed: the lerror number".
        }
    } Catch e { ;The script will only read this if there was an error above
        MsgBox,, % e.What, % e.Message ;Shows up a message with what in? What is this: e.What e.message? What is the name of the command, function or label which was executed? e.Message the corresponding error number? But the % ?
        Exit ; Close the script
    }
    Return Base ; you return the base value (the data that I do not understand) Ah, maybe it's a STATIC address of the game! A "mother" address created randomly at each launch of the game, on which all other pointers are going to serve to be created! that is it ?
}

CloseHandle(hProcess)
{
    DllCall("CloseHandle", "ptr", hProcess)
}
Do you see ? I told you that you were going to have work lol
if you want to start answering me, I continue to learn and try to translate what I understand from the rest of the code.
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

30 Aug 2018, 01:56

Code: Select all

ProcessBaseFromWindow(Window) ; 1) This is the first thing that happens in your code.
{
    static GWL_HINSTANCE := -6 ; You create a variable GWL_HINSTANCE with a value of -6, and you configure this variable so that it is only visible in this function. Why ? GWL_HINSTANCE is a critical value that should not be manipulated?
Static variables are created and assigned a value only once, when you fist call the function, and are kept between calls. An ordinary local variable would be created at each call. It's unlikely, of course, that we'd see any difference in speed in this particular case but it just seems the right thing to do. :)

I could have done without a variable and put -6 in the DllCall, but then anyone reading the code would have to guess what this magic number means, while now you can search for GWL_HINSTANCE.

Code: Select all

    Try { ; Try is a tricks that lets you tell the script that it have to read what's between {} and that if something does not work, it have to jump right to the Catch e! That's it ? Why use this? Because if there is an error the script would crash (it would stop) while that allows it to continue and display a return error, is that?
        hWnd := WinExist(Window) ; check if the windows (assaulCube) exist, if so, you get the unique ID (HWND) of the AssaultCube window and the stock in your variable hWnd!
        If (!hWnd) { ; if the variable hWnd was not created
            Throw Exception("Window not found: " . Window) ; Show up a pop'up saying "Window not found: assaultcube". What is the difference with a msgbox? Maybe it's mostly to tell the script to jump to Catch e?
        }
I am not used to this try/catch thing myself, but it seems convenient. Yes, rather than throwing an exception, a function can show a message box. But it stops execution and waits for the user to close it. Will it always be what you want? With an exception, the caller of the function is free to decide what to do. It can show a message box, using the info stored in the exception object (the "e" variable after the "Catch"). When a function throws an exception, it specifies the error message, while the function's name is automatically stored in e's "What" property. Isn't it convenient? The caller can also choose to handle the error silently.

Usually you write a function once but use it in several places, and in each place you'll be free to decide what to do if it fails. You can react to its failure differently, and you don't have to look in the function's code how it signals failure. It's always an exception object.

Within a Try block, you can call several such functions in sequence without wrapping each of them in error handling code. It would be tedious, so this is again an advantage of the try/catch technique. Otherwise you would, for example, have to check the return value of each function and show an error message box. Several message boxes instead of one.

This particular function, ProcessBaseFromWindow, throws an exception and catches it itself, so maybe the only advantage is one Exit instead of a few. Or I simply had a gut feeling I should do it this way for the code to look uniform. :)

Code: Select all

        FuncName := A_PtrSize = 4 ? "GetWindowLong" : "GetWindowLongPtr" ; What what what ? You say that in your variable FuncName you want to receive: = you configures the answer to be the size of a 32-bit pointer, the "?" What is it ? The GetWindowLongA / Ptr function retrieves information from a window. Do you give the names of the apis you want to use with a ":" in the middle, why? I do not know, surely a trick to switch the 32b or 64b depending on what the application is. I'm lost.
This is the ternary conditional operator: https://autohotkey.com/docs/Variables.htm#ternary. The name of the function is different in 32-bit and 64-bit Windows, hence the need to choose it.

GetWindowLong reads from window memory a handle to the application's instance. HANDLE + INSTANCE = HINSTANCE. As far as I remember, this comes from the early days of Windows when applications ran in shared memory. So if several instances of your application were launched, they were loaded into the same address space and would probably share code but have different data. That must be why the WinMain function has the hInstance and hPrevInstance parameters — so it could know for which instance of the application it was called. Something like that, I guess.

Anyway, it can be used as the address of the main module (the .exe module) in memory. So it's in fact a pointer, not a handle.

Code: Select all

But the % ?
        Exit ; Close the script
You use a leading % with a space in a command parameter to let AutoHotkey know that what follows is not a literal. It's an expression that should be resolved, and the parameter should use the result of that resolution. It can be a function call (its return value will be used).

The "Exit" here doesn't close the entire script, just ends the current thread of execution, in this case the auto-execute thread. It contains the code at the very beginning of the script and starts automatically when the script is loaded.

If I used Return, it would skip the rest of the function but not the rest of the auto-execute thread. It would continue after the call to ProcessBaseFromWindow, which obviously doesn't make sense, since the base address hasn't been obtained.

To be continued...
YMP2
Posts: 47
Joined: 20 Apr 2014, 06:55

Re: Read / Write memory (beginner level)

30 Aug 2018, 04:01

Code: Select all

    WinGet, PID, PID ; Recover the PID from windows name and store it in a variable called PID. But how does winget know that it's the window of assaultcube? Normally it should not be: WinGet, PID, PID, assaultcube?
https://autohotkey.com/docs/misc/WinTit ... oundWindow

Code: Select all

HandleFromPid(Pid) ; 2)ter You are trying to get the HWND (the unique id of the window that is displayed), by the process id!
No, this gets a handle to the process identified by its PID. HWND is a window ID, a different thing. A process can create multiple windows and each of them will have a different HWND.

Code: Select all

    static PROCESS_VM_OPERATION := 0x8, PROCESS_VM_READ := 0x10, PROCESS_VM_WRITE := 0x20 ; You give yourself the rights to read, write and manipulate the processus by the window unique ID ? But where did you find the 0x10 0x8 etc ... Are these numbers giving you administrator access? Is it possible for a program to protect itself and prevent you from accessing the processus by create a handle despite these rights?
You can find them on the internet: https://docs.microsoft.com/en-us/window ... ess-rights and read what rights they give, or in Windows header files. If you have VisualStudio C++ installed, you have those files. These particular values are in WinNT.h.

I don't know if a program can protect itself from such access.
Is a handle a sort of door, you can use to go in a process ?
Yes, you need it to deal with a process. As far as I know, handles are numbers that identify Windows internal objects (or structures). You can't use the structures directly, only pass their identifiers to functions. They are probably created when you request them and deleted when you close them. But that's Windows internals, which I am not an expert in. :)

P.S. As I can see you copied my functions before I made some corrections, so I recommend that you update them. And if I missed some of your questions, don't hesitate to ask again. ;)

Return to “Ask For Help”

Who is online

Users browsing this forum: afe, Albireo, andylysm, bangon2006, Breevz, cadudesun, Google [Bot], IIoPTeP, JaydeNpie, MannyKSoSo, stargate, teadrinker and 197 guests