mirror of
https://github.com/mamedev/mame.git
synced 2024-11-16 07:48:32 +01:00
e6588480c4
Made auto-boot script errors and plugin bootstrap errors fatal. Run auto-boot scripts in a sandbox. Globals can be accessed, but not set. The sandbox is cleared on hard reset, but not on soft reset. Added (hopefully) useful to string metafunctions to device_t and address space that show short names and tags. Fixed issues in plugins that surface when strict type checking is enabled, as this means numbers and nil are not automatically converted to strings. Plugins should be tested with debug builds to check for this. Made save item read_block raise an error on invalid arguments rather than returning an empty string, and made it use luaL_buffer directly rather than using the helper wrapper. Changed some more function bindings to use set_function to avoid issues related to ThePhD/sol2#608, and got rid of some unnecessary lambda captures.
1077 lines
34 KiB
Lua
1077 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 = "BSD-3-Clause"
|
|
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.entries) do
|
|
if entry.write.handlertype == "ram" then
|
|
ram[#ram + 1] = {
|
|
offset = entry.address_start & space.address_mask,
|
|
size = (entry.address_end & space.address_mask) - (entry.address_start & space.address_mask) }
|
|
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
|
|
table.insert(menu_list, 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] = "on"
|
|
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 = false
|
|
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], "on" }
|
|
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], "on" }
|
|
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 ""), "on" }
|
|
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"), "", "on" }
|
|
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"), "", "on" }, 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, "on" }
|
|
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
|
|
local r
|
|
devsel, r = incdec(event, devsel, 1, #devtable)
|
|
return r
|
|
end
|
|
return m, f
|
|
end
|
|
|
|
menu[#menu + 1] = function()
|
|
local pausetable = { _("Automatic"), _("Manual") }
|
|
local m = { _("Pause Mode"), pausetable[pausesel], "on" }
|
|
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
|
|
return true
|
|
end
|
|
return false
|
|
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"), "", "on" }, 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
|
|
return false
|
|
end
|
|
return { string.format(_("Save current memory state to Slot %d"), #menu_blocks[1] + 1), "", "on" }, 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
|
|
return false
|
|
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, "", "on" }, f
|
|
end
|
|
menu[#menu + 1] = function() return { "---", "", "off" }, nil end
|
|
menu[#menu + 1] = function()
|
|
local m = { string.format("%d", leftop), "", "on" }
|
|
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]), "", "on" }
|
|
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 = { string.format("%d", rightop), "", "on" }
|
|
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"), tostring(value), "" }
|
|
else
|
|
m = { _("Difference"), tostring(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], "on" }
|
|
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], "on" }
|
|
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"), "on" }
|
|
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"), "on" }
|
|
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
|
|
return false
|
|
end
|
|
return { _("Undo last search -- #") .. #matches, "", "on" }, f
|
|
end
|
|
menu[#menu + 1] = function() return { "---", "", "off" }, nil end
|
|
menu[#menu + 1] = function()
|
|
local m = { _("Match block"), tostring(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
|
|
cheat_save.path = emu.subst_env(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 = {}
|
|
if not getmetatable(dev.space).__name:match("device_t") then
|
|
env.space = devtable[devcur].space;
|
|
func = func .. "_" .. wid
|
|
else
|
|
env.space = emu.item(dev.space.items["0/m_pointer"])
|
|
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], "on" }
|
|
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"), tostring(matchpg), "on" }
|
|
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)
|
|
local r
|
|
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"), "", "on" }, 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 false -- return false so menu will be popped off the stack
|
|
end
|
|
if index == 0 then return false end
|
|
return menu_func[index](event)
|
|
end
|
|
emu.register_menu(menu_callback, menu_populate, _("Cheat Finder"))
|
|
emu.register_frame_done(function ()
|
|
local screen = manager.machine.render.ui_container
|
|
local height = mame_manager.ui.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.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.menu_active then
|
|
emu.unpause()
|
|
menu_is_showing = false
|
|
tabbed_out = true
|
|
end
|
|
end)
|
|
end
|
|
|
|
return exports
|