Advertisement
Guest User

Mupen64Plus Lua Scripting Patch

a guest
Jul 1st, 2011
714
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.22 KB | None | 0 0
  1. Mupen64Plus 2.0 Lua Scripting patch - 2011 July 1
  2. by Rena Kunisaki
  3.  
  4. This patch adds Lua scripting support to the console UI, as well as fixing some
  5. bugs and enhancing some things elsewhere in the emulator.
  6.  
  7. This patch should not be considered complete. In particular the Lua API may be
  8. changed significantly in the future, so any scripts you write now may not work
  9. with later versions. You may want to wait until the API is finalized.
  10.  
  11. This patch is being submitted to the Mupen64Plus developers for review. Feel
  12. free to add/change/remove things as necessary.
  13.  
  14.  
  15. This patch makes the following changes:
  16. Lua:
  17. -Add Lua scripting support to mupen64plus-ui-console. This is enabled by
  18. compiling with the USE_LUA=1 option.
  19. -BUG: Enabling Lua also enables the debugger. If --saveoptions is passed and
  20. Lua is enabled, this results in the debugger being enabled by default.
  21. -Some Lua scripts are also added to mupen64plus-core/data/lua. These are
  22. executed by the frontend at startup when Lua is enabled.
  23.  
  24. OSD:
  25. -Export OSD functions in core API to be used by frontend and plugins.
  26. -I do not claim copyrigth of api/osd.h since its content is almost entirely
  27. copied from other files.
  28. -Add drop shadows behind OSD text to make it more readable on various background
  29. colours and patterns.
  30. -Add support for some formatting codes in OSD text (line break, tab, and some
  31. basic ANSI escape codes, e.g. set text colour).
  32. -Line breaks in strings at bottom of screen aren't handled properly yet (they
  33. just move down off the screen instead of pushing previous lines up).
  34. -Add callback functionality for frontend and plugins to inject their own OpenGL
  35. calls to draw whatever they want on the screen. (OSD text still shows up above
  36. these drawings.)
  37. -Currently, anything drawn using this callback flickers badly. I don't know
  38. why this is.
  39. -Add error message if OSD font can't be found.
  40. -Add "OSD:" prefix to some error messages to show where they're coming from.
  41.  
  42. Breakpoints:
  43. -Add BPT_FLAG_NOSTOP (don't pause execution when this breakpoint hits). Useful
  44. in conjunction with BPT_FLAG_LOG and/or callbacks.
  45. -Add breakpoint callback functionality. (Breakpoints can call a user-defined
  46. function when they hit.)
  47. -Return value is currently ignored; it could be turned into a bitfield telling
  48. whether to perform various actions such as pausing execution.
  49. -Note that within a breakpoint callback, execution is paused and *cannot* be
  50. stepped. The CPU will not advance until the callback returns. This would be
  51. nice, but not easy, to change.
  52. -Write breakpoints trigger before the memory write occurs, so if you want to
  53. see what value was written, you need to either decode the instruction and
  54. read the register being written, or set another breakpoint for a few
  55. instructions later (or a frame callback for the next frame).
  56. -Remove unused "condition" field from breakpoint struct, as callbacks make it
  57. largely obsolete anyway. (Set a callback and BPT_FLAG_NOSTOP, do whatever check
  58. you want in your callback, call DebugSetRunState yourself if you want.)
  59.  
  60. Debugger:
  61. -Fixed read_memory_32_unaligned() returning wrong values.
  62. -Redefined types in types.h to use sandard uint*_t defines.
  63. -Fixed mupen64plus-ui-console using wrong functions for DebugMemWrite*.
  64.  
  65. This patch modifies only the unix makefiles. I haven't touched the Windows
  66. makefiles since I have no experience with those.
  67.  
  68.  
  69. Lua API:
  70. The Lua API is defined in two places: 1) in the source code in
  71. mupen64plus-ui-console and 2) in the Lua scripts in mupen64plus-core/data/lua.
  72. The scripts essentially just wrap the "raw" API into some more Lua-friendly
  73. functions.
  74.  
  75. Lua functions are called using two helper functions, LuaDebugCallSetup() and
  76. LuaCall(). The procedure is:
  77. if(g_luaDebug) LuaDebugCallSetup(LuaState);
  78. /* ... push the function and arguments to the stack as you would for
  79. * lua_call() and friends...
  80. */
  81. LuaCall(LuaState, NumArgs, NumRet);
  82.  
  83. LuaCall() works the same as lua_call(), but if any error occurs, it prints an
  84. error message to stderr. If g_luaDebug is set this message includes a stack
  85. traceback. It returns the same status codes as lua_pcall().
  86.  
  87. Callbacks are handled by the functions in lua_callbacks.c. These simply retrieve
  88. and call the registered Lua function(s). Most of these functions are disabled if
  89. no Lua function is registered to avoid unnecessary overhead.
  90.  
  91. On the Lua side, there is a callback system implemented in callbacks.lua. This
  92. system is NOT used for debug callbacks due to the overhead involved.
  93.  
  94. To register a callback, a script passes a function and optional parameter to
  95. an "Add*Callback" function, e.g. Mupen64.AddFrameCallback. There can be more
  96. than one function (even the same function multiple times) registered for one
  97. callback event. Callback functions are called in the order they are registered.
  98.  
  99. These functions return a Callback object, which supports at least the following
  100. methods:
  101. callback:remove() - disables this callback. After removing a callback it can no
  102. longer be used and should be discarded.
  103. callback:set_ttl(n) - sets the callback to automatically remove itself after it
  104. has been called n times. n can be nil to disable automatic removal. This
  105. function returns the callback, so you can write e.g.:
  106. MyCallback = Mupen64.AddFrameCallback(MyFunction):set_ttl(1)
  107. to set a callback to be executed only once, on the next frame.
  108.  
  109. Defined callback functions are:
  110. Mupen64.AddFrameCallback(Func, Param) - Sets a callback to run every frame. The
  111. callback receives the current frame number as a parameter.
  112.  
  113. Mupen64.AddAfterRunCallback(Func, Param) - Sets a callback to run once emulation
  114. is stopped and the emulator is about to shut down.
  115.  
  116. Mupen64.AddMemAccessCallback(AccessType, AddrBegin, AddrEnd, Func, Param) - Sets
  117. a callback to run when the specified memory region is accessed.
  118. This function fails if the core was compiled without debugging support.
  119. AccessType specifies the type of accesses to trap. It is a string containing
  120. one or more of the characters (case sensitive):
  121. r: callback on read from this location
  122. w: callback on write to this location
  123. x: callback on executing code from this location
  124. l: also print to the console when this access occurs
  125. n: don't pause execution when this access occurs
  126. AddrBegin and AddrEnd specify the memory range to watch (AddrBegin to
  127. AddrEnd inclusive, in N64 memory space).
  128. The function receives three parameters:
  129. -The current PC register value.
  130. -The address that was accessed.
  131. -A flag value that indicates the type of access. This value has exactly one
  132. of the bits Mupen64.BreakpointFlag.{Read, Write, Exec} set. (It may have
  133. other bits set as well.)
  134.  
  135. Mupen64.OSD.SetCallback(Func, Param) - Sets a callback to run just before OSD is
  136. redrawn, if OSD is enabled. Within this function the script may use LuaGL to
  137. draw on the screen. OSD messages appear above anything drawn by the script.
  138.  
  139. Note that these functions all take a Param - this is passed as the *first*
  140. parameter to the callback function. If Param is nil or not specified, the first
  141. parameter will then be nil. e.g. the correct signature for a frame callback
  142. function is: func(Param, FrameNum).
  143. The intention of this design is that Param can be used for the implicit 'self'
  144. parameter of object methods, e.g.:
  145. function MyObject:DoStuff(FrameNum) self:DoSomethingCool() end
  146. Mupen64.AddFrameCallback(MyObject.DoStuff, MyObject)
  147.  
  148. Debug callbacks (other than breakpoints) do not use this mechanism due to the
  149. overhead involved. There can be only one of each registered at a time
  150. (registering a new function removes the previous one; registering nil removes
  151. the current function and disables the callback). DebugFrontendInit() and
  152. DebugFrontendVI() receive no parameters; DebugFrontendUpdate() receives only the
  153. current PC register value.
  154.  
  155. In Lua, most functions return nil and an error message string on failure;
  156. however, Mupen64Plus functions instead return nil and an error code (one of
  157. Mupen64.Error.*). This should be more useful for detecting specific types of
  158. errors; the error code can be converted to a string using
  159. Mupen64.Core.ErrorMessage(). Some of the functions defined in data/lua however
  160. still return error strings, so your error handlers should be able to handle both
  161. cases.
  162.  
  163. Memory can be accessed by either Mupen64.RAM or
  164. Mupen64.Debug.Memory.{Read,Write}{8,16,32,64}. The former will probably be
  165. faster. Both require the core to have debugging support.
  166. All memory blocks (RAM, registers, etc) are accessed using the API defined in
  167. lua_memory.c. Blocks are accessed like a Lua table (though zero-based) having
  168. fields corresponding to different data types:
  169. Byte, SByte (8-bit); HWord, SHWord (16-bit); Word, SWord (32-bit); Float
  170. (32-bit floating point); Double (64-bit floating point).
  171. S prefix means signed, so e.g. Byte returns values from 0 to 255 while SByte
  172. returns values from -128 to 127.
  173. Each has the suffix "At" to clarify that the index is an address; e.g.
  174. Mupen64.RAM.WordAt[8] accesses a word at address 8, not the 8th word (i.e.
  175. address 8*4). (Only the low 24 bits are considered, so address 8 and address
  176. 0x80000008 both access the same data.)
  177. There is no DWord field because Lua does not support 64-bit integers.
  178.  
  179. Reading and writing work like a normal Lua table, e.g.:
  180. Mupen64.RAM.FloatAt[n] = Mupen64.RAM.FloatAt[n] + 1
  181. Unaligned access is supported.
  182.  
  183. Most of the Mupen64Plus debug APIs don't offer any way to tell the size of the
  184. memory block they return. Thus, the Lua wrappers for such blocks do not enforce
  185. any bounds checking, which means a script can write beyond the end of the block
  186. and corrupt memory.
  187.  
  188. CPU registers are accessed this way as well, and can be accessed by number or by
  189. name, e.g. Mupen64.Registers.CPU.T6.WordAt[0].
  190.  
  191.  
  192. init.lua is responsible for setting up the API, and once execution is about to
  193. begin, it also loads user scripts. It looks in 4 subdirectories of the user's
  194. mupen64plus script directory (~/.local/share/mupen64plus/scripts), which are
  195. printed to stdout for convenience. In order:
  196. 1) The game title followed by the ROM's MD5 hash;
  197. 2) The game title followed by the two CRC values from the ROM header;
  198. 3) The game title followed by its cartridge ID;
  199. 4) The directory '!ALL-GAMES'.
  200. ("Game title" is the ROM's internal name. The exclamation point prefix causes
  201. the directory to sort to the top of the list in most file browsers.)
  202. e.g. for Super Mario 64 (USA):
  203. 1) scripts/SUPER MARIO 64 MD5-20B854B239203BAF6C961B850A4A51A2/
  204. 2) scripts/SUPER MARIO 64 CRC-FF2B5A63-2623028B/
  205. 3) scripts/SUPER MARIO 64 NSME/
  206. 4) scripts/!ALL-GAMES/
  207.  
  208. This allows scripts to be written for different ROMs with varying degrees of
  209. precision, since there may be multiple revisions of a game with the same title
  210. and cartridge ID, and hacks with the same CRCs, whose memory layouts are not
  211. compatible, which would cause problems for scripts that access the game's
  212. memory.
  213.  
  214. After loading a script from one directory it does NOT stop looking, so if there
  215. are scripts with the same name in all four directories, it will load all four of
  216. them.
  217.  
  218. Scripts to load can be specified with the --lua-script command line parameter.
  219. e.g: mupen64plus --lua-script my-cool-script mario.n64
  220. In addition, the script !main.lua will be automatically loaded from each
  221. directory if it exists.
  222.  
  223. Arguments can be passed using --lua-arg. All arguments given are passed as a
  224. table to all scripts. Named arguments can be used, e.g. --lua-arg "foo=42".
  225.  
  226. The --nolua option disables Lua scripting entirely.
  227.  
  228. The --lua-dump-startup option outputs the generated startup script to
  229. generated.lua for debugging. This lets you examine the generated script used to
  230. initialize the Lua state.
  231.  
  232.  
  233. Thoughts:
  234. -API abstraction:
  235. -Lua scripts need to be able to access input. The easiest way to do this is to
  236. add a callback to event_sdl_filter() in mupen64plus-core/src/main/eventloop.c,
  237. that passes the SDL_Event to the callback function. This has the advantage
  238. that the function can modify the event before it gets handled. However, this
  239. means the backend can never switch away from SDL for its event handling. Is
  240. this acceptable? If not, the only alternative is to copy all the relevant info
  241. into a generic struct, which adds overhead and is a bit silly considering
  242. SDL_Event is already fairly generic.
  243. -We could possibly continue to use SDL_Event for this purpose even if we were
  244. no longer actually using SDL, but that's kinda icky.
  245. -We also need mouse support, so we can click on the things scripts draw using
  246. the OSD callbacks.
  247. -Should we make any changes to/wrappers for the OSD API if it's going to be
  248. exposed to the frontend and plugins?
  249.  
  250. -Breakpoints:
  251. -These really should be using a sorted doubly-linked list rather than an array.
  252. (Insert new elements at the appropriate position to keep the list sorted;
  253. don't allow the memory range to be changed, only removing and re-inserting
  254. the breakpoint.) That would eliminate the cap on breakpoints and simplify the
  255. code responsible for adding and removing them without adding any overhead to
  256. lookups. Instead of using/returning an index, breakpoint functions would use
  257. a pointer to a list element.
  258. -The "condition" field/flag and "counter" flag (which don't appear to be used
  259. or implemented) are pretty much obsoleted by the callback functionality.
  260. -What to do with the return value of breakpoint callbacks... I'm imagining it
  261. being a bitflag where each bit tells it to do something.
  262. -One thing might be "skip this instruction", i.e. don't actually perform the
  263. read/write that triggered the breakpoint. (Presumably the script would have
  264. already set the register/value itself.) I'm not sure how easy that would be
  265. to implement.
  266. -Should there be a "hidden" flag, to prevent the breakpoint showing up in the
  267. debugger GUI (once one is created)? This would be used when Lua scripts use
  268. Mupen64.AddMemAccessCallback() (which uses breakpoints) to prevent those
  269. breakpoints cluttering the list. Or do we want those to show up in the list so
  270. the user can remove them?
  271. -It might be useful for Lua to have a way to decode instructions into some
  272. simpler, but still machine-readable format, so that when hitting a breakpoint
  273. you can easily tell e.g. what register is being read/written and what data
  274. size without having to roll your own MIPS decoder. (This could still be done
  275. entirely in Lua, but the idea is for it to be a standard part of the API, set
  276. up in api.lua or some such, instead of every script doing its own
  277. probably-wrong implementation.)
  278.  
  279. -OSD:
  280. -OGLFT seems really buggy and overcomplicated and I did find that it has at
  281. least one bug relating to bounding box calculation (which I couldn't track
  282. down to fix) resulting in some messages not appearing. It could probably be
  283. replaced with a few very simple functions. Now would be a good time to do so
  284. if we're exposing the OSD API.
  285. -ANSI escape code support is cool, but complex... do we want to keep it?
  286. -Why the heck do the things I draw from scripts flicker, when the OSD text
  287. itself doesn't? Is OGLFT doing some magic to prevent it?
  288. -We could create an OSD Message object for Lua, so we can do things like:
  289. Message:Update("hi!")
  290. which is a little more Lua-like. May well be more work for no real gain
  291. though.
  292. -Drop shadow is nice, but outline would be even nicer. I'm not sure how we can
  293. do that with TTF fonts though.
  294.  
  295. -Misc:
  296. -Right now Lua scripting is part of the console UI, but it's not really tied in
  297. tight. In addition to the four main types of plugin, we could add support for
  298. loading an arbitrary number of "utility" plugins, which use the API much like
  299. a frontend does. Lua could then be moved to one of those, and used along with
  300. any UI.
  301. -On the flip side, the UI could be integrated more tightly, allowing scripts
  302. to tell it what ROM to load, etc. But is this necessary?
  303. -Using a library such as LuaGnome, a GUI could be implemented entirely in
  304. Lua. Of course that would mean the GUI requires Lua support and LuaGnome...
  305. and a GUI doesn't really belong in the console UI (but of course a separate
  306. frontend could be made for that).
  307. -A lot of Lua functionality uses the debugger. What should we do if the core
  308. doesn't have debugging capabilities?
  309. -Are we OK with adding to the standard Lua library tables? In init.lua there's
  310. a function string_split() which could easily be changed to string:split().
  311. Some functions can also be modified in ways that shouldn't cause any issues,
  312. e.g. making unpack(t) use t.n instead of #t when it exists (so trailing nils
  313. are preserved). These changes can be done without modifying Lua itself, by
  314. simply replacing those functions in init.lua, e.g.:
  315. local _foo = foo; foo = function(a) return _foo(a+1) end
  316. -Maybe make print() output to OSD instead of terminal?
  317. -We could really use binary operators or at least functions (especially
  318. infix). Lua 5.2 has a library for it, if we want to wait for that. We could
  319. also just add one (the 5.2 library is backported to 5.1), or modify Lua to
  320. have them (not ideal since it breaks compatibility with compiled modules).
  321. -I can hack some magic with number metatables so expressions like:
  322. (1) '&' (3) --are valid (the parentheses and quotes turn it into some
  323. function calls), but modifying the metatable of numbers is akin to modifying
  324. standard library tables, except even *more* likely to cause problems, so we
  325. need to decide if that's acceptable.
  326. -Probably should add Lua support for the core compare functions too.
  327. -Right now all Lua scripts run in the same Lua state, which means they all
  328. share global variables. This may or may not be a bad thing. If people use
  329. local variables properly it should be no problem, but not everyone does.
  330. -We can load scripts into different environments, so that they don't trample
  331. on eachothers' globals, without requiring separate Lua states.
  332. -Shared globals can be a good thing, as it provides an easy way for scripts to
  333. communicate, and avoids loading the same module several times for no good
  334. reason.
  335.  
  336.  
  337. http://pastebin.com/5wDUSmxp mupen64plus-core.patch
  338. http://pastebin.com/eC2dy5JC mupen64plus-ui-console.patch
  339. http://pastebin.com/FH62AiCv mupen64plus-core/data/lua
  340. http://pastebin.com/rebixkDP ~/.local/share/mupen64plus/scripts
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement