 |
Cheat Engine The Official Site of Cheat Engine
|
| View previous topic :: View next topic |
| Author |
Message |
bknight2602 Grandmaster Cheater
Reputation: 0
Joined: 08 Oct 2012 Posts: 598
|
Posted: Sun Mar 22, 2026 3:41 pm Post subject: Old table routines seem to be not working |
|
|
I'm testing out program and features on a new win 11 lap. I have a table that is likely over 15 tears old on a game Crusaders of the Dark Savant. There is a "recalculate" routine built in because the table addresses never loaded in the same locations. The routines take a character's experience, gold and position in the game. The difference between the two calculates a difference and then this difference is applied to the previous table address. This morning when I opened the game and applied the table looked odd not many of the table addresses matched the game value. When comparing the two searches The first occurrence triggers the correction but there is obviously more in this game. The question: is there way to have the code execute and display a table of differences, instead of just assuming the first occurrence is the correct one. the adjusting could be a separate function once the correct is determined? This is only the recalculate portion of the code as it was too long to be fully posted.
| Code: |
function RecalculateAddresses()
--timer_setEnabled(t_Run, false)
print("Hero name ", heroname, "experience to search = " ,expvalue, "and gold to match = ", goldvalue)
goldoffset = 0x8--items are in num format
--print(goldoffset)
errorOnLookupFailure(false);
ms = createMemScan();
ms2 = createMemScan();
--The key to recalculating should be goldoffset added to specific hero experience AOB
memscan_firstScan(ms, soExactValue, vtDword, rtRounded, expvalue, "", "00000000", "7fffffff", "+W*X-C", fsmNotAligned, "", false, false, false, false);
memscan_waitTillDone(ms);
fl = createFoundList(ms);
foundlist_initialize(fl);
foundlist_getCount(fl);
memscan_firstScan(ms2, soExactValue, vtDword, rtRounded, goldvalue, "", "00000000", "7fffffff", "+W*X-C", fsmNotAligned, "", false, false, false, false);
memscan_waitTillDone(ms2);
fl2 = createFoundList(ms2);
foundlist_initialize(fl2);
foundlist_getCount(fl2);
--print("num of search hits for experience is ", foundlist_getCount(fl));
--print("num of search hits for gold is ", foundlist_getCount(fl2));
--print("Gold offset from if statement", goldoffset);
for x = 0, foundlist_getCount(fl)-1 do--Items are in the hex format (0125D60F) without 0x
memrec1 = foundlist_getAddress(fl, x);
memrec2 = tonumber(memrec1, 16);
--print("Experience from foundlist_getAddress(fl, x)", x, memrec1);
for y = 0, foundlist_getCount(fl2)-1 do--Items are in hex format without the 0x
memrec3 = foundlist_getAddress(fl2, y);
memrec4 = tonumber(memrec3, 16);
--print("Gold from foundlist_getAddress(fl2, x)", y, memrec3);
if memrec4 - memrec2 == goldoffset then
experienceaddress = memrec1;--address in hex format without 0x
print("exp address ", experienceaddress);
goldaddress = memrec3;--address in hex format without 0x
byteoffset = memrec3--goldaddress
byteoffset = "0x" .. byteoffset;
print("gold address ", goldaddress);
print("byteoffset ", byteoffset);
break;
end;
end;
if byteoffset ~= nil then
break;
end;
end;
local addresslist = getAddressList();
memrec5 = addresslist_getMemoryRecordByDescription(addresslist, heroname .. " Gold");--address in hex format without 0x
--print(addresslist_getMemoryRecordByDescription(addresslist, heroname .. " Gold"))
--print(memrec5)
goldtableaddress = memoryrecord_getAddress(memrec5);--Item in num format
--print("Gold table address", goldtableaddress);
--print("Gold current table address", string.format('%x', goldtableaddress), "which is ", goldtableaddress);
goldtableaddress = string.format('%x', goldtableaddress);--Items in number format converted to hex format without 0x
goldtableaddress = "0x" .. goldtableaddress
for x = 0, addresslist_getCount(addresslist)-1 do
memrec6 = addresslist_getMemoryRecord(addresslist, x);--Item in hex format without the 0x
if byteoffset ~= goldtableaddress then
memoryrecord_setAddress(memrec6, string.format('%x', memoryrecord_getAddress(memrec6) + byteoffset - goldtableaddress));
end;
end;
recalc = 1;
expvalue = nil
goldvalue = nil
heroname = nil
byteoffset = nil
trainer.exp_value.Text = "";
trainer.gold_value.Text = "";
trainer.characters_rg.ItemIndex = 0;
timer_setEnabled(t_Run, true)
print("The table has been recalculated")
end--function RecalculateAddresses()
|
|
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 39
Joined: 16 Feb 2017 Posts: 1567
|
Posted: Mon Mar 23, 2026 6:06 am Post subject: |
|
|
Technical Recommendations for Refining Your Address Recalculation The issue you're experiencing on Windows 11 is likely due to memory collisions.
Searching for just two values (XP and Gold) with a fixed 8-byte offset is often not enough in modern environments where memory is densely packed.
To make your script more robust, I recommend the following two upgrades:
1. Multi-Point Validation (Deep Verification)
Instead of relying solely on the $XP \to Gold$ relationship, you should verify at least one or two more "anchor" values that are always near the character block (like Level, HP, or Attributes).
This ensures that the code has found the actual character data structure, not just a random sequence of bytes that happens to match your XP and Gold values.
Implementation Logic:
| Code: | -- Add an extra check inside your loop
if memrec4 - memrec2 == goldoffset then
-- Let's say Level is 4 bytes before XP
local levelAddr = memrec2 - 0x4
local currentLevel = readInteger(levelAddr)
if currentLevel == expectedLevelValue then
-- Now we are 99% sure this is the right block
experienceaddress = memrec1
break
end
end |
2. Pattern-Based AOB (Array of Bytes)
SignatureInstead of running two separate memscan processes and looping through their results, you can request/create an AOB Signature.
This is a "fingerprint" of the character block.
Since you mentioned looking at $\pm 64$ bytes around the value, an AOB scan can look for a specific pattern of bytes (static values and wildcards for dynamic ones) in a single pass.
Why this is better:
Speed: It scans the memory once instead of twice.
Accuracy: It looks for a "shape" in the memory rather than just two points.
Even if your XP matches in 10 locations, the surrounding 64 bytes of "Character Data" will only match in one.
Suggested Workflow:
Manually find your character in the Hex Editor.
Copy a string of ~20-30 bytes that includes your Level, XP, and Gold.
Replace the parts that change (like current HP) with ?? (wildcards).
Use AOBScan("AA BB ?? ?? CC DD ...") in your script.
According to these analyses, when multiple matches are found,
you may need to rewrite your RecalculateAddresses function to
include a "Selection Dialog" that allows you to choose the correct one from the list.
_________________
|
|
| Back to top |
|
 |
