 |
Cheat Engine The Official Site of Cheat Engine
|
| View previous topic :: View next topic |
| Author |
Message |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Sat Oct 07, 2023 7:43 am Post subject: Need help with an algorithm |
|
|
For starters here’s the entire script which is unfinished
| Code: | {$lua}
[ENABLE]
local updated = false
local lastHeatLevel = -1
local heatLO = 0
local heatHI = 0
local maxHeat = -1
closeToEscape = false
function escapeCheck()
local heatHI2 = readFloat("[heat]")
createTimer(1,
function()
end
)
local heatLO2 = readFloat("[heat]")
if heatLO2 == 0 and heatHI2 > 0 then
closeToEscape = true
end
end
function bountyCheck()
if readPointer("[globalbounty]") ~= nil and readPointer("[heat]") ~= nil then
--WRITE GLOBAL BOUNTY INTO HUD
writeInteger("[heat]-A43C",readInteger("[globalbounty]"))
--CHECK FOR NEGATIVE GLOBAL BOUNTY BALANCE
if readInteger("[globalbounty]") > 50000000 then
writeInteger("[globalbounty]",0)
end
--NEW COP CHASE IS ENGAGED
if readFloat("[heat]") == 0 and readInteger("[heat]-A438") > 0 then
updated = false
print("ENTERS CHASE")
end
--PLAYER IS ON COOLDOWN TO ESCAPE
heatHI = readFloat("[heat]")
createTimer(10,
function()
end
)
heatLO = readFloat("[heat]")
if heatLO < heatHI then
if readFloat("[heat]") < 2.0 then
print("LAST HEAT IS 1")
lastHeatLevel = 1
elseif readFloat("[heat]") < 8.0 then
print("LAST HEAT IS 2")
lastHeatLevel = 2
elseif readFloat("[heat]") < 16.0 then
print("LAST HEAT IS 3")
lastHeatLevel = 3
elseif readFloat("[heat]") < 26.0 then
print("LAST HEAT IS 4")
lastHeatLevel = 4
elseif readFloat("[heat]") < 35.0 then
print("LAST HEAT IS 5")
lastHeatLevel = 5
elseif readFloat("[heat]") < 52.0 then
print("LAST HEAT IS 6")
lastHeatLevel = 6
end
elseif heatLO > heatHI then
maxHeat = heatLO
end
--PLAYER IS BUSTED
if readByte("[heat]+1E4C9") == 1 and readInteger("[heat]+1E33C") == 1065353216 and updated == false then
writeInteger("[globalbounty]",readInteger("[globalbounty]")-readInteger("[heat]-A438"))
updated = true
end
--PLAYER HAS ESCAPED
if readByte("[heat]+1E4C9") == 1 and updated == false then
print("HAS ESCAPED DAMN")
if closeToEscape == true then
print("CLOSE TO ESCAPE")
if lastHeatLevel == 1 then
print("ESCAPES WITH HEAT 1")
writeInteger("[globalbounty]",readInteger("[globalbounty]")+1.25*readInteger("[heat]-A438"))
elseif lastHeatLevel == 2 then
print("ESCAPES WITH HEAT 2")
writeInteger("[globalbounty]",readInteger("[globalbounty]")+1.75*readInteger("[heat]-A438"))
elseif lastHeatLevel == 3 then
print("ESCAPES WITH HEAT 3")
writeInteger("[globalbounty]",readInteger("[globalbounty]")+2.25*readInteger("[heat]-A438"))
elseif lastHeatLevel == 4 then
print("ESCAPES WITH HEAT 4")
writeInteger("[globalbounty]",readInteger("[globalbounty]")+3.25*readInteger("[heat]-A438"))
elseif lastHeatLevel == 5 then
print("ESCAPES WITH HEAT 5")
writeInteger("[globalbounty]",readInteger("[globalbounty]")+4.00*readInteger("[heat]-A438"))
elseif lastHeatLevel == 6 then
print("ESCAPES WITH HEAT 6")
writeInteger("[globalbounty]",readInteger("[globalbounty]")+5.00*readInteger("[heat]-A438"))
end
end
updated = true
closeToEscape = false
end
end
end
eC=createTimer(nil)
timer_setInterval(eC, 1)
timer_onTimer(eC, escapeCheck)
timer_setEnabled(eC, true)
bC=createTimer(nil)
timer_setInterval(bC, 100)
timer_onTimer(bC, bountyCheck)
timer_setEnabled(bC, true)
[DISABLE]
bC.destroy();
bC=nil
eC.destroy();
eC=nil
|
So, the heat level of this game increases with time (or certain actions) and decreses when you lose the cops.
My idea is to multiply a bonus to the bounty you got on the pursuit when escaped according to the heat level you escaped (escaping heat 6 is much harder than heat 1...). If you get busted, you just get the bounty you earned during the pursuit subtracted to the global bounty you have as a player.
But here is the thing, I don’t have an EASY way to know when the heat level increases or decreases. Therefore, I made this:
| Code: |
--PLAYER IS ON COOLDOWN TO ESCAPE
heatHI = readFloat("[heat]")
createTimer(10,
function()
end
)
heatLO = readFloat("[heat]")
|
I put some kind of timer to use it as a pause alternatively to sleep() which I want to avoid using for obvious reasons. Is there a more resilient way to figure out if the value at the addresses increases or decreases?
Then I got this:
| Code: | function escapeCheck()
local heatHI2 = readFloat("[heat]")
createTimer(1,
function()
end
)
local heatLO2 = readFloat("[heat]")
if heatLO2 == 0 and heatHI2 > 0 then
closeToEscape = true
end
end |
I need to know when the player is about to escape, i.e, when the heat level is close to 0. So, I did something similar as before but doesn’t seem to work most of the time.
Getting back to this part of the code:
| Code: | --PLAYER IS ON COOLDOWN TO ESCAPE
heatHI = readFloat("[heat]")
createTimer(10,
function()
end
)
heatLO = readFloat("[heat]")
if heatLO < heatHI then
if readFloat("[heat]") < 2.0 then
print("LAST HEAT IS 1")
lastHeatLevel = 1
elseif readFloat("[heat]") < 8.0 then
print("LAST HEAT IS 2")
lastHeatLevel = 2
elseif readFloat("[heat]") < 16.0 then
print("LAST HEAT IS 3")
lastHeatLevel = 3
elseif readFloat("[heat]") < 26.0 then
print("LAST HEAT IS 4")
lastHeatLevel = 4
elseif readFloat("[heat]") < 35.0 then
print("LAST HEAT IS 5")
lastHeatLevel = 5
elseif readFloat("[heat]") < 52.0 then
print("LAST HEAT IS 6")
lastHeatLevel = 6
end
elseif heatLO > heatHI then
maxHeat = heatLO
end |
I am honestly not sure how to store which heat level the player escaped at cause let’s say the player manages to escape from heat 5, then it has to go through all the other lower heat levels. But you can also have this another scenario where the player enters cooldown at heat 3, the heat gets decreased until lets say heat 1, cops got sight on the suspect again, pursuit escalates to heat 6 and then the player enters cooldown until he escapes going from there to heat 0 (0.0 value).
All help is much appreciated TIA.
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Sat Oct 07, 2023 12:19 pm Post subject: |
|
|
| mrally2 wrote: | | Code: | createTimer(10,
function()
end
) |
| All this does is queue some code that does literally nothing to run in 10ms. If you think createTimer blocks, you're wrong.
| Code: | print'1'
createTimer(10, function() print'3' end)
print'2'
-- prints 1 / 2 / 3 |
Missing `if syntaxcheck then return end`
Scripts can be disabled without executing the disable section. Try to account for that.
`readPointer("[heat]")` - It's not a pointer, it's a float.
Every time you call readSomething with an address like "[address]" it needs to do 2 calls to readProcessMemory. Not that much if you're doing it once every second, but it's bad for performance if you're doing it in a maxed interval timer.
On a related note, reuse values you've already read. In general try to minimize the calls to readProcessMemory as much as you can.
`readInteger("[globalbounty]") > 50000000` - why is 50000000 important? Does the game just happen to manually wrap around to negative numbers at that hardcoded limit? If it uses normal signed integers, then just read it as a signed integer instead (readInteger 2nd argument `true`) and check if it's less than 0.
`lastHeatLevel` is a mess as you've pointed out. `maxHeat` is written to but never used, why not use that?
Name your values better. `updated` is bad, and random calls to readWhatever don't say anything about what the value is.
Those offsets from the address of heat are ridiculous. It's insane for a single structure to take up over 162 KiB. This might make sense if it were static, but then you should just be using `game.exe+offset` notation instead.
But hey, if it works, it works...
`readInteger("[heat]+1E33C") == 1065353216` - that is a float. 1065353216 = 0x3F800000 = 1.0f
What's the deal with `closeToEscape`? Why not just check when the player has escaped?
Here's something a little closer to what you might want:
| Code: | {$lua}
if syntaxcheck then return end
if bountyTimer then bountyTimer.destroy(); bountyTimer = nil end
[ENABLE]
local lastHeat = assert(readFloat'[heat]', '[heat] not defined')
local maxHeat = lastHeat
local policeChaseActive = false
local heatLevels = {
{
level = 1,
heatBound = 2,
mult = 1.25
},
{
level = 2,
heatBound = 8,
mult = 1.75
},
{
level = 3,
heatBound = 16,
mult = 2.25
},
{
level = 4,
heatBound = 26,
mult = 3.25
},
{
level = 5,
heatBound = 35,
mult = 4
},
{
level = 6,
heatBound = 52,
mult = 5
}
}
local function levelFromHeat(heat)
for _,v in ipairs(heatLevels) do
if heat < v.heatBound then
return v
end
end
return nil
end
bountyTimer = createTimer()
bountyTimer.Interval = 10
bountyTimer.OnTimer = function(t)
local heatAddr = readPointer'heat'
if not heatAddr or heatAddr == 0 then
t.Enabled = false
error'heat not defined'
end
local bountyAddr = readPointer'globalbounty'
if not bountyAddr or bountyAddr == 0 then
t.Enabled = false
error'globalbounty not defined'
end
local newHeat = readFloat(heatAddr)
local globalBounty = readInteger(bountyAddr)
local pursuitBounty = readInteger(heatAddr-0xA438)
-- check negative bounty
if globalBounty > 50000000 then
writeInteger(bountyAddr, 0)
globalBounty = 0
end
-- write global bounty into hud
writeInteger(heatAddr - 0xA43C, globalBounty)
if not policeChaseActive then
-- there might be a race condition where this fails; does heat need to be 0?
if newHeat == 0 and pursuitBounty > 0 then
policeChaseActive = true
maxHeat = 0
print'ENTERS CHASE'
end
else
-- police chase is active
maxHeat = math.max(maxHeat, newHeat)
-- no clue what these values are
if readByte(heatAddr + 0x1E4C9) == 1 then
if readFloat(heatAddr + 0x1E33C) == 1 then
-- player busted
writeInteger(bountyAddr, globalBounty - pursuitBounty)
policeChaseActive = false
elseif newHeat == 0 and lastHeat > 0 then
-- player escaped
local heatLevel = levelFromHeat(maxHeat)
if not heatLevel then
t.Enabled = false
error'max heat exceeded max heat level'
end
print('ESCAPED WITH HEAT ' .. heatLevel.level)
writeInteger(bountyAddr, globalBounty + heatLevel.mult * pursuitBounty)
-- this technically doesn't match up with your code, but it's probably what you wanted
policeChaseActive = false
end
end
end -- is police chase active
lastHeat = newHeat
end --OnTimer
[DISABLE]
-- timer destroyed above
| I might've missed something, didn't take too close of a look
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 37
Joined: 16 Feb 2017 Posts: 1532
|
Posted: Sat Oct 07, 2023 1:25 pm Post subject: |
|
|
Just a simulation:
Test the current result within certain intervals.
| Code: | heatLO = 0.0
heatEX = 0.0
function bountyCheck()
heatLO = heatEX
print(heatLO)
--heatLO = readFloat("[heat]")
if heatLO == 0 then
print("LAST HEAT IS 0")
else
if heatLO > 0.1 and heatLO < 2.1 then
--if readFloat("[heat]") < 2.0 then
print("LAST HEAT IS 1")
lastHeatLevel = 1
elseif heatLO > 2.1 and heatLO < 8.1 then
--elseif readFloat("[heat]") < 8.0 then
print("LAST HEAT IS 2")
lastHeatLevel = 2
elseif heatLO > 8.1 and heatLO < 16.1 then
--elseif readFloat("[heat]") < 16.0 then
print("LAST HEAT IS 3")
lastHeatLevel = 3
elseif heatLO > 16.1 and heatLO < 26.1 then
--elseif readFloat("[heat]") < 26.0 then
print("LAST HEAT IS 4")
lastHeatLevel = 4
elseif heatLO > 26.1 and heatLO < 35.1 then
--elseif readFloat("[heat]") < 35.0 then
print("LAST HEAT IS 5")
lastHeatLevel = 5
elseif heatLO > 35.1 and heatLO < 52.1 then
--elseif readFloat("[heat]") < 52.0 then
print("LAST HEAT IS 6")
lastHeatLevel = 6
end
--elseif heatLO > heatHI then
maxHeat = heatLO
end
print(" ")
end
if bC then bC.Destroy() bC=nil end
bC=createTimer()
bC.Interval=300
bC.OnTimer=bountyCheck
bC.Enabled=false
if eC then eC.Destroy() eC=nil end
eC=createTimer()
eC.Interval=300
eC.Enabled=false
chkIndex = 0
eC.OnTimer=function()
chkIndex=tonumber(chkIndex) + 1
if chkIndex==20 then
bC.Enabled=false
eC.Enabled=false
else
heatEX = math.random(0,52)
end
end
eC.Enabled=true
bC.Enabled=true |
_________________
|
|
| Back to top |
|
 |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Sun Oct 08, 2023 12:13 am Post subject: |
