Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


[Lua Tool] Pointer Path Parser and Address List Insertion V3

 
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Tutorials -> LUA Tutorials
View previous topic :: View next topic  
Author Message
AylinCE
Grandmaster Cheater Supreme
Reputation: 37

Joined: 16 Feb 2017
Posts: 1543

PostPosted: Wed Jan 14, 2026 4:06 pm    Post subject: [Lua Tool] Pointer Path Parser and Address List Insertion V3 Reply with quote

Overview: This Lua-based utility for Cheat Engine simplifies the process of handling complex pointer paths.
It allows users to parse string-representation pointers (e.g., [[base+offset]+offset]) or resolve existing entries from the Address List into a clean, editable format.

Key Features:

Smart Parsing: Automatically extracts base addresses and offsets from standard pointer notation.

Address List Integration: Accepts an Index number from your current Address List to reverse-engineer and display its pointer path.

One-Click Creator: Quickly adds the parsed result back to the Address List with a custom description.

High-DPI Support: Fully scaled UI using getScreenDPI to ensure clarity on 4K and high-resolution monitors.

Live Value Watcher: Real-time monitoring of the resolved address, displaying 4-Byte, Float, and Hexadecimal values simultaneously to help identify data types.

Workflow Optimized: Includes custom hotkeys (e.g., CTRL for instant clipboard paste and ENTER for parsing) and a frameless, draggable modern UI.

How to Use:

    Paste your pointer path or enter the Index of an address list entry into the Input box.

    Click PARSE (or press Enter).

    Review the live value in the watcher panel to verify the pointer is valid.

    Click ADD TO ADDRESS LIST to save it permanently.


Major Update: Smart Interaction & Workflow!

Context-Aware Shortcuts:
Since the form is transparent, I've mapped shortcuts directly to the Input fields.
Use CTRL+S to Save New and CTRL+U to Update an existing record.

Status Bar: The Value label now doubles as a status display, letting you know when a record is successfully updated or added.

Manual Update: If you click UPDATE without parsing an index first, the tool will now intelligently ask you which Index # you'd like to overwrite.

Clean UI: No more accidental clipboard spamming! Standard Windows keys (CTRL+V, CTRL+A) are back to normal.

------------------------- V3 ---------------------------
What's New in V2 to V3 Update?

In this version, we have moved from simple string manipulation to a "Validation-First" architecture, ensuring that the Address List remains clean and stable even with complex or manual inputs.

Heuristic Base Address Resolution:
Implemented a logic that treats 'Module + First Offset' as a single Base unit when a module extension (.exe/.dll) is detected, solving the ambiguity in plain text strings.

Real-time Output Synchronization:
The Add and Edit buttons now prioritize the OutputEdit field. This allows users to manually refine the pointer path before committing it to the Address List.

Hex Integrity Validation: Added a strict verification step using Regex patterns to ensure only valid Hexadecimal characters (0-9, A-F) are processed.

Process Abort on Error:
If an invalid character (like 'S' instead of '5') is detected within an offset, the script now aborts the operation and notifies the user via a MessageDialog, preventing "Ghost 0" or empty offsets in your table.

Enhanced Stability for Plain Text:
Fixed the "Double Bracket" bug in the Parse logic, ensuring that plain strings like base+off1+off2 are correctly wrapped into nested [[base]+off1]+off2 formats.

Full English Documentation:
All core logic and internal functions are now documented with English comments for better community support and clarity.
--------------------------------------------------------------------------
Optional Parse Format:

From this:
[[[executable.exe+00A1B2C3]+10D25C]+20]+30

To this:
executable.exe+00A1B2C3+10D25C+20+30

Or from this:
executable.exe+00A1B2C3+10D25C+20+30

To this:
[[[executable.exe+00A1B2C3]+10D25C]+20]+30
--------------------------------------------------------------------------

