summaryrefslogtreecommitdiffstatshomepage
path: root/plugins
diff options
context:
space:
mode:
author cracyc <cracyc@users.noreply.github.com>2016-04-06 17:03:11 -0500
committer cracyc <cracyc@users.noreply.github.com>2016-04-06 17:03:11 -0500
commit55c9596f4c434eb615e71f62f610666629dc864b (patch)
treee22aacd347c8de4418f44eb246deccad7473b178 /plugins
parentead741a06e693f6f793cde35f7a9978b2371175b (diff)
Add experimental cheat lua plugin [Carl]
Diffstat (limited to 'plugins')
-rw-r--r--plugins/cheat/conv_cheat.lua11
-rw-r--r--plugins/cheat/init.lua433
-rw-r--r--plugins/cheat/plugin.json10
-rw-r--r--plugins/cheat/xml_conv.lua217
4 files changed, 671 insertions, 0 deletions
diff --git a/plugins/cheat/conv_cheat.lua b/plugins/cheat/conv_cheat.lua
new file mode 100644
index 00000000000..afc87bc55b6
--- /dev/null
+++ b/plugins/cheat/conv_cheat.lua
@@ -0,0 +1,11 @@
+xml = require("xml_conv")
+json = dofile("../json/init.lua")
+
+function readAll(file)
+ local f = io.open(file, "rb")
+ local content = f:read("*all")
+ f:close()
+ return content
+end
+
+print(json.stringify(xml.conv_cheat(readAll(arg[1]))))
diff --git a/plugins/cheat/init.lua b/plugins/cheat/init.lua
new file mode 100644
index 00000000000..5f38a856464
--- /dev/null
+++ b/plugins/cheat/init.lua
@@ -0,0 +1,433 @@
+-- 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",
+-- ...
+-- },
+-- "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(), 1)
+ local function readAll(file)
+ local f = io.open(file, "rb")
+ if not f then
+ return nil
+ end
+ local content = f:read("*all")
+ f:close()
+ return content
+ end
+
+ if emu.softname() ~= "" then
+ for name, image in manager:machine().images do
+ if image:exists() and image:software_entry() then
+ filename = filename .. "/" .. image:software_entry()
+ end
+ 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_text: 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_text: 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 parse_cheat(cheat)
+ cheat.cheat_env = { draw_text = draw_text,
+ draw_line = draw_line,
+ draw_box = draw_box,
+ pairs = pairs }
+ cheat.enabled = false
+ -- verify scripts are valid first
+ if not cheat.script then
+ return
+ end
+ for name, script in pairs(cheat.script) do
+ script = load(script, cheat.desc .. name, "t", cheat.cheat_env)
+ if not script then
+ print("error loading cheat script: " .. cheat.desc)
+ cheat = nil
+ return
+ end
+ cheat.script[name] = script
+ 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 = nil
+ 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 = nil
+ 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
+ print("error loading cheat script: " .. cheat.desc)
+ cheat = nil
+ return
+ 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 = nil
+ return
+ end
+ cheat.cheat_env[name] = mem
+ end
+ end
+ local param = cheat.parameter
+ if not param then
+ return
+ end
+ if not param.min then
+ param.min = 0
+ else
+ param.min = tonumber(param.min)
+ end
+ if not param.max then
+ param.max = #param.item
+ else
+ param.max = tonumber(param.max)
+ end
+ if not param.step then
+ param.step = 1
+ else
+ param.step = tonumber(param.step)
+ end
+ 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 pairs(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] = 32 -- MENU_FLAG_DISABLE
+ 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] = 1 -- MENU_FLAG_LEFT_ARROW
+ else
+ menu[num][2] = "Off"
+ menu[num][3] = 2 -- MENU_FLAG_RIGHT_ARROW
+ 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] = 2
+ 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] = 1
+ if cheat.parameter.index < cheat.parameter.last then
+ menu[num][3] = 3
+ 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.x2, draw.y1, draw.y2, draw.color)
+ elseif draw.type = "box" then
+ draw.scr:draw_box(draw.x1, draw.x2, draw.y1, draw.y2, draw.bgcolor, draw.linecolor)
+ end
+ end
+ output = {}
+ end)
+end
+
+return exports
diff --git a/plugins/cheat/plugin.json b/plugins/cheat/plugin.json
new file mode 100644
index 00000000000..4067fdbc479
--- /dev/null
+++ b/plugins/cheat/plugin.json
@@ -0,0 +1,10 @@
+{
+ "plugin": {
+ "name": "cheat",
+ "description": "Cheat plugin",
+ "version": "0.0.1",
+ "author": "Carl",
+ "type": "plugin",
+ "start": "false"
+ }
+}
diff --git a/plugins/cheat/xml_conv.lua b/plugins/cheat/xml_conv.lua
new file mode 100644
index 00000000000..72bbf635033
--- /dev/null
+++ b/plugins/cheat/xml_conv.lua
@@ -0,0 +1,217 @@
+local xml = {}
+
+-- basic xml parser for mamecheat only
+local function xml_parse(data)
+ local cheat_str = data:match("<mamecheat.->(.*)</ *mamecheat>")
+
+ local function get_tags(str)
+ local arr = {}
+ while str ~= "" do
+ local tag, attr, stop
+ tag, attr, stop, str = str:match("<([%w!-]+) ?(.-)(/?)[ -]->(.*)")
+
+ if not tag then
+ return arr
+ end
+ if tag:sub(0, 3) ~= "!--" then
+ local block = {}
+ if stop ~= "/" then
+ local nest
+ nest, str = str:match("(.-)</ *" .. tag .. " *>(.*)")
+ local children = get_tags(nest)
+ if not next(children) then
+ nest = nest:gsub("<!--.--->", "")
+ nest = nest:gsub("^%s*(.-)%s*$", "%1")
+ block["text"] = nest
+ else
+ block = children
+ end
+ end
+ if attr then
+ for name, value in attr:gmatch("(%w-)=\"(.-)\"") do
+ block[name] = value:gsub("^%s*(.-)%s*$", "%1")
+ end
+ end
+ if not arr[tag] then
+ arr[tag] = {}
+ end
+ arr[tag][#arr[tag] + 1] = block
+ end
+ end
+ return arr
+ end
+ local xml_table = get_tags(cheat_str)
+ return xml_table
+end
+
+function xml.conv_cheat(data)
+ local spaces, regions, output
+ data = xml_parse(data)
+ local function convert_expr(data)
+ local write = false
+
+ local function convert_memref(cpu, space, width, addr, rw)
+ local direct = ""
+ if space == "p" then
+ fullspace = "program"
+ elseif space == "d" then
+ fullspace = "data"
+ elseif space == "i" then
+ fullspace = "io"
+ elseif space == "r" or space == "o" then
+ fullspace = "program"
+ direct = "direct_"
+ space = "p"
+ end
+ if width == "b" then
+ width = "u8"
+ elseif width == "w" then
+ width = "u16"
+ elseif width == "d" then
+ width = "u32"
+ elseif width == "q" then
+ width = "u64"
+ end
+ local cpuname = cpu:gsub(":", "_")
+ if space == "m" then
+ regions[cpuname .. space] = ":" .. cpu
+ else
+ spaces[cpuname .. space] = { tag = ":" .. cpu, type = fullspace }
+ end
+ if rw == "=" then
+ write = true
+ ret = cpuname .. space .. ":" .. "write_" .. direct .. width .. "(" .. addr .. ","
+ else
+ ret = cpuname .. space .. ":" .. "read_" .. direct .. width .. "(" .. addr .. ")"
+ end
+ if rw == "==" then
+ ret = ret .. "=="
+ end
+ return ret
+ end
+
+ local function frame()
+ output = true
+ return "screen:frame_number()"
+ end
+
+ data = data:lower()
+ data = data:gsub("lt", "<")
+ data = data:gsub("ge", ">=")
+ data = data:gsub("gt", ">")
+ data = data:gsub("le", "<=")
+ data = data:gsub("!=", "~=")
+ data = data:gsub("frame", frame)
+ data = data:gsub("band", "&")
+ data = data:gsub("bor", "|")
+ data = data:gsub("%f[%w](%x+)%f[%W]", "0x%1")
+ data = data:gsub("([%w:]-).([pmrodi3])([bwdq])@(0x%x+) *(=*)", convert_memref)
+ data = data:gsub("([%w:]-).([pmrodi3])([bwdq])@(%b()) *(=*)", convert_memref)
+ if write then
+ data = data .. ")"
+ end
+ return data
+ end
+
+ local function convert_output(data)
+ local str = "draw_text(screen,"
+ if data["align"] then
+ str = str .. data["align"]
+ else
+ str = str .. "\"left\""
+ end
+ if data["line"] then
+ str = str .. ",\"" .. data["line"] .. "\""
+ else
+ str = str .. ", \"auto\""
+ end
+ str = str .. ", nil,\"" .. data["format"] .. "\""
+ for count, block in pairs(data["argument"]) do
+ local expr = convert_expr(block["text"])
+ if block["count"] then
+ for i = 0, block["count"] - 1 do
+ str = str .. "," .. expr:gsub("argindex", i)
+ end
+ else
+ str = str .. "," .. expr
+ end
+ end
+ return str .. ")"
+ end
+
+ local function convert_script(data)
+ local str = ""
+ local state = "run"
+ for tag, block in pairs(data) do
+ if tag == "state" then
+ state = block
+ elseif tag == "action" then
+ for count, action in pairs(block) do
+ if action["condition"] then
+ str = str .. " if " .. convert_expr(action["condition"]) .. " then "
+ for expr in action["text"]:gmatch("([^,]+)") do
+ str = str .. convert_expr(expr) .. " "
+ end
+ str = str .. "end"
+ else
+ for expr in action["text"]:gmatch("([^,]+)") do
+ str = str .. " " .. convert_expr(expr) .. " "
+ end
+ end
+ end
+ elseif tag == "output" then
+ output = true
+ for count, output in pairs(block) do
+ if output["condition"] then
+ str = str .. " if " .. convert_expr(output["condition"]) .. " then "
+ str = str .. convert_output(output) .. " end "
+ else
+ str = str .. " " .. convert_output(output) .. " "
+ end
+ end
+ end
+ end
+ return state, str
+ end
+
+ for count, cheat in pairs(data["cheat"]) do
+ spaces = {}
+ regions = {}
+ output = false
+ for tag, block in pairs(cheat) do
+ if tag == "comment" then
+ data["cheat"][count]["comment"] = block[1]["text"]
+ elseif tag == "script" then
+ local scripts = {}
+ for count2, script in pairs(block) do
+ local state, str = convert_script(script)
+ scripts[state] = str
+ end
+ data["cheat"][count]["script"] = scripts
+ elseif tag == "parameter" then
+ data["cheat"][count]["parameter"] = block[1]
+ end
+ end
+ if next(spaces) then
+ data["cheat"][count]["space"] = {}
+ for name, space in pairs(spaces) do
+ data["cheat"][count]["space"] = {}
+ data["cheat"][count]["space"][name] = { type = space["type"], tag = space["tag"] }
+ end
+ end
+ if next(regions) then
+ data["cheat"][count]["region"] = {}
+ for name, region in pairs(regions) do
+ data["cheat"][count]["region"] = {}
+ data["cheat"][count]["region"][name] = region
+ end
+ end
+ if output then
+ data["cheat"][count]["screen"] = {}
+ data["cheat"][count]["screen"]["screen"] = ":screen"
+ end
+ end
+ return data["cheat"]
+end
+
+return xml