--[[ Multi-emulator automatic address range + automatic symbol entry creation Features: - Automatically detects PCSX2, Cemu, ePSXe, DOSBox, PPSSPP - Automatically sets scan range and relative base - Automatically adds address entries with [AUTO] prefix - Memory view right-click menu "Copy relative offset to clipboard" (Ctrl+]) - Memory view right-click menu "Add relative offset of selected address to address list" Auto-created entries are prefixed with [AUTO] and will be cleared when switching processes. If you want to keep them, rename them manually. Other: This LUA script is AI-driven. The DOSBox module of this LUA script is based on the following script by the Cheat Engine author: https://forum.cheatengine.org/viewtopic.php?t=624001 This script integrates all functionality of the previously shared "Copy Selection Address relative Offset Value to clipboard.lua" script. Do not load it again. The PPSSPP module of this LUA script is based on the source code of ppsspp_WM.ahk. The Cemu module of this LUA script is based on: https://forum.cheatengine.org/viewtopic.php?p=5781484&sid=99730d28d965f0da2f1c6575921e6458 The PCSX2 module of this LUA script is based on: https://forums.pcsx2.net/Thread-PCSX2-1-7-Cheat-Engine-Script-Compatibility Ver 1.1.2 (fixed OnProcessOpened conflict) ]] ------------------------------------------------------------------------------- -- 1. Ensure memory view window exists ------------------------------------------------------------------------------- local mv = getMemoryViewForm() if mv == nil then mv = createMemoryView() end ------------------------------------------------------------------------------- -- 2. Create menu items "Copy relative offset" and "Add relative offset to address list" ------------------------------------------------------------------------------- -- Clean up any leftover menu items if miCopyRelativeAddress ~= nil then miCopyRelativeAddress.destroy() miCopyRelativeAddress = nil end if miAddRelativeOffset ~= nil then miAddRelativeOffset.destroy() miAddRelativeOffset = nil end -- Helper function: add relative offset of selected address to address list function addRelativeOffsetToAddressList() local hv = mv.HexadecimalView if not hv or not hv.UseRelativeBase then print("Relative base not enabled.") return end local base = hv.RelativeBase local selStart = hv.SelectionStart if selStart == 0 then print("No address selected.") return end local offset = selStart - base local expr = string.format("[_REL_BASE+%s]", string.format("%X", offset)) if addSymbolToAddressList(expr, "Relative Offset Address") then print(string.format("Added relative offset address: %s", expr)) else print("This relative offset address already exists.") end end -- Menu item 1: Copy relative offset to clipboard miCopyRelativeAddress = createMenuItem(mv.memorypopup) miCopyRelativeAddress.Name = "miCopyRelativeAddress" miCopyRelativeAddress.Caption = "Copy relative offset to clipboard" miCopyRelativeAddress.ShortCut = textToShortCut("Ctrl+]") miCopyRelativeAddress.OnClick = function() local hv = mv.HexadecimalView if hv.UseRelativeBase then local s if hv.SelectionStart >= hv.RelativeBase then s = string.format("+%x", hv.SelectionStart - hv.RelativeBase) else s = string.format("-%x", hv.RelativeBase - hv.SelectionStart) end if hv.SelectionStop ~= hv.SelectionStart then if hv.SelectionStop >= hv.RelativeBase then s = string.format("%s to +%x", s, hv.SelectionStop - hv.RelativeBase) else s = string.format("%s to -%x", s, hv.RelativeBase - hv.SelectionStop) end end writeToClipboard(s) end end -- Menu item 2: Add relative offset to address list miAddRelativeOffset = createMenuItem(mv.memorypopup) miAddRelativeOffset.Name = "miAddRelativeOffset" miAddRelativeOffset.Caption = "Add relative offset of selected address to address list" miAddRelativeOffset.OnClick = addRelativeOffsetToAddressList -- Insert menu items (after "Add address") local insertIndex = -1 for i = 0, mv.memorypopup.Items.Count - 1 do local item = mv.memorypopup.Items[i] if item.Name == "Addthisaddresstothelist1" or item.Caption == "Add this address to the list" or item.Caption == "Add this address to the list..." then insertIndex = i break end end if insertIndex ~= -1 then mv.memorypopup.Items.Insert(insertIndex + 1, miCopyRelativeAddress) mv.memorypopup.Items.Insert(insertIndex + 2, miAddRelativeOffset) else mv.memorypopup.Items.Add(miCopyRelativeAddress) mv.memorypopup.Items.Add(miAddRelativeOffset) end -- Robust OnPopup override to control menu item visibility local oldmemorypopuponpopup = mv.memorypopup.OnPopup or function() end mv.memorypopup.OnPopup = function(s) local visible = mv.HexadecimalView.UseRelativeBase if miCopyRelativeAddress then miCopyRelativeAddress.Visible = visible end if miAddRelativeOffset then miAddRelativeOffset.Visible = visible end return oldmemorypopuponpopup(s) end ------------------------------------------------------------------------------- -- 3. Manage auto-added address entries (with [AUTO] prefix) ------------------------------------------------------------------------------- local AUTO_PREFIX = "[AUTO] " function removeScriptedSymbols() local addrList = getAddressList() if not addrList then return end local toRemove = {} for i = 0, addrList.Count - 1 do local rec = addrList.getMemoryRecord(i) if rec.Description and rec.Description:find(AUTO_PREFIX, 1, true) == 1 then table.insert(toRemove, rec) end end for _, rec in ipairs(toRemove) do rec.delete() end end function addSymbolToAddressList(addressExpr, displayName) local addrList = getAddressList() if not addrList then return false end for i = 0, addrList.Count - 1 do local rec = addrList.getMemoryRecord(i) if rec.Address == addressExpr then return false -- Already exists end end local rec = addrList.createMemoryRecord() rec.Address = addressExpr rec.Description = AUTO_PREFIX .. displayName rec.ShowAsHex = true return true end ------------------------------------------------------------------------------- -- 4. Address range retrieval functions for each emulator ------------------------------------------------------------------------------- local TARGET_PROCESSES = {} TARGET_PROCESSES["pcsx2"] = function() local baseAddress = readQword("EEmem") if baseAddress then return string.format("%016X", baseAddress), string.format("%016X", baseAddress + 0x20000000) end return end TARGET_PROCESSES["cemu"] = function() local success, baseAddress = pcall(function() return executeCode(getAddress("Cemu.memory_getBase"), 0, 2000) end) if success and baseAddress then registerSymbol("baseAddress", baseAddress) return string.format("%016X", baseAddress), string.format("%016X", baseAddress + 0x20000000) end return end TARGET_PROCESSES["epsxe"] = function() local aobPattern = "81E1FFFF1F0081C1" local foundAddress = AOBScan(aobPattern) if foundAddress then local address = tonumber(foundAddress[0], 16) + 8 registerSymbol("ePSXe_x32", address) local value = readInteger(address) foundAddress:destroy() return string.format("%016X", value), string.format("%016X", value + 0x200000) end return end TARGET_PROCESSES["dosbox"] = function() local ms = createMemScan() ms.ScanValue = 'IBM COMPATIBLE' ms.VarType = vtString ms.ScanWritable = 'scanDontCare' ms.ScanExecutable = 'scanDontCare' ms.ScanCopyOnWrite = 'scanDontCare' ms.scan() local r = ms.Results ms.destroy() if not r or #r == 0 then return end local chosenAddr = r[1] for _, addr in ipairs(r) do if addr > chosenAddr then chosenAddr = addr end end local baseAddr = chosenAddr - 0xFE00E registerSymbol('base', baseAddr) return string.format("%016X", baseAddr), string.format("%016X", baseAddr + 0x1000000) end ------------------------------------------------------------------------------- -- 5. PPSSPP configuration function ------------------------------------------------------------------------------- local ppsspp_WindowClass = "PPSSPPWnd" local ppsspp_Configured = false function getPPSSPPModuleInfo() local moduleList = enumModules() for _, mod in ipairs(moduleList) do if string.match(mod.Name, "^PPSSPP.*%.exe$") then return mod.Name, mod.Address end end return nil, nil end function configurePPSSPP() local hwnd = findWindow(ppsspp_WindowClass) if hwnd == nil then return false end local currentPid = getOpenedProcessID() local targetPid = getWindowProcessID(hwnd) if currentPid ~= targetPid then return false end local moduleName, processBase = getPPSSPPModuleInfo() if moduleName == nil or processBase == 0 then return false end local WM_USER_GET_BASE_POINTER = 0xB118 local function sendWMs(lParam) return sendMessage(hwnd, WM_USER_GET_BASE_POINTER, 0, lParam) end local reply0 = sendWMs(0) local reply2 = sendWMs(2) if type(reply0) ~= "number" or type(reply2) ~= "number" then return false end local reply1, reply3 if targetIs64Bit() then reply1 = sendWMs(1) reply3 = sendWMs(3) if type(reply1) ~= "number" or type(reply3) ~= "number" then return false end else reply1, reply3 = 0, 0 end local baseAddr = (reply1 * 0x100000000) + reply0 local basePtr = (reply3 * 0x100000000) + reply2 if baseAddr == 0 or basePtr == 0 then return false end local offset = basePtr - processBase if offset < 0 then return false end local actualBase = baseAddr + 0x8800000 removeScriptedSymbols() clearEphemeralSymbols() local fromAddress = string.format("%016X", actualBase) local toAddress = string.format("%016X", actualBase + 0x2000000) MainForm.FromAddress.Text = fromAddress MainForm.ToAddress.Text = toAddress mv.HexadecimalView.UseRelativeBase = true mv.HexadecimalView.RelativeBase = actualBase mv.HexadecimalView.Address = actualBase mv.show() local addrExpr = string.format("[%s+%X]+8800000", moduleName, offset) addSymbolToAddressList(addrExpr, "PPSSPP Memory") registerSymbol("PPSSPP_BasePtr", basePtr) -- Update relative base symbol (for use in expressions) updateRelativeBaseSymbol() return true end ------------------------------------------------------------------------------- -- 6. Clear temporary symbols ------------------------------------------------------------------------------- function clearEphemeralSymbols() unregisterSymbol("ePSXe_x32") unregisterSymbol("base") unregisterSymbol("baseAddress") unregisterSymbol("PPSSPP_BasePtr") unregisterSymbol("_REL_BASE") end -- Update relative base symbol function updateRelativeBaseSymbol() local hv = mv.HexadecimalView if hv and hv.UseRelativeBase then registerSymbol("_REL_BASE", hv.RelativeBase) else unregisterSymbol("_REL_BASE") end end ------------------------------------------------------------------------------- -- 7. Timer: continuously attempt to configure PPSSPP ------------------------------------------------------------------------------- local ppssppTimer = createTimer(nil, false) ppssppTimer.Interval = 2000 ppssppTimer.OnTimer = function() if not ppsspp_Configured then if configurePPSSPP() then ppsspp_Configured = true ppssppTimer.Enabled = false end else ppssppTimer.Enabled = false end end ------------------------------------------------------------------------------- -- 8. OnProcessOpened: handle all emulators (chained call to avoid conflicts) ------------------------------------------------------------------------------- -- Save old callback (if exists) local oldOnProcessOpened = MainForm.OnProcessOpened MainForm.OnProcessOpened = function(pid, handle, caption) -- 1. First call previously registered handler (chained) if oldOnProcessOpened then pcall(oldOnProcessOpened, pid, handle, caption) -- Use pcall to protect against errors from old callback breaking the chain end -- 2. This script's logic if not mv then mv = getMemoryViewForm() if not mv then return end end local hwnd = findWindow(ppsspp_WindowClass) if hwnd ~= nil and getWindowProcessID(hwnd) == pid then ppsspp_Configured = false if configurePPSSPP() then ppsspp_Configured = true ppssppTimer.Enabled = false else if not ppssppTimer.Enabled then ppssppTimer.Enabled = true end end return end -- Other emulators local moduleList = enumModules() local processName for _, module in ipairs(moduleList) do local name = module.Name:lower() if name:match("%.exe$") then processName = name:match("([^/\\]+)%.exe$") break end end local processType if processName then if processName:match("^pcsx2") then processType = "pcsx2" elseif processName:match("^cemu") then processType = "cemu" elseif processName:match("^epsxe") then processType = "epsxe" elseif processName:match("dosbox") then processType = "dosbox" end end local mvhv = mv.HexadecimalView if processType then removeScriptedSymbols() local fromAddress, toAddress = TARGET_PROCESSES[processType]() if fromAddress and toAddress then MainForm.FromAddress.Text = fromAddress MainForm.ToAddress.Text = toAddress mvhv.UseRelativeBase = true mvhv.RelativeBase = tonumber(fromAddress, 16) mvhv.Address = mvhv.RelativeBase mv.show() if processType == "dosbox" then addSymbolToAddressList("base", "DOSBox Base") elseif processType == "epsxe" then addSymbolToAddressList("[ePSXe_x32]", "ePSXe Base") elseif processType == "pcsx2" then addSymbolToAddressList("[EEmem]", "PCSX2 EEmem") elseif processType == "cemu" then addSymbolToAddressList("baseAddress", "Cemu Base") end updateRelativeBaseSymbol() end else MainForm.FromAddress.Text = "0000000000000000" MainForm.ToAddress.Text = "7fffffffffffffff" mvhv.UseRelativeBase = false clearEphemeralSymbols() removeScriptedSymbols() --[[ -- ★★★ Do not reset scan range, only clear this script's symbols and menu items ★★★ clearEphemeralSymbols() removeScriptedSymbols() -- Note: Do not remove menu items because they are hidden along with relative base (visibility controlled by OnPopup) -- Disable relative display, but do not affect scan range text boxes mvhv.UseRelativeBase = false -- Do not modify MainForm.FromAddress.Text and MainForm.ToAddress.Text ]] end end ------------------------------------------------------------------------------- -- 9. If CE already has a process open at startup, trigger it (optional) ------------------------------------------------------------------------------- -- local currentPid = getOpenedProcessID() -- if currentPid ~= 0 then -- MainForm.OnProcessOpened(currentPid, getOpenedProcessHandle(), "Current Process") -- end -- print("Multi-emulator script loaded. (v1.1.2 - OnProcessOpened conflict fixed)")