bknight2602 Grandmaster Cheater
Reputation: 0
Joined: 08 Oct 2012 Posts: 598
|
Posted: Mon Mar 23, 2026 10:39 am Post subject: |
|
|
Good suggestions. I believe the adding another check value might be the easiest for me to work around.
The AOB scan does interest me but how would one code in that type of scan, since the games I play are RPG and every attribute of a character is always changing.
|
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 39
Joined: 16 Feb 2017 Posts: 1567
|
Posted: Mon Mar 23, 2026 4:40 pm Post subject: |
|
|
Logic-Based Validation (A "Smart" Alternative to AOB)
Since you're playing an RPG where values change, you don't necessarily need a static byte signature. Instead, you can use Relational-Based Validation.
Even if your XP and Gold values change, their relative positions (offsets) in the memory block hardly change at all.
Your character data is like a fixed "template" moving through memory.
Here's how it works:
Your script finds 10 different locations with your current XP value.
For each location, it checks:
"Is the value 8 bytes ahead equal to my current Gold?" AND "Is the value 4 bytes behind equal to my current Level?"
The real character block passes all these tests, while the "fake" memory addresses fail.
Here's an improved version of that logic for you:
| Code: | function IsThisMyCharacter(candidateExpAddr, currentGold, currentLevel)
-- Calculate where Gold and Level SHOULD be if this is the right block
local goldAddr = candidateExpAddr + 0x8
local levelAddr = candidateExpAddr - 0x4
-- Read the actual values currently at those calculated addresses
local actualGold = readInteger(goldAddr)
local actualLevel = readInteger(levelAddr)
-- Cross-verify: If Gold and Level match your current stats, it's a 100% hit.
if actualGold == currentGold and actualLevel == currentLevel then
return true
else
return false
end
end |
-- or ..
| Code: | | Integrating the "Validation Logic" into your Script |
To solve the "wrong address" issue on Windows 11, we can insert a validation check inside your nested loops.
This ensures the script only stops when it finds a block where XP, Gold, and Level all align perfectly.
| Code: | -- Add this helper function at the top of your script
function IsValidCharacter(expAddr, expectedGold, expectedLevel)
local goldAddr = expAddr + 0x8
local levelAddr = expAddr - 0x4 -- Adjust this offset if Level is elsewhere
return readInteger(goldAddr) == expectedGold and
readInteger(levelAddr) == expectedLevel
end
function RecalculateAddresses()
-- ... (Your existing search code for ms and ms2) ...
for x = 0, foundlist_getCount(fl)-1 do
memrec1 = foundlist_getAddress(fl, x)
memrec2 = tonumber(memrec1, 16)
for y = 0, foundlist_getCount(fl2)-1 do
memrec3 = foundlist_getAddress(fl2, y)
memrec4 = tonumber(memrec3, 16)
-- 1. Check if Gold is 8 bytes after XP
if memrec4 - memrec2 == goldoffset then
-- 2. NEW: Deep Validation (Check Level or other stats)
-- Let's assume you have a 'levelvalue' variable from your trainer UI
if IsValidCharacter(memrec2, goldvalue, levelvalue) then
experienceaddress = memrec1
goldaddress = memrec3
byteoffset = "0x" .. goldaddress
print("Match Found and Validated at: ", experienceaddress)
break -- Success! Exit the Gold loop
else
print("Found XP/Gold match at " .. memrec1 .. " but Level check failed. Skipping...")
end
end
end
if byteoffset ~= nil then break end -- Exit the XP loop
end
-- ... (Rest of your address list update code) ...
end |
_________________
|
|
| Back to top |
|
 |
