plugins/cheatfind: make menu layout much more flexible (nw)

This commit is contained in:
cracyc 2016-05-02 10:09:42 -05:00
parent 75b1f0ca79
commit 216869186c

View File

@ -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("<mamecheat version=1>\n<cheat desc=\"%s\">\n<script state=\"run\">\n<action>%s.%s@%X=%X</action>\n</script>\n</cheat>\n</mamecheat>", 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("<mamecheat version=1>\n<cheat desc=\"%s\">\n<script state=\"run\">\n<action>%s.%s@%X=%X</action>\n</script>\n</cheat>\n</mamecheat>", 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 ()