mirror of
https://github.com/holub/mame
synced 2025-04-25 09:50:04 +03:00

Added methods for enabling and disabling breakpoints and watchpoints, and made debugger views update when breakpoints/watchpoints are manipulated from Lua. Made breakpoints and watchpoints objects rather than tables. (It’s not possible to enable/disable a breakpoint or watchpoint from the object itself, you have to go through its owners' debug interface.) Exposed more device_t members for dealing with child/sibling tags and devices. Also provided a way to get regions/shares/banks from a device using relative tags rather than going through the memory manager with absolute tags.
1068 lines
34 KiB
Lua
1068 lines
34 KiB
Lua
-- 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
|
|
|
|
-- return table of share regions
|
|
function cheat.getshares()
|
|
local shares = {}
|
|
for tag, share in pairs(manager:machine():memory().shares) do
|
|
shares[tag] = share
|
|
end
|
|
return shares
|
|
end
|
|
|
|
-- save data block
|
|
function cheat.save(space, start, size)
|
|
local data = { block = "", start = start, size = size, space = space, shift = 0 }
|
|
if getmetatable(space).__name:match("addr_space") then
|
|
data.shift = space.shift
|
|
end
|
|
if getmetatable(space).__name:match("device_t") 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 = ""
|
|
local temp = {}
|
|
local j = 1
|
|
if data.shift >= 0 then -- region or byte wide space
|
|
for i = start, start + (size << data.shift), 1 << data.shift do
|
|
if j < 65536 then
|
|
temp[j] = string.pack("B", space:read_u8(i))
|
|
j = j + 1
|
|
else
|
|
block = block .. table.concat(temp) .. string.pack("B", space:read_u8(i))
|
|
temp = {}
|
|
j = 1
|
|
end
|
|
end
|
|
elseif data.shift < 0 then
|
|
local s = -data.shift
|
|
local read = (s == 1) and space.read_u16 or (s == 2) and space.read_u32 or (s == 3) and space.read_u64 or space.read_u8
|
|
local pack = (s == 1) and "<I2" or (s == 2) and "<I4" or (s == 3) and "<I8" or "B"
|
|
for i = start, start + (size >> s) do
|
|
if j < 65536 then
|
|
temp[j] = string.pack(pack, read(space, i))
|
|
j = j + 1
|
|
else
|
|
block = block .. table.concat(temp) .. string.pack(pack, read(space, i))
|
|
temp = {}
|
|
j = 1
|
|
end
|
|
end
|
|
end
|
|
block = block .. table.concat(temp)
|
|
data.block = block
|
|
end
|
|
return data
|
|
end
|
|
|
|
-- compare two data blocks, format is as lua string.unpack, bne and beq val is table of masks, step is address increment value
|
|
function cheat.comp(newdata, olddata, oper, format, val, bcd, step)
|
|
local ret = {}
|
|
local ref = {} -- this is a helper for comparing two match lists
|
|
local bitmask = nil
|
|
if not step or step <= 0 then
|
|
step = 1
|
|
end
|
|
if (olddata.shift < 0) and (step < (1 << -olddata.shift)) then
|
|
step = 1 << -olddata.shift;
|
|
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 }
|
|
|
|
function cfoper.bne(a, b, val, addr)
|
|
if type(val) ~= "table" then
|
|
bitmask = a ~ b
|
|
return bitmask ~= 0
|
|
elseif not val[addr] then
|
|
return false
|
|
else
|
|
bitmask = (a ~ b) & val[addr]
|
|
return bitmask ~= 0
|
|
end
|
|
end
|
|
|
|
function cfoper.beq(a, b, val, addr)
|
|
if type(val) ~= "table" then
|
|
bitmask = ~a ~ b
|
|
return bitmask ~= 0
|
|
elseif not val[addr] then
|
|
return false
|
|
else
|
|
bitmask = (~a ~ b) & val[addr]
|
|
return bitmask ~= 0
|
|
end
|
|
end
|
|
|
|
local function check_bcd(val)
|
|
local a = val + 0x0666666666666666
|
|
a = a ~ val
|
|
return (a & 0x1111111111111110) == 0
|
|
end
|
|
|
|
local function frombcd(val)
|
|
local result = 0
|
|
local mul = 1
|
|
while val ~= 0 do
|
|
result = result + ((val % 16) * mul)
|
|
val = val >> 4
|
|
mul = mul * 10
|
|
end
|
|
return result
|
|
end
|
|
|
|
if not newdata and oper:sub(3, 3) == "v" then
|
|
newdata = olddata
|
|
end
|
|
if olddata.start ~= newdata.start or olddata.size ~= newdata.size or not cfoper[oper] then
|
|
return {}
|
|
end
|
|
if not val then
|
|
val = 0
|
|
end
|
|
|
|
for i = 1, olddata.size, step do
|
|
local oldstat, old = pcall(string.unpack, format, olddata.block, i)
|
|
local newstat, new = pcall(string.unpack, format, newdata.block, i)
|
|
if oldstat and newstat then
|
|
local oldc, newc = old, new
|
|
local comp = false
|
|
local addr = i - 1
|
|
if olddata.shift ~= 0 then
|
|
local s = olddata.shift
|
|
addr = (s < 0) and addr >> -s or (s > 0) and addr << s
|
|
end
|
|
addr = addr + olddata.start
|
|
if not bcd or (check_bcd(old) and check_bcd(new)) then
|
|
if bcd then
|
|
oldc = frombcd(old)
|
|
newc = frombcd(new)
|
|
end
|
|
if cfoper[oper](newc, oldc, val, addr) then
|
|
ret[#ret + 1] = { addr = addr,
|
|
oldval = old,
|
|
newval = new,
|
|
bitmask = bitmask }
|
|
ref[addr] = #ret
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return ret, ref
|
|
end
|
|
|
|
local function check_val(oper, val, matches)
|
|
if oper ~= "beq" and oper ~= "bne" then
|
|
return val
|
|
elseif not matches or not matches[1].bitmask then
|
|
return nil
|
|
end
|
|
local masks = {}
|
|
for num, match in pairs(matches) do
|
|
masks[match.addr] = match.bitmask
|
|
end
|
|
return masks
|
|
end
|
|
|
|
-- compare two blocks and filter by table of previous matches
|
|
function cheat.compnext(newdata, olddata, oldmatch, oper, format, val, bcd, step)
|
|
local matches, refs = cheat.comp(newdata, olddata, oper, format, check_val(oper, val, oldmatch), bcd, step)
|
|
local nonmatch = {}
|
|
local oldrefs = {}
|
|
for num, match in pairs(oldmatch) do
|
|
oldrefs[match.addr] = num
|
|
end
|
|
for addr, ref in pairs(refs) do
|
|
if not oldrefs[addr] then
|
|
nonmatch[ref] = true
|
|
refs[addr] = nil
|
|
else
|
|
matches[ref].oldval = oldmatch[oldrefs[addr]].oldval
|
|
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, format, val, bcd, step)
|
|
local newdata = cheat.save(olddata.space, olddata.start, olddata.size, olddata.space)
|
|
return cheat.comp(newdata, olddata, oper, format, val, bcd, step)
|
|
end
|
|
|
|
-- compare a data block to the current state and filter
|
|
function cheat.compcurnext(olddata, oldmatch, oper, format, val, bcd, step)
|
|
local newdata = cheat.save(olddata.space, olddata.start, olddata.size, olddata.space)
|
|
return cheat.compnext(newdata, olddata, oldmatch, oper, format, val, bcd, step)
|
|
end
|
|
|
|
_G.emu.plugin.cheatfind = cheat
|
|
local devtable = {}
|
|
local devsel = 1
|
|
local devcur = 1
|
|
local formname = { "u8", "big u16", "big u32", "big u64", "little u16", "little u32",
|
|
"little u64", "s8", "big s16", "big s32", "big s64", "little s16", "little s32", "little s64", }
|
|
local formtable = { " I1", ">I2", ">I4", ">I8", "<I2", "<I4", "<I8", " i1", ">i2", ">i4", ">i8", "<i2", "<i4", "<i8", }
|
|
-- " <f", " >f", " <d", " >d" }
|
|
local width = 1
|
|
local bcd = 0
|
|
local align = 0
|
|
local optable = { "lt", "gt", "eq", "ne", "beq", "bne", "ltv", "gtv", "eqv", "nev" }
|
|
local opsel = 1
|
|
local value = 0
|
|
local leftop = 1
|
|
local rightop = 1
|
|
local value_text = ""
|
|
local pausesel = 1
|
|
local pokevalsel = 1
|
|
local matches = {}
|
|
local matchsel = 0
|
|
local matchpg = 0
|
|
local menu_blocks = {}
|
|
local watches = {}
|
|
local menu_func
|
|
local cheat_save
|
|
local name = 1
|
|
local name_player = 1
|
|
local name_type = 1
|
|
local name_other = ""
|
|
|
|
local function start()
|
|
devtable = {}
|
|
devsel = 1
|
|
devcur = 1
|
|
width = 1
|
|
bcd = 0
|
|
opsel = 1
|
|
value = 0
|
|
leftop = 1
|
|
rightop = 1
|
|
matches = {}
|
|
matchsel = 0
|
|
matchpg = 0
|
|
menu_blocks = {}
|
|
watches = {}
|
|
|
|
local space_table = cheat.getspaces()
|
|
for tag, list in pairs(space_table) do
|
|
for name, space in pairs(list) do
|
|
local ram = {}
|
|
for num, entry in pairs(space.map) do
|
|
if entry.writetype == "ram" then
|
|
ram[#ram + 1] = { offset = entry.offset, size = entry.endoff - entry.offset }
|
|
if space.shift > 0 then
|
|
ram[#ram].size = ram[#ram].size >> space.shift
|
|
elseif space.shift < 0 then
|
|
ram[#ram].size = ram[#ram].size << -space.shift
|
|
end
|
|
end
|
|
end
|
|
if next(ram) then
|
|
if tag == ":maincpu" and name == "program" then
|
|
table.insert(devtable, 1, { name = tag .. ", " .. name, tag = tag, sname = name, space = space, ram = ram })
|
|
else
|
|
devtable[#devtable + 1] = { name = tag .. ", " .. name, tag = tag, sname = name, space = space, ram = ram }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
space_table = cheat.getram()
|
|
for tag, ram in pairs(space_table) do
|
|
devtable[#devtable + 1] = { tag = tag, name = "ram", space = ram.dev, ram = {{ offset = 0, size = ram.size }} }
|
|
end
|
|
space_table = cheat.getshares()
|
|
for tag, share in pairs(space_table) do
|
|
devtable[#devtable + 1] = { tag = tag, name = tag, space = share, ram = {{ offset = 0, size = share.size }} }
|
|
end
|
|
end
|
|
|
|
emu.register_start(start)
|
|
|
|
local menu_is_showing = false
|
|
local tabbed_out = false
|
|
|
|
local function menu_populate()
|
|
if pausesel == 1 then
|
|
emu.pause()
|
|
menu_is_showing = true
|
|
end
|
|
local menu = {}
|
|
|
|
local function menu_prepare()
|
|
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_lim(val, min, max, menuitem)
|
|
if min == max then
|
|
menuitem[3] = 0
|
|
elseif val == min then
|
|
menuitem[3] = "r"
|
|
elseif val == max then
|
|
menuitem[3] = "l"
|
|
else
|
|
menuitem[3] = "lr"
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
if cheat_save then
|
|
local cplayer = { "All", "P1", "P2", "P3", "P4" }
|
|
local ctype = { "Infinite Credits", "Infinite Time", "Infinite Lives", "Infinite Energy", "Infinite Ammo", "Infinite Bombs", "Invincibility", _("Other: ") }
|
|
menu[#menu + 1] = function() return { _("Save Cheat"), "", "off" }, nil end
|
|
menu[#menu + 1] = function() return { "---", "", "off" }, nil end
|
|
menu[#menu + 1] = function()
|
|
local c = { _("Default"), _("Custom") }
|
|
local m = { _("Cheat Name"), c[name], 0 }
|
|
menu_lim(name, 1, #c, m)
|
|
local function f(event)
|
|
local r
|
|
name, r = incdec(event, name, 1, #c)
|
|
if (event == "select" or event == "comment") and name == 1 then
|
|
manager:machine():popmessage(string.format(_("Default name is %s"), cheat_save.name))
|
|
end
|
|
return r
|
|
end
|
|
return m, f
|
|
end
|
|
if name == 2 then
|
|
menu[#menu + 1] = function()
|
|
local m = { _("Player"), cplayer[name_player], 0 }
|
|
menu_lim(name_player, 1, #cplayer, m)
|
|
return m, function(event) local r name_player, r = incdec(event, name_player, 1, #cplayer) return r end
|
|
end
|
|
menu[#menu + 1] = function()
|
|
local m = { _("Type"), ctype[name_type] .. (name_type == #ctype and (#name_other ~= 0 and name_other or _("(empty)")) or ""), 0 }
|
|
menu_lim(name_type, 1, #ctype, m)
|
|
local function f(event)
|
|
local r
|
|
name_type, r = incdec(event, name_type, 1, #ctype)
|
|
if name_type == #ctype then
|
|
local char = tonumber(event)
|
|
if char then
|
|
if #name_other > 0 and (char == 8 or char == 0x7f) then
|
|
name_other = name_other:sub(1, utf8.offset(name_other, -1) - 1)
|
|
r = true
|
|
elseif char > 0x1f and (char & ~0x7f) ~= 0x80 and (char & ~0xf) ~= 0xfdd0 and (char & ~0xfffe) ~= 0xfffe then
|
|
name_other = name_other .. utf8.char(char)
|
|
r = true
|
|
end
|
|
elseif event == "select" or event == "comment" or event == "right" then
|
|
manager:machine():popmessage(_("You can enter any type name"))
|
|
end
|
|
end
|
|
return r
|
|
end
|
|
return m, f
|
|
end
|
|
end
|
|
menu[#menu + 1] = function()
|
|
local m = { _("Save"), "", 0 }
|
|
local function f(event)
|
|
if event == "select" then
|
|
local desc
|
|
local written = false
|
|
if name == 2 then
|
|
desc = name_type ~= #ctype and ctype[name_type] or name_other
|
|
if #desc == 0 then
|
|
manager:machine():popmessage(_("Type name is empty"))
|
|
return
|
|
end
|
|
if cplayer[name_player] ~= "All" then
|
|
desc = cplayer[name_player] .. " " .. desc
|
|
end
|
|
else
|
|
desc = cheat_save.name
|
|
end
|
|
local filename = cheat_save.filename .. "_" .. desc
|
|
local file = io.open(filename .. ".json", "w")
|
|
if file then
|
|
file:write(string.format(cheat_save.json, desc))
|
|
file:close()
|
|
-- xml or simple are program space only
|
|
if not getmetatable(devtable[devcur].space).__name:match("device_t") and devtable[devcur].sname == "program" then
|
|
file = io.open(filename .. ".xml", "w")
|
|
file:write(string.format(cheat_save.xml, desc))
|
|
file:close()
|
|
file = io.open(cheat_save.path .. "/cheat.simple", "a")
|
|
file:write(string.format(cheat_save.simple, desc))
|
|
-- old cheat .dat format, write support only (for cheat forum posting of new cheats if posted in simple format)
|
|
file:write(string.format(cheat_save.dat, desc))
|
|
file:close()
|
|
manager:machine():popmessage(string.format(_("Cheat written to %s and added to cheat.simple"), filename))
|
|
end
|
|
written = true
|
|
elseif not getmetatable(devtable[devcur].space).__name:match("device_t") and devtable[devcur].sname == "program" then
|
|
file = io.open(cheat_save.path .. "/cheat.simple", "a")
|
|
if file then
|
|
file:write(string.format(cheat_save.simple, desc))
|
|
-- old cheat .dat format, write support only (for cheat forum posting of new cheats if posted in simple format)
|
|
file:write(string.format(cheat_save.dat, desc))
|
|
file:close()
|
|
manager:machine():popmessage(_("Cheat added to cheat.simple"))
|
|
written = true
|
|
end
|
|
end
|
|
if not written then
|
|
manager:machine():popmessage(_("Unable to write file\nEnsure that cheatpath folder exists"))
|
|
end
|
|
cheat_save = nil
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
return m, f
|
|
end
|
|
menu[#menu + 1] = function() return { _("Cancel"), "", 0 }, function(event) if event == "select" then cheat_save = nil return true end end end
|
|
return menu_prepare()
|
|
end
|
|
|
|
menu[#menu + 1] = function()
|
|
local m = { _("CPU or RAM"), devtable[devsel].name, 0 }
|
|
menu_lim(devsel, 1, #devtable, m)
|
|
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 pausetable = { _("Automatic"), _("Manual") }
|
|
local m = { _("Pause Mode"), pausetable[pausesel], 0 }
|
|
menu_lim(pausesel, 1, pausetable, m)
|
|
local function f(event)
|
|
if (event == "left" or event == "right") then
|
|
if pausesel == 1 then
|
|
pausesel = 2
|
|
menu_is_showing = false
|
|
manager:machine():popmessage(_("Manually toggle pause when needed"))
|
|
else
|
|
pausesel = 1
|
|
manager:machine():popmessage(_("Automatically toggle pause with on-screen menus"))
|
|
emu.pause()
|
|
end
|
|
end
|
|
|
|
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 = {}
|
|
devcur = devsel
|
|
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(_("All slots cleared and current state saved to Slot 1"))
|
|
watches = {}
|
|
opsel = 1
|
|
value = 0
|
|
leftop = 1
|
|
rightop = 1
|
|
value_text = ""
|
|
matchsel = 0
|
|
return true
|
|
end
|
|
end
|
|
local opsel = 1
|
|
return { _("Start new search"), "", 0 }, f
|
|
end
|
|
|
|
if #menu_blocks ~= 0 then
|
|
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(string.format(_("Memory state saved to Slot %d"), #menu_blocks[1]))
|
|
if (leftop == #menu_blocks[1] - 1 and rightop == #menu_blocks[1] - 2 ) then
|
|
leftop = #menu_blocks[1]
|
|
rightop = #menu_blocks[1]-1
|
|
elseif (leftop == #menu_blocks[1] - 2 and rightop == #menu_blocks[1] - 1 ) then
|
|
leftop = #menu_blocks[1]-1
|
|
rightop = #menu_blocks[1]
|
|
elseif (leftop == #menu_blocks[1] - 1 ) then
|
|
leftop = #menu_blocks[1]
|
|
elseif (rightop == #menu_blocks[1] - 1) then
|
|
rightop = #menu_blocks[1]
|
|
end
|
|
devsel = devcur
|
|
return true
|
|
end
|
|
end
|
|
return { string.format(_("Save current memory state to Slot %d"), #menu_blocks[1] + 1), "", 0 }, f
|
|
end
|
|
menu[#menu + 1] = function() return { "---", "", "off" }, nil end
|
|
menu[#menu + 1] = function()
|
|
local function f(event)
|
|
if event == "select" then
|
|
local count = 0
|
|
local step = align == 1 and formtable[width]:sub(3, 3) or "1"
|
|
if step == "f" then
|
|
step = 4
|
|
elseif step == "d" then
|
|
step = 8
|
|
else
|
|
step = tonumber(step)
|
|
end
|
|
if #matches == 0 then
|
|
matches[1] = {}
|
|
for num = 1, #menu_blocks do
|
|
matches[1][num] = cheat.comp(menu_blocks[num][leftop], menu_blocks[num][rightop],
|
|
optable[opsel], formtable[width], value, bcd == 1, step)
|
|
count = count + #matches[1][num]
|
|
end
|
|
else
|
|
lastmatch = matches[#matches]
|
|
matches[#matches + 1] = {}
|
|
for num = 1, #menu_blocks do
|
|
matches[#matches][num] = cheat.compnext(menu_blocks[num][leftop], menu_blocks[num][rightop],
|
|
lastmatch[num], optable[opsel], formtable[width], value, bcd == 1, step)
|
|
count = count + #matches[#matches][num]
|
|
end
|
|
end
|
|
manager:machine():popmessage(string.format(_("%d total matches found"), count))
|
|
matches[#matches].count = count
|
|
matchpg = 0
|
|
devsel = devcur
|
|
return true
|
|
end
|
|
end
|
|
|
|
local slot_slot_comp = _("Perform Compare : Slot %d %s Slot %d")
|
|
local slot_slot_val_comp = _("Perform Compare : Slot %d %s Slot %d %s %d")
|
|
local slot_slot_bit_comp = _("Perform Compare : Slot %d BITWISE%s Slot %d")
|
|
local slot_val_comp = _("Perform Compare : Slot %d %s %d")
|
|
local expression_text
|
|
if optable[opsel] == "lt" then
|
|
if (value == 0 ) then
|
|
expression_text = string.format(slot_slot_comp, leftop, "<", rightop)
|
|
else
|
|
expression_text = string.format(slot_slot_val_comp, leftop, "==", rightop, "-", value)
|
|
end
|
|
elseif optable[opsel] == "gt" then
|
|
if (value == 0 ) then
|
|
expression_text = string.format(slot_slot_comp, leftop, ">", rightop)
|
|
else
|
|
expression_text = string.format(slot_slot_val_comp, leftop, "==", rightop, "+", value)
|
|
end
|
|
elseif optable[opsel] == "eq" then
|
|
expression_text = string.format(slot_slot_comp, leftop, "==", rightop)
|
|
elseif optable[opsel] == "ne" then
|
|
if (value == 0 ) then
|
|
expression_text = string.format(slot_slot_comp, leftop, "!=", rightop)
|
|
else
|
|
expression_text = string.format(slot_slot_val_comp, leftop, "==", rightop, "+/-", value)
|
|
end
|
|
elseif optable[opsel] == "beq" then
|
|
expression_text = string.format(slot_slot_bit_comp, leftop, "==", rightop)
|
|
elseif optable[opsel] == "bne" then
|
|
expression_text = string.format(slot_slot_bit_comp, leftop, "!=", rightop)
|
|
elseif optable[opsel] == "ltv" then
|
|
expression_text = string.format(slot_val_comp, leftop, "<", value)
|
|
elseif optable[opsel] == "gtv" then
|
|
expression_text = string.format(slot_val_comp, leftop, ">", value)
|
|
elseif optable[opsel] == "eqv" then
|
|
expression_text = string.format(slot_val_comp, leftop, "==", value)
|
|
elseif optable[opsel] == "nev" then
|
|
expression_text = string.format(slot_val_comp, leftop, "!=", value)
|
|
end
|
|
return { expression_text, "", 0 }, f
|
|
end
|
|
menu[#menu + 1] = function() return { "---", "", "off" }, nil end
|
|
menu[#menu + 1] = function()
|
|
local m = { _(leftop), "", 0 }
|
|
menu_lim(leftop, 1, #menu_blocks[1], m)
|
|
m[1] = string.format(_("Slot %d"), leftop)
|
|
return m, function(event) local r leftop, r = incdec(event, leftop, 1, #menu_blocks[1]) return r end
|
|
end
|
|
menu[#menu + 1] = function()
|
|
local m = { _(optable[opsel]), "", 0 }
|
|
menu_lim(opsel, 1, #optable, m)
|
|
local 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"))
|
|
elseif optable[opsel] == "gt" then
|
|
manager:machine():popmessage(_("Left greater than right"))
|
|
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"))
|
|
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 = { _(rightop), "", 0 }
|
|
menu_lim(rightop, 1, #menu_blocks[1], m)
|
|
m[1] = string.format(_("Slot %d"), rightop)
|
|
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
|
|
if optable[opsel] == "ltv" or optable[opsel] == "gtv" or optable[opsel] == "eqv" or optable[opsel] == "nev" then
|
|
m = { _("Value"), value, "" }
|
|
else
|
|
m = { _("Difference"), value, "" }
|
|
end
|
|
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()
|
|
local pokevaltable = { _("Slot 1 Value"), _("Last Slot Value"), "0x00", "0x01", "0x02", "0x03", "0x04", "0x05", "0x06",
|
|
"0x07", "0x08", "0x09", "0x63 (99)", "0x99", "0xFF (255)" , "0x3E7 (999)", "0x999", "0x270F (9999)",
|
|
"0x9999", "0xFFFF (65535)" }
|
|
local m = { _("Test/Write Poke Value"), pokevaltable[pokevalsel], 0 }
|
|
menu_lim(pokevalsel, 1, #pokevaltable, m)
|
|
local function f(event)
|
|
local r
|
|
pokevalsel, r = incdec(event, pokevalsel, 1, #pokevaltable)
|
|
if event == "left" or event == "right" or event == "comment" then
|
|
if pokevalsel == 1 then
|
|
manager:machine():popmessage(_("Use this if you want to poke the Slot 1 value (eg. You started with something but lost it)"))
|
|
elseif pokevalsel == 2 then
|
|
manager:machine():popmessage(_("Use this if you want to poke the Last Slot value (eg. You started without an item but finally got it)"))
|
|
else
|
|
manager:machine():popmessage(string.format(_("Use this if you want to poke %s"), pokevaltable[pokevalsel]))
|
|
end
|
|
end
|
|
return r
|
|
end
|
|
return m, f
|
|
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(event, bcd, 0, 1) return r end
|
|
end
|
|
menu[#menu + 1] = function()
|
|
if formtable[width]:sub(3, 3) == "1" then
|
|
return nil
|
|
end
|
|
local m = { _("Aligned only"), _("Off"), 0 }
|
|
menu_lim(align, 0, 1, m)
|
|
if align == 1 then
|
|
m[2] = _("On")
|
|
end
|
|
return m, function(event) local r align, r = incdec(event, align, 0, 1) return r end
|
|
end
|
|
if #matches ~= 0 then
|
|
menu[#menu + 1] = function()
|
|
local function f(event)
|
|
if event == "select" then
|
|
matches[#matches] = nil
|
|
matchpg = 0
|
|
return true
|
|
end
|
|
end
|
|
return { _("Undo last search -- #") .. #matches, "", 0 }, f
|
|
end
|
|
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
|
|
local function f(event)
|
|
local r
|
|
matchsel, r = incdec(event, matchsel, 0, #matches[#matches])
|
|
if r then
|
|
matchpg = 0
|
|
end
|
|
return r
|
|
end
|
|
return m, f
|
|
end
|
|
local function mpairs(sel, list, start)
|
|
if #list == 0 then
|
|
return function() end, nil, nil
|
|
end
|
|
if sel ~= 0 then
|
|
list = {list[sel]}
|
|
end
|
|
local function mpairs_it(list, i)
|
|
local match
|
|
i = i + 1
|
|
local sel = i + start
|
|
for j = 1, #list do
|
|
if sel <= #list[j] then
|
|
match = list[j][sel]
|
|
break
|
|
else
|
|
sel = sel - #list[j]
|
|
end
|
|
end
|
|
if not match then
|
|
return
|
|
end
|
|
return i, match
|
|
end
|
|
return mpairs_it, list, 0
|
|
end
|
|
local bitwidth = formtable[width]:sub(3, 3)
|
|
if bitwidth == "2" then
|
|
bitwidth = " %04x"
|
|
elseif bitwidth == "4" then
|
|
bitwidth = " %08x"
|
|
elseif bitwidth == "8" then
|
|
bitwidth = " %016x"
|
|
elseif bitwidth == "f" or bitwidth == "d" then
|
|
bitwidth = " %010f"
|
|
else
|
|
bitwidth = " %02x"
|
|
end
|
|
|
|
local function match_exec(match)
|
|
local dev = devtable[devcur]
|
|
local wid = formtable[width]:sub(3, 3)
|
|
local widchar
|
|
local pokevalue
|
|
local form
|
|
if pokevalsel == 1 then
|
|
pokevalue = match.oldval
|
|
elseif pokevalsel == 2 then
|
|
pokevalue = match.newval
|
|
elseif pokevalsel == 3 then
|
|
pokevalue = 0
|
|
elseif pokevalsel == 4 then
|
|
pokevalue = 1
|
|
elseif pokevalsel == 5 then
|
|
pokevalue = 2
|
|
elseif pokevalsel == 6 then
|
|
pokevalue = 3
|
|
elseif pokevalsel == 7 then
|
|
pokevalue = 4
|
|
elseif pokevalsel == 8 then
|
|
pokevalue = 5
|
|
elseif pokevalsel == 9 then
|
|
pokevalue = 6
|
|
elseif pokevalsel == 10 then
|
|
pokevalue = 7
|
|
elseif pokevalsel == 11 then
|
|
pokevalue = 8
|
|
elseif pokevalsel == 12 then
|
|
pokevalue = 9
|
|
elseif pokevalsel == 13 then
|
|
pokevalue = 99
|
|
elseif pokevalsel == 14 then
|
|
pokevalue = 153
|
|
elseif pokevalsel == 15 then
|
|
pokevalue = 255
|
|
elseif pokevalsel == 16 and wid == "1" then
|
|
pokevalue = 99
|
|
elseif pokevalsel == 17 and wid == "1" then
|
|
pokevalue = 153
|
|
elseif pokevalsel == 18 and wid == "1" then
|
|
pokevalue = 99
|
|
elseif pokevalsel == 19 and wid == "1" then
|
|
pokevalue = 153
|
|
elseif pokevalsel == 20 and wid == "1" then
|
|
pokevalue = 255
|
|
elseif pokevalsel == 16 then
|
|
pokevalue = 999
|
|
elseif pokevalsel == 17 then
|
|
pokevalue = 2457
|
|
elseif pokevalsel == 18 then
|
|
pokevalue = 9999
|
|
elseif pokevalsel == 19 then
|
|
pokevalue = 39321
|
|
elseif pokevalsel == 20 then
|
|
pokevalue = 65535
|
|
end
|
|
|
|
local cheat = { desc = string.format(_("Test Cheat %08X:%02X"), match.addr, pokevalue), script = {} }
|
|
|
|
if wid == "2" then
|
|
wid = "u16"
|
|
form = "%08x %04x"
|
|
widchar = "w"
|
|
elseif wid == "4" then
|
|
wid = "u32"
|
|
form = "%08x %08x"
|
|
widchar = "d"
|
|
elseif wid == "8" then
|
|
wid = "u64"
|
|
form = "%08x %016x"
|
|
widchar = "q"
|
|
elseif wid == "f" then
|
|
wid = "u32"
|
|
form = "%08x %f"
|
|
widchar = "d"
|
|
elseif wid == "d" then
|
|
wid = "u64"
|
|
form = "%08x %f"
|
|
widchar = "q"
|
|
else
|
|
wid = "u8"
|
|
form = "%08x %02x"
|
|
widchar = "b"
|
|
end
|
|
|
|
if getmetatable(dev.space).__name:match("device_t") then
|
|
cheat.ram = { ram = dev.tag }
|
|
cheat.script.run = "ram:write(" .. match.addr .. "," .. pokevalue .. ")"
|
|
elseif getmetatable(dev.space).__name:match("memory_share") then
|
|
cheat.share = { share = dev.tag }
|
|
cheat.script.run = "share:write_" .. wid .. "(" .. match.addr .. "," .. pokevalue .. ")"
|
|
else
|
|
cheat.space = { cpu = { tag = dev.tag, type = dev.sname } }
|
|
cheat.script.run = "cpu:write_" .. wid .. "(" .. match.addr .. "," .. pokevalue .. ")"
|
|
end
|
|
if match.mode == 1 then
|
|
if not emu.plugin.cheat then
|
|
manager:machine():popmessage(_("Cheat engine not available"))
|
|
else
|
|
emu.plugin.cheat.inject(cheat)
|
|
end
|
|
elseif match.mode == 2 then
|
|
cheat_save = {}
|
|
menu = 1
|
|
menu_player = 1
|
|
menu_type = 1
|
|
local setname = emu.romname()
|
|
if emu.softname() ~= "" then
|
|
if emu.softname():find(":") then
|
|
setname = emu.softname():gsub(":", "/")
|
|
else
|
|
for name, image in pairs(manager:machine().images) do
|
|
if image:exists() and image:software_list_name() ~= "" then
|
|
setname = image:software_list_name() .. "/" .. emu.softname()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- lfs.env_replace is defined in boot.lua
|
|
cheat_save.path = lfs.env_replace(manager:machine():options().entries.cheatpath:value()):match("([^;]+)")
|
|
cheat_save.filename = string.format("%s/%s", cheat_save.path, setname)
|
|
cheat_save.name = cheat.desc
|
|
local json = require("json")
|
|
cheat.desc = "%s"
|
|
cheat_save.json = json.stringify({[1] = cheat}, {indent = true})
|
|
cheat_save.xml = string.format("<mamecheat version=\"1\">\n <cheat desc=\"%%s\">\n <script state=\"run\">\n <action>%s.pp%s@%X=%X</action>\n </script>\n </cheat>\n</mamecheat>", dev.tag:sub(2), widchar, match.addr, match.newval)
|
|
cheat_save.simple = string.format("%s,%s,%X,%s,%X,%%s\n", setname, dev.tag, match.addr, widchar, pokevalue)
|
|
cheat_save.dat = string.format(":%s:40000000:%X:%08X:FFFFFFFF:%%s\n", setname, match.addr, pokevalue)
|
|
manager:machine():popmessage(string.format(_("Default name is %s"), cheat_save.name))
|
|
return true
|
|
else
|
|
local func = "return space:read"
|
|
local env = { space = devtable[devcur].space }
|
|
if not getmetatable(dev.space).__name:match("device_t") then
|
|
func = func .. "_" .. wid
|
|
end
|
|
func = func .. "(" .. match.addr .. ")"
|
|
watches[#watches + 1] = { addr = match.addr, func = load(func, func, "t", env), format = form }
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
for num2, match in mpairs(matchsel, matches[#matches], matchpg * 100) do
|
|
if num2 > 100 then
|
|
break
|
|
end
|
|
menu[#menu + 1] = function()
|
|
if not match.mode then
|
|
match.mode = 1
|
|
end
|
|
local modes = { _("Test"), _("Write"), _("Watch") }
|
|
local m = { string.format("%08x" .. bitwidth .. bitwidth, match.addr, match.oldval,
|
|
match.newval), modes[match.mode], 0 }
|
|
menu_lim(match.mode, 1, #modes, m)
|
|
local function f(event)
|
|
local r
|
|
match.mode, r = incdec(event, match.mode, 1, 3)
|
|
if event == "select" then
|
|
r = match_exec(match)
|
|
end
|
|
return r
|
|
end
|
|
return m, f
|
|
end
|
|
end
|
|
if matches[#matches].count > 100 then
|
|
menu[#menu + 1] = function()
|
|
local m = { _("Page"), matchpg, 0 }
|
|
local max
|
|
if matchsel == 0 then
|
|
max = math.ceil(matches[#matches].count / 100) - 1
|
|
else
|
|
max = #matches[#matches][matchsel]
|
|
end
|
|
menu_lim(matchpg, 0, max, m)
|
|
local function f(event)
|
|
matchpg, r = incdec(event, matchpg, 0, max)
|
|
return r
|
|
end
|
|
return m, f
|
|
end
|
|
end
|
|
end
|
|
if #watches ~= 0 then
|
|
menu[#menu + 1] = function()
|
|
return { _("Clear Watches"), "", 0 }, function(event) if event == "select" then watches = {} return true end end
|
|
end
|
|
end
|
|
end
|
|
return menu_prepare()
|
|
end
|
|
|
|
local function menu_callback(index, event)
|
|
if event == "cancel" and pausesel == 1 then
|
|
emu.unpause()
|
|
menu_is_showing = false
|
|
return {0,0,0}
|
|
end
|
|
return menu_func[index](event)
|
|
end
|
|
emu.register_menu(menu_callback, menu_populate, _("Cheat Finder"))
|
|
emu.register_frame_done(function ()
|
|
local tag, screen = next(manager:machine().screens)
|
|
local height = mame_manager:ui():get_line_height()
|
|
for num, watch in ipairs(watches) do
|
|
screen:draw_text("left", num * height, string.format(watch.format, watch.addr, watch.func()))
|
|
end
|
|
if tabbed_out and manager:ui():is_menu_active() then
|
|
emu.pause()
|
|
menu_is_showing = true
|
|
tabbed_out = false
|
|
end
|
|
end)
|
|
emu.register_periodic(function ()
|
|
if menu_is_showing and not manager:ui():is_menu_active() then
|
|
emu.unpause()
|
|
menu_is_showing = false
|
|
tabbed_out = true
|
|
end
|
|
end)
|
|
end
|
|
|
|
return exports
|