Code:
if parserForm then parserForm.Destroy() parserForm=nil end
DP1=getScreenDPI()/96
parserForm=createForm()
parserForm.height=290*DP1 parserForm.width=420*DP1
parserForm.PopupMode=0 parserForm.caption="Pointer Parser & Creator"
parserForm.Position="poDesktopCenter" parserForm.ShowInTaskBar="stAlways"
parserForm.BorderStyle="bsNone" parserForm.Font.Size=12*DP1
parserForm.setLayeredAttributes(0x000100, 255, LWA_COLORKEY | LWA_ALPHA )
parserForm.Color=0x000100 parserForm.Font.Style="fsBold"

-------------------------
local prs = {}
----------------------- prs.prsTitle ----- 
prs.prsTitle=createPanel(parserForm)
prs.prsTitle.AutoSize=false
prs.prsTitle.height=40*DP1 prs.prsTitle.width=400*DP1 prs.prsTitle.left=10*DP1 prs.prsTitle.top=5*DP1
prs.prsTitle.caption="Pointer Parser & Creator V3" prs.prsTitle.BevelWidth=4
prs.prsTitle.Color=0x878787 prs.prsTitle.Font.Size=14*DP1
prs.prsTitle.OnMouseDown=function() parserForm.DragNow() end
-----------------------
----------------------- prs.prsPnl1 ----- 
prs.prsPnl1=createPanel(parserForm)
prs.prsPnl1.AutoSize=false
prs.prsPnl1.height=235*DP1 prs.prsPnl1.width=400*DP1 prs.prsPnl1.left=10*DP1 prs.prsPnl1.top=48*DP1
prs.prsPnl1.caption="" prs.prsPnl1.Color=0x878787 prs.prsPnl1.BevelWidth=4
-----------------------
----------------------- prs.lblInput ----- 
prs.lblInput=createLabel(prs.prsPnl1)
prs.lblInput.AutoSize=false
prs.lblInput.height=21*DP1 prs.lblInput.width=380*DP1 prs.lblInput.left=10*DP1 prs.lblInput.top=5*DP1
prs.lblInput.caption="Input (Pointer Path or Address List Index):"
prs.lblInput.alignment="taCenter" prs.lblInput.Font.Color=0xFDFDFD
-----------------------
----------------------- prs.inputEdit ----- 
prs.inputEdit=createEdit(prs.prsPnl1)
prs.inputEdit.AutoSize=false
prs.inputEdit.height=27*DP1 prs.inputEdit.width=380*DP1 prs.inputEdit.left=10*DP1 prs.inputEdit.top=30*DP1
prs.inputEdit.text="" prs.inputEdit.Color=0xBDBDBD
-----------------------
----------------------- prs.btnParse ----- 
prs.btnParse=createButton(prs.prsPnl1)
prs.btnParse.AutoSize=false
prs.btnParse.height=27*DP1 prs.btnParse.width=380*DP1 prs.btnParse.left=10*DP1 prs.btnParse.top=62*DP1
prs.btnParse.caption="PARSE" prs.btnParse.Font.Size=12*DP1
-----------------------
----------------------- prs.lblOutput ----- 
prs.lblOutput=createLabel(prs.prsPnl1)
prs.lblOutput.AutoSize=false
prs.lblOutput.height=21*DP1 prs.lblOutput.width=380*DP1 prs.lblOutput.left=10*DP1 prs.lblOutput.top=85*DP1
prs.lblOutput.caption="Formatted Output (Address+Offsets):"
prs.lblOutput.alignment="taCenter" prs.lblOutput.Font.Color=0xFDFDFD
-----------------------
----------------------- prs.outputEdit ----- 
prs.outputEdit=createEdit(prs.prsPnl1)
prs.outputEdit.AutoSize=false
prs.outputEdit.height=27*DP1 prs.outputEdit.width=380*DP1 prs.outputEdit.left=10*DP1 prs.outputEdit.top=107*DP1
prs.outputEdit.text="" prs.outputEdit.Color=0xBDBDBD
-----------------------
----------------------- prs.btnAdd ----- 
prs.btnAdd=createButton(prs.prsPnl1)
prs.btnAdd.AutoSize=false
prs.btnAdd.height=27*DP1 prs.btnAdd.width=185*DP1 prs.btnAdd.left=10*DP1 prs.btnAdd.top=137*DP1
prs.btnAdd.caption="ADD NEW ADDRESS" prs.btnAdd.Font.Size=10*DP1
-----------------------
----------------------- prs.btnAdd ----- 
prs.btnEdt=createButton(prs.prsPnl1)
prs.btnEdt.AutoSize=false
prs.btnEdt.height=27*DP1 prs.btnEdt.width=185*DP1 prs.btnEdt.left=205*DP1 prs.btnEdt.top=137*DP1
prs.btnEdt.caption="UPDATE" prs.btnEdt.Font.Size=10*DP1
-----------------------
----------------------- prs.lblOutput ----- 
prs.lblValue=createLabel(prs.prsPnl1)
prs.lblValue.AutoSize=false
prs.lblValue.height=70*DP1 prs.lblValue.width=380*DP1 prs.lblValue.left=10*DP1 prs.lblValue.top=165*DP1
prs.lblValue.Font.Size=12 prs.lblValue.OptimalFill=true prs.lblValue.Caption = "\nReady...\n"
prs.lblValue.Font.Color=0xFDFDFD
-----------------------
----------------------- prs.btnAdd ----- 
prs.btnCls=createButton(prs.prsTitle)
prs.btnCls.AutoSize=false
prs.btnCls.height=26*DP1 prs.btnCls.width=30*DP1
prs.btnCls.left, prs.btnCls.top= prs.prsTitle.Width - 40*DP1, 7*DP1
prs.btnCls.caption="X" prs.btnCls.Font.Size=14*DP1
-----------------------
prs.btnCls.OnClick=function() parserForm.Close() end
--############################################################################--