|
|
| ParkourPenguin wrote: | All this does is queue some code that does literally nothing to run in 10ms. If you think createTimer blocks, you're wrong.
| Code: | print'1'
createTimer(10, function() print'3' end)
print'2'
-- prints 1 / 2 / 3 |
|
Got it.
On a side note, I am honestly not sure how timers work in LUA. No matter the way I define the timer and function, whenever I disable it manually or the game crashes and CE is the one disabling it, it doesn’t kill the timer correctly most of the time. Is this a known bug?
Missing `if syntaxcheck then return end`
What is this for? I remember reading years ago on the forum people recommending it for all scripts. Let’s say the value of the variable used for the timer is nil, does it account for that? And lastly, where is it best to put this line in (at start of script, end, on each timer function, etc)?
Scripts can be disabled without executing the disable section. Try to account for that.
How so? Why would it get disabled this way?
| ParkourPenguin wrote: |
`readPointer("[heat]")` - It's not a pointer, it's a float.
|
Correct, but I thought is right comparing if the address of that registersymbol is not nil cause there’s a chance the script I made to give me that pointer might’ve not get enabled so there isn’t any symbol with that name or for whatever reason even though the script gets enabled correctly, the value at the address might be nil due to changes in the stack (perhaps?)
| ParkourPenguin wrote: |
Every time you call readSomething with an address like "[address]" it needs to do 2 calls to readProcessMemory. Not that much if you're doing it once every second, but it's bad for performance if you're doing it in a maxed interval timer.
On a related note, reuse values you've already read. In general try to minimize the calls to readProcessMemory as much as you can.
|
Interesting. If so, then how should it be done correctly? Allocating memory, dumping the value onto it and then reading value from this new address instead?
I haven’t noticed that much of a hit in performance doing things this way. Also, my goal is to "make it work" easily and then port all the code to a proper language like cpp or c# in the future. It’s just a prototype.
| ParkourPenguin wrote: |
`readInteger("[globalbounty]") > 50000000` - why is 50000000 important? Does the game just happen to manually wrap around to negative numbers at that hardcoded limit? If it uses normal signed integers, then just read it as a signed integer instead (readInteger 2nd argument `true`) and check if it's less than 0.
|
Good question. This game for some reason if you write manually a number bigger than 50mil gets overflow and switches to the negative spectrum. They might have hardcoded it this way to prevent people cheating in MP.
| ParkourPenguin wrote: |
`lastHeatLevel` is a mess as you've pointed out. `maxHeat` is written to but never used, why not use that?
Name your values better. `updated` is bad, and random calls to readWhatever don't say anything about what the value is.
|
Yeeeeeah, I am not very proud of what I did there. Was testing stuff, deleting, adding, changing back and forth until I gave up eventually.
Not sure what you meant here "and random calls to readWhatever don't say anything about what the value is."
| ParkourPenguin wrote: |
Those offsets from the address of heat are ridiculous. It's insane for a single structure to take up over 162 KiB. This might make sense if it were static, but then you should just be using `game.exe+offset` notation instead.
But hey, if it works, it works...
|
Yeah, it’s actually like that in their previous game too. Is this really that bad? The way I see it is they tried to make it easier for themselves to debug. And no, those are not static (as far as I know).
`readInteger("[heat]+1E33C") == 1065353216` - that is a float. 1065353216 = 0x3F800000 = 1.0f
True, but where are you going with this?
What's the deal with `closeToEscape`? Why not just check when the player has escaped?
This one is my way to figure out when the player is about to end the chase by escaping, i.e, knowing when the heat level is close 0.0 (ending)
I thought doing this because I need to have the total bounty prepared to be written inside the globalbounty address since I am using a bonus offset which I am multiplying according to the heat level the player escaped at.
I am aware what I am trying to do is too difficult but I don’t have an address or a combo of addresses around the heat level region (which seems where all the game logic stuff is being set up) that can tell me when the player has escaped. If I cannot make this work, I might have to search for these address(es)...
| ParkourPenguin wrote: |
Here's something a little closer to what you might want:
| Code: | {$lua}
if syntaxcheck then return end
if bountyTimer then bountyTimer.destroy(); bountyTimer = nil end
[ENABLE]
local lastHeat = assert(readFloat'[heat]', '[heat] not defined')
local maxHeat = lastHeat
local policeChaseActive = false
local heatLevels = {
{
level = 1,
heatBound = 2,
mult = 1.25
},
{
level = 2,
heatBound = 8,
mult = 1.75
},
{
level = 3,
heatBound = 16,
mult = 2.25
},
{
level = 4,
heatBound = 26,
mult = 3.25
},
{
level = 5,
heatBound = 35,
mult = 4
},
{
level = 6,
heatBound = 52,
mult = 5
}
}
local function levelFromHeat(heat)
for _,v in ipairs(heatLevels) do
if heat < v.heatBound then
return v
end
end
return nil
end
bountyTimer = createTimer()
bountyTimer.Interval = 10
bountyTimer.OnTimer = function(t)
local heatAddr = readPointer'heat'
if not heatAddr or heatAddr == 0 then
t.Enabled = false
error'heat not defined'
end
local bountyAddr = readPointer'globalbounty'
if not bountyAddr or bountyAddr == 0 then
t.Enabled = false
error'globalbounty not defined'
end
local newHeat = readFloat(heatAddr)
local globalBounty = readInteger(bountyAddr)
local pursuitBounty = readInteger(heatAddr-0xA438)
-- check negative bounty
if globalBounty > 50000000 then
writeInteger(bountyAddr, 0)
globalBounty = 0
end
-- write global bounty into hud
writeInteger(heatAddr - 0xA43C, globalBounty)
if not policeChaseActive then
-- there might be a race condition where this fails; does heat need to be 0?
if newHeat == 0 and pursuitBounty > 0 then
policeChaseActive = true
maxHeat = 0
print'ENTERS CHASE'
end
else
-- police chase is active
maxHeat = math.max(maxHeat, newHeat)
-- no clue what these values are
if readByte(heatAddr + 0x1E4C9) == 1 then
if readFloat(heatAddr + 0x1E33C) == 1 then
-- player busted
writeInteger(bountyAddr, globalBounty - pursuitBounty)
policeChaseActive = false
elseif newHeat == 0 and lastHeat > 0 then
-- player escaped
local heatLevel = levelFromHeat(maxHeat)
if not heatLevel then
t.Enabled = false
error'max heat exceeded max heat level'
end
print('ESCAPED WITH HEAT ' .. heatLevel.level)
writeInteger(bountyAddr, globalBounty + heatLevel.mult * pursuitBounty)
-- this technically doesn't match up with your code, but it's probably what you wanted
policeChaseActive = false
end
end
end -- is police chase active
lastHeat = newHeat
end --OnTimer
[DISABLE]
-- timer destroyed above
| I might've missed something, didn't take too close of a look |
I will take a look up closely and post here soon.
| AylinCE wrote: |
Just a simulation:
Test the current result within certain intervals.
|
Will check it out too.[/quote]
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Sun Oct 08, 2023 11:26 am Post subject: |
|
|
| mrally2 wrote: | | On a side note, I am honestly not sure how timers work in LUA. No matter the way I define the timer and function, whenever I disable it manually or the game crashes and CE is the one disabling it, it doesn’t kill the timer correctly most of the time. Is this a known bug? | You leaked a timer. e.g.:
| Code: | mytimer = createTimer() -- timer 1
mytimer.OnTimer = ...
mytimer = createTimer() -- timer 2
mytimer.OnTimer = ...
-- the first timer is leaked and that OnTimer code will always run | CE doesn't "disable" timers when the game crashes. Timers have absolutely nothing to do with the target process.
| mrally2 wrote: | | What is this for? I remember reading years ago on the forum people recommending it for all scripts. Let’s say the value of the variable used for the timer is nil, does it account for that? And lastly, where is it best to put this line in (at start of script, end, on each timer function, etc)? | {$lua} blocks in AA scripts are meant for substituting AA code into the AA script. e.g.:
| Code: | {$lua}
return 'define(text,whatever)'
{$asm} | The returned string gets substituted back into the script.
Importantly, the script may not be syntactically valid if the {$lua} block is not run. Therefore, CE has to execute the code in the {$lua} block to check if the syntax of the AA script is correct. This means the Lua code in the {$lua} block gets run more often than when the script is enabled or disabled.
The variable `syntaxcheck` is set to indicate whether CE is performing a syntaxcheck or if the script is actually being executed.
This is useful for avoiding unnecessary work, e.g. AOBScan:
| Code: | [ENABLE]
{$lua}
-- if CE is just checking the syntax, then just define the text to be any syntactically valid address
if syntaxcheck then return 'define(INJECT,0)' end
-- otherwise, actually do the aobscan:
local result = assert(AOBScanUnique(...))
return ('define(INJECT,%X)'):format(result)
{$asm} |
If the {$lua} block doesn't substitute anything back into the script and is only used for side effects, then it should just return nothing when CE is checking the syntax of the AA code. Hence:
| Code: | {$lua}
if syntaxcheck then return end
...
{$asm} | That line should be at the top of every single {$lua} block that doesn't substitute anything into the AA script.
I have no idea what you mean by "the value of the variable used for the timer is nil".
| mrally2 wrote: | Scripts can be disabled without executing the disable section. Try to account for that.
How so? Why would it get disabled this way? | This prompt can occur when reattaching to another process. Most AA scripts won't be able to run the disable code cleanly if the original process dies and/or CE is attached to a different process.
This is the purpose of this line:
| Code: | | if bountyTimer then bountyTimer.destroy(); bountyTimer = nil end | When this is placed above [ENABLE], it gets run when the script is both enabled and disabled. Less chance to accidentally leak timers that way.
| mrally2 wrote: | | Correct, but I thought is right comparing if the address of that registersymbol is not nil cause there’s a chance the script I made to give me that pointer might’ve not get enabled so there isn’t any symbol with that name or for whatever reason even though the script gets enabled correctly, the value at the address might be nil due to changes in the stack (perhaps?) | That code reads the pointer value at the pointer value at `heat`. There should only be one dereference there.
The only ways this could go wrong are that `heat` might not be a registered symbol, `heat` could be 0 (uninitialized), and `heat` might be initialized but has outlived the lifetime of the object it points to. My code accounts for the first two possibilities:
| Code: | local heatAddr = readPointer'heat'
if not heatAddr or heatAddr == 0 then
... | The third possibility can't really be accounted for. Get a better injection point to copy the address if it's a problem.
I don't know what you mean by "changes in the stack".
| mrally2 wrote: | | Interesting. If so, then how should it be done correctly? Allocating memory, dumping the value onto it and then reading value from this new address instead? | No. Allocating memory and dumping the value onto it does nothing. You'd still need to call readProcessMemory to read from the memory you allocated.
Look at the code I wrote. It reads from the symbol "heat" once and stores it into `heatAddr`. After that, it reuses `heatAddr` and doesn't read from the symbol "heat" again for that timer iteration.
Same thing regarding reusing values read- see `globalBounty` and `pursuitBounty`.
| mrally2 wrote: | | Not sure what you meant here "and random calls to readWhatever don't say anything about what the value is." |
| Code: | --PLAYER IS BUSTED
if readByte("[heat]+1E4C9") == 1 and readInteger("[heat]+1E33C") == 1065353216 and ... | If a complete stranger reads this, how are they suppose to know what these values are? The comment is a little helpful regarding what your code is doing, but it doesn't say anything about the semantics of those values being read.
This would be better if it's correct (I'm just guessing):
| Code: | local isEscapingPursuit = readByte(...) == 1
if isEscapingPursuit and ... | Sure, you could just substitute the expression for the variable and get your code, but shorter code doesn't always mean better code. It's important for code to be readable and maintainable.
| mrally2 wrote: | | Yeah, it’s actually like that in their previous game too. Is this really that bad? The way I see it is they tried to make it easier for themselves to debug. | As I said, if it works, it works. That wasn't a gripe on you but more so the absurdity of the situation. If something does break, however, that would be one of the first things I'd check.
| mrally2 wrote: | `readInteger("[heat]+1E33C") == 1065353216` - that is a float. 1065353216 = 0x3F800000 = 1.0f
True, but where are you going with this? | Use readFloat instead and compare it against 1.
| Code: | readInteger(...) == 1065353216
readFloat(...) == 1 | The second is more readable code.
| "mrally2 wrote: | | I am aware what I am trying to do is too difficult but I don’t have an address or a combo of addresses around the heat level region (which seems where all the game logic stuff is being set up) that can tell me when the player has escaped. If I cannot make this work, I might have to search for these address(es)... | If ultimap is available or the code filter doesn't crash you can try those.
Otherwise, while not in a chase, do an unknown initial value scan. Get in a chase and do a changed value scan. Unchanged value scan a few times, exit chase, changed value, unchanged value a few times, get in chase, changed value...
Keep doing "unchanged value" scans manually until they don't take that long. Then you can tell CE to automatically repeat them.
As for the value type, I don't know what would be best. I'd guess "byte" should catch anything important. "4 byte" might be bad if the game encodes bools as 1-byte values. Then multiple bools could be packed together and changed/unchanged scans might see the wrong value changing.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Sun Oct 08, 2023 6:30 pm Post subject: |
|
|
| ParkourPenguin wrote: | | mrally2 wrote: | | On a side note, I am honestly not sure how timers work in LUA. No matter the way I define the timer and function, whenever I disable it manually or the game crashes and CE is the one disabling it, it doesn’t kill the timer correctly most of the time. Is this a known bug? | You leaked a timer. e.g.:
| Code: | mytimer = createTimer() -- timer 1
mytimer.OnTimer = ...
mytimer = createTimer() -- timer 2
mytimer.OnTimer = ...
-- the first timer is leaked and that OnTimer code will always run | CE doesn't "disable" timers when the game crashes. Timers have absolutely nothing to do with the target process.
| mrally2 wrote: | | What is this for? I remember reading years ago on the forum people recommending it for all scripts. Let’s say the value of the variable used for the timer is nil, does it account for that? And lastly, where is it best to put this line in (at start of script, end, on each timer function, etc)? | {$lua} blocks in AA scripts are meant for substituting AA code into the AA script. e.g.:
| Code: | {$lua}
return 'define(text,whatever)'
{$asm} | The returned string gets substituted back into the script.
Importantly, the script may not be syntactically valid if the {$lua} block is not run. Therefore, CE has to execute the code in the {$lua} block to check if the syntax of the AA script is correct. This means the Lua code in the {$lua} block gets run more often than when the script is enabled or disabled.
The variable `syntaxcheck` is set to indicate whether CE is performing a syntaxcheck or if the script is actually being executed.
This is useful for avoiding unnecessary work, e.g. AOBScan:
| Code: | [ENABLE]
{$lua}
-- if CE is just checking the syntax, then just define the text to be any syntactically valid address
if syntaxcheck then return 'define(INJECT,0)' end
-- otherwise, actually do the aobscan:
local result = assert(AOBScanUnique(...))
return ('define(INJECT,%X)'):format(result)
{$asm} |
If the {$lua} block doesn't substitute anything back into the script and is only used for side effects, then it should just return nothing when CE is checking the syntax of the AA code. Hence:
| Code: | {$lua}
if syntaxcheck then return end
...
{$asm} | That line should be at the top of every single {$lua} block that doesn't substitute anything into the AA script.
I have no idea what you mean by "the value of the variable used for the timer is nil".
| mrally2 wrote: | Scripts can be disabled without executing the disable section. Try to account for that.
How so? Why would it get disabled this way? | This prompt can occur when reattaching to another process. Most AA scripts won't be able to run the disable code cleanly if the original process dies and/or CE is attached to a different process.
This is the purpose of this line:
| Code: | | if bountyTimer then bountyTimer.destroy(); bountyTimer = nil end | When this is placed above [ENABLE], it gets run when the script is both enabled and disabled. Less chance to accidentally leak timers that way.
| mrally2 wrote: | | Correct, but I thought is right comparing if the address of that registersymbol is not nil cause there’s a chance the script I made to give me that pointer might’ve not get enabled so there isn’t any symbol with that name or for whatever reason even though the script gets enabled correctly, the value at the address might be nil due to changes in the stack (perhaps?) | That code reads the pointer value at the pointer value at `heat`. There should only be one dereference there.
The only ways this could go wrong are that `heat` might not be a registered symbol, `heat` could be 0 (uninitialized), and `heat` might be initialized but has outlived the lifetime of the object it points to. My code accounts for the first two possibilities:
| Code: | local heatAddr = readPointer'heat'
if not heatAddr or heatAddr == 0 then
... | The third possibility can't really be accounted for. Get a better injection point to copy the address if it's a problem.
I don't know what you mean by "changes in the stack".
| mrally2 wrote: | | Interesting. If so, then how should it be done correctly? Allocating memory, dumping the value onto it and then reading value from this new address instead? | No. Allocating memory and dumping the value onto it does nothing. You'd still need to call readProcessMemory to read from the memory you allocated.
Look at the code I wrote. It reads from the symbol "heat" once and stores it into `heatAddr`. After that, it reuses `heatAddr` and doesn't read from the symbol "heat" again for that timer iteration.
Same thing regarding reusing values read- see `globalBounty` and `pursuitBounty`.
| mrally2 wrote: | | Not sure what you meant here "and random calls to readWhatever don't say anything about what the value is." |
| Code: | --PLAYER IS BUSTED
if readByte("[heat]+1E4C9") == 1 and readInteger("[heat]+1E33C") == 1065353216 and ... | If a complete stranger reads this, how are they suppose to know what these values are? The comment is a little helpful regarding what your code is doing, but it doesn't say anything about the semantics of those values being read.
This would be better if it's correct (I'm just guessing):
| Code: | local isEscapingPursuit = readByte(...) == 1
if isEscapingPursuit and ... | Sure, you could just substitute the expression for the variable and get your code, but shorter code doesn't always mean better code. It's important for code to be readable and maintainable.
| mrally2 wrote: | | Yeah, it’s actually like that in their previous game too. Is this really that bad? The way I see it is they tried to make it easier for themselves to debug. | As I said, if it works, it works. That wasn't a gripe on you but more so the absurdity of the situation. If something does break, however, that would be one of the first things I'd check.
| mrally2 wrote: | `readInteger("[heat]+1E33C") == 1065353216` - that is a float. 1065353216 = 0x3F800000 = 1.0f
True, but where are you going with this? | Use readFloat instead and compare it against 1.
| Code: | readInteger(...) == 1065353216
readFloat(...) == 1 | The second is more readable code.
| "mrally2 wrote: | | I am aware what I am trying to do is too difficult but I don’t have an address or a combo of addresses around the heat level region (which seems where all the game logic stuff is being set up) that can tell me when the player has escaped. If I cannot make this work, I might have to search for these address(es)... | If ultimap is available or the code filter doesn't crash you can try those.
Otherwise, while not in a chase, do an unknown initial value scan. Get in a chase and do a changed value scan. Unchanged value scan a few times, exit chase, changed value, unchanged value a few times, get in chase, changed value...
Keep doing "unchanged value" scans manually until they don't take that long. Then you can tell CE to automatically repeat them.
As for the value type, I don't know what would be best. I'd guess "byte" should catch anything important. "4 byte" might be bad if the game encodes bools as 1-byte values. Then multiple bools could be packed together and changed/unchanged scans might see the wrong value changing. |
The script you made for the bounty is on point!
About the timer leakage, is this what happens when I define 2 global variables with the same name on different scripts and I use them to initialize the timer or did I get it wrong?
So, there’s no way to check if process is detached, kill all timers?
| ParkourPenguin wrote: | {$lua} blocks in AA scripts are meant for substituting AA code into the AA script. e.g.:
| Code: | {$lua}
return 'define(text,whatever)'
{$asm} | The returned string gets substituted back into the script.
Importantly, the script may not be syntactically valid if the {$lua} block is not run. Therefore, CE has to execute the code in the {$lua} block to check if the syntax of the AA script is correct. This means the Lua code in the {$lua} block gets run more often than when the script is enabled or disabled. | What do you mean exactly by "the returned string gets substituted back into the script" and "This means the Lua code in the {$lua} block gets run more often than when the script is enabled or disabled"
By "the value of the variable used for the timer is nil" I meant what if the eC variable in eC=createTimer(nil) becomes nil because the timer gets leaked somehow?
| ParkourPenguin wrote: | This is the purpose of this line:
| Code: | | if bountyTimer then bountyTimer.destroy(); bountyTimer = nil end | When this is placed above [ENABLE], it gets run when the script is both enabled and disabled. Less chance to accidentally leak timers that way.
|
So this is the most efficient way to destroy timers and avoid leakage, interesting.
I appreciate the advice on the better coding practices.
I got another script which I don’t really know how to make, if its okay not to create another post.
There’s over 183 spots around the map where you have "jackspots" available to change your car on the fly (might be useful to escape a chase instantly when in cooldown).
Every spot has a jackspot ID which the following the instructions are writing ot it
| Code: |
006ED446 - 39 50 20 - cmp [eax+20],edx
006FC928 - 39 78 20 - cmp [eax+20],edi
006FF660 - 39 48 20 - cmp [eax+20],ecx
00747C3B - 8B 43 20 - mov eax,[ebx+20]
00747C99 - 8B 53 20 - mov edx,[ebx+20]
|
If you substitute the jackspot ID with 0 (or a value that is none of the IDs already defined) then the car will not spawn. I want to randomize this to have very few cars around the map instead.
I made this script but it overwrites the value of all the addresses to 0.
| Code: |
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
newmem:
push ecx
push edx
push eax
call rand // puts a random number into EAX
xor edx,edx // makes EDX 0 to make sure you're only dividing EAX by a number
mov ecx,#10 // moves 10 (decimal) into ECX.
div ecx // divides EDX:EAX (the random number) by ECX (10)
// remainder is stored in EDX, which is now effectively a pseudo-random value between 0 and 9 inclusive.
pop eax
pop edx
pop ecx
cmp edx,(int)0
jne HIDE_JACKSPOT
originalcode:
cmp [eax+20],edi
je NFS13.exe+2FC937
jmp exit
HIDE_JACKSPOT:
mov [eax+20],(int)0
cmp [eax+20],edi
je NFS13.exe+2FC937
jmp exit
exit:
jmp returnhere
"NFS13.exe"+2FC928:
jmp newmem
returnhere:
|
What’s wrong with this script? And, the best way would be not overwriting the values at [eax+20] as I dont have a way to revert the original values back into place. Is this possible?
I have one more thing. I wanna overwrite the value of the odometer present inside the HUD with the health level the car has as it was left unused.
The HUD is coded inside the game files as Widget.Nitrous.Odometer(DIST,LONG)
To be able to search its real value I’d need first to change that string in memory to Widget.Nitrous.Odometer(FULLWORD). Then instead of 534.3 MI I get 534328... which is an integer and not a float????
After searching the value, I get around 5 results. The first result does seem normal but the other 4 they oscilate between the value being increased normally as the car gets driven and 0 (perhaps these are copy addresses? and no, they are not in 00XXXXXX, they are inside 0BXXXXXX same as the heat level and all gamelogic stuff)
Getting which instruction writes to the first result I get this:
| Code: | | 00587326 - F3 0F58 86 C41B0000 - addss xmm0,[esi+00001BC4] |
If I see which addresses it writes to it’s 30 or more. Noping it only does mess with the UI but the milage counter still runs normally.
What would you suggest doing here?
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Sun Oct 08, 2023 7:30 pm Post subject: |
|
|
| mrally2 wrote: | | About the timer leakage, is this what happens when I define 2 global variables with the same name on different scripts and I use them to initialize the timer or did I get it wrong? | Yes. That's why global state is considered generally bad in programming. Given how CE's Lua API was designed, there's not too much that can be done without creating some helper functions.
| mrally2 wrote: | | So, there’s no way to check if process is detached, kill all timers? | There is no good way to kill all timers. This has been discussed before, and it's a good thing. There are some timers that shouldn't die.
If you really want that kind of behaviour, you'll need to make your own helper functions for creating and managing timers.
| mrally2 wrote: | | What do you mean exactly by "the returned string gets substituted back into the script" and "This means the Lua code in the {$lua} block gets run more often than when the script is enabled or disabled" | This:
| Code: | ...
{$lua}
return 'define(text,whatever)'
{$asm}
... | Becomes this:
| Code: | ...
define(text,whatever)
... |
Open a new AA script window (Memory Viewer -> Tools -> Auto Assemble) and paste this code:
| Code: | {$lua}
[ENABLE]
print('Enabled\tsyntaxcheck:', tostring(syntaxcheck))
[DISABLE]
print('Disabled\tsyntaxcheck:', tostring(syntaxcheck))
{$asm} | Assign it to the cheat table. Activate it, deactivate it, open it and click "Ok" at the bottom, etc. See what happens.
| mrally2 wrote: | | By "the value of the variable used for the timer is nil" I meant what if the eC variable in eC=createTimer(nil) becomes nil because the timer gets leaked somehow? | eC doesn't just become nil. It's a global variable. The only way it could become nil is if you set it to nil.
When I said leaked, I meant you overwrote the old timer that was in the global variable with a new timer without destroying the old timer first. The old timer then becomes inaccessible to Lua and will run forever, analogous to a memory leak.
| mrally2 wrote: | Every spot has a jackspot ID which the following the instructions are writing ot it
... | Minor note: those instructions access memory. None of them write to memory.
| mrally2 wrote: | I made this script but it overwrites the value of all the addresses to 0.
| I assume `rand` refers to the C standard library function.
Anyway, you overwrite edx with `pop edx`. It's good practice to back up and restore registers, but only restore them once you're done using them.
`pop` doesn't modify eflags, so it's fine to put those between `cmp edx,0` and `jne HIDE_JACKSPOT`.
| mrally2 wrote: | I have one more thing. I wanna overwrite the value of the odometer present inside the HUD with the health level the car has as it was left unused.
...
What would you suggest doing here? | Find the value that corresponds to the odometer and do a code injection on an instruction that accesses it (preferably writes to it).
If you can't find it, do an unknown initial value scan and changed / unchanged value scans for various value types. It could be something you don't expect.
(if changing a value doesn't work, changing code that writes to the value almost certainly won't work either)
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Mon Oct 09, 2023 9:01 am Post subject: |
|
|
| ParkourPenguin wrote: | Yes. That's why global state is considered generally bad in programming. Given how CE's Lua API was designed, there's not too much that can be done without creating some helper functions.
|
True true. What are helper functions though?
| ParkourPenguin wrote: | This:
| Code: | ...
{$lua}
return 'define(text,whatever)'
{$asm}
... | Becomes this:
| Code: | ...
define(text,whatever)
... |
Open a new AA script window (Memory Viewer -> Tools -> Auto Assemble) and paste this code:
| Code: | {$lua}
[ENABLE]
print('Enabled\tsyntaxcheck:', tostring(syntaxcheck))
[DISABLE]
print('Disabled\tsyntaxcheck:', tostring(syntaxcheck))
{$asm} | Assign it to the cheat table. Activate it, deactivate it, open it and click "Ok" at the bottom, etc. See what happens.
|
It says either Enabled syntaxcheck: false or Disabled syntaxcheck: false
I’m still unsure where you are going with this but I get that doing the define through lua has the same effect. It goes the other way around, no? If using autoAssemble([[...]]) inside a lua block.
| ParkourPenguin wrote: | eC doesn't just become nil. It's a global variable. The only way it could become nil is if you set it to nil.
When I said leaked, I meant you overwrote the old timer that was in the global variable with a new timer without destroying the old timer first. The old timer then becomes inaccessible to Lua and will run forever, analogous to a memory leak.
|
Ok, i see now. My premise was wrong, its not nil, is just leaked.
| ParkourPenguin wrote: | | Minor note: those instructions access memory. None of them write to memory. |
you are right, my bad
| ParkourPenguin wrote: | I assume `rand` refers to the C standard library function.
Anyway, you overwrite edx with `pop edx`. It's good practice to back up and restore registers, but only restore them once you're done using them.
`pop` doesn't modify eflags, so it's fine to put those between `cmp edx,0` and `jne HIDE_JACKSPOT`.
|
Ok so, I redid the script from above:
| Code: | [ENABLE]
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
//create variable
alloc(hideJackspot,100)
registersymbol(hideJackspot)
//give it a value
hideJackspot:
dd (int)0
newmem:
push ecx
push edx
push eax
call rand // puts a random number into EAX
xor edx,edx // makes EDX 0 to make sure you're only dividing EAX by a number
mov ecx,#10 // moves 10 (decimal) into ECX.
div ecx // divides EDX:EAX (the random number) by ECX (10)
// remainder is stored in EDX, which is now effectively a pseudo-random value between 0 and 9 inclusive.
cmp edx,(int)0
pop eax
pop edx
pop ecx
je HIDE_JACKSPOT
originalcode:
cmp [eax+20],edi
je NFS13.exe+2FC937
jmp exit
HIDE_JACKSPOT:
cmp [hideJackspot],(int)173 //WE LEAVE ONLY 10 JACKSPOTS AVAILABLE
je originalcode
mov [eax+20],(int)0 //WE HIDE THE JACKSPOT
add [hideJackspot],(int)1
jmp originalcode
exit:
jmp returnhere
"NFS13.exe"+2FC928:
jmp newmem
returnhere:
[DISABLE]
dealloc(*)
unregistersymbol(*)
"NFS13.exe"+2FC928:
cmp [eax+20],edi
je NFS13.exe+2FC937 |
Using this instruction as a basis, it doesn’t overwrite the value of the number of addresses(jackspots) as intended and I am not sure why. It’s not overwriting around 40 values instead of 10 (183-173)
EDIT: Could using two conditional jumps one after the other be the causant of the problem? je NFS13.exe+2FC937 jmp exit
After that, I tried this on another instruction:
| Code: | [ENABLE]
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
//create variable
alloc(hideJackspot,100)
registersymbol(hideJackspot)
//give it a value
hideJackspot:
dd (int)0
newmem:
push ecx
push edi
push eax
call rand // puts a random number into EAX
xor edi,edi // makes EDX 0 to make sure you're only dividing EAX by a number
mov ecx,#10 // moves 10 (decimal) into ECX.
div ecx // divides EDX:EAX (the random number) by ECX (10)
// remainder is stored in EDX, which is now effectively a pseudo-random value between 0 and 9 inclusive.
cmp edi,(int)0
pop eax
pop edi
pop ecx
je HIDE_JACKSPOT
originalcode:
mov edx,[ebx+20]
movaps xmm1,xmm0
jmp exit
HIDE_JACKSPOT:
cmp [hideJackspot],(int)173 //WE LEAVE ONLY 10 JACKSPOTS AVAILABLE
je originalcode
mov [ebx+20],(int)0 //WE HIDE THE JACKSPOT
add [hideJackspot],(int)1
jmp originalcode
exit:
jmp returnhere
"NFS13.exe"+347C99:
jmp newmem
nop
returnhere:
[DISABLE]
dealloc(*)
unregistersymbol(*)
"NFS13.exe"+347C99:
mov edx,[ebx+20]
movaps xmm1,xmm0 |
This one does overwrite the correct amount of values BUT it seems that it always takes the same addresses (EDIT: If you sort the addresses from smallest to biggest, it leaves the bigger ones unchanged) no matter the game instance (restarting the game doesn’t affect it). How can I make it more random? Increasing the range does nothing, still gives the same result.
| ParkourPenguin wrote: | Find the value that corresponds to the odometer and do a code injection on an instruction that accesses it (preferably writes to it).
If you can't find it, do an unknown initial value scan and changed / unchanged value scans for various value types. It could be something you don't expect.
(if changing a value doesn't work, changing code that writes to the value almost certainly won't work either) |
Oh God...
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Mon Oct 09, 2023 10:33 am Post subject: |
|
|
| mrally2 wrote: | | True true. What are helper functions though? | A helper function is a function that helps something else work the way you want it to work. It typically does something else in addition to what the original thing did and/or acts as an abstraction over several related functions.
e.g. a function "createManagedTimer" that stores timers in a table indexed by some string. All managed timers can be destroyed easily by looping through the table w/ `pairs`, leaking isn't a problem as it can destroy existing timers with the same key, and other timers that shouldn't be arbitrarily destroyed won't be affected (i.e. it's probably a bad idea to hook `createTimer` itself).
| mrally2 wrote: | It says either Enabled syntaxcheck: false or Disabled syntaxcheck: false
I’m still unsure where you are going with this | My point is that {$lua} blocks gets executed arbitrarily. They don't only happen when the script is enabled or disabled. If you want the code in {$lua} blocks to run only when you enable or disable the script, you have to add `if syntaxcheck then return end` to stop them from running if the script isn't being enabled / disabled.
| mrally2 wrote: | | it doesn’t overwrite the value of the number of addresses(jackspots) as intended and I am not sure why. It’s not overwriting around 40 values instead of 10 (183-173) | When is the original injection point run? Continuously for all jackspots? Only when you open the map or load into an area, once for each jackspot?
You don't check whether or not you've already hidden that jackspot. Maybe that's a problem, maybe it's something else.
Also, you can use `#` instead of `(int)` to specify decimal numbers (default is hex).
| mrally2 wrote: | | Code: | xor edi,edi // makes EDX 0
...
cmp edi,(int)0 |
| EDI is not the same register as EDX. This `cmp` will always be true.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Mon Oct 09, 2023 11:45 am Post subject: |
|
|
| ParkourPenguin wrote: | | mrally2 wrote: | | True true. What are helper functions though? | A helper function is a function that helps something else work the way you want it to work. It typically does something else in addition to what the original thing did and/or acts as an abstraction over several related functions.
e.g. a function "createManagedTimer" that stores timers in a table indexed by some string. All managed timers can be destroyed easily by looping through the table w/ `pairs`, leaking isn't a problem as it can destroy existing timers with the same key, and other timers that shouldn't be arbitrarily destroyed won't be affected (i.e. it's probably a bad idea to hook `createTimer` itself).
| mrally2 wrote: | It says either Enabled syntaxcheck: false or Disabled syntaxcheck: false
I’m still unsure where you are going with this | My point is that {$lua} blocks gets executed arbitrarily. They don't only happen when the script is enabled or disabled. If you want the code in {$lua} blocks to run only when you enable or disable the script, you have to add `if syntaxcheck then return end` to stop them from running if the script isn't being enabled / disabled.
| mrally2 wrote: | | it doesn’t overwrite the value of the number of addresses(jackspots) as intended and I am not sure why. It’s not overwriting around 40 values instead of 10 (183-173) | When is the original injection point run? Continuously for all jackspots? Only when you open the map or load into an area, once for each jackspot?
You don't check whether or not you've already hidden that jackspot. Maybe that's a problem, maybe it's something else.
Also, you can use `#` instead of `(int)` to specify decimal numbers (default is hex).
| mrally2 wrote: | | Code: | xor edi,edi // makes EDX 0
...
cmp edi,(int)0 |
| EDI is not the same register as EDX. This `cmp` will always be true. |
I see, didn’t know that’s the name they gave them in english.
" If you want the code in {$lua} blocks to run only when you enable or disable the script, you have to add `if syntaxcheck then return end` to stop them from running if the script isn't being enabled / disabled."
Ok, got it.
"When is the original injection point run? Continuously for all jackspots? Only when you open the map or load into an area, once for each jackspot?"
For the first instruction (used in first script posted) is contiunously whilst for the latter is only when entering freeroam mode (after exiting an event such as race, MP, etc)
"You don't check whether or not you've already hidden that jackspot. Maybe that's a problem, maybe it's something else."
| Code: | HIDE_JACKSPOT:
cmp [hideJackspot],(int)173 //WE LEAVE ONLY 10 JACKSPOTS AVAILABLE
je originalcode
cmp [ebx+20],(int)0
je originalcode
mov [ebx+20],(int)0 //WE HIDE THE JACKSPOT
add [hideJackspot],(int)1
jmp originalcode
|
Adding cmp [ebx+20],(int)0 je originalcode creates the same outcome so might be something else...
"Also, you can use `#` instead of `(int)` to specify decimal numbers (default is hex)."
I will take it into account, ty
"EDI is not the same register as EDX. This `cmp` will always be true."
But here’s the interesting thing. If I change EDI for EDX, it will only overwrite to 18 addresses even though it’s set to do so for 173.
Leaving it with EDI, seems to work relatively okay. Is just that the same addresses are overwritten.
Also, wasn’t there a rule that you should avoid using the same register as the original code or have I made it up?
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Mon Oct 09, 2023 1:36 pm Post subject: |
|
|
The push / pop instructions back up and restore the values in the registers so the original code isn't affected.
The original code `mov edx,[ebx+20]` overwrites EDX, so there's no need to back it up in the first place.
You wrote the reason why you need to use EDX in a comment. The div instruction won't magically start using EDI just because you want it to.
Checking if you've already hidden the jackspot isn't going to do anything at the second injection point since it's only run once per jackspot. Try it on the other injection point that's run continuously.
Your script only overwrites 18 addresses because the code jumps to HIDE_JACKSPOT about 1 in every 10 chances (18 in 183 is expected).
The call to `rand` makes it so that only about 10% of the jackspots get hidden, but that limit in the HIDE_JACKSPOT code makes it so that no more than 173 jackspots can be hidden. It's statistically impossible for that limit to be relevant in the first place.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Tue Oct 10, 2023 12:15 am Post subject: |
|
|
| ParkourPenguin wrote: | The push / pop instructions back up and restore the values in the registers so the original code isn't affected.
The original code `mov edx,[ebx+20]` overwrites EDX, so there's no need to back it up in the first place.
You wrote the reason why you need to use EDX in a comment. The div instruction won't magically start using EDI just because you want it to.
Checking if you've already hidden the jackspot isn't going to do anything at the second injection point since it's only run once per jackspot. Try it on the other injection point that's run continuously.
Your script only overwrites 18 addresses because the code jumps to HIDE_JACKSPOT about 1 in every 10 chances (18 in 183 is expected).
The call to `rand` makes it so that only about 10% of the jackspots get hidden, but that limit in the HIDE_JACKSPOT code makes it so that no more than 173 jackspots can be hidden. It's statistically impossible for that limit to be relevant in the first place. |
Alright, so following your advice I was able to make it leave around 10 jackspots available (the quantity might vary due to what you explained before).
It’s not the ideal way of doing it but it’s good to me.
As a curiosity, writing 2 conditional jumps one after the other it’s wrong and the second one will not get executed by any means, right?
Another one I have is, a randomizer like this wont work if done using a {$lua} block, dumping the value into a brand new register symbol and using it for comparison as the AA code is running way faster than lua, isn’t it?
Sorry to ask these supid questions but I am learning asm and lua on fly.
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Tue Oct 10, 2023 1:13 am Post subject: |
|
|
| mrally2 wrote: | | As a curiosity, writing 2 conditional jumps one after the other it’s wrong and the second one will not get executed by any means, right? | If the two conditional jumps read the same flags, then the second would be redundant. It might get executed if the first conditional jump didn't branch.
| Code: | cmp eax,5
je label1
jne label2 // redundant- if the first didn't branch, then clearly eax is not equal to 5 and this must branch; equivalent to `jmp label2` in this context
// code here is dead and will never be run
...
label1:
// here, eax is equal to 5
...
label2:
// here, eax is not equal to 5
... | (note that this example only applies to integers; floating point numbers, particularly NaN values, are very weird and can be both not "equal" and not "not equal")
If the two conditional jumps read different flags, then no, each works fine. Conditional jumps only read from the eflags register- it doesn't get cleared or anything.
| Code: | cmp eax,5
je label1
jg label2 // eax isn't equal to 5- it may or may not be greater than 5
// here, eax is less than 5
...
label1:
// here, eax is equal to 5
...
label2:
// here, eax is greater than 5
... |
| mrally2 wrote: | | Another one I have is, a randomizer like this wont work if done using a {$lua} block, dumping the value into a brand new register symbol and using it for comparison as the AA code is running way faster than lua, isn’t it? | I'm not sure what you mean by this.
There's {$luacode} and {$ccode} if you want to write a code injection in something other than assembly. {$ccode} is significantly faster as there's no IPC to worry about ({$luacode} is run in CE).
https://forum.cheatengine.org/viewtopic.php?t=618134
Registered symbols can only be associated with a single address. Instructions that access multiple addresses can't be simply used like any typical injection copy script.
If you know there's only going to be exactly 183 addresses the instruction accesses, then you could write something that works; however, it's probably better to analyze data instead. Try to find whatever data structure the jackspots are contained in. It could be an array of pointers or maybe some kind of a linked list. If you're not familiar with data structures, this will be difficult. Maybe the pointer scanner could help give you the answer.
Once you find the container that stores the jackspots (or better yet a static pointer to them), then you can use that in a Lua script.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| Back to top |
|
 |
