From 216869186c3521f724edee4c16b94b45c9656838 Mon Sep 17 00:00:00 2001 From: cracyc Date: Mon, 2 May 2016 10:09:42 -0500 Subject: [PATCH] plugins/cheatfind: make menu layout much more flexible (nw) --- plugins/cheatfind/init.lua | 610 ++++++++++++++++++++----------------- 1 file changed, 331 insertions(+), 279 deletions(-) diff --git a/plugins/cheatfind/init.lua b/plugins/cheatfind/init.lua index 93bca9fed82..f0b516c72e7 100644 --- a/plugins/cheatfind/init.lua +++ b/plugins/cheatfind/init.lua @@ -76,7 +76,18 @@ function cheatfind.startplugin() local ref = {} -- this is a helper for comparing two match lists local bitmask = nil - local function bne(a, b, val, addr) + local cfoper = { + lt = function(a, b, val) return (a < b and val == 0) or (val > 0 and (a + val) == b) end, + gt = function(a, b, val) return (a > b and val == 0) or (val > 0 and (a - val) == b) end, + eq = function(a, b, val) return a == b end, + ne = function(a, b, val) return (a ~= b and val == 0) or + (val > 0 and ((a - val) == b or (a + val) == b)) end, + ltv = function(a, b, val) return a < val end, + gtv = function(a, b, val) return a > val end, + eqv = function(a, b, val) return a == val end, + nev = function(a, b, val) return a ~= val end } + + function cfoper.bne(a, b, val, addr) if type(val) ~= "table" then bitmask = a ~ b return bitmask ~= 0 @@ -88,7 +99,7 @@ function cheatfind.startplugin() end end - local function beq(a, b, val, addr) + function cfoper.beq(a, b, val, addr) if type(val) ~= "table" then bitmask = ~a ~ b return bitmask ~= 0 @@ -100,17 +111,6 @@ function cheatfind.startplugin() end end - local cfoper = { - lt = function(a, b, val) return (a < b and val == 0) or (val > 0 and (a + val) == b) end, - gt = function(a, b, val) return (a > b and val == 0) or (val > 0 and (a - val) == b) end, - eq = function(a, b, val) return a == b end, - ne = function(a, b, val) return (a ~= b and val == 0) or - (val > 0 and ((a - val) == b or (a + val) == b)) end, - ltv = function(a, b, val) return a < val end, - gtv = function(a, b, val) return a > val end, - eqv = function(a, b, val) return a == val end, - nev = function(a, b, val) return a ~= val end, - bne = bne, beq = beq } local function check_bcd(val) local a = val + 0x0666666666666666 @@ -232,9 +232,8 @@ function cheatfind.startplugin() local matches = {} local matchsel = 0 local menu_blocks = {} - local midx = { region = 1, init = 2, save = 4, comp = 5, lop = 6, op = 7, rop = 8, val = 9, - width = 11, bcd = 12, undo = 13, match = 15, watch = 0 } local watches = {} + local menu_func local function start() devtable = {} @@ -290,46 +289,208 @@ function cheatfind.startplugin() 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]) + local function incdec(event, val, min, max) + local ret + 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, ret + end + + + menu[#menu + 1] = function() + local m = { "CPU or RAM", devtable[devsel].tag, 0 } + if #devtable == 1 then + m[3] = 0 + else + menu_lim(devsel, 1, #devtable, m) + end + local function f(event) + if (event == "left" or event == "right") and #menu_blocks ~= 0 then + manager:machine():popmessage("Changes to this only take effect when \"Start new search\" is selected") + end + devsel = incdec(event, devsel, 1, #devtable) + return true + end + return m, f + end + + menu[#menu + 1] = function() + local function f(event) + local ret = false + 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") + watches = {} + leftop = 2 + rightop = 1 + matchsel = 0 + return true + end + devcur = devsel + end + return { "Start new search", "", 0 }, f end - menu[midx.init] = { "Start new search", "", 0 } if #menu_blocks ~= 0 then - menu[midx.init + 1] = { "---", "", "off" } - menu[midx.save] = { "Save current -- #" .. #menu_blocks[1] + 1, "", 0 } - menu[midx.comp] = { "Compare", "", 0 } - 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] = "Current" + menu[#menu + 1] = function() return { "---", "", "off" }, nil end + menu[#menu + 1] = function() + local function f(event) + 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") + leftop = (leftop == #menu_blocks[1]) and #menu_blocks[1] + 1 or leftop + rightop = (rightop == #menu_blocks[1] - 1) and #menu_blocks[1] or rightop + return true + end + end + return { "Save current -- #" .. #menu_blocks[1] + 1, "", 0 }, f end - menu[midx.op] = { "Operator", optable[opsel], "" } - menu_lim(opsel, 1, #optable, menu[midx.op]) - menu[midx.rop] = { "Right operand", rightop, "" } - menu_lim(rightop, 1, #menu_blocks[1], menu[midx.rop]) - menu[midx.val] = { "Value", value, "" } - menu_lim(value, 0, 100, menu[midx.val]) -- max value? - if value == 0 and optable[opsel]:sub(3, 3) ~= "v" then - menu[midx.val][2] = "Any" + menu[#menu + 1] = function() + function f(event) + if event == "select" then + local count = 0 + if #matches == 0 then + matches[1] = {} + for num = 1, #menu_blocks do + if leftop == #menu_blocks[1] + 1 then + matches[1][num] = cheat.compcur(menu_blocks[num][rightop], optable[opsel], + formtable[width], value, bcd == 1) + else + matches[1][num] = cheat.comp(menu_blocks[num][leftop], menu_blocks[num][rightop], + optable[opsel], formtable[width], value, bcd == 1) + end + count = count + #matches[1][num] + end + else + lastmatch = matches[#matches] + matches[#matches + 1] = {} + for num = 1, #menu_blocks do + if leftop == #menu_blocks[1] + 1 then + matches[#matches][num] = cheat.compcurnext(menu_blocks[num][rightop], lastmatch[num], + optable[opsel], formtable[width], value, bcd == 1) + else + matches[#matches][num] = cheat.compnext(menu_blocks[num][leftop], menu_blocks[num][rightop], + lastmatch[num], optable[opsel], formtable[width], value, bcd == 1) + end + count = count + #matches[#matches][num] + end + end + manager:machine():popmessage(count .. " total matches found") + return true + end + end + return { "Compare", "", 0 }, f end - menu[midx.val + 1] = { "---", "", "off" } - menu[midx.width] = { "Data Format", formname[width], 0 } - menu_lim(width, 1, #formtable, menu[midx.width]) - menu[midx.bcd] = { "BCD", "Off", 0 } - menu_lim(bcd, 0, 1, menu[midx.bcd]) - if bcd == 1 then - menu[midx.bcd][2] = "On" + menu[#menu + 1] = function() + local m = { "Left operand", leftop, "" } + menu_lim(leftop, 1, #menu_blocks[1] + 1, m) + if leftop == #menu_blocks[1] + 1 then + m[2] = "Current" + end + return m, function(event) local r leftop, r = incdec(event, leftop, 1, #menu_blocks[1] + 1) return r end + end + menu[#menu + 1] = function() + local m = { "Operator", optable[opsel], "" } + menu_lim(opsel, 1, #optable, m) + function f(event) + local r + opsel, r = incdec(event, opsel, 1, #optable) + if event == "left" or event == "right" or event == "comment" then + if optable[opsel] == "lt" then + manager:machine():popmessage("Left less than right, value is difference") + elseif optable[opsel] == "gt" then + manager:machine():popmessage("Left greater than right, value is difference") + elseif optable[opsel] == "eq" then + manager:machine():popmessage("Left equal to right") + elseif optable[opsel] == "ne" then + manager:machine():popmessage("Left not equal to right, value is difference") + elseif optable[opsel] == "beq" then + manager:machine():popmessage("Left equal to right with bitmask") + elseif optable[opsel] == "bne" then + manager:machine():popmessage("Left not equal to right with bitmask") + elseif optable[opsel] == "ltv" then + manager:machine():popmessage("Left less than value") + elseif optable[opsel] == "gtv" then + manager:machine():popmessage("Left greater than value") + elseif optable[opsel] == "eqv" then + manager:machine():popmessage("Left equal to value") + elseif optable[opsel] == "nev" then + manager:machine():popmessage("Left not equal to value") + end + end + return r + end + return m, f + end + menu[#menu + 1] = function() + if optable[opsel]:sub(3, 3) == "v" then + return nil + end + local m = { "Right operand", rightop, "" } + menu_lim(rightop, 1, #menu_blocks[1], m) + return m, function(event) local r rightop, r = incdec(event, rightop, 1, #menu_blocks[1]) return r end + end + menu[#menu + 1] = function() + if optable[opsel] == "bne" or optable[opsel] == "beq" or optable[opsel] == "eq" then + return nil + end + local m = { "Value", value, "" } + local max = 100 -- max value? + menu_lim(value, 0, max, m) + if value == 0 and optable[opsel]:sub(3, 3) ~= "v" then + m[2] = "Any" + end + return m, function(event) local r value, r = incdec(event, value, 0, max) return r end + end + menu[#menu + 1] = function() return { "---", "", "off" }, nil end + menu[#menu + 1] = function() + local m = { "Data Format", formname[width], 0 } + menu_lim(width, 1, #formtable, m) + return m, function(event) local r width, r = incdec(event, width, 1, #formtable) return r end + end + menu[#menu + 1] = function() + if optable[opsel] == "bne" or optable[opsel] == "beq" then + return nil + end + local m = { "BCD", "Off", 0 } + menu_lim(bcd, 0, 1, m) + if bcd == 1 then + m[2] = "On" + end + return m, function(event) local r bcd, r = incdec(bcd, 0, 1) return r end + end + menu[#menu + 1] = function() + if #matches == 0 then + return nil + end + function f(event) + if event == "select" and #matches > 0 then + matches[#matches] = nil + return true + end + end + return { "Undo last search -- #" .. #matches, "", 0 }, f end - menu[midx.undo] = { "Undo last search -- #" .. #matches, "", 0 } if #matches ~= 0 then - menu[midx.undo + 1] = { "---", "", "off" } - menu[midx.match] = { "Match block", matchsel, "" } - menu_lim(matchsel, 0, #matches[#matches], menu[midx.match]) - if matchsel == 0 then - menu[midx.match][2] = "All" + menu[#menu + 1] = function() return { "---", "", "off" }, nil end + menu[#menu + 1] = function() + local m = { "Match block", matchsel, "" } + menu_lim(matchsel, 0, #matches[#matches], m) + if matchsel == 0 then + m[2] = "All" + end + return m, function(event) local r matchsel, r = incdec(matchsel, 0, #matches[#matches]) return r end end local function mpairs(sel, list) if #list == 0 then @@ -357,250 +518,141 @@ function cheatfind.startplugin() end return mpairs_it, list, 0 end + local bitwidth = formtable[width]:sub(2, 2):lower() + local mstart = #menu + 1 + if bitwidth == "h" then + bitwidth = " %04x" + elseif bitwidth == "l" then + bitwidth = " %08x" + elseif bitwidth == "j" then + bitwidth = " %016x" + else + bitwidth = " %02x" + end for num2, match in mpairs(matchsel, matches[#matches]) do if #menu > 100 then break end - local numform = "" - local bitwidth = formtable[width]:sub(2, 2):lower() - if bitwidth == "h" then - numform = " %04x" - elseif bitwidth == "l" then - numform = " %08x" - elseif bitwidth == "j" then - numform = " %016x" - else - numform = " %02x" - end - menu[#menu + 1] = { string.format("%08x" .. numform .. numform, match.addr, match.oldval, + menu[#menu + 1] = function() + local m = { string.format("%08x" .. bitwidth .. bitwidth, match.addr, match.oldval, match.newval), "", 0 } - if not match.mode then - match.mode = 1 + if not match.mode then + match.mode = 1 + end + if match.mode == 1 then + m[2] = "Test" + elseif match.mode == 2 then + m[2] = "Write" + else + m[2] = "Watch" + end + menu_lim(match.mode, 1, 3, m) + function f(event) + local r + match.mode, r = incdec(event, match.mode, 1, 3) + if event == "select" then + local match + if matchsel == 0 then + local sel = index - mstart + for i = 1, #matches[#matches] do + if sel <= #matches[#matches][i] then + match = matches[#matches][i][sel] + break + else + sel = sel - #matches[#matches][i] + end + end + else + match = matches[#matches][matchsel][index - mstart] + end + local dev = devtable[devcur] + local cheat = { desc = string.format("Test cheat at addr %08X", match.addr), script = {} } + local wid = formtable[width]:sub(2, 2):lower() + local xmlcheat + local form + if wid == "h" then + wid = "u16" + form = "%08x %04x" + xmlcheat = "pw" + elseif wid == "l" then + wid = "u32" + form = "%08x %08x" + xmlcheat = "pd" + elseif wid == "j" then + wid = "u64" + form = "%08x %016x" + xmlcheat = "pq" + else + wid = "u8" + form = "%08x %02x" + xmlcheat = "pb" + end + xmlcheat = string.format("\n\n\n\n", cheat.desc, dev.tag:sub(2), xmlcheat, match.addr, match.newval) + + if dev.space.shortname then + cheat.ram = { ram = dev.tag } + cheat.script.on = "ram:write(" .. match.addr .. "," .. match.newval .. ")" + else + cheat.space = { cpu = { tag = dev.tag, type = "program" } } + cheat.script.run = "cpu:write_" .. wid .. "(" .. match.addr .. "," .. match.newval .. ")" + end + if match.mode == 1 then + if not _G.ce then + manager:machine():popmessage("Cheat engine not available") + else + _G.ce.inject(cheat) + end + elseif match.mode == 2 then + local filename = string.format("%s/%s_%08X_cheat", manager:machine():options().entries.cheatpath:value():match("([^;]+)"), emu.romname(), match.addr) + local json = require("json") + local file = io.open(filename .. ".json", "w") + if file then + file:write(json.stringify({[1] = cheat}, {indent = true})) + file:close() + file = io.open(filename .. ".xml", "w") + file:write(xmlcheat) + file:close() + manager:machine():popmessage("Cheat written to " .. filename) + else + manager:machine():popmessage("Unable to write file\nCheck cheatpath dir exists") + end + else + local func = "return space:read" + local env = { space = devtable[devcur].space } + if not dev.space.shortname then + func = func .. "_" .. wid + end + func = func .. "(" .. match.addr .. ")" + watches[#watches + 1] = { addr = match.addr, func = load(func, func, "t", env), format = form } + r = true + end + end + return r + end + return m, f end - if match.mode == 1 then - menu[#menu][2] = "Test" - elseif match.mode == 2 then - menu[#menu][2] = "Write" - else - menu[#menu][2] = "Watch" - end - menu_lim(match.mode, 1, 3, menu[#menu]) end end if #watches ~= 0 then - menu[#menu + 1] = { "Clear Watches", "", 0 } - midx.watch = #menu + menu[#menu + 1] = function() + return { "Clear Watches", "", 0 }, function(event) if event == "select" then watches = {} return true end end + end end end - return menu + local menu_list = {} + menu_func = {} + for num, func in ipairs(menu) do + local item, f = func() + if item then + menu_list[#menu_list + 1] = item + menu_func[#menu_list] = f + end + end + return menu_list 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 \"Start new search\" 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") - watches = {} - leftop = 2 - rightop = 1 - matchsel = 0 - ret = true - end - devcur = devsel - elseif index == midx.undo then - if event == "select" and #matches > 0 then - matches[#matches] = nil - end - ret = true - 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") - leftop = (leftop == #menu_blocks[1]) and #menu_blocks[1] + 1 or leftop - rightop = (rightop == #menu_blocks[1] - 1) and #menu_blocks[1] or rightop - ret = true - end - elseif index == midx.op then - opsel = incdec(opsel, 1, #optable) - if event == "left" or event == "right" or event == "comment" then - if optable[opsel] == "lt" then - manager:machine():popmessage("Left less than right, value is difference") - elseif optable[opsel] == "gt" then - manager:machine():popmessage("Left greater than right, value is difference") - elseif optable[opsel] == "eq" then - manager:machine():popmessage("Left equal to right, value is ignored") - elseif optable[opsel] == "ne" then - manager:machine():popmessage("Left not equal to right, value is difference") - elseif optable[opsel] == "beq" then - manager:machine():popmessage("Left equal to right with bitmask, value is ignored") - elseif optable[opsel] == "bne" then - manager:machine():popmessage("Left not equal to right with bitmask, value is ignored") - elseif optable[opsel] == "ltv" then - manager:machine():popmessage("Left less than value, right is ignored") - elseif optable[opsel] == "gtv" then - manager:machine():popmessage("Left greater than value, right is ignored") - elseif optable[opsel] == "eqv" then - manager:machine():popmessage("Left equal to value, right is ignored") - elseif optable[opsel] == "nev" then - manager:machine():popmessage("Left not equal to value, right is ignored") - end - end - ret = true - elseif index == midx.val then - value = incdec(value, 0, 100) - elseif index == midx.lop then - leftop = incdec(leftop, 1, #menu_blocks[1] + 1) - elseif index == midx.rop then - rightop = incdec(rightop, 1, #menu_blocks[1]) - elseif index == midx.width then - width = incdec(width, 1, #formtable) - elseif index == midx.bcd then - bcd = incdec(bcd, 0, 1) - elseif index == midx.comp then - if event == "select" then - local count = 0 - if #matches == 0 then - matches[1] = {} - for num = 1, #menu_blocks do - if leftop == #menu_blocks[1] + 1 then - matches[1][num] = cheat.compcur(menu_blocks[num][rightop], optable[opsel], - formtable[width], value, bcd == 1) - else - matches[1][num] = cheat.comp(menu_blocks[num][leftop], menu_blocks[num][rightop], - optable[opsel], formtable[width], value, bcd == 1) - end - count = count + #matches[1][num] - end - else - lastmatch = matches[#matches] - matches[#matches + 1] = {} - for num = 1, #menu_blocks do - if leftop == #menu_blocks[1] + 1 then - matches[#matches][num] = cheat.compcurnext(menu_blocks[num][rightop], lastmatch[num], - optable[opsel], formtable[width], value, bcd == 1) - else - matches[#matches][num] = cheat.compnext(menu_blocks[num][leftop], menu_blocks[num][rightop], - lastmatch[num], optable[opsel], formtable[width], value, bcd == 1) - end - count = count + #matches[#matches][num] - end - end - manager:machine():popmessage(count .. " total matches found") - ret = true - end - elseif index == midx.match then - matchsel = incdec(matchsel, 0, #matches[#matches]) - elseif index == midx.watch then - watches = {} - ret = true - elseif index > midx.match then - local match - if matchsel == 0 then - local sel = index - midx.match - for i = 1, #matches[#matches] do - if sel <= #matches[#matches][i] then - match = matches[#matches][i][sel] - break - else - sel = sel - #matches[#matches][i] - end - end - else - match = matches[#matches][matchsel][index - midx.match] - end - match.mode = incdec(match.mode, 1, 3) - if event == "select" then - local dev = devtable[devcur] - local cheat = { desc = string.format("Test cheat at addr %08X", match.addr), script = {} } - local wid = formtable[width]:sub(2, 2):lower() - local xmlcheat - local form - if wid == "h" then - wid = "u16" - form = "%08x %04x" - xmlcheat = "pw" - elseif wid == "l" then - wid = "u32" - form = "%08x %08x" - xmlcheat = "pd" - elseif wid == "j" then - wid = "u64" - form = "%08x %016x" - xmlcheat = "pq" - else - wid = "u8" - form = "%08x %02x" - xmlcheat = "pb" - end - xmlcheat = string.format("\n\n\n\n", cheat.desc, dev.tag:sub(2), xmlcheat, match.addr, match.newval) - - if dev.space.shortname then - cheat.ram = { ram = dev.tag } - cheat.script.on = "ram:write(" .. match.addr .. "," .. match.newval .. ")" - else - cheat.space = { cpu = { tag = dev.tag, type = "program" } } - cheat.script.run = "cpu:write_" .. wid .. "(" .. match.addr .. "," .. match.newval .. ")" - end - if match.mode == 1 then - if not _G.ce then - manager:machine():popmessage("Cheat engine not available") - else - _G.ce.inject(cheat) - end - elseif match.mode == 2 then - local filename = string.format("%s/%s_%08X_cheat", manager:machine():options().entries.cheatpath:value():match("([^;]+)"), emu.romname(), match.addr) - local json = require("json") - local file = io.open(filename .. ".json", "w") - if file then - file:write(json.stringify({[1] = cheat}, {indent = true})) - file:close() - file = io.open(filename .. ".xml", "w") - file:write(xmlcheat) - file:close() - manager:machine():popmessage("Cheat written to " .. filename) - else - manager:machine():popmessage("Unable to write file\nCheck cheatpath dir exists") - end - else - local func = "return space:read" - local env = { space = devtable[devcur].space } - if not dev.space.shortname then - func = func .. "_" .. wid - end - func = func .. "(" .. match.addr .. ")" - watches[#watches + 1] = { addr = match.addr, func = load(func, func, "t", env), format = form } - end - end - ret = true - end - devsel = devcur - return ret + return menu_func[index](event) end emu.register_menu(menu_callback, menu_populate, "Cheat Finder") emu.register_frame_done(function ()