2003.02.05 ~09 snes9x console http://www.icculus.org/~phaethon/snes/console.html I started on this hack the day before shuttle Columbia disintegrated. Motivation to hack this up came from several places. Primarily, I wanted arbitrary bindings of the buttons on my gamepad (Thrustmaster FireStorm Dual Analog 3) for use in snes9x. A strong impetus came from ZSNES and its GUI. I liked the immediacy of changing key binds and fiddling with options in zsnes. In snes9x, changing options meant saving game state, quitting, futzing with command-line options, starting, and restoring state. Another source of inspiration was Brad Jorsch's control-remapping patch, which is applied to the Debianized snes9x package. I thought the configuration file for that to be rather clumsy, and it didn't bind joystick axes and buttons. The notion of a console came from Quake (1, 2, 3) and Unreal Tournament (pre-2003). I settled on mimicking Quake's console since (a) I am familiar with Quake III's console, and (b) snes9x lacks the underlying objects, widgets, and language system to mimick Unreal's console. Also Quake's console is based on a command language, simpler than Unreal's more advanced Algol-family language. I mimicked most of the functionality and commands found in Quake's console, with various extensions of commands and cvars appropriate for snes9x. The cvars that affect emulator settings are not directly tied to their associated C++ variables. Rather, there are explicit calls to synchronize the values between cvars and their variables. Somewhat inefficient, but saved massive refactoring. FlightGear has an interesting configuration system. A key binding describes how a particular "FlightGear property" is modified. For example, swapping values with another property, incrementing the property by a certain amount, or _conditionally_ setting the value of one property based on the value of another property, at key press or key release. Bindings for (analog) joysticks are described in the same syntax, but describes a formula for translating the joystick position into a property value, using a scaling factor, an offset value, and a "dead-zone" range. Anyway, FlightGear's binding of analog joysticks inspired me to figure out a way to incorporate axis-handling in the new snes9x console. I spent much time thinking over keys, input, and key states. Often, the state of a key is treated as a boolean, as either true (pressed) or false (released). I thought of an axis of a directional (digital) pad as being one key with three possible states: 0, positive, and negative (i.e. left and right do not occur at the same time on a joystick). In the case of an analog axis, there would need to be continuous values on either side of 0. At first, I considered using floating-point values, i.e. -1.0 for one end, +1.0 for the other, and fractional values to represent intermediate axis positions. Well, due to some inconsistency in the cvars mechanism at the time with float-point values, I wanted to stay away from floats. That's when I remembered joystick calibrators worked with integers, where 32767 represented maximum and -32767 minimum. So I went with integer-only values for a key state. For regular keys, the state would still be 0 or 1, but for axes, would be a value from -32767 through +32767. In Tribes 2, a key is bound to a function -- a key handler. This function is passed one parameter, a value of either 0 or 1, indicating whether the function was called because the key was released (0) or the key was pressed (1). I never dealt with joystick handling in Tribes 2, but a reasonable extension would be an axis-handler function being passed a value indicating the axis position. Quake III supplies commands linked to action primitives, in a "start" and a "stop" flavor. For example, "+attack" means start firing, and "-attack" means stop firing. If a key is bound simply to just "+attack", when the key is released, Quake III automagically runs the command "-attack" (i.e. replaces the '+' with a '-'). The '+'-'-' swapping is apparent with a simple test: "/bind SPACE +crap". Hit Space, and the Q3 console complains about not finding "+crap" nor "-crap". The complaint about "-crap" happens when Space is released. A while ago, in an attempt to extend the Q3 console capability to be more shell-like, I added an "alias" capability. This capability allowed a cvar to run as a command without need for an explicit "vstr". The basic idea was, for some alias named "foo", to prefix it with an ampersand, "&foo". Should the Q3 engine fail to understand a command (and pass it off to Q3VM code), the VM code tries to find a cvar matching the unknown command's name, but with a leading ampersand. Then, on a successful match, runs the cvar's content as a command. I took this idea over to the snes9x console. The primary goal was to have a bind named "+turbokey", defined entirely in terms of commands (instead of C++ code). This alias would then be bound to Tab, such that holding down Tab puts the emulator into "fast-forward mode" (skipping boring/useless/pointless parts of a game), and releasing Tab returns the emulator to "normal" speed. Most of the keynames were ripped from Quake III. Q3 basically goes off on its own naming system for high-bit key, so there had to be a way to map the host key to Q3's (or rather, snes9x console's). This comprises the most massive patch to files outside of the console/ directory -- just mapping host keys to s9xconsole keys. I didn't want to mimick FlightGear's axis handling completely. One problem I have with FlightGear's joystick handling is the complexity involved in trying to control the throttle properly with an auto-centering joystick (i.e. a little push to increase a little, big push to increase a lot, instead of the throttle every time the joystick auto-centers). The Tribes 2 style was more attractive, but that meant having some primitive form of conditional and flow control. That got nasty. I don't want to get into it, other than the commands for conditional and flow control are more reminiscent of assembly than any kind of HLL. I implemented a primitive form of multitasking to deal with rapid-fire (press, release, repeat, but if the console doesn't yield to the rest of the emu, the emu never gets to see the buttons) and a primitive form of multithreading to deal with infinitely-looping commands. Also, as part of rapid-fire, I had threads record the key that initiated the thread. Then the thread manager kills off all threads associated with a key whenever its state changes (e.g. killing rapid-fire loop on release). Originally I had a cvar named "axisval" that records the axis position for the AXISn "keys" (analog axes). One AXIS bind could make several comparisons to the cvar, but then the multithreading meant another AXIS bind would then change value of "axival", before the first thread manage dall its comparisions. Race condition. I resolved this by attaching the axis number to the end of the cvar name, so that each AXIS bind has its own associated cvar (instead of one cvar shared across them all). So, now, any of the three joysticks on my gamepad (one D-pad, two ministicks) can emulate the SNES Directional-Pad.