Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Mupen64Plus 2.0 Lua Scripting patch - 2011 July 1
- by Rena Kunisaki
- This patch adds Lua scripting support to the console UI, as well as fixing some
- bugs and enhancing some things elsewhere in the emulator.
- This patch should not be considered complete. In particular the Lua API may be
- changed significantly in the future, so any scripts you write now may not work
- with later versions. You may want to wait until the API is finalized.
- This patch is being submitted to the Mupen64Plus developers for review. Feel
- free to add/change/remove things as necessary.
- This patch makes the following changes:
- Lua:
- -Add Lua scripting support to mupen64plus-ui-console. This is enabled by
- compiling with the USE_LUA=1 option.
- -BUG: Enabling Lua also enables the debugger. If --saveoptions is passed and
- Lua is enabled, this results in the debugger being enabled by default.
- -Some Lua scripts are also added to mupen64plus-core/data/lua. These are
- executed by the frontend at startup when Lua is enabled.
- OSD:
- -Export OSD functions in core API to be used by frontend and plugins.
- -I do not claim copyrigth of api/osd.h since its content is almost entirely
- copied from other files.
- -Add drop shadows behind OSD text to make it more readable on various background
- colours and patterns.
- -Add support for some formatting codes in OSD text (line break, tab, and some
- basic ANSI escape codes, e.g. set text colour).
- -Line breaks in strings at bottom of screen aren't handled properly yet (they
- just move down off the screen instead of pushing previous lines up).
- -Add callback functionality for frontend and plugins to inject their own OpenGL
- calls to draw whatever they want on the screen. (OSD text still shows up above
- these drawings.)
- -Currently, anything drawn using this callback flickers badly. I don't know
- why this is.
- -Add error message if OSD font can't be found.
- -Add "OSD:" prefix to some error messages to show where they're coming from.
- Breakpoints:
- -Add BPT_FLAG_NOSTOP (don't pause execution when this breakpoint hits). Useful
- in conjunction with BPT_FLAG_LOG and/or callbacks.
- -Add breakpoint callback functionality. (Breakpoints can call a user-defined
- function when they hit.)
- -Return value is currently ignored; it could be turned into a bitfield telling
- whether to perform various actions such as pausing execution.
- -Note that within a breakpoint callback, execution is paused and *cannot* be
- stepped. The CPU will not advance until the callback returns. This would be
- nice, but not easy, to change.
- -Write breakpoints trigger before the memory write occurs, so if you want to
- see what value was written, you need to either decode the instruction and
- read the register being written, or set another breakpoint for a few
- instructions later (or a frame callback for the next frame).
- -Remove unused "condition" field from breakpoint struct, as callbacks make it
- largely obsolete anyway. (Set a callback and BPT_FLAG_NOSTOP, do whatever check
- you want in your callback, call DebugSetRunState yourself if you want.)
- Debugger:
- -Fixed read_memory_32_unaligned() returning wrong values.
- -Redefined types in types.h to use sandard uint*_t defines.
- -Fixed mupen64plus-ui-console using wrong functions for DebugMemWrite*.
- This patch modifies only the unix makefiles. I haven't touched the Windows
- makefiles since I have no experience with those.
- Lua API:
- The Lua API is defined in two places: 1) in the source code in
- mupen64plus-ui-console and 2) in the Lua scripts in mupen64plus-core/data/lua.
- The scripts essentially just wrap the "raw" API into some more Lua-friendly
- functions.
- Lua functions are called using two helper functions, LuaDebugCallSetup() and
- LuaCall(). The procedure is:
- if(g_luaDebug) LuaDebugCallSetup(LuaState);
- /* ... push the function and arguments to the stack as you would for
- * lua_call() and friends...
- */
- LuaCall(LuaState, NumArgs, NumRet);
- LuaCall() works the same as lua_call(), but if any error occurs, it prints an
- error message to stderr. If g_luaDebug is set this message includes a stack
- traceback. It returns the same status codes as lua_pcall().
- Callbacks are handled by the functions in lua_callbacks.c. These simply retrieve
- and call the registered Lua function(s). Most of these functions are disabled if
- no Lua function is registered to avoid unnecessary overhead.
- On the Lua side, there is a callback system implemented in callbacks.lua. This
- system is NOT used for debug callbacks due to the overhead involved.
- To register a callback, a script passes a function and optional parameter to
- an "Add*Callback" function, e.g. Mupen64.AddFrameCallback. There can be more
- than one function (even the same function multiple times) registered for one
- callback event. Callback functions are called in the order they are registered.
- These functions return a Callback object, which supports at least the following
- methods:
- callback:remove() - disables this callback. After removing a callback it can no
- longer be used and should be discarded.
- callback:set_ttl(n) - sets the callback to automatically remove itself after it
- has been called n times. n can be nil to disable automatic removal. This
- function returns the callback, so you can write e.g.:
- MyCallback = Mupen64.AddFrameCallback(MyFunction):set_ttl(1)
- to set a callback to be executed only once, on the next frame.
- Defined callback functions are:
- Mupen64.AddFrameCallback(Func, Param) - Sets a callback to run every frame. The
- callback receives the current frame number as a parameter.
- Mupen64.AddAfterRunCallback(Func, Param) - Sets a callback to run once emulation
- is stopped and the emulator is about to shut down.
- Mupen64.AddMemAccessCallback(AccessType, AddrBegin, AddrEnd, Func, Param) - Sets
- a callback to run when the specified memory region is accessed.
- This function fails if the core was compiled without debugging support.
- AccessType specifies the type of accesses to trap. It is a string containing
- one or more of the characters (case sensitive):
- r: callback on read from this location
- w: callback on write to this location
- x: callback on executing code from this location
- l: also print to the console when this access occurs
- n: don't pause execution when this access occurs
- AddrBegin and AddrEnd specify the memory range to watch (AddrBegin to
- AddrEnd inclusive, in N64 memory space).
- The function receives three parameters:
- -The current PC register value.
- -The address that was accessed.
- -A flag value that indicates the type of access. This value has exactly one
- of the bits Mupen64.BreakpointFlag.{Read, Write, Exec} set. (It may have
- other bits set as well.)
- Mupen64.OSD.SetCallback(Func, Param) - Sets a callback to run just before OSD is
- redrawn, if OSD is enabled. Within this function the script may use LuaGL to
- draw on the screen. OSD messages appear above anything drawn by the script.
- Note that these functions all take a Param - this is passed as the *first*
- parameter to the callback function. If Param is nil or not specified, the first
- parameter will then be nil. e.g. the correct signature for a frame callback
- function is: func(Param, FrameNum).
- The intention of this design is that Param can be used for the implicit 'self'
- parameter of object methods, e.g.:
- function MyObject:DoStuff(FrameNum) self:DoSomethingCool() end
- Mupen64.AddFrameCallback(MyObject.DoStuff, MyObject)
- Debug callbacks (other than breakpoints) do not use this mechanism due to the
- overhead involved. There can be only one of each registered at a time
- (registering a new function removes the previous one; registering nil removes
- the current function and disables the callback). DebugFrontendInit() and
- DebugFrontendVI() receive no parameters; DebugFrontendUpdate() receives only the
- current PC register value.
- In Lua, most functions return nil and an error message string on failure;
- however, Mupen64Plus functions instead return nil and an error code (one of
- Mupen64.Error.*). This should be more useful for detecting specific types of
- errors; the error code can be converted to a string using
- Mupen64.Core.ErrorMessage(). Some of the functions defined in data/lua however
- still return error strings, so your error handlers should be able to handle both
- cases.
- Memory can be accessed by either Mupen64.RAM or
- Mupen64.Debug.Memory.{Read,Write}{8,16,32,64}. The former will probably be
- faster. Both require the core to have debugging support.
- All memory blocks (RAM, registers, etc) are accessed using the API defined in
- lua_memory.c. Blocks are accessed like a Lua table (though zero-based) having
- fields corresponding to different data types:
- Byte, SByte (8-bit); HWord, SHWord (16-bit); Word, SWord (32-bit); Float
- (32-bit floating point); Double (64-bit floating point).
- S prefix means signed, so e.g. Byte returns values from 0 to 255 while SByte
- returns values from -128 to 127.
- Each has the suffix "At" to clarify that the index is an address; e.g.
- Mupen64.RAM.WordAt[8] accesses a word at address 8, not the 8th word (i.e.
- address 8*4). (Only the low 24 bits are considered, so address 8 and address
- 0x80000008 both access the same data.)
- There is no DWord field because Lua does not support 64-bit integers.
- Reading and writing work like a normal Lua table, e.g.:
- Mupen64.RAM.FloatAt[n] = Mupen64.RAM.FloatAt[n] + 1
- Unaligned access is supported.
- Most of the Mupen64Plus debug APIs don't offer any way to tell the size of the
- memory block they return. Thus, the Lua wrappers for such blocks do not enforce
- any bounds checking, which means a script can write beyond the end of the block
- and corrupt memory.
- CPU registers are accessed this way as well, and can be accessed by number or by
- name, e.g. Mupen64.Registers.CPU.T6.WordAt[0].
- init.lua is responsible for setting up the API, and once execution is about to
- begin, it also loads user scripts. It looks in 4 subdirectories of the user's
- mupen64plus script directory (~/.local/share/mupen64plus/scripts), which are
- printed to stdout for convenience. In order:
- 1) The game title followed by the ROM's MD5 hash;
- 2) The game title followed by the two CRC values from the ROM header;
- 3) The game title followed by its cartridge ID;
- 4) The directory '!ALL-GAMES'.
- ("Game title" is the ROM's internal name. The exclamation point prefix causes
- the directory to sort to the top of the list in most file browsers.)
- e.g. for Super Mario 64 (USA):
- 1) scripts/SUPER MARIO 64 MD5-20B854B239203BAF6C961B850A4A51A2/
- 2) scripts/SUPER MARIO 64 CRC-FF2B5A63-2623028B/
- 3) scripts/SUPER MARIO 64 NSME/
- 4) scripts/!ALL-GAMES/
- This allows scripts to be written for different ROMs with varying degrees of
- precision, since there may be multiple revisions of a game with the same title
- and cartridge ID, and hacks with the same CRCs, whose memory layouts are not
- compatible, which would cause problems for scripts that access the game's
- memory.
- After loading a script from one directory it does NOT stop looking, so if there
- are scripts with the same name in all four directories, it will load all four of
- them.
- Scripts to load can be specified with the --lua-script command line parameter.
- e.g: mupen64plus --lua-script my-cool-script mario.n64
- In addition, the script !main.lua will be automatically loaded from each
- directory if it exists.
- Arguments can be passed using --lua-arg. All arguments given are passed as a
- table to all scripts. Named arguments can be used, e.g. --lua-arg "foo=42".
- The --nolua option disables Lua scripting entirely.
- The --lua-dump-startup option outputs the generated startup script to
- generated.lua for debugging. This lets you examine the generated script used to
- initialize the Lua state.
- Thoughts:
- -API abstraction:
- -Lua scripts need to be able to access input. The easiest way to do this is to
- add a callback to event_sdl_filter() in mupen64plus-core/src/main/eventloop.c,
- that passes the SDL_Event to the callback function. This has the advantage
- that the function can modify the event before it gets handled. However, this
- means the backend can never switch away from SDL for its event handling. Is
- this acceptable? If not, the only alternative is to copy all the relevant info
- into a generic struct, which adds overhead and is a bit silly considering
- SDL_Event is already fairly generic.
- -We could possibly continue to use SDL_Event for this purpose even if we were
- no longer actually using SDL, but that's kinda icky.
- -We also need mouse support, so we can click on the things scripts draw using
- the OSD callbacks.
- -Should we make any changes to/wrappers for the OSD API if it's going to be
- exposed to the frontend and plugins?
- -Breakpoints:
- -These really should be using a sorted doubly-linked list rather than an array.
- (Insert new elements at the appropriate position to keep the list sorted;
- don't allow the memory range to be changed, only removing and re-inserting
- the breakpoint.) That would eliminate the cap on breakpoints and simplify the
- code responsible for adding and removing them without adding any overhead to
- lookups. Instead of using/returning an index, breakpoint functions would use
- a pointer to a list element.
- -The "condition" field/flag and "counter" flag (which don't appear to be used
- or implemented) are pretty much obsoleted by the callback functionality.
- -What to do with the return value of breakpoint callbacks... I'm imagining it
- being a bitflag where each bit tells it to do something.
- -One thing might be "skip this instruction", i.e. don't actually perform the
- read/write that triggered the breakpoint. (Presumably the script would have
- already set the register/value itself.) I'm not sure how easy that would be
- to implement.
- -Should there be a "hidden" flag, to prevent the breakpoint showing up in the
- debugger GUI (once one is created)? This would be used when Lua scripts use
- Mupen64.AddMemAccessCallback() (which uses breakpoints) to prevent those
- breakpoints cluttering the list. Or do we want those to show up in the list so
- the user can remove them?
- -It might be useful for Lua to have a way to decode instructions into some
- simpler, but still machine-readable format, so that when hitting a breakpoint
- you can easily tell e.g. what register is being read/written and what data
- size without having to roll your own MIPS decoder. (This could still be done
- entirely in Lua, but the idea is for it to be a standard part of the API, set
- up in api.lua or some such, instead of every script doing its own
- probably-wrong implementation.)
- -OSD:
- -OGLFT seems really buggy and overcomplicated and I did find that it has at
- least one bug relating to bounding box calculation (which I couldn't track
- down to fix) resulting in some messages not appearing. It could probably be
- replaced with a few very simple functions. Now would be a good time to do so
- if we're exposing the OSD API.
- -ANSI escape code support is cool, but complex... do we want to keep it?
- -Why the heck do the things I draw from scripts flicker, when the OSD text
- itself doesn't? Is OGLFT doing some magic to prevent it?
- -We could create an OSD Message object for Lua, so we can do things like:
- Message:Update("hi!")
- which is a little more Lua-like. May well be more work for no real gain
- though.
- -Drop shadow is nice, but outline would be even nicer. I'm not sure how we can
- do that with TTF fonts though.
- -Misc:
- -Right now Lua scripting is part of the console UI, but it's not really tied in
- tight. In addition to the four main types of plugin, we could add support for
- loading an arbitrary number of "utility" plugins, which use the API much like
- a frontend does. Lua could then be moved to one of those, and used along with
- any UI.
- -On the flip side, the UI could be integrated more tightly, allowing scripts
- to tell it what ROM to load, etc. But is this necessary?
- -Using a library such as LuaGnome, a GUI could be implemented entirely in
- Lua. Of course that would mean the GUI requires Lua support and LuaGnome...
- and a GUI doesn't really belong in the console UI (but of course a separate
- frontend could be made for that).
- -A lot of Lua functionality uses the debugger. What should we do if the core
- doesn't have debugging capabilities?
- -Are we OK with adding to the standard Lua library tables? In init.lua there's
- a function string_split() which could easily be changed to string:split().
- Some functions can also be modified in ways that shouldn't cause any issues,
- e.g. making unpack(t) use t.n instead of #t when it exists (so trailing nils
- are preserved). These changes can be done without modifying Lua itself, by
- simply replacing those functions in init.lua, e.g.:
- local _foo = foo; foo = function(a) return _foo(a+1) end
- -Maybe make print() output to OSD instead of terminal?
- -We could really use binary operators or at least functions (especially
- infix). Lua 5.2 has a library for it, if we want to wait for that. We could
- also just add one (the 5.2 library is backported to 5.1), or modify Lua to
- have them (not ideal since it breaks compatibility with compiled modules).
- -I can hack some magic with number metatables so expressions like:
- (1) '&' (3) --are valid (the parentheses and quotes turn it into some
- function calls), but modifying the metatable of numbers is akin to modifying
- standard library tables, except even *more* likely to cause problems, so we
- need to decide if that's acceptable.
- -Probably should add Lua support for the core compare functions too.
- -Right now all Lua scripts run in the same Lua state, which means they all
- share global variables. This may or may not be a bad thing. If people use
- local variables properly it should be no problem, but not everyone does.
- -We can load scripts into different environments, so that they don't trample
- on eachothers' globals, without requiring separate Lua states.
- -Shared globals can be a good thing, as it provides an easy way for scripts to
- communicate, and avoids loading the same module several times for no good
- reason.
- http://pastebin.com/5wDUSmxp mupen64plus-core.patch
- http://pastebin.com/eC2dy5JC mupen64plus-ui-console.patch
- http://pastebin.com/FH62AiCv mupen64plus-core/data/lua
- http://pastebin.com/rebixkDP ~/.local/share/mupen64plus/scripts
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement