 |
Cheat Engine The Official Site of Cheat Engine
|
| View previous topic :: View next topic |
| Author |
Message |
S-nonymous027 How do I cheat?
Reputation: 0
Joined: 25 Jul 2021 Posts: 9 Location: Earth
|
Posted: Tue Jul 11, 2023 12:38 am Post subject: Any suggestions to make this GUI code more efficient? |
|
|
I have a Lua script that controls two forms containing GUI elements, it gets called every frame at 60 FPS.
Here is a part of it, for clarity (the whole thing's huge--534 lines in total):
| Code: |
if healthLayer >= damageLayer then
HealingTopBarVisible = true
HealthBar.HealingBottom.Visible = true
HealthBar.HealingBottom.Width = 800
elseif healthLayer == damageLayer - 1 then
HealingTopBarVisible = false
HealthBar.HealingBottom.Visible = true
HealthBar.HealingBottom.Width = 4*healthTop
else
HealingTopBarVisible = false
HealthBar.HealingBottom.Visible = false
end
for selection,bar in ipairs(HealingTopBars) do
if selection == HealingTopBar and healthLayer > 0 then
bar.Visible = true and HealingTopBarVisible
bar.Width = 4*healthTop
else
bar.Visible = false
end
end
--------------------------------------------------------------------------
for layer,indicator in ipairs(HPLayerIndicators) do
if damageLayer > layer then
indicator.Visible = true
if layer > 1 then
firstOffset = 2
else
firstOffset = 1
end
if healingLayer > layer then
secondOffset = 3
if dangerHealthLayer >= layer then
thirdOffset = 0
elseif warningHealthLayer >= layer then
thirdOffset = 1
else
thirdOffset = 2
end
else
if healthLayer > layer then
secondOffset = 2
else
secondOffset = 1
end
thirdOffset = 0
end
indicator.Picture = HPLayerColors[firstOffset][secondOffset+thirdOffset]
else
indicator.Visible = false
end
end
|
(the full code is attached below)
The code uses 20% of my CPU on average, and I want to lower that.
Any way how to do this?[/code]
| Description: |
|
 Download |
| Filename: |
SonicGUI.lua |
| Filesize: |
15.44 KB |
| Downloaded: |
226 Time(s) |
_________________
A health bar is useless if it cannot protect its owner from death |
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4709
|
Posted: Tue Jul 11, 2023 10:51 am Post subject: |
|
|
That script has a bunch of syntax errors with both superfluous and missing ending parenthesis.
Some code is completely missing. e.g. what's `checkFPS`?
That's one small snippet from a ridiculously long function. You really need to refactor it.
You're only using global variables in that function?? The `local` keyword exists and should really be the default.
`al["whatever"]` - indexing into the address list every time is unnecessary. Get the memory records once and reuse them.
I'm not sure what happens when assigning to various properties of GUI elements, but reassigning the same value is unnecessary. Remember what was assigned, and change it only if necessary.
Are you sure you want to update that at 60 fps? Timers typically have trouble going that high, and it's not like all the values exist in your process. Every individual call to ReadProcessMemory (i.e. indexing MemoryRecord.NumericalValue) is a significant performance hit.
If you really want better performance, do a bunch of code injections in the target process to write important values to some predetermined area of memory, and use a single call to ReadProcessMemory to get that (e.g. call `copyMemory` into a MemoryStream, then read values).
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
S-nonymous027 How do I cheat?
Reputation: 0
Joined: 25 Jul 2021 Posts: 9 Location: Earth
|
Posted: Tue Jul 11, 2023 8:32 pm Post subject: |
|
|
| ParkourPenguin wrote: | That script has a bunch of syntax errors with both superfluous and missing ending parenthesis.
Some code is completely missing. e.g. what's `checkFPS`?
That's one small snippet from a ridiculously long function. You really need to refactor it.
You're only using global variables in that function?? The `local` keyword exists and should really be the default.
`al["whatever"]` - indexing into the address list every time is unnecessary. Get the memory records once and reuse them.
I'm not sure what happens when assigning to various properties of GUI elements, but reassigning the same value is unnecessary. Remember what was assigned, and change it only if necessary.
Are you sure you want to update that at 60 fps? Timers typically have trouble going that high, and it's not like all the values exist in your process. Every individual call to ReadProcessMemory (i.e. indexing MemoryRecord.NumericalValue) is a significant performance hit.
If you really want better performance, do a bunch of code injections in the target process to write important values to some predetermined area of memory, and use a single call to ReadProcessMemory to get that (e.g. call `copyMemory` into a MemoryStream, then read values). |
Whoopsie, seems like I've uploaded the wrong one then... that one must've been my prototype (the one I'm using has none of them syntax errors).
All the values does exist within the (edit 2: game's) process, save for calculated variables like "speed" and "eta" (est. time of arrival)
60 fps is necessary since I want the forms to display values in real time.
As for the memoryrecord access and local variables, I'm working on it.
I also found a bug that wrongly colors the bottom layer of the health bar.
Edit: Is this what you mean by remembering the assigned values?
| Code: |
address = 0x01234567
oldValue = 0
function displayValue()
local value = readInteger(0x01234567)
if value ~= oldValue then
--do something
oldValue = value
end
end
|
_________________
A health bar is useless if it cannot protect its owner from death
Last edited by S-nonymous027 on Tue Jul 11, 2023 11:02 pm; edited 1 time in total |
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4709
|
Posted: Tue Jul 11, 2023 10:53 pm Post subject: |
|
|
| S-nonymous027 wrote: | | All the values does exist within the process... | There are two separate processes involved here: CE and the game. Every time you index `MemoryRecord.NumericalValue`, CE must call ReadProcessMemory to read the value in the game's process. The time required to do this isn't negligible when you're doing it several dozen times per second.
Yes, that's more or less what I mean by remembering the assigned value. Importantly, you should only modify the GUI when something has actually changed. e.g. if `indicator.Picture` is currently `picture1`, and the next loop `indicator.Picture` should still be `picture1`, there's no need to assign anything to `indicator.Picture`: just leave it alone. Doing nothing is always the fastest thing you can possibly do.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
S-nonymous027 How do I cheat?
Reputation: 0
Joined: 25 Jul 2021 Posts: 9 Location: Earth
|
Posted: Tue Jul 11, 2023 10:58 pm Post subject: |
|
|
The only thing left for me to do now is figuring out how to read all the values in one call of ReadProcessMemory. I never did that before, any clues on how to do it?
_________________
A health bar is useless if it cannot protect its owner from death |
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4709
|
Posted: Wed Jul 12, 2023 12:14 am Post subject: |
|
|
It'll take some effort. Make sure ReadProcessMemory is actually a bottleneck. Try replacing everything that calls ReadProcessMemory (i.e. every index using `NumericalValue`) with a sensible random number. If there are no calls to ReadProcessMemory and it doesn't help performance at all, then it's probably the GUI stuff that's expensive and the following would be a waste of time.
Separate this problem into two parts: in the target process, copy values into some common area of memory. In CE's process, read from this common area of memory and assign it to values.
The first part largely depends on how the address list reads values. More specifically, in the target process, you want to read the values the same way as CE does in the address list. If it's a pointer, traverse the pointer path; if it's an injection copy, use the registered symbol as a base address.
I guess the most general way is to create a thread in the target process, but you could also use a code injection (or several). Just make sure the code injection(s) are being run at at least 60 Hz.
| Code: | [ENABLE]
globalalloc(ValuePool,4096)
label(value1)
label(value2)
label(value3)
label(valCopyTerminate)
label(code)
label(loop)
registersymbol(valCopyTerminate)
ValuePool:
value1:
resd 1 // 4-byte
value2:
resd 1 // float
value3:
resq 1 // double
valCopyTerminate:
dd 0
db CC
code:
push rbp
mov rbp,rsp
sub rsp,20
loop:
{$try}
// note: can't directly use `mov rdx,[game.exe+1234]` (RIP-relative addressing)
mov rdx,game.exe+1234
mov rdx,[rdx]
mov rdx,[rdx+53C] // 1st offset 53C
mov eax,[rdx+3C] // 2nd offset 3C
mov [value1],eax // 4-byte value
mov eax,[rdx+78] // 2nd offset 78
mov [value2],eax // float value (floats are 4 bytes)
// registered symbol (again watch for RIP-relative addressing):
mov rdx,PlayerAddr
mov rdx,[rdx] // might want to check if `rdx` is 0 before the next instruction
mov rax,[rdx+40] // original instruction might be `movsd xmm0,[rdx+40]`, and `PlayerAddr` holds rdx
mov [value3],rax // double value (doubles are 8 bytes)
{$except}
mov ecx,#15
call Sleep
mov ecx,[valCopyTerminate]
test ecx,ecx
jz loop
// exit:
mov rsp,rbp
pop rbp
ret
createthread(code)
[DISABLE]
valCopyTerminate:
dd 1
| Edit: RSI is a nonvolatile register; use RDX instead
The second part is a little easier if you're familiar with CE's Lua API. Something like this:
| Code: | // allocate enough memory to hold all the values (4+4+8=16)
local buffer = createMemoryStream()
buffer.Size = 16
// read from other process's memory into this process
local addr = getAddressSafe'ValuePool'
if not addr then return end // only run if the memory exists
copyMemory(addr, buffer.Size, buffer.Memory, 1)
// start reading values. 4-byte value:
local value1 = buffer.readDword()
// unfortunately, there is no "readFloat"; workaround:
local value2 = byteTableToFloat(buffer.read(4))
// same for double
local value3 = byteTableToDouble(buffer.read(8))
buffer.destroy()
buffer = nil | See celua.txt for documentation on CE's Lua API. The wiki might be helpful too.
_________________
I don't know where I'm going, but I'll figure it out when I get there.
Last edited by ParkourPenguin on Wed Jul 12, 2023 12:36 pm; edited 1 time in total |
|
| Back to top |
|
 |
S-nonymous027 How do I cheat?
Reputation: 0
Joined: 25 Jul 2021 Posts: 9 Location: Earth
|
Posted: Wed Jul 12, 2023 2:50 am Post subject: |
|
|
After I fixed both ReadProcessMemory and GUI code, it now runs at 15% CPU on average and it's much smoother than before.
Turns out both were the culprit.
Thanks a lot!
Edit:
Someone should make a tutorial based on this. It's kinda irritating not to know how much load each function you call puts on your CPU.
| Description: |
Behold the entirety of the GUI!
Green = HP (healed portion shown in celeste/light blue)
Blue gradient = energy, consumed by boosting
Light blue = speed
Marked bar (lowermost) = progress |
|
| Filesize: |
724.53 KB |
| Viewed: |
1932 Time(s) |

|
_________________
A health bar is useless if it cannot protect its owner from death |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You can download files in this forum
|
|