Here is my Cheat Engine Tutorial:
[Link]
.
First of all, here's a brief explanation of how to execute Lua codes with Cheat Engine:
A) How to insert a Lua Code in a Lua Script
Spoiler
Spoiler
Insert this code in "Show Chaeat Table Lua Script" (press Ctrl+Alt+L to open)
Spoiler
Code: Select all
local userProfilePath = os.getenv("USERPROFILE")
local function safeDofile(filePath)
local success, err = pcall(dofile, filePath)
end
local luaFilePath1 = userProfilePath .. "\\Desktop\\Lua\\generateAOBMenu.lua"
safeDofile(luaFilePath1)
local luaFilePath2 = userProfilePath .. "\\Desktop\\Lua\\LuaScriptForAllDissectDataOffsets.lua"
safeDofile(luaFilePath2)
-----------------------------------------------------------------------------------------------
---- ↓↓↓ This is used to make some Lua scripts work correctly. ↓↓↓
if generateAOBmenuitem then
generateAOBmenuitem.OnClick = function()
local addr = getMemoryViewForm().DisassemblerView.SelectedAddress
createThread(function()
synchronize(function() generateWildcardAOB(addr) end)
end)
end
else
end
Spoiler
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1) AOB Scanner & Memory Extractor:
This code is used to find and extract the AOB from the 'Memory View' more easily and quickly. Once the tool is open, you need to right-click on a script and copy it. After that, you must paste it into the textbox in the GUI labeled 'Paste the script containing AOB here (use right-click -> Paste):' and finally click the 'Search AOB and Extract' button.
Be careful, some games require you to perform a specific action; otherwise, the tool won't work (because some arrays are not immediately visible to Cheat Engine as they haven't been loaded into memory yet). For example, to find the array of a script named 'Infinite Ammo,' you will probably need to fire a shot first.
Code:
Spoiler
Code: Select all
-- ===================================================
-- Tool: AOB Scanner & Memory Extractor
-- Completely corrected version
-- ===================================================
-- =============================
-- Creating the main form
local form = createForm()
form.Caption = 'AOB Scanner & Memory Extractor'
form.Width = 900
form.Height = 700
form.Position = poScreenCenter
-- =============================
-- Label for input
local inputLabel = createLabel(form)
inputLabel.Caption = 'Paste the script containing AOB here (use right-click -> Paste):'
inputLabel.Left = 10
inputLabel.Top = 10
-- Creating the memo for input
local inputMemo = createMemo(form)
inputMemo.Left = 10
inputMemo.Top = 30
inputMemo.Width = 870
inputMemo.Height = 200
inputMemo.ScrollBars = ssBoth
inputMemo.WordWrap = false
-- =============================
-- Context menu for the memo
local popup = createPopupMenu(form)
local menuItemPaste = createMenuItem(popup)
menuItemPaste.Caption = 'Paste'
menuItemPaste.OnClick = function(sender)
local clipboardText = getClipboardText()
if clipboardText then
inputMemo.Text = clipboardText
else
showMessage("The clipboard does not contain text!")
end
end
popup.Items.add(menuItemPaste)
inputMemo.OnMouseUp = function(sender, x, y, button)
if button == 2 then
local globalX = inputMemo.ClientToScreenX(x)
local globalY = inputMemo.ClientToScreenY(y)
popup.Popup(globalX, globalY)
end
end
-- =============================
-- Button to perform the scan
local scanButton = createButton(form)
scanButton.Caption = 'Search AOB and Extract'
scanButton.Left = 10
scanButton.Top = 240
scanButton.Width = 200
-- Label for the amount of bytes
local preInjectLabel = createLabel(form)
preInjectLabel.Caption = 'Bytes before the injection point:'
preInjectLabel.Left = 220
preInjectLabel.Top = 240
local injectLabel = createLabel(form)
injectLabel.Caption = 'Bytes from the injection point:'
injectLabel.Left = 220
injectLabel.Top = 270
-- Textbox for the amount of bytes
local preInjectBytesTextbox = createEdit(form)
preInjectBytesTextbox.Left = 400
preInjectBytesTextbox.Top = 240
preInjectBytesTextbox.Width = 50
preInjectBytesTextbox.Text = '50'
local injectBytesTextbox = createEdit(form)
injectBytesTextbox.Left = 400
injectBytesTextbox.Top = 270
injectBytesTextbox.Width = 50
injectBytesTextbox.Text = '50'
-- Label and Memo for output
local outputLabel = createLabel(form)
outputLabel.Caption = 'AOB scan results:'
outputLabel.Left = 10
outputLabel.Top = 310
local outputMemo = createMemo(form)
outputMemo.Left = 10
outputMemo.Top = 330
outputMemo.Width = 870
outputMemo.Height = 350
outputMemo.ReadOnly = true
outputMemo.ScrollBars = ssBoth
outputMemo.WordWrap = false
-- =============================
-- Function to extract all AOBs from the script
local function extractAOBsFromScript(scriptText)
local aobs = {}
-- Cleaning the text from comments and extra spaces
local cleanedText = scriptText:gsub('//.*', ''):gsub('%-%-.*', ''):gsub('%s+', ' ')
-- Pattern to capture all variants of aobscan
for aobType, params in cleanedText:gmatch('aobscan(%w*)%s*%(([^%)]+)%)') do
-- Extract parameters
local parts = {}
for param in params:gmatch('[^,]+') do
local cleanedParam = param:gsub('^%s+', ''):gsub('%s+$', '')
table.insert(parts, cleanedParam)
end
if #parts >= 2 then
local aobInfo = {
type = 'aobscan'..(aobType or ''),
name = parts[1],
pattern = parts[#parts]
}
-- Specific handling for each type
if aobType == 'module' then
if #parts >= 3 then
aobInfo.module = parts[2]
end
elseif aobType == 'region' then
if #parts >= 5 then
aobInfo.startAddress = parts[2]
aobInfo.endAddress = parts[3]
aobInfo.protection = parts[4]
end
end
table.insert(aobs, aobInfo)
end
end
return aobs
end
-- =============================
-- Function to search for an AOB and extract a portion of memory
local function findAndExtractAOB(aobPattern, preInjectBytes, injectBytes)
-- First, we check if the AOB is valid
if not aobPattern or aobPattern == '' then
return nil, "Empty AOB pattern"
end
-- Convert the AOB into a string without spaces
local searchPattern = aobPattern:gsub(' ', '')
-- Use AOBScan to find the address
local addresses = AOBScan(searchPattern)
if not addresses or addresses.Count == 0 then
return nil, "AOB not found: "..aobPattern
end
-- Take the first result
local foundAddress = tonumber('0x'..addresses[0])
addresses.Destroy()
-- Get the memory view
local memView = getMemoryViewForm()
if not memView then
memView = createMemoryView()
if not memView then
return nil, "Unable to create Memory View"
end
end
-- Select the address in the disassembler
memView.DisassemblerView.SelectedAddress = foundAddress
-- Read bytes from memory, using the amount specified in the textboxes
local startAddress = foundAddress - preInjectBytes
if startAddress < 0 then startAddress = 0 end
local bytesBeforeInject = readBytes(startAddress, preInjectBytes, true)
-- Read bytes from the injection point
local bytesAfterInject = readBytes(foundAddress, injectBytes, true)
if not bytesBeforeInject and not bytesAfterInject then
return nil, "Unable to read bytes from memory"
end
-- Create a string with the read bytes
local byteString = {}
for i = 1, #bytesBeforeInject do
table.insert(byteString, string.format('%02X', bytesBeforeInject[i]))
end
for i = 1, #bytesAfterInject do
table.insert(byteString, string.format('%02X', bytesAfterInject[i]))
end
return table.concat(byteString, ' '), "AOB found at "..string.format('%X', foundAddress)
end
-- =============================
-- Click event on the scan button
function scanButton.OnClick(sender)
local scriptText = inputMemo.Lines.Text
if scriptText == nil or scriptText == '' then
showMessage("You must paste the script to analyze!")
return
end
-- Get values from the textboxes
local preInjectBytes = tonumber(preInjectBytesTextbox.Text) or 50
local injectBytes = tonumber(injectBytesTextbox.Text) or 50
-- Extract AOBs
local aobs = extractAOBsFromScript(scriptText)
if #aobs == 0 then
showMessage("No AOB found in the script!")
return
end
-- Prepare output
local outputLines = {}
table.insert(outputLines, "Found "..#aobs.." AOB in the script:")
table.insert(outputLines, "========================================")
-- Process each AOB
for i, aob in ipairs(aobs) do
table.insert(outputLines, "")
table.insert(outputLines, string.format("%d. Type: %s", i, aob.type))
table.insert(outputLines, string.format(" Name: %s", aob.name))
if aob.module ~= '' then
table.insert(outputLines, string.format(" Module: %s", aob.module))
end
table.insert(outputLines, string.format(" Pattern: %s", aob.pattern))
-- Search for the AOB
local bytes, message = findAndExtractAOB(aob.pattern, preInjectBytes, injectBytes)
table.insert(outputLines, " "..message)
if bytes then
-- Count the bytes
local byteCount = select(2, bytes:gsub('%x%x', '')) + 1
table.insert(outputLines, string.format(" Extracted Bytes (%d bytes):", byteCount))
-- Divide the bytes into parts for "Before INJECTING Point" and "INJECTING Point"
local preInjectBytesList = {}
local injectBytesList = {}
-- Already read the bytes into a table for better management
local byteParts = {}
for byte in bytes:gmatch('%x+') do
table.insert(byteParts, byte)
end
for i = 1, #byteParts do
if i <= preInjectBytes then
table.insert(preInjectBytesList, byteParts[i])
else
table.insert(injectBytesList, byteParts[i])
end
end
-- Build strings for the desired format
table.insert(outputLines, "Before INJECTING Point")
table.insert(outputLines, " " .. table.concat(preInjectBytesList, ' '))
table.insert(outputLines, "INJECTING Point")
table.insert(outputLines, " " .. table.concat(injectBytesList, ' '))
end
table.insert(outputLines, "----------------------------------------")
end
-- Show results
outputMemo.Lines.Text = table.concat(outputLines, '\n')
end
-- =============================
-- Displaying the form
form.Show()
Spoiler
--
2) Aob Extractor:
This tool is used to extract the array from a script that provides it; the AOB will be extracted from the end of the script, specifically the bytes found within curly braces {}.
Code:
Spoiler
Code: Select all
-- ===================================================
-- Tool: AOB Extractor with Clipboard Paste (Combined)
-- Description:
-- - Allows you to paste the copied content from the main window
-- into a textbox using right-click (context menu).
-- - Isolates the block of text contained between the last pair of curly braces,
-- ignoring the rest of the script.
-- - Processes the isolated block to extract and display AOBs.
-- ===================================================
-- =============================
-- Creating the main form
local form = createForm()
form.Caption = 'AOB Extractor with Clipboard Paste'
form.Width = 800
form.Height = 600
form.Position = poScreenCenter
-- =============================
-- Label for input
local inputLabel = createLabel(form)
inputLabel.Caption = 'Paste the script here (use right-click -> Paste):'
inputLabel.Left = 10
inputLabel.Top = 10
-- Creating the memo for input (multiline) where the script will be pasted
local inputMemo = createMemo(form)
inputMemo.Left = 10
inputMemo.Top = 30
inputMemo.Width = 760
inputMemo.Height = 200
inputMemo.ScrollBars = ssBoth
inputMemo.WordWrap = false
-- =============================
-- Creating the context menu for the memo
local popup = createPopupMenu(form)
local menuItemPaste = createMenuItem(popup)
menuItemPaste.Caption = 'Paste'
menuItemPaste.OnClick = function(sender)
local clipboardText = getClipboardText()
if clipboardText then
inputMemo.Text = clipboardText
else
showMessage("The clipboard does not contain text!")
end
end
popup.Items.add(menuItemPaste)
-- Associate the context menu with right-click on the memo
inputMemo.OnMouseUp = function(sender, x, y, button)
if button == 2 then -- 2 = right mouse button
local globalX = inputMemo.ClientToScreenX(x)
local globalY = inputMemo.ClientToScreenY(y)
popup.Popup(globalX, globalY)
end
end
-- =============================
-- Button to extract the AOB
local extractButton = createButton(form)
extractButton.Caption = 'Extract AOB'
extractButton.Left = 10
extractButton.Top = 240
extractButton.Width = 100
-- Label and Memo for the output "Before"
local beforeLabel = createLabel(form)
beforeLabel.Caption = 'Compared Before Injecting point Bytes:'
beforeLabel.Left = 10
beforeLabel.Top = 280
local outputMemoBefore = createMemo(form)
outputMemoBefore.Left = 10
outputMemoBefore.Top = 300
outputMemoBefore.Width = 760
outputMemoBefore.Height = 120
outputMemoBefore.ReadOnly = true
outputMemoBefore.ScrollBars = ssBoth
outputMemoBefore.WordWrap = false
-- Label and Memo for the output "After"
local afterLabel = createLabel(form)
afterLabel.Caption = 'Compared AoB from Injecting Point:'
afterLabel.Left = 10
afterLabel.Top = 430
local outputMemoAfter = createMemo(form)
outputMemoAfter.Left = 10
outputMemoAfter.Top = 450
outputMemoAfter.Width = 760
outputMemoAfter.Height = 100
outputMemoAfter.ReadOnly = true
outputMemoAfter.ScrollBars = ssBoth
outputMemoAfter.WordWrap = false
-- =============================
-- Function to extract the block of text contained between the last pair of curly braces.
-- This way, the relevant code will be isolated (ignoring any initial blocks
-- of the script).
local function extractInjectionBlock(fullText)
-- Find the position of the last opening curly brace "{"
local lastOpen = nil
for pos = #fullText, 1, -1 do
if fullText:sub(pos, pos) == "{" then
lastOpen = pos
break
end
end
if not lastOpen then
return nil -- no block found
end
-- From the found position, look for the first "}" that follows
local lastClose = fullText:find("}", lastOpen, true)
if not lastClose then
return nil
end
-- Extract the block (excluding the braces themselves)
local block = fullText:sub(lastOpen + 1, lastClose - 1)
return block
end
-- =============================
-- Function to process the text (containing only the injection block) and extract the bytes from it
local function extractAOB(inputText)
local beforeBytes = {}
local afterBytes = {}
local currentGroup = "before"
-- Iterate through each line
for line in inputText:gmatch("([^\n]+)") do
-- Priority handling of markers
if line:find("INJECTING HERE") then
currentGroup = "after"
elseif line:find("DONE INJECTING") then
-- Skip the DONE INJECTING marker
-- Skip non-significant lines (comments, braces, etc.)
elseif line:match("^%s*%{") or line:match("^%s*%}") or line:find("ORIGINAL CODE") or line:match("^%s*//") then
-- ignore the line
else
-- Try to extract the bytes from the line:
-- Look for the pattern after the colon ":" until a dash (if present) or until the end of the line
local bytesStr = line:match(":%s*([0-9A-Fa-f ]+)%s*%-")
if not bytesStr then
bytesStr = line:match(":%s*([0-9A-Fa-f ]+)%s*$")
end
if bytesStr then
bytesStr = bytesStr:gsub("^%s+", ""):gsub("%s+$", "")
if #bytesStr > 0 then
if currentGroup == "before" then
table.insert(beforeBytes, bytesStr)
else
table.insert(afterBytes, bytesStr)
end
end
end
end
end
-- Helper function: joins the arrays into a string with a single space between the bytes
local function joinBytes(arr)
local s = table.concat(arr, " ")
s = s:gsub("%s+", " ")
return s
end
-- Helper function: counts the number of bytes (hex pairs)
local function countBytes(aobStr)
local count = 0
for byte in aobStr:gmatch("%x%x") do
count = count + 1
end
return count
end
local aobBefore = joinBytes(beforeBytes)
local aobAfter = joinBytes(afterBytes)
local countBefore = countBytes(aobBefore)
local countAfter = countBytes(aobAfter)
local outputTextBefore = "Compared Before Injecting point Bytes:\n" ..
aobBefore .. "\n" ..
"Byte count: " .. countBefore .. "\n" ..
string.format("0x%X", countBefore) .. "\n" ..
string.format("%X", countBefore)
local outputTextAfter = "Compared AoB from Injecting Point:\n" ..
aobAfter .. "\n" ..
"Byte count: " .. countAfter .. "\n" ..
string.format("0x%X", countAfter) .. "\n" ..
string.format("%X", countAfter)
return outputTextBefore, outputTextAfter
end
-- =============================
-- Click event on the "Extract AOB" button
function extractButton.OnClick(sender)
local fullText = inputMemo.Lines.Text
if fullText == nil or fullText == '' then
showMessage("You must paste the script to analyze!")
return
end
-- Isolate the injection block (the last block between curly braces)
local injectionBlock = extractInjectionBlock(fullText)
if not injectionBlock then
showMessage("No injection block (delimited by {}) found!")
return
end
local outputBefore, outputAfter = extractAOB(injectionBlock)
outputMemoBefore.Lines.Text = outputBefore
outputMemoAfter.Lines.Text = outputAfter
end
-- =============================
-- Showing the form
form.Show()
Spoiler
---
3) AoB <scripts> Comparator:
This tool extracts and compares the arrays from two scripts, replacing differing bytes with a ? or two ??.
Code:
Spoiler
Code: Select all
-- ============================================================================
-- Tool: AOB Comparator with dual comparison modes
-- Description:
-- - There are two text boxes (inputMemo1 and inputMemo2) for pasting scripts
-- (via right-click -> Paste).
-- - From each script, the last block (between { and }) containing the injection is isolated.
-- - Two groups of bytes are extracted:
-- ▸ "Before Injecting point" – all lines up to the "INJECTING HERE" marker
-- ▸ "AoB from Injecting Point" – all lines after "INJECTING HERE"
-- - The comparison can occur in two modes:
-- ▸ "Comparison ?" mode (nibble by nibble): it compares each nibble and
-- if there is a difference, the nibble is replaced with "?".
-- ▸ "Comparison ??" mode (full byte): if the byte does not match entirely,
-- the entire byte will be replaced with "??".
-- - The results are displayed in two memos, with the byte count in decimal
-- and hexadecimal.
-- ============================================================================
-- =============================
-- Creating the main form
local form = createForm()
form.Caption = 'AOB Comparator'
form.Width = 900
form.Height = 750
form.Position = poScreenCenter
-- =============================
-- Labels for the inputs
local inputLabel1 = createLabel(form)
inputLabel1.Caption = 'Script 1 (paste the script here via right-click -> Paste):'
inputLabel1.Left = 10
inputLabel1.Top = 10
local inputLabel2 = createLabel(form)
inputLabel2.Caption = 'Script 2 (paste the script here via right-click -> Paste):'
inputLabel2.Left = 460
inputLabel2.Top = 10
-- =============================
-- Memo for Script 1
local inputMemo1 = createMemo(form)
inputMemo1.Left = 10
inputMemo1.Top = 30
inputMemo1.Width = 430
inputMemo1.Height = 200
inputMemo1.ScrollBars = ssBoth
inputMemo1.WordWrap = false
-- Memo for Script 2
local inputMemo2 = createMemo(form)
inputMemo2.Left = 460
inputMemo2.Top = 30
inputMemo2.Width = 430
inputMemo2.Height = 200
inputMemo2.ScrollBars = ssBoth
inputMemo2.WordWrap = false
-- =============================
-- Function to create a "Paste" popup menu for a memo
local function attachPastePopup(memoControl)
local popup = createPopupMenu(form)
local menuItemPaste = createMenuItem(popup)
menuItemPaste.Caption = 'Paste'
menuItemPaste.OnClick = function(sender)
local clipboardText = getClipboardText()
if clipboardText then
memoControl.Text = clipboardText
else
showMessage("The clipboard does not contain text!")
end
end
popup.Items.add(menuItemPaste)
memoControl.OnMouseUp = function(sender, x, y, button)
if button == 2 then -- right mouse button
local globalX = memoControl.ClientToScreenX(x)
local globalY = memoControl.ClientToScreenY(y)
popup.Popup(globalX, globalY)
end
end
end
-- Associate the context menu with both memos
attachPastePopup(inputMemo1)
attachPastePopup(inputMemo2)
-- =============================
-- Buttons for the comparison
-- Create two side-by-side buttons
-- Button "Comparison ??" (full byte mode, default)
local compareFullButton = createButton(form)
compareFullButton.Caption = 'Comparison ??'
compareFullButton.Left = 10
compareFullButton.Top = 240
compareFullButton.Width = 120
-- Button "Comparison ?" (nibble by nibble mode)
local comparePartialButton = createButton(form)
comparePartialButton.Caption = 'Comparison ?'
comparePartialButton.Left = 140
comparePartialButton.Top = 240
comparePartialButton.Width = 120
-- =============================
-- Labels and memo for the output "Before"
local beforeLabel = createLabel(form)
beforeLabel.Caption = 'Compared Before Injecting point Bytes:'
beforeLabel.Left = 10
beforeLabel.Top = 280
local outputMemoBefore = createMemo(form)
outputMemoBefore.Left = 10
outputMemoBefore.Top = 300
outputMemoBefore.Width = 880
outputMemoBefore.Height = 150
outputMemoBefore.ReadOnly = true
outputMemoBefore.ScrollBars = ssBoth
outputMemoBefore.WordWrap = false
-- Labels and memo for the output "After"
local afterLabel = createLabel(form)
afterLabel.Caption = 'Compared AoB from Injecting Point:'
afterLabel.Left = 10
afterLabel.Top = 460
local outputMemoAfter = createMemo(form)
outputMemoAfter.Left = 10
outputMemoAfter.Top = 480
outputMemoAfter.Width = 880
outputMemoAfter.Height = 150
outputMemoAfter.ReadOnly = true
outputMemoAfter.ScrollBars = ssBoth
outputMemoAfter.WordWrap = false
-- =============================
-- Function to isolate the last injection block (content between { and })
local function extractInjectionBlock(fullText)
local lastOpen = nil
for pos = #fullText, 1, -1 do
if fullText:sub(pos, pos) == "{" then
lastOpen = pos
break
end
end
if not lastOpen then return nil end
local lastClose = fullText:find("}", lastOpen, true)
if not lastClose then return nil end
local block = fullText:sub(lastOpen + 1, lastClose - 1)
return block
end
-- =============================
-- Function to extract AoBs from the injection block
-- Returns two strings:
-- aobBefore: concatenated bytes (separated by a space) up to the "INJECTING HERE" marker
-- aobAfter: concatenated bytes after the "INJECTING HERE" marker
local function extractAOB(blockText)
local arrBefore = {}
local arrAfter = {}
local currentGroup = "before"
for line in blockText:gmatch("([^\n]+)") do
if line:find("INJECTING HERE") then
currentGroup = "after"
elseif line:find("DONE INJECTING") then
-- ignore marker
elseif line:match("^%s*%{") or line:match("^%s*%}") or line:find("ORIGINAL CODE") or line:match("^%s*//") then
-- ignore non-significant lines
else
-- Extract the bytes: look for the part after ":" and before "-" (if present)
local bytesStr = line:match(":%s*([0-9A-Fa-f ]+)%s*%-")
if not bytesStr then
bytesStr = line:match(":%s*([0-9A-Fa-f ]+)%s*$")
end
if bytesStr then
bytesStr = bytesStr:gsub("^%s+", ""):gsub("%s+$", "")
if #bytesStr > 0 then
if currentGroup == "before" then
table.insert(arrBefore, bytesStr)
else
table.insert(arrAfter, bytesStr)
end
end
end
end
end
local function joinBytes(arr)
local s = table.concat(arr, " ")
s = s:gsub("%s+", " ")
return s
end
local aobBefore = joinBytes(arrBefore)
local aobAfter = joinBytes(arrAfter)
return aobBefore, aobAfter
end
-- =============================
-- Helper functions for comparison
-- Splits the string into an array of bytes (tokens separated by spaces)
local function splitBytes(aobStr)
local bytes = {}
for byte in aobStr:gmatch("%S+") do
table.insert(bytes, byte)
end
return bytes
end
-- "Comparison ?" mode (nibble by nibble)
local function compareBytePartial(b1, b2)
local result = ""
local n1 = b1:sub(1,1)
local n2 = b2:sub(1,1)
if n1:upper() == n2:upper() then
result = result .. n1:upper()
else
result = result .. "?"
end
n1 = b1:sub(2,2)
n2 = b2:sub(2,2)
if n1:upper() == n2:upper() then
result = result .. n1:upper()
else
result = result .. "?"
end
return result
end
local function compareAOBsPartial(aobStr1, aobStr2)
local bytes1 = splitBytes(aobStr1)
local bytes2 = splitBytes(aobStr2)
local maxLen = math.max(#bytes1, #bytes2)
local result = {}
for i = 1, maxLen do
local b1 = bytes1[i] or "??"
local b2 = bytes2[i] or "??"
if b1 == "??" or b2 == "??" then
table.insert(result, "??")
else
table.insert(result, compareBytePartial(b1, b2))
end
end
return table.concat(result, " ")
end
-- "Comparison ??" mode (full byte)
local function compareByteFull(b1, b2)
if b1:upper() == b2:upper() then
return b1:upper()
else
return "??"
end
end
local function compareAOBsFull(aobStr1, aobStr2)
local bytes1 = splitBytes(aobStr1)
local bytes2 = splitBytes(aobStr2)
local maxLen = math.max(#bytes1, #bytes2)
local result = {}
for i = 1, maxLen do
local b1 = bytes1[i] or "??"
local b2 = bytes2[i] or "??"
table.insert(result, compareByteFull(b1, b2))
end
return table.concat(result, " ")
end
-- Count the number of bytes (tokens separated by spaces) in an AoB string
local function countBytesFromAOB(aobStr)
local count = 0
for byte in aobStr:gmatch("%S+") do
count = count + 1
end
return count
end
-- =============================
-- Main function to perform the comparison based on the chosen mode:
-- "full" => Comparison ?? (full byte)
-- "partial" => Comparison ? (nibble by nibble)
local function doComparison(compareMode)
local fullText1 = inputMemo1.Lines.Text
local fullText2 = inputMemo2.Lines.Text
if fullText1 == nil or fullText1 == '' or fullText2 == nil or fullText2 == '' then
showMessage("You must paste both scripts to analyze!")
return
end
local injectionBlock1 = extractInjectionBlock(fullText1)
local injectionBlock2 = extractInjectionBlock(fullText2)
if not injectionBlock1 or not injectionBlock2 then
showMessage("Unable to locate the injection block in either of the two scripts!")
return
end
local aobBefore1, aobAfter1 = extractAOB(injectionBlock1)
local aobBefore2, aobAfter2 = extractAOB(injectionBlock2)
local comparedBefore, comparedAfter
if compareMode == "partial" then
comparedBefore = compareAOBsPartial(aobBefore1, aobBefore2)
comparedAfter = compareAOBsPartial(aobAfter1, aobAfter2)
else
comparedBefore = compareAOBsFull(aobBefore1, aobBefore2)
comparedAfter = compareAOBsFull(aobAfter1, aobAfter2)
end
local countBefore = countBytesFromAOB(comparedBefore)
local countAfter = countBytesFromAOB(comparedAfter)
local outputTextBefore = "Compared Before Injecting point Bytes:\n" ..
comparedBefore .. "\n" ..
"Byte count: " .. countBefore .. "\n" ..
string.format("0x%X", countBefore) .. "\n" ..
string.format("%X", countBefore)
local outputTextAfter = "Compared AoB from Injecting Point:\n" ..
comparedAfter .. "\n" ..
"Byte count: " .. countAfter .. "\n" ..
string.format("0x%X", countAfter) .. "\n" ..
string.format("%X", countAfter)
outputMemoBefore.Lines.Text = outputTextBefore
outputMemoAfter.Lines.Text = outputTextAfter
end
-- =============================
-- Associate OnClick events to the buttons
compareFullButton.OnClick = function(sender)
doComparison("full") -- Comparison ??
end
comparePartialButton.OnClick = function(sender)
doComparison("partial") -- Comparison ?
end
-- =============================
-- Display the form
form.Show()
Spoiler
----
4) Pure AoB Comparator:
This tool is used to compare two arrays ('Pure' without extracting them from a script). The differing bytes will be replaced with a ? or two ??.
Code:
Spoiler
Code: Select all
form = createForm()
form.Caption = 'Array Comparator'
form.Width = 800
form.Height = 600
-- Input memos: Array 1 and Array 2
input1_memo = createMemo(form)
input1_memo.Left = 10
input1_memo.Top = 30
input1_memo.Width = 350
input1_memo.Height = 300
input1_memo.ScrollBars = ssVertical
input2_memo = createMemo(form)
input2_memo.Left = 370
input2_memo.Top = 30
input2_memo.Width = 350
input2_memo.Height = 300
input2_memo.ScrollBars = ssVertical
-- Label for input memos
label1 = createLabel(form)
label1.Caption = 'Array 1:'
label1.Left = input1_memo.Left
label1.Top = input1_memo.Top - 20
label2 = createLabel(form)
label2.Caption = 'Array 2:'
label2.Left = input2_memo.Left
label2.Top = label1.Top
-- Comparison buttons
btn_single = createButton(form)
btn_single.Caption = 'Comparison ?'
btn_single.Width = 120
btn_single.Height = 30
btn_single.Left = 10
btn_single.Top = 340
btn_double = createButton(form)
btn_double.Caption = 'Comparison ??'
btn_double.Width = 120
btn_double.Height = 30
btn_double.Left = btn_single.Left + btn_single.Width + 10
btn_double.Top = btn_single.Top
-- Memo for the result
result_memo = createMemo(form)
result_memo.Left = 10
result_memo.Top = 400
result_memo.Width = 710
result_memo.Height = 150
result_memo.ScrollBars = ssBoth
result_memo.ReadOnly = true
result_label = createLabel(form)
result_label.Caption = 'Result:'
result_label.Left = result_memo.Left
result_label.Top = result_memo.Top - 20
-- Function for parsing lines
function parseLine(line)
line = line:upper():gsub('[^%x]', '')
local bytes = {}
for i = 1, #line, 2 do
local byte = line:sub(i, i+1)
if #byte == 2 then
table.insert(bytes, byte)
end
end
return bytes
end
-- Full input parsing
function parseInput(lines)
local array = {}
for i = 0, lines.Count - 1 do
local line = lines[i]
local bytes = parseLine(line)
table.insert(array, bytes)
end
return array
end
-- Logic for comparing individual bytes
function compareBytes(a, b, mode)
if not a or not b then
return '??'
end
if a == b then
return a
end
if mode == '?' then
local res = ''
for i = 1, 2 do
local c1 = a:sub(i, i)
local c2 = b:sub(i, i)
res = res .. (c1 == c2 and c1 or '?')
end
return res
elseif mode == '??' then
return '??'
end
return '??'
end
-- Main comparison function
function compareAndDisplay(mode)
local array1 = parseInput(input1_memo.Lines)
local array2 = parseInput(input2_memo.Lines)
local result_lines = {}
local byte_count = 0
local max_lines = math.max(#array1, #array2)
for line_i = 1, max_lines do
local line1 = array1[line_i] or {}
local line2 = array2[line_i] or {}
local max_bytes = math.max(#line1, #line2)
local line_result = {}
for byte_i = 1, max_bytes do
local b1 = line1[byte_i]
local b2 = line2[byte_i]
table.insert(line_result, compareBytes(b1, b2, mode))
end
table.insert(result_lines, table.concat(line_result, ' '))
byte_count = byte_count + max_bytes
end
local result_text = table.concat(result_lines, '\r\n')
result_text = result_text .. '\r\nByte count: ' .. byte_count
result_text = result_text .. '\r\n0x' .. string.format('%X', byte_count)
result_text = result_text .. '\r\n' .. string.format('%X', byte_count)
result_memo.Lines.Text = result_text
end
-- Linking buttons to the function
btn_single.OnClick = function() compareAndDisplay('?') end
btn_double.OnClick = function() compareAndDisplay('??') end
form.show()
Spoiler
-----
5) Complete AoB Comparator:
This tool is an innovative integration of the 'AoB <Scripts> Comparator' and the 'Pure AoB Comparator,' merging their functionalities into a single, user-friendly GUI. This comprehensive interface allows for seamless comparisons, enhancing usability and efficiency in one consolidated application.
Code:
Spoiler
Code: Select all
-- ======================================================================
-- Super Comparator
-- Combines AOB Comparator (from Script 2) and Array Comparator (from Script 1)
-- ======================================================================
-- =============================================
-- Function to create a "Paste" menu on memos
-- =============================================
local function attachPastePopup(memoControl, form)
local popup = createPopupMenu(form)
local menuItemPaste = createMenuItem(popup)
menuItemPaste.Caption = 'Paste'
menuItemPaste.OnClick = function(sender)
local clipboardText = getClipboardText()
if clipboardText then
-- If the memo belongs to the Array Comparator section, remove newline characters
if memoControl.Name == "array1" or memoControl.Name == "array2" then
clipboardText = clipboardText:gsub("[\r\n]+", " ")
end
memoControl.Lines.Text = clipboardText
else
showMessage("The clipboard does not contain any text!")
end
end
popup.Items.add(menuItemPaste)
memoControl.OnMouseUp = function(sender, x, y, button)
if button == 2 then
local globalX = memoControl.ClientToScreenX(x)
local globalY = memoControl.ClientToScreenY(y)
popup.Popup(globalX, globalY)
end
end
end
--------------------------------------------------------------------------------
-- AOB COMPARATOR SECTION (from SCRIPT 2)
--------------------------------------------------------------------------------
-- Function to isolate the last injection block (contained between { and })
local function extractInjectionBlock(fullText)
local lastOpen = nil
for pos = #fullText, 1, -1 do
if fullText:sub(pos, pos) == "{" then
lastOpen = pos
break
end
end
if not lastOpen then return nil end
local lastClose = fullText:find("}", lastOpen, true)
if not lastClose then return nil end
local block = fullText:sub(lastOpen + 1, lastClose - 1)
return block
end
-- Function to extract the AoB sequences from the injection block.
-- Returns:
-- aobBefore: concatenated bytes (separated by a space) until the "INJECTING HERE" marker
-- aobAfter: concatenated bytes after the "INJECTING HERE" marker
local function extractAOB(blockText)
local arrBefore = {}
local arrAfter = {}
local currentGroup = "before"
for line in blockText:gmatch("([^\n]+)") do
if line:find("INJECTING HERE") then
currentGroup = "after"
elseif line:find("DONE INJECTING") then
-- ignore marker
elseif line:match("^%s*%{") or line:match("^%s*%}") or
line:find("ORIGINAL CODE") or line:match("^%s*//") then
-- ignore non-significant lines
else
local bytesStr = line:match(":%s*([0-9A-Fa-f ]+)%s*%-")
if not bytesStr then
bytesStr = line:match(":%s*([0-9A-Fa-f ]+)%s*$")
end
if bytesStr then
bytesStr = bytesStr:gsub("^%s+", ""):gsub("%s+$", "")
if #bytesStr > 0 then
if currentGroup == "before" then
table.insert(arrBefore, bytesStr)
else
table.insert(arrAfter, bytesStr)
end
end
end
end
end
local function joinBytes(arr)
local s = table.concat(arr, " ")
s = s:gsub("%s+", " ")
return s
end
return joinBytes(arrBefore), joinBytes(arrAfter)
end
-- Splits the AoB string into tokens (bytes separated by spaces)
local function splitBytes(aobStr)
local bytes = {}
for byte in aobStr:gmatch("%S+") do
table.insert(bytes, byte)
end
return bytes
end
-- Compare nibble by nibble (Partial Comparison mode)
local function compareBytePartial(b1, b2)
local result = ""
local n1 = b1:sub(1,1)
local n2 = b2:sub(1,1)
if n1:upper() == n2:upper() then
result = result .. n1:upper()
else
result = result .. "?"
end
n1 = b1:sub(2,2)
n2 = b2:sub(2,2)
if n1:upper() == n2:upper() then
result = result .. n1:upper()
else
result = result .. "?"
end
return result
end
-- Partial Comparison: comparing nibble by nibble
local function compareAOBsPartial(aobStr1, aobStr2)
local bytes1 = splitBytes(aobStr1)
local bytes2 = splitBytes(aobStr2)
local maxLen = math.max(#bytes1, #bytes2)
local result = {}
for i = 1, maxLen do
local b1 = bytes1[i] or "??"
local b2 = bytes2[i] or "??"
if b1 == "??" or b2 == "??" then
table.insert(result, "??")
else
table.insert(result, compareBytePartial(b1, b2))
end
end
return table.concat(result, " ")
end
-- Full Comparison: if bytes do not match, return "??"
local function compareByteFull(b1, b2)
if b1:upper() == b2:upper() then
return b1:upper()
else
return "??"
end
end
local function compareAOBsFull(aobStr1, aobStr2)
local bytes1 = splitBytes(aobStr1)
local bytes2 = splitBytes(aobStr2)
local maxLen = math.max(#bytes1, #bytes2)
local result = {}
for i = 1, maxLen do
local b1 = bytes1[i] or "??"
local b2 = bytes2[i] or "??"
table.insert(result, compareByteFull(b1, b2))
end
return table.concat(result, " ")
end
-- Count the tokens (bytes) in an AoB string
local function countBytesFromAOB(aobStr)
local count = 0
for byte in aobStr:gmatch("%S+") do
count = count + 1
end
return count
end
--------------------------------------------------------------------------------
-- ARRAY COMPARATOR SECTION (from SCRIPT 1 - unchanged)
--------------------------------------------------------------------------------
local function parseInput(text)
text = text:gsub("^%s+", ""):gsub("%s+$", "")
local tokens = {}
for token in text:gmatch("([0-9A-Fa-f%?][0-9A-Fa-f%?])") do
table.insert(tokens, token)
end
return tokens
end
local function compareBytes(a, b, mode)
local A = a:upper()
local B = b:upper()
if A == B then
return A
end
if mode == "??" then
return "??"
elseif mode == "?" then
local high = (A:sub(1,1) == B:sub(1,1)) and A:sub(1,1) or "?"
local low = (A:sub(2,2) == B:sub(2,2)) and A:sub(2,2) or "?"
return high .. low
else
return "??"
end
end
--------------------------------------------------------------------------------
-- MAIN FUNCTION: "Super Comparator" Form
--------------------------------------------------------------------------------
local function showSuperComparatorForm()
local form = createForm()
form.Caption = 'Super Comparator'
form.Width = 900
form.Height = 900
form.Position = poScreenCenter
--------------------------------------------------------------------------------
-- AOB Comparator Section (top) - uses Script 2 functions
--------------------------------------------------------------------------------
local aobGroup = createGroupBox(form)
aobGroup.Caption = 'AOB Comparator'
aobGroup.Left = 10
aobGroup.Top = 10
aobGroup.Width = 880
aobGroup.Height = 400
local inputLabel1 = createLabel(aobGroup)
inputLabel1.Caption = 'Script 1 (paste script with right click -> Paste):'
inputLabel1.Left = 10
inputLabel1.Top = 20
local inputLabel2 = createLabel(aobGroup)
inputLabel2.Caption = 'Script 2 (paste script with right click -> Paste):'
inputLabel2.Left = 460
inputLabel2.Top = 20
local inputMemo1 = createMemo(aobGroup)
inputMemo1.Left = 10
inputMemo1.Top = 40
inputMemo1.Width = 430
inputMemo1.Height = 150
inputMemo1.ScrollBars = ssBoth
inputMemo1.WordWrap = false
attachPastePopup(inputMemo1, form)
local inputMemo2 = createMemo(aobGroup)
inputMemo2.Left = 460
inputMemo2.Top = 40
inputMemo2.Width = 430
inputMemo2.Height = 150
inputMemo2.ScrollBars = ssBoth
inputMemo2.WordWrap = false
attachPastePopup(inputMemo2, form)
local compareFullButton = createButton(aobGroup)
compareFullButton.Caption = 'Full Comparison (??)'
compareFullButton.Left = 10
compareFullButton.Top = 200
compareFullButton.Width = 120
local comparePartialButton = createButton(aobGroup)
comparePartialButton.Caption = 'Partial Comparison (?)'
comparePartialButton.Left = 140
comparePartialButton.Top = 200
comparePartialButton.Width = 120
local beforeLabel = createLabel(aobGroup)
beforeLabel.Caption = 'Compared Bytes BEFORE Injecting Point:'
beforeLabel.Left = 10
beforeLabel.Top = 240
local outputMemoBefore = createMemo(aobGroup)
outputMemoBefore.Left = 10
outputMemoBefore.Top = 260
outputMemoBefore.Width = 430
outputMemoBefore.Height = 120
outputMemoBefore.ReadOnly = true
outputMemoBefore.ScrollBars = ssBoth
outputMemoBefore.WordWrap = false
local afterLabel = createLabel(aobGroup)
afterLabel.Caption = 'Compared AoB AFTER Injecting Point:'
afterLabel.Left = 460
afterLabel.Top = 240
local outputMemoAfter = createMemo(aobGroup)
outputMemoAfter.Left = 460
outputMemoAfter.Top = 260
outputMemoAfter.Width = 430
outputMemoAfter.Height = 120
outputMemoAfter.ReadOnly = true
outputMemoAfter.ScrollBars = ssBoth
outputMemoAfter.WordWrap = false
-- Main function to perform the AoB comparison
local function doAOBComparison(compareMode)
local fullText1 = inputMemo1.Lines.Text
local fullText2 = inputMemo2.Lines.Text
if not fullText1 or fullText1 == '' or not fullText2 or fullText2 == '' then
showMessage("Both scripts must be pasted for analysis!")
return
end
local injectionBlock1 = extractInjectionBlock(fullText1)
local injectionBlock2 = extractInjectionBlock(fullText2)
if not injectionBlock1 or not injectionBlock2 then
showMessage("Unable to locate the injection block in one of the scripts!")
return
end
local aobBefore1, aobAfter1 = extractAOB(injectionBlock1)
local aobBefore2, aobAfter2 = extractAOB(injectionBlock2)
local comparedBefore, comparedAfter
if compareMode == "partial" then
comparedBefore = compareAOBsPartial(aobBefore1, aobBefore2)
comparedAfter = compareAOBsPartial(aobAfter1, aobAfter2)
else
comparedBefore = compareAOBsFull(aobBefore1, aobBefore2)
comparedAfter = compareAOBsFull(aobAfter1, aobAfter2)
end
local countBefore = countBytesFromAOB(comparedBefore)
local countAfter = countBytesFromAOB(comparedAfter)
local outputTextBefore = "Compared Bytes BEFORE Injecting Point:\n" ..
comparedBefore .. "\n" ..
"Byte count: " .. countBefore .. "\n" ..
string.format("0x%X", countBefore) .. "\n" ..
string.format("%X", countBefore)
local outputTextAfter = "Compared AoB AFTER Injecting Point:\n" ..
comparedAfter .. "\n" ..
"Byte count: " .. countAfter .. "\n" ..
string.format("0x%X", countAfter) .. "\n" ..
string.format("%X", countAfter)
outputMemoBefore.Lines.Text = outputTextBefore
outputMemoAfter.Lines.Text = outputTextAfter
end
compareFullButton.OnClick = function(sender)
doAOBComparison("full")
end
comparePartialButton.OnClick = function(sender)
doAOBComparison("partial")
end
--------------------------------------------------------------------------------
-- Array Comparator Section (bottom) - taken exactly from SCRIPT 1
--------------------------------------------------------------------------------
local arrayGroup = createGroupBox(form)
arrayGroup.Caption = 'Array Comparator'
arrayGroup.Left = 10
arrayGroup.Top = 420
arrayGroup.Width = 880
arrayGroup.Height = 400
local array_label1 = createLabel(arrayGroup)
array_label1.Caption = 'Array 1:'
array_label1.Left = 10
array_label1.Top = 10
local array_input1_memo = createMemo(arrayGroup)
array_input1_memo.Left = 10
array_input1_memo.Top = 30
array_input1_memo.Width = 430
array_input1_memo.Height = 150
array_input1_memo.ScrollBars = ssVertical
array_input1_memo.WordWrap = false
--array_input1_memo.Name = "array1"
array_input1_memo.OnChange = function(sender)
local newText = sender.Lines.Text:gsub("[\r\n]+", " ")
if newText ~= sender.Lines.Text then
sender.Lines.Text = newText
end
sender.SelStart = #newText
end
attachPastePopup(array_input1_memo, form)
local array_label2 = createLabel(arrayGroup)
array_label2.Caption = 'Array 2:'
array_label2.Left = 460
array_label2.Top = 10
local array_input2_memo = createMemo(arrayGroup)
array_input2_memo.Left = 460
array_input2_memo.Top = 30
array_input2_memo.Width = 430
array_input2_memo.Height = 150
array_input2_memo.ScrollBars = ssVertical
array_input2_memo.WordWrap = false
--array_input2_memo.Name = "array2"
array_input2_memo.OnChange = function(sender)
local newText = sender.Lines.Text:gsub("[\r\n]+", " ")
if newText ~= sender.Lines.Text then
sender.Lines.Text = newText
end
sender.SelStart = #newText
end
attachPastePopup(array_input2_memo, form)
local array_btn_single = createButton(arrayGroup)
array_btn_single.Caption = 'Partial Comparison (?)'
array_btn_single.Width = 120
array_btn_single.Height = 30
array_btn_single.Left = 10
array_btn_single.Top = 190
local array_btn_double = createButton(arrayGroup)
array_btn_double.Caption = 'Full Comparison (??)'
array_btn_double.Width = 120
array_btn_double.Height = 30
array_btn_double.Left = array_btn_single.Left + array_btn_single.Width + 10
array_btn_double.Top = array_btn_single.Top
local array_result_label = createLabel(arrayGroup)
array_result_label.Caption = 'Result:'
array_result_label.Left = 10
array_result_label.Top = 230
local array_result_memo = createMemo(arrayGroup)
array_result_memo.Left = 10
array_result_memo.Top = 250
array_result_memo.Width = 880
array_result_memo.Height = 150
array_result_memo.ScrollBars = ssBoth
array_result_memo.ReadOnly = true
-- Function to perform the array comparison
local function doArrayComparison(mode)
local array1 = parseInput(array_input1_memo.Lines.Text)
local array2 = parseInput(array_input2_memo.Lines.Text)
local maxBytes = math.max(#array1, #array2)
local resultTokens = {}
for i = 1, maxBytes do
local b1 = array1[i] or "??"
local b2 = array2[i] or "??"
table.insert(resultTokens, compareBytes(b1, b2, mode))
end
local byte_count = maxBytes
local result_text = table.concat(resultTokens, " ") .. "\r\n" ..
"Byte count: " .. byte_count .. "\r\n" ..
"0x" .. string.format("%X", byte_count) .. "\r\n" ..
string.format("%X", byte_count)
array_result_memo.Lines.Text = result_text
end
array_btn_single.OnClick = function() doArrayComparison("?") end
array_btn_double.OnClick = function() doArrayComparison("??") end
form.Show()
end
--------------------------------------------------------------------------------
-- Integration into the main menu: adds the "Super Comparator" button
--------------------------------------------------------------------------------
local function addSuperComparatorMenu()
local mainForm = getMainForm()
if not mainForm then return end
local menuItems = mainForm.Menu.Items
local insertIndex = menuItems.Count
local targetCaption = "Help"
for i = 0, menuItems.Count - 1 do
if menuItems[i].Caption == targetCaption then
insertIndex = i
break
end
end
local menuItem = createMenuItem(menuItems)
menuItem.Caption = "Super Comparator"
menuItem.OnClick = showSuperComparatorForm
menuItems.insert(insertIndex, menuItem)
end
-- Add the button to the menu at startup
addSuperComparatorMenu()
Spoiler
.....