bknight2602 Grandmaster Cheater
Reputation: 0
Joined: 08 Oct 2012 Posts: 598
|
Posted: Mon Mar 23, 2026 6:38 pm Post subject: |
|
|
Suppose for a second there are 6 characters all have the same exp, same gold, same level
The game memory structure is the same for each character. How would the routine pick out the individual character's memory location when they are identical?
This example would be a game that just started(I don't use a game like this to sort, it would never come out correctly, I give all the gold to one character, usually the first). As usual each character values are contained equally apart say 0x72 as is the case with the game in question.
I was not able to post all the code but this is what it looks like, so code would be easy for you to change maybe easy for me with your suggestion.
I assume one would need to enter another variable, lvl, current Hp or what ever to allow the check to operate properly. That would be easy just build another edit box.
| Description: |
|
| Filesize: |
26.47 KB |
| Viewed: |
446 Time(s) |

|
|
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 39
Joined: 16 Feb 2017 Posts: 1567
|
Posted: Tue Mar 24, 2026 1:12 am Post subject: |
|
|
Pro-Tip: How to distinguish 6 identical heroes without "Hero Name"
If the hero names are stored elsewhere and you are left with 6 identical data blocks (same XP, Gold, Level), you can distinguish them using Relative Memory Ordering.
In RPGs, character blocks are almost always stored in a contiguous sequence (one after another).
If you find 6 identical matches, the one with the lowest memory address is your 1st Hero, the next one is the 2nd, and so on.
The Strategy:
Collect All Matches:
Instead of stopping at the first find, let your script collect all 6 addresses into a list (table).
Sort the Addresses:
Sort the list from lowest to highest.
Address_1 (Lowest) = 1st Hero
Address_2 = 2nd Hero ...
Cross-Verify with the 0x72 Rule: Since you mentioned each hero is exactly 0x72 bytes apart, you can verify your results:
If (Address_2 - Address_1) == 0x72 then you are 100% certain you found the correct sequence!
How to implement this in Lua:
| Code: | -- Instead of 'break', store all hits
local foundAddresses = {}
for i = 0, foundlist_getCount(fl) - 1 do
table.insert(foundAddresses, tonumber(foundlist_getAddress(fl, i), 16))
end
-- Sort them numerically
table.sort(foundAddresses)
-- Assign based on the Radio Button selection in your UI
-- If user selected "1st Hero", use foundAddresses[1]
-- If user selected "4th Hero", use foundAddresses[4] |
Of course, you can't expect this to be perfect:
Even if the characters are statistically identical, they cannot occupy the same space in memory.
By listing the addresses, see if letting the memory structure itself tell you who is who solves the problem.
_________________
|
|
| Back to top |
|
 |
bknight2602 Grandmaster Cheater
Reputation: 0
Joined: 08 Oct 2012 Posts: 598
|
Posted: Tue Mar 24, 2026 6:26 am Post subject: |
|
|
The outputting of the found list was my original question, so thanks. I will still recode to another check on some attribute, but I haven't thought about which to use maybe current Hp might be best.
Thanks
|
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 39
Joined: 16 Feb 2017 Posts: 1567
|
Posted: Tue Mar 24, 2026 7:15 am Post subject: |
|
|
Please note:
On the Cheat Engine Forums, if the answers helped resolve your issue, please consider using the 'Reputation' (Add Rep (Thumbs-up icon under profile picture)) system.
In our community, these small gestures are not just about personal 'points';
They help other users identify verified, working methods when searching for similar problems in the archives.
It keeps the forum organized and rewards the time spent on debugging.
_________________
|
|
| Back to top |
|
 |
bknight2602 Grandmaster Cheater
Reputation: 0
Joined: 08 Oct 2012 Posts: 598
|
Posted: Wed Mar 25, 2026 6:35 pm Post subject: |
|
|
| Code: |
function RecalculateAddresses()
print("Hero name ", heroname, "experience to search = " ,expvalue, "and gold to match = ", goldvalue)
goldoffset = 0x8--items are in num format
Hpoffset = 0xA
--print(goldoffset)
errorOnLookupFailure(false);
ms = createMemScan();
ms2 = createMemScan();
--The key to recalculating should be goldoffset added to specific hero experience AOB
memscan_firstScan(ms, soExactValue, vtDword, rtRounded, expvalue, "", "00000000", "7fffffff", "+W*X-C", fsmNotAligned, "", false, false, false, false);
memscan_waitTillDone(ms);
fl = createFoundList(ms);
foundlist_initialize(fl);
foundlist_getCount(fl);
memscan_firstScan(ms2, soExactValue, vtDword, rtRounded, goldvalue, "", "00000000", "7fffffff", "+W*X-C", fsmNotAligned, "", false, false, false, false);
memscan_waitTillDone(ms2);
fl2 = createFoundList(ms2);
foundlist_initialize(fl2);
foundlist_getCount(fl2);
--print("num of search hits for experience is ", foundlist_getCount(fl));
--print("num of search hits for gold is ", foundlist_getCount(fl2));
--print("Gold offset from if statement", goldoffset);
for x = 0, foundlist_getCount(fl)-1 do--Items are in the hex format (0125D60F) without 0x
memrec1 = foundlist_getAddress(fl, x);
memrec2 = tonumber(memrec1, 16);
--print("Experience from foundlist_getAddress(fl, x)", x, memrec1);
for y = 0, foundlist_getCount(fl2)-1 do--Items are in hex format without the 0x
memrec3 = foundlist_getAddress(fl2, y);
memrec4 = tonumber(memrec3, 16);
--print("Gold from foundlist_getAddress(fl2, x)", y, memrec3);
if memrec4 - memrec2 == goldoffset then
if IsValidCharacter(memrec2, goldvalue, Hpvalue) then
experienceaddress = memrec1
goldaddress = memrec3
byteoffset = "0x" .. goldaddress
print("Match Found and Validated at: ", experienceaddress)
experienceaddress = memrec1;--address in hex format without 0x
break
else
print("Found XP/Gold match at " .. memrec1 .. " but Level check failed. Skipping...")
end
print("exp address ", experienceaddress);
goldaddress = memrec3;--address in hex format without 0x
byteoffset = memrec3--goldaddress
byteoffset = "0x" .. byteoffset;
print("gold address ", goldaddress);
print("byteoffset ", byteoffset);
break;
end;
end;
if byteoffset ~= nil then
break;
end;
end;
local addresslist = getAddressList();
memrec5 = addresslist_getMemoryRecordByDescription(addresslist, heroname .. " Gold");--address in hex format without 0x
--print(addresslist_getMemoryRecordByDescription(addresslist, heroname .. " Gold"))
--print(memrec5)
goldtableaddress = memoryrecord_getAddress(memrec5);--Item in num format
--print("Gold table address", goldtableaddress);
--print("Gold current table address", string.format('%x', goldtableaddress), "which is ", goldtableaddress);
goldtableaddress = string.format('%x', goldtableaddress);--Items in number format converted to hex format without 0x
goldtableaddress = "0x" .. goldtableaddress
for x = 0, addresslist_getCount(addresslist)-1 do
memrec6 = addresslist_getMemoryRecord(addresslist, x);--Item in hex format without the 0x
if byteoffset ~= goldtableaddress then
memoryrecord_setAddress(memrec6, string.format('%x', memoryrecord_getAddress(memrec6) + byteoffset - goldtableaddress));
end;
end;
recalc = 1;
expvalue = nil
goldvalue = nil
heroname = nil
Hpvalue = nil
byteoffset = nil
trainer.exp_value.Text = "";
trainer.gold_value.Text = "";
trainer.characters_rg.ItemIndex = 0;
t_RunIndex = 2;
timer_setEnabled(t_Run, true)
print("The table has been recalculated")
end--function RecalculateAddresses()
|
I believe this is what you're suggesting, but please check it feels that there are too many breaks. The other bit of code is earlier in the whole code. It took some finagling to get the Panel correct, it wasn't built for another editbox and button. I would be glad to give you a thumbs up if I knew where it was done?
|
|
| Back to top |
|
 |
AylinCE Grandmaster Cheater Supreme
Reputation: 39
Joined: 16 Feb 2017 Posts: 1567
|
Posted: Thu Mar 26, 2026 2:27 am Post subject: |
|
|
You've done a great job! Adding the `IsValidCharacter` check inside the loop is the most reliable way to not only match numbers but also find the actual 'Hero' structure.
Regarding break statements:
In Lua, this method (or using a found flag) is perfectly fine for completely exiting nested loops.
It's normal for the code to look a bit cluttered;
The important thing is that it works flawlessly and doesn't strain the processor by stopping the scan when a match is found.
Reputation System:
On Cheat Engine forums, you'll find a small 'Add Rep' icon right below the profile picture or username of the person who helped you.
Clicking on it will give you a rating along with a small note.
Thanks for your kind consideration!
This revised version, which simplifies some of the string-to-hex complexity in your code and makes the logic more readable, might be useful:
Note: I'm referring to specific parts of your code, not the entire code itself.
| Code: | function RecalculateAddresses()
print("Hero name:", heroname, "| XP to search:", expvalue, "| Gold to match:", goldvalue)
local goldoffset = 0x8
local foundMatch = false
local targetGoldAddr = nil
-- Memory Scanners
local ms = createMemScan()
local ms2 = createMemScan()
-- First Scan: Experience
memscan_firstScan(ms, soExactValue, vtDword, rtRounded, expvalue, "", "00000000", "7fffffff", "+W*X-C", fsmNotAligned, "", false, false, false, false)
memscan_waitTillDone(ms)
local fl = createFoundList(ms)
foundlist_initialize(fl)
-- Second Scan: Gold
memscan_firstScan(ms2, soExactValue, vtDword, rtRounded, goldvalue, "", "00000000", "7fffffff", "+W*X-C", fsmNotAligned, "", false, false, false, false)
memscan_waitTillDone(ms2)
local fl2 = createFoundList(ms2)
foundlist_initialize(fl2)
-- Nested Search & Validation
for x = 0, foundlist_getCount(fl) - 1 do
local addrXP = tonumber(foundlist_getAddress(fl, x), 16)
for y = 0, foundlist_getCount(fl2) - 1 do
local addrGold = tonumber(foundlist_getAddress(fl2, y), 16)
-- Offset Check: Is Gold at the expected position relative to XP?
if addrGold - addrXP == goldoffset then
-- Final Validation: Check structure integrity
if IsValidCharacter(addrXP, goldvalue, Hpvalue) then
experienceaddress = string.format('%X', addrXP)
goldaddress = string.format('%X', addrGold)
targetGoldAddr = addrGold -- Store as number for math
print("Match Found and Validated at: ", experienceaddress)
foundMatch = true
break -- Exit Gold loop
end
end
end
if foundMatch then break end -- Exit XP loop
end
-- Update Address List if validated
if foundMatch then
local addresslist = getAddressList()
local memrecGold = addresslist_getMemoryRecordByDescription(addresslist, heroname .. " Gold")
local currentTableGoldAddr = memoryrecord_getAddress(memrecGold)
for i = 0, addresslist_getCount(addresslist) - 1 do
local rec = addresslist_getMemoryRecord(addresslist, i)
-- Apply the difference to all records
memoryrecord_setAddress(rec, string.format('%X', memoryrecord_getAddress(rec) + targetGoldAddr - currentTableGoldAddr))
end
print("The table has been successfully recalculated.")
else
print("Validation failed: No matching hero structure found.")
end
-- UI Cleanup
trainer.exp_value.Text = ""
trainer.gold_value.Text = ""
-- ... (rest of your cleanup)
end |
What We Fixed:
Mathematical Consistency:
Instead of adding 0x and performing string concatenation, we perform all calculations in numerical base using `tonumber` and finally convert to hexadecimal using `string.format('%X')`.
This prevents Lua from giving errors during addition/subtraction.
Clean Loop:
We used the `foundMatch` flag instead of multiple `break` statements.
Error Handling:
If nothing is found, instead of corrupting the table, it gives the user a "No match found" warning.[/b]
_________________
|
|
| 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
|
|