mrally2 Cheater
Reputation: 0
Joined: 01 Apr 2020 Posts: 43
|
Posted: Tue Oct 10, 2023 2:43 am Post subject: |
|
|
| ParkourPenguin wrote: | If the two conditional jumps read the same flags, then the second would be redundant. It might get executed if the first conditional jump didn't branch.
| Code: | cmp eax,5
je label1
jne label2 // redundant- if the first didn't branch, then clearly eax is not equal to 5 and this must branch; equivalent to `jmp label2` in this context
// code here is dead and will never be run
...
label1:
// here, eax is equal to 5
...
label2:
// here, eax is not equal to 5
... | (note that this example only applies to integers; floating point numbers, particularly NaN values, are very weird and can be both not "equal" and not "not equal")
If the two conditional jumps read different flags, then no, each works fine. Conditional jumps only read from the eflags register- it doesn't get cleared or anything.
| Code: | cmp eax,5
je label1
jg label2 // eax isn't equal to 5- it may or may not be greater than 5
// here, eax is less than 5
...
label1:
// here, eax is equal to 5
...
label2:
// here, eax is greater than 5
... |
|
Very interesting, thanks for explaining.
"(note that this example only applies to integers; floating point numbers, particularly NaN values, are very weird and can be both not "equal" and not "not equal")"
I guess this is one of the reasons people say never directly compare floating point numbers, no?
| ParkourPenguin wrote: | I'm not sure what you mean by this.
There's {$luacode} and {$ccode} if you want to write a code injection in something other than assembly. {$ccode} is significantly faster as there's no IPC to worry about ({$luacode} is run in CE).
https://forum.cheatengine.org/viewtopic.php?t=618134
|
I didn’t mean exactly that but I believe is the same intention as you described. What I had in mind was if getting a randomized number inside {$lua}{$asm}, dump it into a register symbol then do the comparison in ASM as I posted.
Here’s an example as I am doing this to randomize the roadblocks:
| Code: |
{$lua}
//////////////////////////////////MORE CODE ABOVE
myTimer = createTimer()
myTimer.Interval = 100
myTimer.OnTimer = function()
if readFloat("[heat]") < 8.0 then
writeInteger('forced_drop', classA[math.random(1,7)])
elseif readFloat("[heat]") < 26.0 then
writeInteger('forced_drop', classA[math.random(1,12)])
elseif readFloat("[heat]") < 38.0 then
writeInteger('forced_drop', classA[math.random(1,16)])
else
local blk = math.random(0,2)
if blk < 2 then
writeInteger('forced_drop', classA[math.random(1,16)])
else
writeInteger('forced_drop', classA[17])
end
end
end
{$asm}
alloc(forced_drop, 4)
registersymbol(forced_drop)
// timers don't run immediately - initialize this first
forced_drop:
dd 2426
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
newmem:
push edi
lea edi,[ecx+0C]
mov edi,[forced_drop]
mov eax,edi
mov edx,[ebp+0C]
pop edi
/////////////////////////////////MORE CODE BELOW
|
| ParkourPenguin wrote: |
Registered symbols can only be associated with a single address. Instructions that access multiple addresses can't be simply used like any typical injection copy script.
If you know there's only going to be exactly 183 addresses the instruction accesses, then you could write something that works; however, it's probably better to analyze data instead. Try to find whatever data structure the jackspots are contained in. It could be an array of pointers or maybe some kind of a linked list. If you're not familiar with data structures, this will be difficult. Maybe the pointer scanner could help give you the answer.
Once you find the container that stores the jackspots (or better yet a static pointer to them), then you can use that in a Lua script. |
I get what you mean but I am not sure it’s worth it for this little randomizer. If it was something more serious or more used, I guess I’d look into it. I did also think about making the randomizer in pure lua code, maybe I’m gonna reconsider it.
Gonna find a hook for that structure so I get a pointer to it, then randomize the jackspots. When overwriting the spots to 0, gonna store all the values and their addresses into 2 separate arrays so I can restore them easily. Does this look convenient?
|
|
| Back to top |
|
 |
ParkourPenguin I post too much
Reputation: 152
Joined: 06 Jul 2014 Posts: 4711
|
Posted: Tue Oct 10, 2023 10:16 am Post subject: |
|
|
| mrally2 wrote: | | When overwriting the spots to 0, gonna store all the values and their addresses into 2 separate arrays so I can restore them easily. Does this look convenient? |
You mean store the values into one array and the addresses in a separate array? I'd couple them together. Maybe index values using their addresses, or make the table an array of other tables each containing an address and value.
_________________
I don't know where I'm going, but I'll figure it out when I get there. |
|
| 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
|
|