Cheat Table Lua Script.
So I've been playing with various Cheat Engine functions, mostly to automate as much as possible.
Below is what I've got so far.
Cheat Table builders can use it in their tables as they see fit. I've tried to include sources to everything in the comments. It's still a work in progress, and me learning some Lua (and Cheat Engine), so there are things that are not fully implemented yet.
There are several things going on. I've tried to avoid making changes in the individual AAScripts, and instead handling them using global functions.
- All (default) settings used throughout the Cheat Table are defined at the beginning, and will be read from a settings file (if exist) and overwritten.
Especially the *ControlID
s are important here, as they are used for automatically loading the console commands, when the game is ready, and then registering the commands, again when they are ready to do that. I've manually edited the XML and changed the <ID>#</ID>
to a VERY high number, so it hopefully would never conflict with the autogenerated ones. The ID is an int64 (int32 on 32-bit systems?), so it can't be any high number
- Logging to file. This was to avoid having the Lua Engine output window stealing focus every time a
print()
was executed, but is also useful for debugging. Change the VerboseLevel
setting to specify how much should be output to the logfile.
- Added Compact Menu, so the menu entry will be added on load.
onMemRecPostExecute
and MainForm.OnProcessOpened
CE global functions that are triggered when Memory Record have been enabled/disabled, and when a process have been opened/attached (i.e. attached to bg3).
There take care of automatically enabling console commands and register commands, as the game is in a state to do so. And it updates the Description text so they are nice and descriptive
- bg3Timer that is heavily inspired by the [Link].
This basically does anything that isn't handled by the on*
event functions, such as opening/attaching to the game process.
Code: Select all
local bg3Executable = { 'bg3_dx11.exe', 'bg3.exe' } -- Fallback if ExecutablePath+File doesn't exist.
----------------------
-- DEFAULT SETTINGS --
--vvvvvvvvvvvvvvvvvv--
bg3setting = {}
bg3setting['SettingsFile'] = 'bg3_cheatTable.settings.txt'
bg3setting['EnableLogging'] = true
bg3setting['LogFile'] = 'bg3_cheatTable.log'
bg3setting['ColorReady'] = '00FF00'
bg3setting['ColorPending'] = 'FF8000'
bg3setting['ColorNotReady'] = '0000FF'
bg3setting['Attach'] = true
bg3setting['AttachTimeout'] = 600 -- Set to zero to disable timeout. 600 = 10 minutes.
bg3setting['Reattach'] = true -- Otherwise will only attach once.
bg3setting['ReattachControlID'] = 200000001
bg3setting['LoadCommandsControlID'] = 100000001
bg3setting['RegisterCommandsControlID'] = 100000002
bg3setting['ExecutableAutoStart'] = false
bg3setting['ExecutablePath'] = ''
bg3setting['ExecutableFile'] = ''
bg3setting['executableParameters'] = '--skip-launcher'
bg3setting['VerboseLevel'] = 1 -- 0 = Never. 1 = OnFailure. 2 = OnSuccess. 4 = Debug
bg3setting['TimerInterval'] = 5000 -- Milliseconds between ticks.
--^^^^^^^^^^^^^^^^^^--
-- DEFAULT SETTINGS --
----------------------
--[[ READ SETTINGS from file
EvenLess
Parses settings from a file.
]]
for line in io.lines(bg3setting['SettingsFile']) do
--[[ Validate syntax of the read line.
Valid line CAN begin with zero or more spaces.
The property value can only consist of alphanumeric characters and under-
scores. All spaces before and after the property name will be stripped.
The property name and property value is separated with one "equal" (=)
character. Anything after the first "equal" (=) character, will be part of
the property value. This is on purpose to allow for more or less any
possible value. Can just trim() that as well, if wanted.
]]
if string.match(line, '^ *[a-zA-Z0-9_]') and string.find(line, '=') then
local position = string.find(line, '=')
local property = string.sub(line, 1, position-1):trim()
local value = string.sub(line, position+1)
bg3setting[property] = value
end
end
--[[ Write to logfile.
EvenLess
If EnableLogging is true, the supplied string will be written to the LogFile
specified in the settings.
]]
function writeLog(s)
if bg3setting['EnableLogging'] ~= true then return end
local logMessage = tostring(s)
local logFile = io.open(bg3setting['LogFile'], 'a')
local logTime = tostring(os.date('%Y-%m-%d %H:%M:%S'))
io.output(logFile):write(string.format("%s %s\n", logTime, logMessage))
io.close(logFile)
end
writeLog('Cheat Table opened.')
----------------------
-- GLOBAL FUNCTIONS --
-- AND VARIABLES --
--vvvvvvvvvvvvvvvvvv--
--[[ Convert string to boolean.
EvenLess
Lazy method. Any string is true, unless one of the specified ones.
]]
function toboolean(s)
local s2b = {}
s2b['0'] = false
s2b['no'] = false
s2b['off'] = false
s2b['false'] = false
if s2b[string.lower(tostring(s):trim())] ~= nil then return false end
return true
end
--[[ Add Compact View menu to Cheat Engine.
https://forum.cheatengine.org/viewtopic.php?p=5510752#5510752
]]
-- Text for menu item, so easier to change in one place, instead of 3.
compactMenuText = {}
compactMenuText['enable'] = 'Enable Compact View'
compactMenuText['disable'] = 'Disable Compact View'
-- Switches between Compact View being enabled/disabled.
function cycleFullCompact(sender, force)
local state = not(compactMenuItem.Caption == compactMenuText['enable'])
if force ~= nil then
state = not force
end
compactMenuItem.Caption = state and compactMenuText['enable'] or compactMenuText['disable']
getMainForm().Splitter1.Visible = state
getMainForm().Panel4.Visible = state
getMainForm().Panel5.Visible = state
end
-- Add the compact menu item to the Cheat Engine menu.
function addCompactMenu()
if compactMenuAlreadyExists then return end
local parent = getMainForm().Menu.Items
compactMenuItem = createMenuItem(parent)
parent.add(compactMenuItem)
compactMenuItem.Caption = compactMenuText['enable']
compactMenuItem.OnClick = cycleFullCompact
compactMenuAlreadyExists = 'yes'
end
addCompactMenu()
--[[ onMemRecPreExecute
https://github.com/cheat-engine/cheat-engine/blob/master/Cheat%20Engine/bin/celua.txt#L2271
EvenLess
Do something before enable/disable of CheatEntry.
]]
function onMemRecPreExecute(memoryRecord, newState)
-- placeholder
end
--[[ onMemRecPostExecute
https://github.com/cheat-engine/cheat-engine/blob/master/Cheat%20Engine/bin/celua.txt#L2271
https://forum.cheatengine.org/viewtopic.php?p=5712091#5712091 (ParkourPenguin)
EvenLess
Do something after enable/disable of CheatEntry.
]]
function onMemRecPostExecute(memoryRecord, newState, succeeded)
--print(string.format('DEBUG: "%s" [ID: %s] state changed.', memoryRecord.Description, memoryRecord.ID))
if succeeded == true and memoryRecord.Type == vtAutoAssembler and memoryRecord.Active == true then
-- A AutoAssembler script was successfully enabled.
--[[ Find Console Commands was enabled.
EvenLess
Things to do if "Find ConsoleCommands" were enabled.
]]
if memoryRecord.ID == bg3setting['LoadCommandsControlID'] then
-- Update Register Commands to reflect it's state.
local mRec = AddressList.getMemoryRecordByID(bg3setting['RegisterCommandsControlID'])
mRec.Description = 'Register Commands not ready.'
mRec.Color = tonumber(bg3setting['ColorNotReady'], 16)
-- Check if cmdList was populated and get number of commands (max. 3000).
local numberOfCommands = nil
local commandList = readPointer("cmdList")
if commandList ~= nil and commandList ~= 0 then
numberOfCommands = readInteger(commandList + 0x2C)
if numberOfCommands > 0 then
if numberOfCommands > 3000 then
numberOfCommands = 3000 -- just in case
end
end
end
commandList = readPointer(commandList + 0x20)
if commandList ~= nil and commandList ~= 0 then
-- cmdList was populated and more than 0 commands was found.
mRec.Description = string.format('Register >>%s<< Commands',
numberOfCommands)
mRec.Color = tonumber(bg3setting['ColorPending'], 16)
mRec.Active = true
end
end
if memoryRecord.ID == bg3setting['RegisterCommandsControlID'] then
if verbose >= 4 then
writeLog(string.format('Record "%s" [ID: %s] was activated.',
tostring(memoryRecord.Description),
tostring(memoryRecord.ID)))
end
local numberOfCommands = string.match(memoryRecord.Description, '>>(%d+)<<')
memoryRecord.Description = string.format('Registered >>%s<< Commands',
numberOfCommands)
memoryRecard.Color = tonumber(bg3setting['ColorReady'], 16)
end
end
if succeeded == true and memoryRecord.Type == vtAutoAssembler and memoryRecord.Active == false then
-- Do something when disabled.
end
end
--[[ MainForm.OnProcessOpened
https://github.com/cheat-engine/cheat-engine/blob/65b383535cba0325c25b95310137e13b409e75ae/Cheat%20Engine/bin/celua.txt#L377C14-L377C14
EvenLess
]]
MainForm.OnProcessOpened = function (openedPID, processHandle, caption)
--local openedPath = enumModules(openedPID)[1].PathToFile
--local openedFilePath = extractFilePath(openedPath)
--local openedFileName = extractFileName(openedPath)
--local openedProcess = process
local verbose = tonumber(bg3setting['VerboseLevel'])
if verbose >= 4 then
writeLog(string.format('Opened process "%s" [PID: %s].',
tostring(caption),
tostring(openedPID)))
end
local memoryRecord = AddressList.getMemoryRecordByID(bg3setting['LoadCommandsControlID'])
memoryRecord.Color = tonumber(bg3setting['ColorNotReady'], 16) -- Convert from hexadecimal to number.
if verbose >= 4 then
writeLog(string.format('getMemoryRecordByID(%s): %s',
tostring(bg3setting['LoadCommandsControlID']),
tostring(memoryRecord.Description)))
end
-- Verify that it was the bg3 executable that was opened/attached.
local actualPID = nil
for i = 1, #bg3Executable do
actualPID = getProcessIDFromProcessName(bg3Executable[i])
if verbose >= 4 then
writeLog(string.format("getProcessIDFromProcessName(%s): %s",
tostring(bg3Executable[i]),
tostring(actualPID)))
end
if actualPID ~= nil then break end
end
if openedPID == actualPID then
if verbose >= 4 then
writeLog(string.format("openedPID(%s) == actualPID(%s)",
tostring(openedPID),
tostring(actualPID)))
end
memoryRecord.Color = tonumber(bg3setting['ColorReady'], 16) -- Convert from hexadecimal to number.
memoryRecord.Active = true
memoryRecord.Description = "Console Commands loaded."
end
end
--^^^^^^^^^^^^^^^^^^--
-- GLOBAL FUNCTIONS --
-- AND VARIABLES --
----------------------
--[[ Timer. Check/execute things ad-hoc.
https://wiki.cheatengine.org/index.php?title=Tutorials:Lua:Setup_Auto_Attach
EvenLess
Continously checks/executes various tasks.
]]
local tickCounter = 0 -- Used to calculate runtime.
local attachCount = 0 -- Used to count number of times attached to game process.
local function bg3Timer_callback(timer) -- Tick callback function.
local runtime = bg3setting['TimerInterval'] * tickCounter
local attach = toboolean(bg3setting['Attach'])
local reattach = toboolean(bg3setting['Reattach'])
local timeout = bg3setting['AttachTimeout'] * 1000
local verbose = tonumber(bg3setting['VerboseLevel'])
--[[ Find the active game process, if any.
EvenLess
Attempt to find process ID for both bg3 executables (in their specified order).
If Game process is found, store the process ID and update settings with actual
ExecutablePath and ExecutableFile.
]]
local actualPID = nil
for i = 1, #bg3Executable do
-- Loop through the bg3Executables to get its PID, if the game is running.
actualPID = getProcessIDFromProcessName(bg3Executable[i])
if actualPID ~= nil then
if verbose >= 4 then
writeLog(string.format('Found game process "%s" [PID: %s].',
tostring(bg3Executable[i]),
tostring(actualPID)))
end
-- Get path for the found process and update settings, if they do not match.
local executablePath = enumModules(actualPID)[1].PathToFile
local executableFilePath = extractFilePath(executablePath)
local executableFileName = extractFileName(executablePath)
if bg3setting['ExecutablePath'] ~= executableFilePath then
bg3setting['ExecutablePath'] = executableFilePath
end
if bg3setting['ExecutableFile'] ~= executableFileName then
bg3setting['ExecutableFile'] = executableFileName
end
break
end
end
--[[ If the game process is NOT running.
EvenLess
]]
if actualPID == nil then
if verbose >= 4 then
writeLog("The game process isn't found. Waiting for the game to be started ...")
end
--[[ If ExecutableAutoStart is set to true.
if toboolean(bg3setting['ExecutableAutoStart']) == true then
-- AutoStart is enabled.
if fileExists(bg3setting['ExecutablePath'] .. bg3setting['ExecutableFile']) then
if verbose >= 2 then
writeLog(string.format('Attempting to run "%s".', tostring(bg3setting['ExecutablePath') .. tostring(bg3setting['ExecutableFile'])))
end
end
-- Start the game.
createProcess(bg3setting['ExecutablePath'] .. bg3setting['ExecutableFile'], bg3setting['ExecutableParameters'])
actualPID = getProcessIDFromProcessName(bg3setting['ExecutableFile'])
end
]]
end
--[[ If the game process IS running.
EvenLess
]]
if actualPID ~= nil then
-- Placeholder
end
--[[ If if the (actual) game process isn't opened/attached.
EvenLess
]]
local openedPID = getOpenedProcessID()
if actualPID ~= openedPID then
if verbose >= 4 then
writeLog(string.format("The game process [PID: %s] isn't opened/attached [PID: %s].",
tostring(actualPID),
tostring(openedPID)))
end
--[[ Update memoryRecords to reflect state.
EvenLess
]]
-- Update Register Commands to indicate "Not Ready".
local memoryRecord = AddressList.getMemoryRecordByID(bg3setting['RegisterCommandsControlID'])
--memoryRecord.Active = false
memoryRecord.disableWithoutExecute()
memoryRecord.Color = tonumber(bg3setting['ColorNotReady'], 16) -- Convert from hexadecimal to number.
memoryRecord.Description = 'Console Commands not loaded'
-- Update Console Commands to indicate "Not Ready".
local memoryRecord = AddressList.getMemoryRecordByID(bg3setting['LoadCommandsControlID'])
--memoryRecord.Active = false
memoryRecord.disableWithoutExecute()
memoryRecord.Color = tonumber(bg3setting['ColorNotReady'], 16) -- Convert from hexadecimal to number.
memoryRecord.Description = 'Console Commands not ready'
--[[ If the game process IS running.
]]
if actualPID ~= nil then
if attach == true then
if reattach == true or attachCount <= 0 then
openProcess(actualPID)
attachCount = attachCount + 1
if verbose >= 2 then
writeLog(string.format('Attached to "%s" [PID: %s].',
tostring(executableFileName),
tostring(openedPID)))
end
end
end
end
end
-- If "Find Console Commands" is enabled.
local memoryRecord = AddressList.getMemoryRecordByID(bg3setting['LoadCommandsControlID'])
if memoryRecord.Active == true then
if verbose >= 4 then
writeLog(string.format("getMemoryRecordByID(%s).Active == true",
tostring(bg3setting['LoadCommandsControlID'])))
end
local numberOfCommands = nil
local commandList = readPointer("cmdList")
if commandList ~= nil and commandList ~= 0 then
if verbose >= 4 then
writeLog("commandList ~= nil and commandList ~= 0")
end
numberOfCommands = readInteger(commandList + 0x2C)
if numberOfCommands > 0 then
if verbose >= 4 then
writeLog(string.format("numberOfCommands(%s) > 0", tostring(numberOfCommands)))
end
if numberOfCommands > 3000 then
numberOfCommands = 3000 -- just in case
end
end
commandList = readPointer(commandList + 0x20)
if commandList ~= nil and commandList ~= 0 then
local memoryRecord = AddressList.getMemoryRecordByID(bg3setting['RegisterCommandsControlID'])
memoryRecord.Description = string.format('Register >>%s<< Commands',
numberOfCommands)
memoryRecord.Color = tonumber(bg3setting['ColorReady'], 16)
memoryRecord.Active = true
end
end
end
tickCounter = tickCounter + 1
end
-- Create the timer and attach the callback function.
local bg3Timer = createTimer(getMainForm()) -- Create timer with the main form as its parent.
bg3Timer.Interval = bg3setting['TimerInterval'] -- Set timer interval.
bg3Timer.OnTimer = bg3Timer_callback -- Set timer tick callback function.
--[[ Initialize the speech engine.
EvenLess
For som reason speak() or speakEnglish() must be run at least once from the
Cheat Table LUA Script, otherwise calls in the AssemblerScripts doesn't work.
]]
speakEnglish('')