Part Two: Under the Hood
Today, we're going to cover what a program actually is.
Yesterday, in part one, we covered finding a value, so let's talk about what a value is.
You might remember that, when we went looking for values, Cheat Engine gave us a list of stuff that looked like this:
Why does it give us these addresses? What is significant about those random strings of numbers and sometimes-letters-but-only-some-letters?
What those addresses represent is the series of bytes the program uses to refer to when it needs a particular piece of memory. In yesterday's exercise, the address that referred to the number of level 1 spells that we had cast looked like this:
However, if you were to repeat this exercise today, or after you've restarted the game, you'll find different addresses:
This is because, when a program asks for memory, the computer just hands over whatever memory it happens to have available. It's not guaranteed to be in one particular place - the promise your computer makes is "Here's an address with the amount of memory that you need, and it will always be at that address until you tell me you don't need it anymore."
Now, that's an oversimplification; the process more or less asks your computer for whole gigantic bricks of memory and takes from that as needed. However, that still results in essentially random addresses - you can read more about it here, as this is not just a process-centric problem, but is endemic to anything on computers that uses memory.
So now you're probably wondering, well, how do cheat tables work then? Because Cheat Tables always point to the "right" value.
Here we see that I have a thing labeled "pointerscan result", and a P-> with an address after it. And that thing is one of the most fundamental building blocks of programming.
Let's take a step back.
Code - everything on a computer - at its most basic level can be represented by 0s and 1s. You probably knew that. But what does that really mean? You're probably imagining something like the Matrix.
Except with like, 0s and 1s, right? It's a lot simpler than that.
Do you remember really basic codes? Like, transpose a letter one letter to the right. So "BUTTS" because "CVUUT". Pretty easy, right?
This is, essentially, what a program is. The computer has a manual for what every number means. So, if we make our own little psuedo-computer here...
"0" means "read the equation after this"
"1" means "write the answer to the previously read equation"
So then I can write:
And you know to write "4".
But what if I want to add a new command, "2", that doubles the previously read equation? Computers are just 0s and 1s, right? Well, how do you think we represented 2 + 2 above?
In a computer, this would look more like:
0000 0010 0010
And instead of "4", it writes 0100.
I'm not here to write a full guide to binary, so here's the Wikipedia link. Go learn yourself. Oh, and binary can be translated really easily to hexadecimal, so that's why addresses are those strings of numbers / letters.
So basically, we have a way to tell the computer to do things in binary. If you're interested in why binary is so good for computers, you can read about transistors right here. The tl;dr is that hardware does on (1) and off (0) real well, but not much in-between. This basically translates to how software works because it has to be physically represented on hardware to do... well, to exist, really. (For more fun, read about how computers handle decimal numbers here!)
We also need a way to tell computers how to remember things. There's no point in having a command like "write the result of the last equation" if the computer doesn't remember what that was, right?
So let's update what our 0000 command means: Read the equation and put the result of it into register A. A register is just a chunk of memory.
Now, we can take the same "program" as before...
0000 0010 0010
But, after it runs the first command, now register A will have the value "0100". That also updated our 0001 command - now, it means write the value of register A. (You may be wondering what the value of register A was BEFORE the 0000 command. The answer is, it depends.)
If you've never programmed before, that joke might take you a bit to get. Come back in a few months and you'll appreciate it.
This is pretty long, but we're almost done. Bear with me here.
Let's add a new command: 0010 ("2" in binary). This command means "write the value from the first address given to the second". Furthermore, let's pretend we have 8 registers, labeled 0000 - 1000. Finally, I'm going to update command 0001 to mean "write the value of the given address".
Try to figure out what the program below is doing before you read the explanation:
0000 0010 0010
0010 0000 0001
Did you figure it out?
Are you sure?
Did you even try?
I'm on to you.
Ok ok, here's what it's doing:
register A = 2+2
register B = register A
Hey! Now you understand the fundamentals of how a computer functions.
What if we wanted to store the value of a register in a register, though? Well, we can already do that - above, we indexed the value of each register to 0000 - 1000, right? So it's literally as simple as saying "interpret the value of this register as a register". We could call that command 0011 ("3") if you wanted to.
That's a pointer. Literally just a chunk of memory that has the address of another chunk of memory.
The next part of this guide is going to cover pointer scanning and why that works. But, if this is the first time you've heard of what a pointer is, or really understood what a program is... Take some time to let it settle in your mind. Understanding these concepts is essential to following what's going on in the pointer scanning, as well as taking things further: code injection and whatnot.
Besides, there's no rush. It's not like computers are gonna go anywhere.