summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/cheatfind/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/cheatfind/init.lua')
-rw-r--r--plugins/cheatfind/init.lua498
1 files changed, 498 insertions, 0 deletions
diff --git a/plugins/cheatfind/init.lua b/plugins/cheatfind/init.lua
new file mode 100644
index 00000000000..eadcb5b6c42
--- /dev/null
+++ b/plugins/cheatfind/init.lua
@@ -0,0 +1,498 @@
+-- license:BSD-3-Clause
+-- copyright-holders:Carl
+-- This includes a library of functions to be used at the Lua console as cf.getspaces() etc...
+local exports = {}
+exports.name = "cheatfind"
+exports.version = "0.0.1"
+exports.description = "Cheat finder helper library"
+exports.license = "The BSD 3-Clause License"
+exports.author = { name = "Carl" }
+
+local cheatfind = exports
+
+function cheatfind.startplugin()
+ local cheat = {}
+
+ -- return table of devices and spaces
+ function cheat.getspaces()
+ local spaces = {}
+ for tag, device in pairs(manager:machine().devices) do
+ if device.spaces then
+ spaces[tag] = {}
+ for name, space in pairs(device.spaces) do
+ spaces[tag][name] = space
+ end
+ end
+ end
+ return spaces
+ end
+
+ -- return table of ram devices
+ function cheat.getram()
+ local ram = {}
+ for tag, device in pairs(manager:machine().devices) do
+ if device:shortname() == "ram" then
+ ram[tag] = {}
+ ram[tag].dev = device
+ ram[tag].size = emu.item(device.items["0/m_size"]):read(0)
+ end
+ end
+ return ram
+ end
+
+ -- save data block
+ function cheat.save(space, start, size)
+ local data = { block = "", start = start, size = size, space = space }
+ if space.shortname then
+ if space:shortname() == "ram" then
+ data.block = emu.item(space.items["0/m_pointer"]):read_block(start, size)
+ if not data.block then
+ return nil
+ end
+ end
+ else
+ local block = ""
+ for i = start, start + size do
+ block = block .. string.pack("B", space:read_u8(i))
+ end
+ data.block = block
+ end
+ return data
+ end
+
+ -- compare two data blocks
+ function cheat.comp(olddata, newdata, oper, val, signed, width, endian)
+ local ret = {}
+ local ref = {} -- this is a helper for comparing two match lists
+ local format = ""
+ local size = olddata.size
+
+ if endian == 1 then
+ format = format .. ">"
+ else
+ format = format .. "<"
+ end
+ if width == 16 then
+ size = size & 0xfffe
+ if signed == 1 then
+ format = format .. "h"
+ else
+ format = format .. "H"
+ end
+ elseif width == 32 then
+ size = size & 0xfffc
+ if signed == 1 then
+ format = format .. "l"
+ else
+ format = format .. "L"
+ end
+ elseif width == 64 then
+ size = size & 0xfff8
+ if signed == 1 then
+ format = format .. "j"
+ else
+ format = format .. "J"
+ end
+ else
+ if signed == 1 then
+ format = format .. "b"
+ else
+ format = format .. "B"
+ end
+ end
+
+ if olddata.start ~= newdata.start or olddata.size ~= newdata.size then
+ return {}
+ end
+ if not val then
+ val = 0
+ end
+ if oper == "+" or oper == "inc" or oper == "<" or oper == "lt" then
+ for i = 1, size do
+ local old = string.unpack(format, olddata.block, i)
+ local new = string.unpack(format, newdata.block, i)
+ if old < new then
+ if (val > 0 and (old + val) == new) or val == 0 then
+ ret[#ret + 1] = { addr = olddata.start + i - 1,
+ oldval = old,
+ newval = new}
+ ref[ret[#ret].addr] = #ret
+ end
+ end
+ end
+ elseif oper == "-" or oper == "dec" or oper == ">" or oper == "gt" then
+ for i = 1, size do
+ local old = string.unpack(format, olddata.block, i)
+ local new = string.unpack(format, newdata.block, i)
+ if old > new then
+ if (val > 0 and (old - val) == new) or val == 0 then
+ ret[#ret + 1] = { addr = olddata.start + i - 1,
+ oldval = old,
+ newval = new}
+ ref[ret[#ret].addr] = #ret
+ end
+ end
+ end
+ elseif oper == "=" or oper == "eq" then
+ for i = 1, size do
+ local old = string.unpack(format, olddata.block, i)
+ local new = string.unpack(format, newdata.block, i)
+ if old == new then
+ ret[#ret + 1] = { addr = olddata.start + i - 1,
+ oldval = old,
+ newval = new}
+ ref[ret[#ret].addr] = #ret
+ end
+ end
+ elseif oper == "!" or oper == "ne" then
+ for i = 1, size do
+ local old = string.unpack(format, olddata.block, i)
+ local new = string.unpack(format, newdata.block, i)
+ if old ~= new then
+ ret[#ret + 1] = { addr = olddata.start + i - 1,
+ oldval = old,
+ newval = new}
+ ref[ret[#ret].addr] = #ret
+ end
+ end
+ elseif oper == "^" or oper == "xor" then
+ if val >= width then
+ return {}
+ end
+ for i = 1, size do
+ local old = string.unpack(format, olddata.block, i)
+ local new = string.unpack(format, newdata.block, i)
+ if ((old ~ new) & (1 << val)) ~= 0 then
+ ret[#ret + 1] = { addr = olddata.start + i - 1,
+ oldval = old,
+ newval = new}
+ ref[ret[#ret].addr] = #ret
+ end
+ end
+ end
+ return ret, ref
+ end
+
+ -- compare successive pairs of blocks from a table returning only addresses that match every comparison
+ function cheat.compall(dattable, oper, val, signed, width, endian)
+ if #dattable < 2 then
+ return {}
+ end
+ local matches, refs = cheat.comp(dattable[1], dattable[2], oper, val, signed, width, endian)
+ local nonmatch = {}
+ for i = 2, #dattable - 1 do
+ local matchnext, refsnext = cheat.comp(dattable[i], dattable[i+1], oper, val, signed, width, endian)
+ for addr, ref in pairs(refs) do
+ if not refsnext[addr] then
+ nonmatch[ref] = true
+ refs[addr] = nil
+ else
+ matches[ref].newval = matchnext[refsnext[addr]].newval
+ end
+ end
+ end
+ local resort = {}
+ for num, match in pairs(matches) do
+ if not nonmatch[num] then
+ resort[#resort + 1] = match
+ end
+ end
+ return resort
+ end
+
+
+ -- compare a data block to the current state
+ function cheat.compcur(olddata, oper, val, signed, width, endian)
+ local newdata = cheat.save(olddata.space, olddata.start, olddata.size, olddata.space)
+ return cheat.comp(olddata, newdata, oper, val, signed, width, endian)
+ end
+
+ _G.cf = cheat
+
+ local devtable = {}
+ local devsel = 1
+ local devcur = 1
+ local bitwidth = 3
+ local signed = 0
+ local endian = 0
+ local optable = { "<", ">", "=", "!", "^" }
+ local opsel = 1
+ local bit = 0
+ local value = 0
+ local leftop = 1
+ local rightop = 0
+ local matches = {}
+ local matchsel = 1
+ local menu_blocks = {}
+ local midx = { region = 1, init = 2, save = 3, width = 5, signed = 6, endian = 7, op = 8, val = 9,
+ lop = 10, rop = 11, comp = 12, match = 14 }
+
+ local function start()
+ devtable = {}
+ devsel = 1
+ devcur = 1
+ bitwidth = 3
+ signed = 0
+ endian = 0
+ opsel = 1
+ bit = 0
+ value = 0
+ leftop = 1
+ rightop = 1
+ matches = {}
+ matchsel = 1
+ menu_blocks = {}
+
+ table = cheat.getspaces()
+ for tag, list in pairs(table) do
+ if list.program then
+ local ram = {}
+ for num, entry in pairs(list.program.map) do
+ if entry.writetype == "ram" then
+ ram[#ram + 1] = { offset = entry.offset, size = entry.endoff - entry.offset }
+ end
+ end
+ if next(ram) then
+ devtable[#devtable + 1] = { tag = tag, space = list.program, ram = ram }
+ end
+ end
+ end
+ table = cheat.getram()
+ for tag, ram in pairs(table) do
+ devtable[#devtable + 1] = { tag = tag, space = ram.dev, ram = {{ offset = 0, size = ram.size }} }
+ end
+ end
+
+ emu.register_start(start)
+
+ local function menu_populate()
+ local menu = {}
+
+ local function menu_lim(val, min, max, menuitem)
+ if val == min then
+ menuitem[3] = "r"
+ elseif val == max then
+ menuitem[3] = "l"
+ else
+ menuitem[3] = "lr"
+ end
+ end
+
+ menu[midx.region] = { "CPU or RAM", devtable[devsel].tag, 0 }
+ if #devtable == 1 then
+ menu[midx.region][3] = 0
+ else
+ menu_lim(devsel, 1, #devtable, menu[midx.region])
+ end
+ menu[midx.init] = { "Init", "", 0 }
+ if #menu_blocks ~= 0 then
+ menu[midx.save] = { "Save current", "", 0 }
+ menu[midx.save + 1] = { "---", "", "off" }
+ menu[midx.width] = { "Bit Width", 1 << bitwidth , 0 }
+ menu_lim(bitwidth, 3, 6, menu[midx.width])
+ menu[midx.signed] = { "Signed", "false", 0 }
+ menu_lim(signed, 0, 1, menu[midx.signed])
+ if signed == 1 then
+ menu[midx.signed][2] = "true"
+ end
+ menu[midx.endian] = { "Endian", "little", 0 }
+ menu_lim(endian, 0, 1, menu[midx.endian])
+ if endian == 1 then
+ menu[midx.endian][2] = "big"
+ end
+ menu[midx.op] = { "Operator", optable[opsel], "" }
+ menu_lim(opsel, 1, #optable, menu[midx.op])
+ if optable[opsel] == "^" then
+ menu[midx.val] = { "Bit", bit, "" }
+ menu_lim(bit, 0, (1 << bitwidth), menu[midx.val])
+ else
+ menu[midx.val] = { "Value", value, "" }
+ menu_lim(value, 0, 100, menu[midx.val]) -- max value?
+ if value == 0 then
+ menu[midx.val][2] = "Any"
+ end
+ end
+ menu[midx.lop] = { "Left operand", leftop, "" }
+ menu_lim(leftop, 1, #menu_blocks[1] + 1, menu[midx.lop])
+ if leftop == #menu_blocks[1] + 1 then
+ menu[midx.lop][2] = "All"
+ end
+ menu[midx.rop] = { "Right operand", rightop, "" }
+ menu_lim(rightop, 1, #menu_blocks[1] + 1, menu[midx.rop])
+ if rightop == #menu_blocks[1] + 1 then
+ menu[midx.rop][2] = "Current"
+ end
+ menu[midx.comp] = { "Compare", "", 0 }
+ if #matches ~= 0 then
+ menu[midx.comp + 1] = { "---", "", "off" }
+ menu[midx.match] = { "Match block", matchsel, "" }
+ if #matches == 1 then
+ menu[midx.match][3] = 0
+ else
+ menu_lim(matchsel, 1, #matches, menu[midx.match])
+ end
+ for num2, match in ipairs(matches[matchsel]) do
+ if #menu > 50 then
+ break
+ end
+ local numform = ""
+ if bitwidth == 4 then
+ numform = " %04x"
+ elseif bitwidth == 5 then
+ numform = " %08x"
+ elseif bitwidth == 6 then
+ numform = " %016x"
+ else
+ numform = " %02x"
+ end
+ menu[#menu + 1] = { string.format("%08x" .. numform .. numform, match.addr, match.oldval,
+ match.newval), "", 0 }
+ if not match.mode then
+ match.mode = 1
+ end
+ if match.mode == 1 then
+ menu[#menu][2] = "Test"
+ else
+ menu[#menu][2] = "Write"
+ end
+ menu_lim(match.mode, 1, 2, menu[#menu])
+ end
+ end
+ end
+ return menu
+ end
+
+ local function menu_callback(index, event)
+ local ret = false
+
+ local function incdec(val, min, max)
+ if event == "left" and val ~= min then
+ val = val - 1
+ ret = true
+ elseif event == "right" and val ~= max then
+ val = val + 1
+ ret = true
+ end
+ return val
+ end
+
+ if index == midx.region then
+ if (event == "left" or event == "right") and #menu_blocks ~= 0 then
+ manager:machine():popmessage("Changes to this only take effect when Init is selected")
+ end
+ devsel = incdec(devsel, 1, #devtable)
+ return true
+ elseif index == midx.init then
+ if event == "select" then
+ menu_blocks = {}
+ matches = {}
+ for num, region in ipairs(devtable[devcur].ram) do
+ menu_blocks[num] = {}
+ menu_blocks[num][1] = cheat.save(devtable[devcur].space, region.offset, region.size)
+ end
+ manager:machine():popmessage("Data cleared and current state saved")
+ ret = true
+ end
+ devcur = devsel
+ elseif index == midx.save then
+ if event == "select" then
+ for num, region in ipairs(devtable[devcur].ram) do
+ menu_blocks[num][#menu_blocks[num] + 1] = cheat.save(devtable[devcur].space, region.offset, region.size)
+ end
+ manager:machine():popmessage("Current state saved")
+ ret = true
+ end
+ elseif index == midx.width then
+ bitwidth = incdec(bitwidth, 3, 6)
+ if event == "left" or event == "right" then
+ bit = 0
+ end
+ elseif index == midx.signed then
+ signed = incdec(signed, 0, 1)
+ elseif index == midx.endian then
+ endian = incdec(endian, 0, 1)
+ elseif index == midx.op then
+ if event == "left" or event == "right" or event == "comment" then
+ if optable[opsel] == "<" then
+ manager:machine():popmessage("Left less than right, value is difference")
+ elseif optable[opsel] == ">" then
+ manager:machine():popmessage("Left greater than right, value is difference")
+ elseif optable[opsel] == "=" then
+ manager:machine():popmessage("Left equal to right, value is ignored")
+ elseif optable[opsel] == "!" then
+ manager:machine():popmessage("Left not equal to right, value is ignored")
+ elseif optable[opsel] == "^" then
+ manager:machine():popmessage("Left bit different than right")
+ end
+ end
+ opsel = incdec(opsel, 1, #optable)
+ elseif index == midx.val then
+ if optable[opsel] == "^" then
+ bit = incdec(bit, 0, (1 << bitwidth))
+ else
+ value = incdec(value, 0, 100)
+ end
+ elseif index == midx.lop then
+ leftop = incdec(leftop, 1, #menu_blocks[1] + 1)
+ if (event == "left" or event == "right" or event == "comment") and leftop == #menu_blocks[1] + 1 then
+ manager:machine():popmessage("Any compares every saved data pair and returns only matches that are true for all. Right operand is ignored.")
+ end
+ elseif index == midx.rop then
+ rightop = incdec(rightop, 1, #menu_blocks[1] + 1)
+ elseif index == midx.comp then
+ if event == "select" then
+ matches = {}
+ local val
+ if optable[opsel] == "^" then
+ val = bit
+ else
+ val = value
+ end
+ for num = 1, #menu_blocks do
+ if leftop == #menu_blocks[1] + 1 then
+ matches[#matches + 1] = cheat.compall(menu_blocks[num], optable[opsel], val, signed,
+ 1 << bitwidth, endian)
+ elseif rightop == #menu_blocks[1] + 1 then
+ matches[#matches + 1] = cheat.compcur(menu_blocks[num][leftop], optable[opsel], val,
+ signed, 1 << bitwidth, endian)
+ else
+ matches[#matches + 1] = cheat.comp(menu_blocks[num][leftop], menu_blocks[num][rightop],
+ optable[opsel], val, signed, 1 << bitwidth, endian)
+ end
+ end
+ ret = true
+ end
+ elseif index == midx.match then
+ matchsel = incdec(matchsel, 0, #matches)
+ elseif index > midx.match then
+ local match = matches[matchsel][index - midx.match]
+ match.mode = incdec(match.mode, 1, 2)
+ if event == "select" then
+ local dev = devtable[devcur]
+ local cheat = { desc = string.format("Test cheat at addr %08x", match.addr), script = {} }
+ if dev.space.shortname then
+ cheat.ram = dev.tag
+ cheat.script.on = "ram:write(" .. match.addr .. "," .. match.newval .. ")"
+ else
+ cheat.space = { cpu = { tag = dev.tag, type = "program" } }
+ cheat.script.on = "cpu:write_u8(" .. match.addr .. "," .. match.newval .. ")"
+ end
+ if match.mode == 1 then
+ if not _G.ce then
+ manager:machine():popmessage("Cheat engine not available")
+ else
+ ce.inject(cheat)
+ end
+ else
+
+ end
+
+ end
+ end
+ devsel = devcur
+ return ret
+ end
+ emu.register_menu(menu_callback, menu_populate, "Cheat Finder")
+end
+
+return exports