JK89526 wrote: ↑Wed May 24, 2023 5:40 pm
I am writing a cheat trainer in the C++ programming language. You apparently do not quite understand me. This will indeed be correct: 1000A3284 + 25C = 1000A34E0. In theory, I should sum the offsets with the base address, as I wrote above (base2 = base + 1 offset; base3 = base2 + 2 offset;.....and so on). In my program, I sum the offsets with the base address and get a non-existent address: 0xccccccccccccc.... I began to figure out what the problem was. First I checked the sum of the base address and the first offset: 1000A3284 + 25C = 1000A34E0. (the address is correct) To check in Cheat Engine, I added a new address via the "Add Address Manually" button and wrote the address "*DLL name*.dll" + 00120C78. The address I received is 632B0C78. Then I clicked on the "Pointer" checkbox. Next to the address input window (where it was written: "*DLL name*.dll"+00120C78) there is a label with the text: "-> 1000A3284". (address 632B0C78 was converted to a 64bit address) So far, there is nothing special about this, everything is correct. Then in the box for entering the text of the first offset, I entered: 25C. Next to it is an underlined label with the text: "1000A3284 + 25C = 1000A34E0". (Everything is correct, everything is the same in the program). Then I click the "Add Offset" button. After that, a box for entering text for the second offset appears. And immediately the text of the label, which is next to the box for entering the text of the first offset, changes to: "[1000A3284 + 25C] -> 1003E6C10" (How is 1003E6C10 obtained if 1000A3284 + 25C = 1000A34E0?). That is, the second offset will already be: 1003E6C10 + *second offset*. (It was all in the Cheat Engine). If in my program I add the first and second offset to the sum of the base address, then I will get a non-existent address: 0xccccccccccccc.... How does Cheat Engine convert the address 1000A34E0 to 1003E6C10?? And how can I implement it in my program? (At the moment, while I am writing a replay, the address that is being converted is already not 1003E6C10, but 1003E7010. Although in the previous session of the game it was 1003E6C10)
SunBeam and LeFiXER are correct. You've almost got it but you need to read up just a little more on pointers and how memory addresses work and is how it's read. You got the right motions down but seem to be lacking a bit of the full grasp of what exactly it's all doing.
So let's say you're reading your player health in a game.
The address; 0xDEADBEEF has your health value. so the
value of 0xDEADBEEF is your health. Let's say the game you're hacking stores health as a simple 4byte int(int32), no obfuscation of any sort, and we're not reading Multi Level Pointers rn, just the simple address that contains your health. So let's read your health from the game into your cpp program:
Code: Select all
int myPlayerHealth; //This is a 32bit(4 bytes) signed integer so 4bytes = 00 00 00 00 in hex(spacing there to obviate what 4 bytes looks like)
ReadProcessMemory(handle, (LPCVOID)0xDEADBEEF, &myPlayerHealth, sizeof(myPlayerHealth), 0);
//RPM = ReadProcessMemory
//Let's break down ReadProcessMemory a bit, excluding the handle
//RPM's second argument is [in] LPCVOID lpBaseAddress
//What this means is you input an address to read the value of, typecasting it as a Long Pointer to Constant Void(LPCVOID) so that
//RPM knows how to read the address that you're inputting.
//So with that knowledge we have:
//ReadProcessMemory(handle, (LPCVOID)0xDEADBEEF
//But now we want to store the value it's reading at that address so what's next?
//The third argument of RPM is [out] LPVOID lpBuffer
//Which means you're going to put a variable here that is going to be the output of the RPM function.
//Read "&" as "address of"; so if we have &myPlayerHealth then that means we're outputting a value to the "address of" myPlayerHealth
//So in other words, in memory; myPlayerHealth has it's own address with it's own value. We're setting the value at myPlayerHealth to the value of the address 0xDEADBEEF
//Okay, so we've got a storage place to read the value of 0xDEADBEEF into. Now what?
//Fourth argument of RPM; [in] SIZE_T nSize
//This is telling RPM how much memory to read at the address 0xDEADBEEF. We know your health in the game is a simple 4byte int so here we pass that on to RPM to read 4 bytes at the address of 0xDEADBEEF
//Note that these all mean the same thing:
//ReadProcessMemory(handle, (LPCVOID)0xDEADBEEF, sizeof(myPlayerHealth)
//ReadProcessMemory(handle, (LPCVOID)0xDEADBEEF, sizeof(int)
//ReadProcessMemory(handle, (LPCVOID)0xDEADBEEF, 4
//All that means is that we're reading 4 bytes at 0xDEADBEEF. You can literally just put 4 there if you want. I like using sizeof(varName) most a the time.
//Okay, sweet! that's pretty much it, there's only 1 more argument to RPM function and it's optional;
//[out] SIZE_T *lpNumberOfBytesRead
//which means this is another output variable that just stores how many bytes were read from at 0xDEADBEEF
//it's useful if you're reading a more variable amount of bytes and need to calculate that later but we're not doing that here so we can just fill it in with 0
//ReadProcessMemory(handle, (LPCVOID)0xDEADBEEF, &myPlayerHealth, sizeof(myPlayerHealth), 0);
//So in conclusion:
//ReadProcessMemory(fromThisProcessHandle, readTheMemoryAtThisAddress, intoTheMemoryAtThisAddress, readThisMuchMemoryFromDEADBEEF, logHowMuchMemoryWeReadFromDEADBEEF);
So, now that we hopefully have a better grasp of ReadProcessMemory and what exactly is happening when we read memory, let's take a look at the issue:
In CE, you have;
[1000A3284 + 25C] -> 1003E6C10
What the hell, right? 1000A3284 + 25C = 1000A34E0 so how tf is it getting 1003E6C10??
Here's what's happening and where you almost have it.
Cheat Engine is doing the math for you (1000A3284 + 25C) and getting the same address as you are; 1000A34E0
Sweet! You're on the right track!
But CE is also doing one extra step. As SunBeam pointed out; Read "[...]" as "
value of"
So in other words whenever you see
[address + offset] in CE it means; add this offset to this address and then get me the value of that new address(1000A34E0 in this case) so it's adding those together and then performing another ReadProcessMemory on that new sum(1000A34E0). That's called a Multi Level Pointer/Pointer Chain. It's where the
value of an address is yet another address. So you then have to read the other address that you get from that one.
tl;dr; Here's one way to read MultiLevelPointers in C++:
Code: Select all
//you don't need this many variables to achieve this but for the sake of clarity of what's happening, I'm putting them into separate variables
uintptr_t baseAddress = *DLL name*.dll;
uintptr_t addressPointedTo;
uintptr_t secondAddressPointedTo;
int offset1 = 0x00120C78, offset2 = 0x25C; //can use DWORD (4byte int alias) instead of int here; kinda helps with keeping track of your offset variables
int myPlayerHealth; //where we will store your health being read into your C++ program
ReadProcessMemory(handle, (LPCVOID)(baseAddress + offset1), &addressPointedTo, sizeof(addressPointedTo), 0);
cout << addressPointedTo << endl; //output: 1000A3284 (cout might print this as decimal, I can't remember, but that would be 4295635588)
ReadProcessMemory(handle, (LPCVOID)(addressPointedTo + offset2), &secondAddressPointedTo, sizeof(secondAddressPointedTo), 0);
cout << secondAddressPointedTo << endl; //output: 1000A34E0 (decimal; 4295636192)
//Now we're at our health/whatever address(secondAddressPointedTo) so this one we're going to read as a 4byte int instead of uintptr_t which is 8 byte unsigned int
ReadProcessMemory(handle, (LPCVOID)secondAddressPointedTo, &myPlayerHealth, sizeof(myPlayerHealth), 0);
cout << myPlayerHealth << endl; //idk if you're actually looking for health but this should print the value that you're looking for which is whatever is at the end of your pointer chain/MultiLevelPointer