summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/cheat/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/cheat/init.lua')
-rw-r--r--plugins/cheat/init.lua469
1 files changed, 469 insertions, 0 deletions
diff --git a/plugins/cheat/init.lua b/plugins/cheat/init.lua
new file mode 100644
index 00000000000..ecaf6237377
--- /dev/null
+++ b/plugins/cheat/init.lua
@@ -0,0 +1,469 @@
+-- license:BSD-3-Clause
+-- copyright-holders:Carl
+--
+-- json cheat file format
+-- [{
+-- "desc": "text",
+-- "parameter": {
+-- "min": "minval(0)",
+-- "max": "maxval(numitems)",
+-- "step": "stepval(1)",
+-- "item" [{
+-- "value": "itemval(index*stepval+minval)",
+-- "text": "text"
+-- },
+-- ... ]
+-- },
+-- "space": {
+-- "varname": {
+-- "tag": "tag",
+-- "type": "program|data|io"
+-- },
+-- ...
+-- },
+-- "screen": {
+-- "varname": "tag",
+-- ...
+-- },
+-- "region": {
+-- "varname": "tag",
+-- ...
+-- },
+-- "ram": {
+-- "varname": "tag",
+-- ...
+-- },
+-- "script": {
+-- "on|off|run|change": "script",
+-- ...
+-- },
+-- "comment": "text"
+-- },
+-- ... ]
+--
+-- Scripts are lua scripts with a limited api. Most library functions are unavailable.
+-- Like the XML cheats, param is the current parameter value and variables are shared between scripts within a cheat
+-- Differences from XML cheats:
+-- - actions are only one line which include the entire script
+-- - "condexpr" is replaced with lua control statements (if-then-else-end)
+-- - variables are only limited by the limits of the lua interperter, you can have strings and tables
+-- - the address spaces in the "space" blocks are accessible to the script if included,
+-- same with regions (the "m" space in debug expr)
+-- - frame is replaced by screen:frame_number() so if you use frame a screen needs to be in the device section
+-- - output is a function and argindex isn't supported, output args need to be explicit and a screen device
+-- must be provided
+
+local exports = {}
+exports.name = "cheat"
+exports.version = "0.0.1"
+exports.description = "Cheat plugin"
+exports.license = "The BSD 3-Clause License"
+exports.author = { name = "Carl" }
+
+local cheat = exports
+
+function cheat.startplugin()
+ local cheats = {}
+ local output = {}
+ local line = 0
+
+ local function load_cheats()
+ local filename = emu.romname()
+ local json = require("json")
+ local file = emu.file(manager:machine():options().entries.cheatpath:value():gsub("([^;]+)", "%1;%1/cheat"), 1)
+
+ if emu.softname() ~= "" then
+ for name, image in pairs(manager:machine().images) do
+ if image:exists() and image:software_list_name() ~= "" then
+ filename = image:software_list_name() .. "/" .. emu.softname()
+ end
+ end
+ end
+
+ if file:open(filename .. ".json") then
+ local xml = require("cheat/xml_conv")
+
+ if file:open(filename .. ".xml") then
+ return {}
+ end
+ return xml.conv_cheat(file:read(file:size()))
+ end
+
+ return json.parse(file:read(file:size()))
+ end
+
+ local function draw_text(screen, x, y, color, form, ...)
+ local str = form:format(...)
+ if y == "auto" then
+ y = line
+ line = line + 1
+ end
+ if not screen then
+ print("draw_text: invalid screen")
+ return
+ end
+ if type(x) == "string" then
+ y = y * manager:machine():ui():get_line_height()
+ end
+ output[#output + 1] = { type = "text", scr = screen, x = x, y = y, str = str, color = color }
+ end
+
+ local function draw_line(screen, x1, y1, x2, y2, color)
+ if not screen then
+ print("draw_line: invalid screen")
+ return
+ end
+ output[#output + 1] = { type = "line", scr = screen, x1 = x1, x2 = x2, y1 = y1, y2 = y2, color = color }
+ end
+
+ local function draw_box(screen, x1, y1, x2, y2, bgcolor, linecolor)
+ if not screen then
+ print("draw_box: invalid screen")
+ return
+ end
+ output[#output + 1] = { type = "box", scr = screen, x1 = x1, x2 = x2, y1 = y1, y2 = y2, bgcolor = bgcolor, linecolor = linecolor }
+ end
+
+ local function tobcd(val)
+ local result = 0
+ local shift = 0
+ while val ~= 0 do
+ result = result + ((val % 10) << shift)
+ val = val / 10
+ shift = shift + 4
+ end
+ return result
+ 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
+
+ local function parse_cheat(cheat)
+ cheat.cheat_env = { draw_text = draw_text,
+ draw_line = draw_line,
+ draw_box = draw_box,
+ tobcd = tobcd,
+ frombcd = frombcd,
+ pairs = pairs,
+ ipairs = ipairs,
+ table_insert = table.insert,
+ table_remove = table.remove }
+ cheat.enabled = false
+ -- verify scripts are valid first
+ if not cheat.script then
+ return
+ end
+ for name, script in pairs(cheat.script) do
+ script, err = load(script, cheat.desc .. name, "t", cheat.cheat_env)
+ if not script then
+ print("error loading cheat script: " .. cheat.desc .. " " .. err)
+ cheat = { desc = cheat.desc .. " error" }
+ return
+ end
+ cheat.script[name] = script
+ end
+ -- initialize temp[0-9] for backward compatbility reasons
+ for i = 0, 9 do
+ cheat.cheat_env["temp" .. i] = 0
+ end
+ if cheat.space then
+ for name, space in pairs(cheat.space) do
+ local cpu, mem
+ cpu = manager:machine().devices[space.tag]
+ if not cpu then
+ print("error loading cheat script: " .. cheat.desc)
+ cheat = { desc = cheat.desc .. " error" }
+ return
+ end
+ if space.type then
+ mem = cpu.spaces[space.type]
+ else
+ mem = cpu.spaces["program"]
+ end
+ if not mem then
+ print("error loading cheat script: " .. cheat.desc)
+ cheat = { desc = cheat.desc .. " error" }
+ return
+ end
+ cheat.cheat_env[name] = mem
+ end
+ end
+ if cheat.screen then
+ for name, screen in pairs(cheat.screen) do
+ local scr
+ scr = manager:machine().screens[screen]
+ if not scr then
+ local tag
+ tag, scr = next(manager:machine().screens) -- get any screen
+ end
+ cheat.cheat_env[name] = scr
+ end
+ end
+ if cheat.region then
+ for name, region in pairs(cheat.region) do
+ local mem
+ mem = manager:machine():memory().regions[region]
+ if not mem then
+ print("error loading cheat script: " .. cheat.desc)
+ cheat = { desc = cheat.desc .. " error" }
+ return
+ end
+ cheat.cheat_env[name] = mem
+ end
+ end
+ if cheat.ram then
+ for name, ram in pairs(cheat.ram) do
+ local ram
+ ram = manager:machine().devices[ram]
+ if not ram then
+ print("error loading cheat script: " .. cheat.desc)
+ cheat = { desc = cheat.desc .. " error" }
+ return
+ end
+ cheat.cheat_env[name] = emu.item(ram.items["0/pointer"])
+ end
+ end
+ local param = cheat.parameter
+ if not param then
+ return
+ end
+ param.min = tonumber(param.min) or 0
+ param.min = tonumber(param.min) or #param.item
+ param.step = tonumber(param.step) or 1
+ if param.item then
+ for count, item in pairs(param.item) do
+ if not item.value then
+ item.value = (count * param.step) + param.min
+ else
+ item.value = tonumber(item.value)
+ end
+ end
+ param.last = #param.item
+ else
+ param.last = ((param.max - param.min) / param.step) + 1
+ end
+ param.index = 0
+ param.value = param.min
+ cheat.cheat_env.param = param.min
+ end
+
+ local function menu_populate()
+ local menu = {}
+ for num, cheat in ipairs(cheats) do
+ menu[num] = {}
+ menu[num][1] = cheat.desc
+ if not cheat.parameter then
+ if not cheat.script then
+ if cheat.desc == "" then
+ menu[num][1] = "---"
+ end
+ menu[num][2] = ""
+ menu[num][3] = "off"
+ elseif not cheat.script.run and not cheat.script.off then
+ menu[num][2] = "Set"
+ menu[num][3] = 0
+ else
+ if cheat.enabled then
+ menu[num][2] = "On"
+ menu[num][3] = "l"
+ else
+ menu[num][2] = "Off"
+ menu[num][3] = "r"
+ end
+ end
+ else
+ if cheat.parameter.index == 0 then
+ if not cheat.script.run and not cheat.script.off then
+ menu[num][2] = "Set"
+ else
+ menu[num][2] = "Off"
+ end
+ menu[num][3] = "r"
+ else
+ if cheat.parameter.item then
+ menu[num][2] = cheat.parameter.item[cheat.parameter.index].text
+ else
+ menu[num][2] = cheat.parameter.value
+ end
+ menu[num][3] = "l"
+ if cheat.parameter.index < cheat.parameter.last then
+ menu[num][3] = "lr"
+ end
+ end
+ end
+ end
+ return menu
+ end
+
+ local function menu_callback(index, event)
+ local function param_calc(param)
+ if param.item then
+ if not param.item[param.index] then -- uh oh
+ param.index = 1
+ end
+ param.value = param.item[param.index].value
+ return
+ end
+ param.value = param.min + (param.step * (param.index - 1))
+ if param.value > param.max then
+ param.value = param.max
+ end
+ end
+
+ local cheat = cheats[index]
+ if not cheat then
+ return false
+ end
+ if event == "up" or event == "down" or event == "comment" then
+ if cheat.comment then
+ manager:machine():popmessage("Cheat Comment:\n" .. cheat.comment)
+ end
+ elseif event == "left" then
+ if cheat.parameter then
+ local param = cheat.parameter
+ if param.index == 1 then
+ param.index = 0
+ cheat.enabled = false
+ cheat.cheat_env.param = param.min
+ if cheat.script.off then
+ cheat.script.off()
+ end
+ return true
+ elseif param.index == 0 then
+ return false
+ end
+ param.index = param.index - 1
+ param_calc(param)
+ cheat.cheat_env.param = param.value
+ if cheat.script.change and (cheat.script.run or cheat.script.off) then
+ cheat.script.change()
+ end
+ return true
+ else
+ if not cheat.script.run and not cheat.script.off then
+ return false
+ end
+ if cheat.enabled then
+ cheat.enabled = false
+ if cheat.script.off then
+ cheat.script.off()
+ end
+ return true
+ end
+ return false
+ end
+ elseif event == "right" then
+ if cheat.parameter then
+ local param = cheat.parameter
+ if param.index == 0 then
+ cheat.enabled = true
+ if cheat.script.on then
+ cheat.script.on()
+ end
+ elseif param.index == param.last then
+ return false
+ end
+ param.index = param.index + 1
+ param_calc(param)
+ cheat.cheat_env.param = param.value
+ if cheat.script.change and (cheat.script.run or cheat.script.off) then
+ cheat.script.change()
+ end
+ return true
+ else
+ if not cheat.script.run and not cheat.script.off then
+ return false
+ end
+ if not cheat.enabled then
+ cheat.enabled = true
+ if cheat.script.on then
+ cheat.script.on()
+ end
+ return true
+ end
+ return false
+ end
+ elseif event == "select" then
+ if not cheat.script.run and not cheat.script.off then
+ if cheat.parameter and cheat.script.change and cheat.parameter.index ~= 0 then
+ param_calc(cheat.parameter)
+ cheat.cheat_env.param = cheat.parameter.value
+ cheat.script.change()
+ local subtext
+ if cheat.parameter.item then
+ subtext = cheat.parameter.item[cheat.parameter.index]
+ else
+ subtext = cheat.parameter.value
+ end
+ manager:machine():popmessage("Activated: " .. cheat.desc .. " = " .. subtext)
+ elseif not cheat.parameter and cheat.script.on then
+ cheat.script.on()
+ manager:machine():popmessage("Activated: " .. cheat.desc)
+ end
+ end
+ end
+ return false
+ end
+
+ emu.register_menu(function(index, event)
+ return menu_callback(index, event)
+ end,
+ function()
+ return menu_populate()
+ end, "Cheat")
+
+ emu.register_start(function()
+ cheats = load_cheats()
+ for num, cheat in pairs(cheats) do
+ parse_cheat(cheat)
+ end
+ end)
+
+ emu.register_frame(function()
+ for num, cheat in pairs(cheats) do
+ if cheat.enabled and cheat.script.run then
+ cheat.script.run()
+ end
+ end
+ end)
+
+ emu.register_frame_done(function()
+ line = 0
+ for num, draw in pairs(output) do
+ if draw.type == "text" then
+ if not draw.color then
+ draw.scr:draw_text(draw.x, draw.y, draw.str)
+ else
+ draw.scr:draw_text(draw.x, draw.y, draw.str, draw.color)
+ end
+ elseif draw.type == "line" then
+ draw.scr:draw_line(draw.x1, draw.y1, draw.x2, draw.y2, draw.color)
+ elseif draw.type == "box" then
+ draw.scr:draw_box(draw.x1, draw.y1, draw.x2, draw.y2, draw.bgcolor, draw.linecolor)
+ end
+ end
+ output = {}
+ end)
+
+ local ce = {}
+
+ -- interface to script cheat engine
+ function ce.inject(newcheat)
+ cheats[#cheats + 1] = newcheat
+ parse_cheat(newcheat)
+ manager:machine():popmessage(newcheat.desc .. " added")
+ end
+
+ _G.ce = ce
+
+end
+
+return exports