-- Global variables for state tracking
local editingIndex = nil
local originalDesc = ""
local parts = {} -- Shared table for all buttons

-- #################### CORE LOGIC FUNCTIONS #################### --

--- Splits the input string into a Base Address and a list of Offsets.
-- Handles both bracketed [base+off]+off and plain base+off+off formats.
function getSmartParts(text)
    parts = {} -- Reset global table to prevent data accumulation
    local input = text:gsub("%s+", "") -- Remove all spaces

    -- CASE 1: Input contains brackets (e.g., [[base+off]+off])
    -- We extract the innermost content as the Base Address.
    if input:find("%[") and input:find("%]") then
        local innerBase = input:match("%[+([^%]]+)%]")
        if innerBase then
            table.insert(parts, innerBase)
            -- Collect remaining offsets outside the last bracket
            local remainder = input:match("%](.+)$")
            if remainder then
                for off in remainder:gmatch("%+([%x%d%a]+)") do
                    table.insert(parts, off)
                end
            end
        end
    else
        -- CASE 2: Plain text (e.g., executable.exe+00A1B2C3+10D2SC+20+30)
        -- This is the "Ambiguity" DarkByte mentioned. We use our logic to solve it.
        local temp = {}
        input = text:gsub("%]+", ""):gsub("%[+", "")
        for p in input:gmatch("[^%+]+") do table.insert(temp, p) end

        if #temp > 0 then
            -- LOGIC: If the 1st part is a module (.exe) or the 2nd part is a long Hex,
            -- we marry them as a single Base Address.
            if #temp >= 2 and (temp[1]:find("%.") or #temp[2] > 5) then
                table.insert(parts, temp[1] .. "+" .. temp[2]) -- The "Static Base"
                for i = 3, #temp do table.insert(parts, temp[i]) end -- The rest are Offsets
            else
                -- If no module is detected, only the 1st part is the Base
                table.insert(parts, temp[1])
                for i = 2, #temp do table.insert(temp, p) table.insert(parts, temp[i]) end
            end
        end
    end
    return parts
end

-- Logic: Parse Action (Converts between Plain, Bracketed, and Address List Index)
prs.btnParse.onClick = function()
    local input = prs.inputEdit.Text:gsub("%s+", "")
    if input == "" then return end
    parts = {}
    local baseAddr = ""
    local offsets = {}

    -- NEW: Check if input is a numeric Index (Address List Reference)
    local index = tonumber(input)
    if index then
        local al = getAddressList()
        local memRec = al[index]
        if memRec then
            baseAddr = memRec.Address
            for i=0, memRec.OffsetCount-1 do
                table.insert(offsets, string.format("%X", memRec.Offset[i]))
            end
            -- Reverse offsets back to standard notation
            local revOffsets = {}
            for i=#offsets, 1, -1 do table.insert(revOffsets, offsets[i]) end
            offsets = revOffsets
            --end    -- Join Address and Offsets
            local result = baseAddr
            for _, off in ipairs(offsets) do
              result = result .. "+" .. off
            end
            prs.outputEdit.Text = result
            getSmartParts(input)
            prs.lblValue.Caption = "Fetched from Index #" .. index
        else
            showMessage("Index #" .. indexInput .. " not found in Address List!")
            return
        end

    else
        getSmartParts(input)

       -- REVERSE MODE: Convert plain text -> [[base]+off]
      if not input:find("%[") and not input:find("%]") and input:find("%+") then
        local res = parts[1]
        for i = 2, #parts do
            res = "[" .. res .. "]+" .. parts[i]
        end
        prs.outputEdit.Text = res
      else
        -- STANDARD MODE: Convert bracketed -> plain text (base+off+off)
        local res = parts[1]
        for i = 2, #parts do res = res .. "+" .. parts[i] end
        prs.outputEdit.Text = res
      end

      prs.outputEdit.SetFocus()
      prs.lblValue.Caption = "Parsed successfully."
    end
end

-- Global logic: Validate hex strings before applying
function isValidHex(s)
    return s:match("^[0-9A-Fa-f]+$") ~= nil
end

-- Logic: ADD NEW RECORD (with full validation)
prs.btnAdd.onClick = function()
    local currentText = prs.outputEdit.Text:gsub("%s+", "")
    if currentText == "" then
        showMessage("Output is empty! Please parse an address first.")
        return
    end

    -- Re-parse to ensure the latest manual edits are captured
    local currentParts = getSmartParts(currentText)

    -- VALIDATION STEP
    if #currentParts > 1 then
        for i = 2, #currentParts do
            if not isValidHex(currentParts[i]) then
                showMessage("Error: Invalid Offset found: '" .. currentParts[i] .. "'\nPlease correct it before adding.")
                return -- Stop the process!
            end
        end
    end

    -- Process creation only if validated
    local moduleName = currentParts[1]:match("^([^%+%.]+)") or "Ptr"
    local autoName = "Ptr_" .. moduleName .. "_" .. (currentParts[#currentParts] or "base")

    local desc = inputQuery("New Record", "Description:", autoName)
    if not desc or desc == "" then desc = autoName end

    local al = getAddressList()
    local mr = al.createMemoryRecord()
    mr.Description = desc
    mr.Address = currentParts[1]

    if #currentParts > 1 then
        mr.OffsetCount = #currentParts - 1
        for i = 0, mr.OffsetCount - 1 do
            mr.Offset[i] = tonumber(currentParts[#currentParts - i], 16)
        end
    end
    prs.lblValue.Caption = "Successfully added: " .. desc
end

-- Logic: UPDATE RECORD (with full validation)
prs.btnEdt.onClick = function()
    local currentText = prs.outputEdit.Text:gsub("%s+", "")
    if currentText == "" then return end

    local currentParts = getSmartParts(currentText)

    -- VALIDATION STEP
    if #currentParts > 1 then
        for i = 2, #currentParts do
            if not isValidHex(currentParts[i]) then
                showMessage("Error: Invalid Offset: '" .. currentParts[i] .. "'\nUpdate cancelled.")
                return -- Abort!
            end
        end
    end

    local al = getAddressList()
    local targetIndex = editingIndex or tonumber(inputQuery("Update", "Index #:", ""))

    if targetIndex and al[targetIndex] then
        local mr = al[targetIndex]
        mr.Address = currentParts[1]

        if #currentParts > 1 then
            mr.OffsetCount = #currentParts - 1
            for i = 0, mr.OffsetCount - 1 do
                mr.Offset[i] = tonumber(currentParts[#currentParts - i], 16)
            end
        else
            mr.OffsetCount = 0
        end
        prs.lblValue.Caption = "Record #" .. targetIndex .. " updated successfully."
    else
        showMessage("Invalid Index or record not found!")
    end
end

-- Helper: UI Reset on Input Clear
prs.inputEdit.OnChange = function()
    if prs.inputEdit.Text == "" then
        prs.outputEdit.Text = ""
        prs.lblValue.Caption = "\nReady...\n"
    end
end

-- Live Value Watcher Logic
prs.outputEdit.OnChange = function()
    local addrText = prs.outputEdit.Text
    if addrText == "" or addrText:find("Unknown") then
        prs.lblValue.Caption = "\nValue: ???\n"
        return
    end

    local actualAddr = getAddressSafe(addrText)
    if actualAddr and actualAddr ~= 0 then
        local v4b = readInteger(actualAddr) or 0
        local vfl = readFloat(actualAddr) or 0
        prs.lblValue.Caption = string.format("4-Byte: %d ... Float: %.2f \nHex: %X", v4b, vfl, v4b)
    else
        prs.lblValue.Caption = "Value: ???\n"
    end
end

-- Keyboard Shortcut Management
RealOnKeyDown = function(sender, key)
    local ctrl = isKeyPressed(VK_CONTROL)
    if ctrl and key == VK_S then prs.btnAdd.doClick() return 1 end
    if ctrl and key == VK_U then prs.btnEdt.doClick() return 1 end
    if key == VK_ESCAPE then parserForm.close() end
end

prs.inputEdit.OnKeyDown = function(sender, key)
    if key == 13 and not isKeyPressed(VK_CONTROL) then prs.btnParse.doClick()
    else RealOnKeyDown(sender, key) end
end

prs.outputEdit.OnKeyDown = RealOnKeyDown


Enjoy it! Smile

_________________
Hi Hitler Different Trainer forms for you!
https://forum.cheatengine.org/viewtopic.php?t=619279
Enthusiastic people: Always one step ahead
Do not underestimate me Master: You were a beginner in the past


Last edited by AylinCE on Thu Jan 15, 2026 8:38 pm; edited 4 times in total
Back to top
View user's profile Send private message Visit poster's website MSN Messenger
cancandodo
Advanced Cheater
Reputation: 0

Joined: 09 Mar 2012
Posts: 75

PostPosted: Wed Jan 14, 2026 6:40 pm    Post subject: Reply with quote

tks,it work correct
bug just trans one address one time
and need to type desciption manual

may be i should ues autohotkey
Back to top
View user's profile Send private message
AylinCE
Grandmaster Cheater Supreme
Reputation: 37

Joined: 16 Feb 2017
Posts: 1543

PostPosted: Wed Jan 14, 2026 8:54 pm    Post subject: Reply with quote

Thanks for the great feedback! I've updated the tool to make the workflow even faster and more intuitive based on your suggestions.

What's New:

Smart Update Mode: If you enter an Index number, the tool now detects it as an 'Update' request. Click UPDATE to overwrite the existing record instead of creating a duplicate.

    Quick Shortcuts:

    ENTER: Instant Parse.

    CTRL+S: Add New Record.

    CTRL+U: Update Record.

    ESC: Quick Close.


Auto-Naming: The tool now suggests a default name based on the pointer path, so you don't have to type descriptions manually.

Status Bar: The value display now provides real-time feedback for your actions (Added, Updated, or Invalid Index).

Fix: Standard Windows hotkeys (CTRL+V, CTRL+A) are now fully functional and stable.

Enjoy the high-speed parsing!

_________________
Hi Hitler Different Trainer forms for you!
https://forum.cheatengine.org/viewtopic.php?t=619279
Enthusiastic people: Always one step ahead
Do not underestimate me Master: You were a beginner in the past
Back to top
View user's profile Send private message Visit poster's website MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Tutorials -> LUA Tutorials All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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 cannot download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites