/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

#include <map>
#include <set>
#include <vector>
#include <string>
#include <algorithm>
#include <assert.h>
#include <ctype.h>

#include "snes9x.h"
#include "memmap.h"
#include "apu/apu.h"
#include "snapshot.h"
#include "controls.h"
#include "crosshairs.h"
#include "movie.h"
#include "display.h"
#ifdef NETPLAY_SUPPORT
#include "netplay.h"
#endif

using namespace	std;

#define NONE					(-2)
#define MP5						(-1)
#define JOYPAD0					0
#define JOYPAD1					1
#define JOYPAD2					2
#define JOYPAD3					3
#define JOYPAD4					4
#define JOYPAD5					5
#define JOYPAD6					6
#define JOYPAD7					7
#define MOUSE0					8
#define MOUSE1					9
#define SUPERSCOPE				10
#define ONE_JUSTIFIER			11
#define TWO_JUSTIFIERS			12
#define MACSRIFLE				13
#define NUMCTLS					14 // This must be LAST

#define POLL_ALL				NUMCTLS

#define SUPERSCOPE_FIRE			0x80
#define SUPERSCOPE_CURSOR		0x40
#define SUPERSCOPE_TURBO		0x20
#define SUPERSCOPE_PAUSE		0x10
#define SUPERSCOPE_OFFSCREEN	0x02

#define JUSTIFIER_TRIGGER		0x80
#define JUSTIFIER_START			0x20
#define JUSTIFIER_SELECT		0x08

#define MACSRIFLE_TRIGGER		0x01

#define MAP_UNKNOWN				(-1)
#define MAP_NONE				0
#define MAP_BUTTON				1
#define MAP_AXIS				2
#define MAP_POINTER				3

#define FLAG_IOBIT0				(Memory.FillRAM[0x4213] & 0x40)
#define FLAG_IOBIT1				(Memory.FillRAM[0x4213] & 0x80)
#define FLAG_IOBIT(n)			((n) ? (FLAG_IOBIT1) : (FLAG_IOBIT0))

bool8	pad_read = 0, pad_read_last = 0;
uint8	read_idx[2 /* ports */][2 /* per port */];

struct exemulti
{
	int32				pos;
	bool8				data1;
	s9xcommand_t		*script;
};

struct crosshair
{
	uint8				set;
	uint8				img;
	uint8				fg, bg;
};

static struct
{
	int16				x, y;
	int16				V_adj;
	bool8				V_var;
	int16				H_adj;
	bool8				H_var;
	bool8				mapped;
}	pseudopointer[8];

static struct
{
	uint16				buttons;
	uint16				turbos;
	uint16				toggleturbo;
	uint16				togglestick;
	uint8				turbo_ct;
}	joypad[8];

static struct
{
	uint8				delta_x, delta_y;
	int16				old_x, old_y;
	int16				cur_x, cur_y;
	uint8				buttons;
	uint32				ID;
	struct crosshair	crosshair;
}	mouse[2];

static struct
{
	int16				x, y;
	uint8				phys_buttons;
	uint8				next_buttons;
	uint8				read_buttons;
	uint32				ID;
	struct crosshair	crosshair;
}	superscope;

static struct
{
	int16				x[2], y[2];
	uint8				buttons;
	bool8				offscreen[2];
	uint32				ID[2];
	struct crosshair	crosshair[2];
}	justifier;

static struct
{
	int8				pads[4];
}	mp5[2];

static struct
{
	int16				x, y;
	uint8				buttons;
	uint32				ID;
	struct crosshair	crosshair;
}	macsrifle;

static set<struct exemulti *>		exemultis;
static set<uint32>					pollmap[NUMCTLS + 1];
static map<uint32, s9xcommand_t>	keymap;
static vector<s9xcommand_t *>		multis;
static uint8						turbo_time;
static uint8						pseudobuttons[256];
static bool8						FLAG_LATCH = FALSE;
static int32						curcontrollers[2] = { NONE,    NONE };
static int32						newcontrollers[2] = { JOYPAD0, NONE };
static char							buf[256];

static const char	*color_names[32] =
{
	"Trans",
	"Black",
	"25Grey",
	"50Grey",
	"75Grey",
	"White",
	"Red",
	"Orange",
	"Yellow",
	"Green",
	"Cyan",
	"Sky",
	"Blue",
	"Violet",
	"MagicPink",
	"Purple",
	NULL,
	"tBlack",
	"t25Grey",
	"t50Grey",
	"t75Grey",
	"tWhite",
	"tRed",
	"tOrange",
	"tYellow",
	"tGreen",
	"tCyan",
	"tSky",
	"tBlue",
	"tViolet",
	"tMagicPink",
	"tPurple"
};

static const char	*speed_names[4] =
{
	"Var",
	"Slow",
	"Med",
	"Fast"
};

static const int	ptrspeeds[4] = { 1, 1, 4, 8 };

// Note: these should be in asciibetical order!
#define THE_COMMANDS \
	S(BeginRecordingMovie), \
	S(ClipWindows), \
	S(Debugger), \
	S(DecEmuTurbo), \
	S(DecFrameRate), \
	S(DecFrameTime), \
	S(DecTurboSpeed), \
	S(EmuTurbo), \
	S(EndRecordingMovie), \
	S(ExitEmu), \
	S(IncEmuTurbo), \
	S(IncFrameRate), \
	S(IncFrameTime), \
	S(IncTurboSpeed), \
	S(LoadFreezeFile), \
	S(LoadMovie), \
	S(LoadOopsFile), \
	S(Pause), \
	S(QuickLoad000), \
	S(QuickLoad001), \
	S(QuickLoad002), \
	S(QuickLoad003), \
	S(QuickLoad004), \
	S(QuickLoad005), \
	S(QuickLoad006), \
	S(QuickLoad007), \
	S(QuickLoad008), \
	S(QuickLoad009), \
	S(QuickLoad010), \
	S(QuickSave000), \
	S(QuickSave001), \
	S(QuickSave002), \
	S(QuickSave003), \
	S(QuickSave004), \
	S(QuickSave005), \
	S(QuickSave006), \
	S(QuickSave007), \
	S(QuickSave008), \
	S(QuickSave009), \
	S(QuickSave010), \
	S(Reset), \
	S(SaveFreezeFile), \
	S(SaveSPC), \
	S(Screenshot), \
	S(SeekToFrame), \
	S(SoftReset), \
	S(SoundChannel0), \
	S(SoundChannel1), \
	S(SoundChannel2), \
	S(SoundChannel3), \
	S(SoundChannel4), \
	S(SoundChannel5), \
	S(SoundChannel6), \
	S(SoundChannel7), \
	S(SoundChannelsOn), \
	S(SwapJoypads), \
	S(ToggleBG0), \
	S(ToggleBG1), \
	S(ToggleBG2), \
	S(ToggleBG3), \
	S(ToggleBackdrop), \
	S(ToggleEmuTurbo), \
	S(ToggleSprites), \
	S(ToggleTransparency) \

#define S(x)	x

enum command_numbers
{
	THE_COMMANDS,
	LAST_COMMAND
};

#undef S
#define S(x)	#x

static const char	*command_names[LAST_COMMAND + 1] =
{
	THE_COMMANDS,
	NULL
};

#undef S
#undef THE_COMMANDS

static void DisplayStateChange (const char *, bool8);
static void DoGunLatch (int, int);
static void DoMacsRifleLatch (int, int);
static int maptype (int);
static bool strless (const char *, const char *);
static int findstr (const char *, const char **, int);
static int get_threshold (const char **);
static const char * maptypename (int);
static int32 ApplyMulti (s9xcommand_t *, int32, int16);
static void do_polling (int);
static void UpdatePolledMouse (int);


static string& operator += (string &s, int i)
{
	snprintf(buf, sizeof(buf), "%d", i);
	s.append(buf);
	return (s);
}

static string& operator += (string &s, double d)
{
	snprintf(buf, sizeof(buf), "%g", d);
	s.append(buf);
	return (s);
}

static void DisplayStateChange (const char *str, bool8 on)
{
	snprintf(buf, sizeof(buf), "%s: %s", str, on ? "on":"off");
	S9xSetInfoString(buf);
}

static void DoGunLatch (int x, int y)
{
	x += 40;

	if (x > 295)
		x = 295;
	else if (x < 40)
		x = 40;

	if (y > PPU.ScreenHeight - 1)
		y = PPU.ScreenHeight - 1;
	else if (y < 0)
		y = 0;

	PPU.GunVLatch = (uint16) (y + 1);
	PPU.GunHLatch = (uint16) x;
}

static void DoMacsRifleLatch (int x, int y)
{
	PPU.GunVLatch = (uint16) (y + 42);// + (int16) macsrifle.adjust_y;
	PPU.GunHLatch = (uint16) (x + 76);// + (int16) macsrifle.adjust_x;
}

static int maptype (int t)
{
	switch (t)
	{
		case S9xNoMapping:
			return (MAP_NONE);

		case S9xButtonJoypad:
		case S9xButtonMouse:
		case S9xButtonSuperscope:
		case S9xButtonJustifier:
		case S9xButtonMacsRifle:
		case S9xButtonCommand:
		case S9xButtonPseudopointer:
		case S9xButtonPort:
		case S9xButtonMulti:
			return (MAP_BUTTON);

		case S9xAxisJoypad:
		case S9xAxisPseudopointer:
		case S9xAxisPseudobuttons:
		case S9xAxisPort:
			return (MAP_AXIS);

		case S9xPointer:
		case S9xPointerPort:
			return (MAP_POINTER);

		default:
			return (MAP_UNKNOWN);
	}
}

void S9xControlsReset (void)
{
	S9xControlsSoftReset();
	mouse[0].buttons  &= ~0x30;
	mouse[1].buttons  &= ~0x30;
	justifier.buttons &= ~JUSTIFIER_SELECT;
	macsrifle.buttons = 0;
}

void S9xControlsSoftReset (void)
{
	for (set<struct exemulti *>::iterator it = exemultis.begin(); it != exemultis.end(); it++)
		delete *it;
	exemultis.clear();

	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 2; j++)
			read_idx[i][j]=0;

	FLAG_LATCH = FALSE;

	curcontrollers[0] = newcontrollers[0];
	curcontrollers[1] = newcontrollers[1];
}

void S9xUnmapAllControls (void)
{
	S9xControlsReset();

	keymap.clear();

	for (int i = 0; i < (int) multis.size(); i++)
		free(multis[i]);
	multis.clear();

	for (int i = 0; i < NUMCTLS + 1; i++)
		pollmap[i].clear();

	for (int i = 0; i < 8; i++)
	{
		pseudopointer[i].x = 0;
		pseudopointer[i].y = 0;
		pseudopointer[i].H_adj = 0;
		pseudopointer[i].V_adj = 0;
		pseudopointer[i].H_var = 0;
		pseudopointer[i].V_var = 0;
		pseudopointer[i].mapped = false;

		joypad[i].buttons  = 0;
		joypad[i].turbos   = 0;
		joypad[i].turbo_ct = 0;
	}

	for (int i = 0; i < 2; i++)
	{
		mouse[i].old_x = mouse[i].old_y = 0;
		mouse[i].cur_x = mouse[i].cur_y = 0;
		mouse[i].buttons = 1;
		mouse[i].ID = InvalidControlID;

		if (!(mouse[i].crosshair.set & 1))
			mouse[i].crosshair.img = 0; // no image for mouse because its only logical position is game-specific, not known by the emulator
		if (!(mouse[i].crosshair.set & 2))
			mouse[i].crosshair.fg  = 5;
		if (!(mouse[i].crosshair.set & 4))
			mouse[i].crosshair.bg  = 1;

		justifier.x[i] = justifier.y[i] = 0;
		justifier.offscreen[i] = 0;
		justifier.ID[i] = InvalidControlID;

		if (!(justifier.crosshair[i].set & 1))
			justifier.crosshair[i].img = 4;
		if (!(justifier.crosshair[i].set & 2))
			justifier.crosshair[i].fg  = i ? 14 : 12;
		if (!(justifier.crosshair[i].set & 4))
			justifier.crosshair[i].bg  = 1;
	}

	justifier.buttons = 0;

	superscope.x = superscope.y = 0;
	superscope.phys_buttons = 0;
	superscope.next_buttons = 0;
	superscope.read_buttons = 0;
	superscope.ID = InvalidControlID;

	if (!(superscope.crosshair.set & 1))
		superscope.crosshair.img = 2;
	if (!(superscope.crosshair.set & 2))
		superscope.crosshair.fg  = 5;
	if (!(superscope.crosshair.set & 4))
		superscope.crosshair.bg  = 1;

	macsrifle.x = macsrifle.y = 0;
	macsrifle.buttons = 0;
	macsrifle.ID = InvalidControlID;

	if (!(macsrifle.crosshair.set & 1))
		macsrifle.crosshair.img = 2;
	if (!(macsrifle.crosshair.set & 2))
		macsrifle.crosshair.fg  = 5;
	if (!(macsrifle.crosshair.set & 4))
		macsrifle.crosshair.bg  = 1;

	memset(pseudobuttons, 0, sizeof(pseudobuttons));

	turbo_time = 1;
}

void S9xSetController (int port, enum controllers controller, int8 id1, int8 id2, int8 id3, int8 id4)
{
	if (port < 0 || port > 1)
		return;

	switch (controller)
	{
		case CTL_NONE:
			break;

		case CTL_JOYPAD:
			if (id1 < 0 || id1 > 7)
				break;

			newcontrollers[port] = JOYPAD0 + id1;
			return;

		case CTL_MOUSE:
			if (id1 < 0 || id1 > 1)
				break;
			if (!Settings.MouseMaster)
			{
				S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select SNES Mouse: MouseMaster disabled");
				break;
			}

			newcontrollers[port] = MOUSE0 + id1;
			return;

		case CTL_SUPERSCOPE:
			if (!Settings.SuperScopeMaster)
			{
				S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select SNES Superscope: SuperScopeMaster disabled");
				break;
			}

			newcontrollers[port] = SUPERSCOPE;
			return;

		case CTL_JUSTIFIER:
			if (id1 < 0 || id1 > 1)
				break;
			if (!Settings.JustifierMaster)
			{
				S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select Konami Justifier: JustifierMaster disabled");
				break;
			}

			newcontrollers[port] = ONE_JUSTIFIER + id1;
			return;

		case CTL_MACSRIFLE:
			if (!Settings.MacsRifleMaster)
			{
				S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select SNES M.A.C.S. Rifle: MacsRifleMaster disabled");
				break;
			}

			newcontrollers[port] = MACSRIFLE;
			return;

		case CTL_MP5:
			if (id1 < -1 || id1 > 7)
				break;
			if (id2 < -1 || id2 > 7)
				break;
			if (id3 < -1 || id3 > 7)
				break;
			if (id4 < -1 || id4 > 7)
				break;
			if (!Settings.MultiPlayer5Master)
			{
				S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select MP5: MultiPlayer5Master disabled");
				break;
			}

			newcontrollers[port] = MP5;
			mp5[port].pads[0] = (id1 < 0) ? NONE : JOYPAD0 + id1;
			mp5[port].pads[1] = (id2 < 0) ? NONE : JOYPAD0 + id2;
			mp5[port].pads[2] = (id3 < 0) ? NONE : JOYPAD0 + id3;
			mp5[port].pads[3] = (id4 < 0) ? NONE : JOYPAD0 + id4;
			return;

		default:
			fprintf(stderr, "Unknown controller type %d\n", controller);
			break;
	}

	newcontrollers[port] = NONE;
}

bool S9xVerifyControllers (void)
{
	bool	ret = false;
	int		port, i, used[NUMCTLS];

	for (i = 0; i < NUMCTLS; used[i++] = 0) ;

	for (port = 0; port < 2; port++)
	{
		switch (i = newcontrollers[port])
		{
			case MOUSE0:
			case MOUSE1:
				if (!Settings.MouseMaster)
				{
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select SNES Mouse: MouseMaster disabled");
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				if (used[i]++ > 0)
				{
					snprintf(buf, sizeof(buf), "Mouse%d used more than once! Disabling extra instances", i - MOUSE0 + 1);
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, buf);
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				break;

			case SUPERSCOPE:
				if (!Settings.SuperScopeMaster)
				{
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select SNES Superscope: SuperScopeMaster disabled");
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				if (used[i]++ > 0)
				{
					snprintf(buf, sizeof(buf), "Superscope used more than once! Disabling extra instances");
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, buf);
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				break;

			case ONE_JUSTIFIER:
			case TWO_JUSTIFIERS:
				if (!Settings.JustifierMaster)
				{
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select Konami Justifier: JustifierMaster disabled");
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				if (used[ONE_JUSTIFIER]++ > 0)
				{
					snprintf(buf, sizeof(buf), "Justifier used more than once! Disabling extra instances");
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, buf);
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				break;

			case MACSRIFLE:
				if (!Settings.MacsRifleMaster)
				{
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select SNES M.A.C.S. Rifle: MacsRifleMaster disabled");
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				if (used[i]++ > 0)
				{
					snprintf(buf, sizeof(buf), "M.A.C.S. Rifle used more than once! Disabling extra instances");
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, buf);
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				break;

			case MP5:
				if (!Settings.MultiPlayer5Master)
				{
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, "Cannot select MP5: MultiPlayer5Master disabled");
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				for (i = 0; i < 4; i++)
				{
					if (mp5[port].pads[i] != NONE)
					{
						if (used[mp5[port].pads[i] - JOYPAD0]++ > 0)
						{
							snprintf(buf, sizeof(buf), "Joypad%d used more than once! Disabling extra instances", mp5[port].pads[i] - JOYPAD0 + 1);
							S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, buf);
							mp5[port].pads[i] = NONE;
							ret = true;
							break;
						}
					}
				}

				break;

			case JOYPAD0:
			case JOYPAD1:
			case JOYPAD2:
			case JOYPAD3:
			case JOYPAD4:
			case JOYPAD5:
			case JOYPAD6:
			case JOYPAD7:
				if (used[i - JOYPAD0]++ > 0)
				{
					snprintf(buf, sizeof(buf), "Joypad%d used more than once! Disabling extra instances", i - JOYPAD0 + 1);
					S9xMessage(S9X_CONFIG_INFO, S9X_ERROR, buf);
					newcontrollers[port] = NONE;
					ret = true;
					break;
				}

				break;

			default:
				break;
		}
	}

	return (ret);
}

void S9xGetController (int port, enum controllers *controller, int8 *id1, int8 *id2, int8 *id3, int8 *id4)
{
	int	i;

	*controller = CTL_NONE;
	*id1 = *id2 = *id3 = *id4 = -1;

	if (port < 0 || port > 1)
		return;

	switch (i = newcontrollers[port])
	{
		case MP5:
			*controller = CTL_MP5;
			*id1 = (mp5[port].pads[0] == NONE) ? -1 : mp5[port].pads[0] - JOYPAD0;
			*id2 = (mp5[port].pads[1] == NONE) ? -1 : mp5[port].pads[1] - JOYPAD0;
			*id3 = (mp5[port].pads[2] == NONE) ? -1 : mp5[port].pads[2] - JOYPAD0;
			*id4 = (mp5[port].pads[3] == NONE) ? -1 : mp5[port].pads[3] - JOYPAD0;
			return;

		case JOYPAD0:
		case JOYPAD1:
		case JOYPAD2:
		case JOYPAD3:
		case JOYPAD4:
		case JOYPAD5:
		case JOYPAD6:
		case JOYPAD7:
			*controller = CTL_JOYPAD;
			*id1 = i - JOYPAD0;
			return;

		case MOUSE0:
		case MOUSE1:
			*controller = CTL_MOUSE;
			*id1 = i - MOUSE0;
			return;

		case SUPERSCOPE:
			*controller = CTL_SUPERSCOPE;
			*id1 = 1;
			return;

		case ONE_JUSTIFIER:
		case TWO_JUSTIFIERS:
			*controller = CTL_JUSTIFIER;
			*id1 = i - ONE_JUSTIFIER;
			return;

		case MACSRIFLE:
			*controller = CTL_MACSRIFLE;
			*id1 = 1;
			return;
	}
}

void S9xReportControllers (void)
{
	static char	mes[128];
	char		*c = mes;

	S9xVerifyControllers();

	for (int port = 0; port < 2; port++)
	{
		c += sprintf(c, "Port %d: ", port + 1);

		switch (newcontrollers[port])
		{
			case NONE:
				c += sprintf(c, "<none>. ");
				break;

			case MP5:
				c += sprintf(c, "MP5 with pads");
				for (int i = 0; i < 4; i++)
				{
					if (mp5[port].pads[i] == NONE)
						c += sprintf(c, " <none>. ");
					else
						c += sprintf(c, " #%d. ", mp5[port].pads[i] + 1 - JOYPAD0);
				}

				break;

			case JOYPAD0:
			case JOYPAD1:
			case JOYPAD2:
			case JOYPAD3:
			case JOYPAD4:
			case JOYPAD5:
			case JOYPAD6:
			case JOYPAD7:
				c += sprintf(c, "Pad #%d. ", (int) (newcontrollers[port] - JOYPAD0 + 1));
				break;

			case MOUSE0:
			case MOUSE1:
				c += sprintf(c, "Mouse #%d. ", (int) (newcontrollers[port] - MOUSE0 + 1));
				break;

			case SUPERSCOPE:
				if (port == 0)
					c += sprintf(c, "Superscope (cannot fire). ");
				else
					c += sprintf(c, "Superscope. ");
				break;

			case ONE_JUSTIFIER:
				if (port == 0)
					c += sprintf(c, "Blue Justifier (cannot fire). ");
				else
					c += sprintf(c, "Blue Justifier. ");
				break;

			case TWO_JUSTIFIERS:
				if (port == 0)
					c += sprintf(c, "Blue and Pink Justifiers (cannot fire). ");
				else
					c += sprintf(c, "Blue and Pink Justifiers. ");
				break;

			case MACSRIFLE:
				if (port == 0)
					c += sprintf(c, "M.A.C.S. Rifle (cannot fire). ");
				else
					c += sprintf(c, "M.A.C.S. Rifle. ");
				break;
		}
	}

	S9xMessage(S9X_INFO, S9X_CONFIG_INFO, mes);
}

char * S9xGetCommandName (s9xcommand_t command)
{
	string	s;
	char	c;

	switch (command.type)
	{
		case S9xButtonJoypad:
			if (command.button.joypad.buttons == 0)
				return (strdup("None"));
			if (command.button.joypad.buttons & 0x000f)
				return (strdup("None"));

			s = "Joypad";
			s += command.button.joypad.idx + 1;

			c = ' ';
			if (command.button.joypad.toggle)	{ if (c) s += c; s += "Toggle"; c = 0; }
			if (command.button.joypad.sticky)	{ if (c) s += c; s += "Sticky"; c = 0; }
			if (command.button.joypad.turbo )	{ if (c) s += c; s += "Turbo";  c = 0; }

			c = ' ';
			if (command.button.joypad.buttons & SNES_UP_MASK    )	{ s += c; s += "Up";     c = '+'; }
			if (command.button.joypad.buttons & SNES_DOWN_MASK  )	{ s += c; s += "Down";   c = '+'; }
			if (command.button.joypad.buttons & SNES_LEFT_MASK  )	{ s += c; s += "Left";   c = '+'; }
			if (command.button.joypad.buttons & SNES_RIGHT_MASK )	{ s += c; s += "Right";  c = '+'; }
			if (command.button.joypad.buttons & SNES_A_MASK     )	{ s += c; s += "A";      c = '+'; }
			if (command.button.joypad.buttons & SNES_B_MASK     )	{ s += c; s += "B";      c = '+'; }
			if (command.button.joypad.buttons & SNES_X_MASK     )	{ s += c; s += "X";      c = '+'; }
			if (command.button.joypad.buttons & SNES_Y_MASK     )	{ s += c; s += "Y";      c = '+'; }
			if (command.button.joypad.buttons & SNES_TL_MASK    )	{ s += c; s += "L";      c = '+'; }
			if (command.button.joypad.buttons & SNES_TR_MASK    )	{ s += c; s += "R";      c = '+'; }
			if (command.button.joypad.buttons & SNES_START_MASK )	{ s += c; s += "Start";  c = '+'; }
			if (command.button.joypad.buttons & SNES_SELECT_MASK)	{ s += c; s += "Select"; c = '+'; }

			break;

		case S9xButtonMouse:
			if (!command.button.mouse.left && !command.button.mouse.right)
				return (strdup("None"));

			s = "Mouse";
			s += command.button.mouse.idx + 1;
			s += " ";

			if (command.button.mouse.left )	s += "L";
			if (command.button.mouse.right)	s += "R";

			break;

		case S9xButtonSuperscope:
			if (!command.button.scope.fire && !command.button.scope.cursor && !command.button.scope.turbo && !command.button.scope.pause && !command.button.scope.aim_offscreen)
				return (strdup("None"));

			s = "Superscope";

			if (command.button.scope.aim_offscreen)	s += " AimOffscreen";

			c = ' ';
			if (command.button.scope.fire  )	{ s += c; s += "Fire";        c = '+'; }
			if (command.button.scope.cursor)	{ s += c; s += "Cursor";      c = '+'; }
			if (command.button.scope.turbo )	{ s += c; s += "ToggleTurbo"; c = '+'; }
			if (command.button.scope.pause )	{ s += c; s += "Pause";       c = '+'; }

			break;

		case S9xButtonJustifier:
			if (!command.button.justifier.trigger && !command.button.justifier.start && !command.button.justifier.aim_offscreen)
				return (strdup("None"));

			s = "Justifier";
			s += command.button.justifier.idx + 1;

			if (command.button.justifier.aim_offscreen)	s += " AimOffscreen";

			c = ' ';
			if (command.button.justifier.trigger)	{ s += c; s += "Trigger"; c = '+'; }
			if (command.button.justifier.start  )	{ s += c; s += "Start";   c = '+'; }

			break;

		case S9xButtonMacsRifle:
			if (!command.button.macsrifle.trigger)
				return (strdup("None"));

			s = "MacsRifle";

			c = ' ';
			if (command.button.macsrifle.trigger)	{ s += c; s += "Trigger"; c = '+'; }

			break;

		case S9xButtonCommand:
			if (command.button.command >= LAST_COMMAND)
				return (strdup("None"));

			return (strdup(command_names[command.button.command]));

		case S9xPointer:
			if (!command.pointer.aim_mouse0 && !command.pointer.aim_mouse1 && !command.pointer.aim_scope && !command.pointer.aim_justifier0 && !command.pointer.aim_justifier1 && !command.pointer.aim_macsrifle)
				return (strdup("None"));

			s = "Pointer";

			c = ' ';
			if (command.pointer.aim_mouse0    )	{ s += c; s += "Mouse1";     c = '+'; }
			if (command.pointer.aim_mouse1    )	{ s += c; s += "Mouse2";     c = '+'; }
			if (command.pointer.aim_scope     )	{ s += c; s += "Superscope"; c = '+'; }
			if (command.pointer.aim_justifier0)	{ s += c; s += "Justifier1"; c = '+'; }
			if (command.pointer.aim_justifier1)	{ s += c; s += "Justifier2"; c = '+'; }
			if (command.pointer.aim_macsrifle)  { s += c; s += "MacsRifle";  c = '+'; }

			break;

		case S9xButtonPseudopointer:
			if (!command.button.pointer.UD && !command.button.pointer.LR)
				return (strdup("None"));
			if (command.button.pointer.UD == -2 || command.button.pointer.LR == -2)
				return (strdup("None"));

			s = "ButtonToPointer ";
			s += command.button.pointer.idx + 1;

			if (command.button.pointer.UD)	s += (command.button.pointer.UD == 1) ? 'd' : 'u';
			if (command.button.pointer.LR)	s += (command.button.pointer.LR == 1) ? 'r' : 'l';

			s += " ";
			s += speed_names[command.button.pointer.speed_type];

			break;

		case S9xAxisJoypad:
			s = "Joypad";
			s += command.axis.joypad.idx + 1;
			s += " Axis ";

			switch (command.axis.joypad.axis)
			{
				case 0:	s += (command.axis.joypad.invert ? "Right/Left" : "Left/Right");	break;
				case 1:	s += (command.axis.joypad.invert ? "Down/Up"    : "Up/Down"   );	break;
				case 2:	s += (command.axis.joypad.invert ? "A/Y"        : "Y/A"       );	break;
				case 3:	s += (command.axis.joypad.invert ? "B/X"        : "X/B"       );	break;
				case 4:	s += (command.axis.joypad.invert ? "R/L"        : "L/R"       );	break;
				default:	return (strdup("None"));
			}

			s += " T=";
			s += int((command.axis.joypad.threshold + 1) * 1000 / 256) / 10.0;
			s += "%";

			break;

		case S9xAxisPseudopointer:
			s = "AxisToPointer ";
			s += command.axis.pointer.idx + 1;
			s += command.axis.pointer.HV ? 'v' : 'h';
			s += " ";

			if (command.axis.pointer.invert)	s += "-";

			s += speed_names[command.axis.pointer.speed_type];

			break;

		case S9xAxisPseudobuttons:
			s = "AxisToButtons ";
			s += command.axis.button.negbutton;
			s += "/";
			s += command.axis.button.posbutton;
			s += " T=";
			s += int((command.axis.button.threshold + 1) * 1000 / 256) / 10.0;
			s += "%";

			break;

		case S9xButtonPort:
		case S9xAxisPort:
		case S9xPointerPort:
			return (strdup("BUG: Port should have handled this instead of calling S9xGetCommandName()"));

		case S9xNoMapping:
			return (strdup("None"));

		case S9xButtonMulti:
		{
			if (command.button.multi_idx >= (int) multis.size())
				return (strdup("None"));

			s = "{";
			if (multis[command.button.multi_idx]->multi_press)	s = "+{";

			bool	sep = false;

			for (s9xcommand_t *m = multis[command.button.multi_idx]; m->multi_press != 3; m++)
			{
				if (m->type == S9xNoMapping)
				{
					s += ";";
					sep = false;
				}
				else
				{
					if (sep)					s += ",";
					if (m->multi_press == 1)	s += "+";
					if (m->multi_press == 2)	s += "-";

					s += S9xGetCommandName(*m);
					sep = true;
				}
			}

			s += "}";

			break;
		}

		default:
			return (strdup("BUG: Unknown command type"));
	}

	return (strdup(s.c_str()));
}

static bool strless (const char *a, const char *b)
{
	return (strcmp(a, b) < 0);
}

static int findstr (const char *needle, const char **haystack, int numstr)
{
	const char	**r;

	r = lower_bound(haystack, haystack + numstr, needle, strless);
	if (r >= haystack + numstr || strcmp(needle, *r))
		return (-1);

	return (r - haystack);
}

static int get_threshold (const char **ss)
{
	const char	*s = *ss;
	int			i;

	if (s[0] != 'T' || s[1] != '=')
		return (-1);

	s += 2;
	i = 0;

	if (s[0] == '0')
	{
		if (s[1] != '.')
			return (-1);

		s++;
	}
	else
	{
		do
		{
			if (*s < '0' || *s > '9')
				return (-1);

			i = i * 10 + 10 * (*s - '0');
			if (i > 1000)
				return (-1);

			s++;
		}
		while (*s != '.' && *s != '%');
	}

	if (*s == '.')
	{
		if (s[1] < '0' || s[1] > '9' || s[2] != '%')
			return (-1);

		i += s[1] - '0';
	}

	if (i > 1000)
		return (-1);

	*ss = s;

	return (i);
}

s9xcommand_t S9xGetCommandT (const char *name)
{
	s9xcommand_t	cmd;
	int				i, j;
	const char		*s;

	memset(&cmd, 0, sizeof(cmd));
	cmd.type         = S9xBadMapping;
	cmd.multi_press  = 0;
	cmd.button_norpt = 0;

	if (!strcmp(name, "None"))
		cmd.type = S9xNoMapping;
	else if (!strncmp(name, "Joypad", 6))
	{
		if (name[6] < '1' || name[6] > '8' || name[7] != ' ')
			return (cmd);

		if (!strncmp(name + 8, "Axis ", 5))
		{
			cmd.axis.joypad.idx = name[6] - '1';
			s = name + 13;

			if (!strncmp(s, "Left/Right ", 11))	{ j = 0; i = 0; s += 11; }
			else if (!strncmp(s, "Right/Left ", 11))	{ j = 0; i = 1; s += 11; }
			else if (!strncmp(s, "Up/Down ",     8))	{ j = 1; i = 0; s +=  8; }
			else if (!strncmp(s, "Down/Up ",     8))	{ j = 1; i = 1; s +=  8; }
			else if (!strncmp(s, "Y/A ",         4))	{ j = 2; i = 0; s +=  4; }
			else if (!strncmp(s, "A/Y ",         4))	{ j = 2; i = 1; s +=  4; }
			else if (!strncmp(s, "X/B ",         4))	{ j = 3; i = 0; s +=  4; }
			else if (!strncmp(s, "B/X ",         4))	{ j = 3; i = 1; s +=  4; }
			else if (!strncmp(s, "L/R ",         4))	{ j = 4; i = 0; s +=  4; }
			else if (!strncmp(s, "R/L ",         4))	{ j = 4; i = 1; s +=  4; }
			else
				return (cmd);

			cmd.axis.joypad.axis      = j;
			cmd.axis.joypad.invert    = i;
			i = get_threshold(&s);
			if (i < 0)
				return (cmd);
			cmd.axis.joypad.threshold = (i - 1) * 256 / 1000;

			cmd.type = S9xAxisJoypad;
		}
		else
		{
			cmd.button.joypad.idx = name[6] - '1';
			s = name + 8;
			i = 0;

			if ((cmd.button.joypad.toggle = strncmp(s, "Toggle", 6) ? 0 : 1))	s += i = 6;
			if ((cmd.button.joypad.sticky = strncmp(s, "Sticky", 6) ? 0 : 1))	s += i = 6;
			if ((cmd.button.joypad.turbo  = strncmp(s, "Turbo",  5) ? 0 : 1))	s += i = 5;

			if (cmd.button.joypad.toggle && !(cmd.button.joypad.sticky || cmd.button.joypad.turbo))
				return (cmd);

			if (i)
			{
				if (*s != ' ')
					return (cmd);
				s++;
			}

			i = 0;

			if (!strncmp(s, "Up",     2))	{ i |= SNES_UP_MASK;     s += 2; if (*s == '+') s++; }
			if (!strncmp(s, "Down",   4))	{ i |= SNES_DOWN_MASK;   s += 4; if (*s == '+') s++; }
			if (!strncmp(s, "Left",   4))	{ i |= SNES_LEFT_MASK;   s += 4; if (*s == '+') s++; }
			if (!strncmp(s, "Right",  5))	{ i |= SNES_RIGHT_MASK;  s += 5; if (*s == '+') s++; }

			if (*s == 'A')	{ i |= SNES_A_MASK;  s++; if (*s == '+') s++; }
			if (*s == 'B')	{ i |= SNES_B_MASK;  s++; if (*s == '+') s++; }
			if (*s == 'X')	{ i |= SNES_X_MASK;  s++; if (*s == '+') s++; }
			if (*s == 'Y')	{ i |= SNES_Y_MASK;  s++; if (*s == '+') s++; }
			if (*s == 'L')	{ i |= SNES_TL_MASK; s++; if (*s == '+') s++; }
			if (*s == 'R')	{ i |= SNES_TR_MASK; s++; if (*s == '+') s++; }

			if (!strncmp(s, "Start",  5))	{ i |= SNES_START_MASK;  s += 5; if (*s == '+') s++; }
			if (!strncmp(s, "Select", 6))	{ i |= SNES_SELECT_MASK; s += 6; }

			if (i == 0 || *s != 0 || *(s - 1) == '+')
				return (cmd);

			cmd.button.joypad.buttons = i;

			cmd.type = S9xButtonJoypad;
		}
	}
	else
	if (!strncmp(name, "Mouse", 5))
	{
		if (name[5] < '1' || name[5] > '2' || name[6] != ' ')
			return (cmd);

		cmd.button.mouse.idx = name[5] - '1';
		s = name + 7;
		i = 0;

		if ((cmd.button.mouse.left  = (*s == 'L')))	s += i = 1;
		if ((cmd.button.mouse.right = (*s == 'R')))	s += i = 1;

		if (i == 0 || *s != 0)
			return (cmd);

		cmd.type = S9xButtonMouse;
	}
	else
	if (!strncmp(name, "Superscope ", 11))
	{
		s = name + 11;
		i = 0;

		if ((cmd.button.scope.aim_offscreen     = strncmp(s, "AimOffscreen", 12) ? 0 : 1))	{ s += i = 12; if (*s == ' ') s++; else if (*s != 0) return (cmd); }
		if ((cmd.button.scope.fire              = strncmp(s, "Fire",          4) ? 0 : 1))	{ s += i =  4; if (*s == '+') s++; }
		if ((cmd.button.scope.cursor            = strncmp(s, "Cursor",        6) ? 0 : 1))	{ s += i =  6; if (*s == '+') s++; }
		if ((cmd.button.scope.turbo             = strncmp(s, "ToggleTurbo",  11) ? 0 : 1))	{ s += i = 11; if (*s == '+') s++; }
		if ((cmd.button.scope.pause             = strncmp(s, "Pause",         5) ? 0 : 1))	{ s += i =  5; }

		if (i == 0 || *s != 0 || *(s - 1) == '+')
			return (cmd);

		cmd.type = S9xButtonSuperscope;
	}
	else
	if (!strncmp(name, "Justifier", 9))
	{
		if (name[9] < '1' || name[9] > '2' || name[10] != ' ')
			return (cmd);

		cmd.button.justifier.idx = name[9] - '1';
		s = name + 11;
		i = 0;

		if ((cmd.button.justifier.aim_offscreen = strncmp(s, "AimOffscreen", 12) ? 0 : 1))	{ s += i = 12; if (*s == ' ') s++; else if (*s != 0) return (cmd); }
		if ((cmd.button.justifier.trigger       = strncmp(s, "Trigger",       7) ? 0 : 1))	{ s += i =  7; if (*s == '+') s++; }
		if ((cmd.button.justifier.start         = strncmp(s, "Start",         5) ? 0 : 1))	{ s += i =  5; }

		if (i == 0 || *s != 0 || *(s - 1) == '+')
			return (cmd);

		cmd.type = S9xButtonJustifier;
	}
	else
	if (!strncmp(name, "MacsRifle ", 10))
	{
		s = name + 10;
		i = 0;

		if ((cmd.button.macsrifle.trigger = strncmp(s, "Trigger", 7) ? 0 : 1))	{ s += i =  7; }

		if (i == 0 || *s != 0 || *(s - 1) == '+')
			return (cmd);

		cmd.type = S9xButtonMacsRifle;
	}
	else
	if (!strncmp(name, "Pointer ", 8))
	{
		s = name + 8;
		i = 0;

		if ((cmd.pointer.aim_mouse0     = strncmp(s, "Mouse1",      6) ? 0 : 1))	{ s += i =  6; if (*s == '+') s++; }
		if ((cmd.pointer.aim_mouse1     = strncmp(s, "Mouse2",      6) ? 0 : 1))	{ s += i =  6; if (*s == '+') s++; }
		if ((cmd.pointer.aim_scope      = strncmp(s, "Superscope", 10) ? 0 : 1))	{ s += i = 10; if (*s == '+') s++; }
		if ((cmd.pointer.aim_justifier0 = strncmp(s, "Justifier1", 10) ? 0 : 1))	{ s += i = 10; if (*s == '+') s++; }
		if ((cmd.pointer.aim_justifier1 = strncmp(s, "Justifier2", 10) ? 0 : 1))	{ s += i = 10; if (*s == '+') s++; }
		if ((cmd.pointer.aim_macsrifle  = strncmp(s, "MacsRifle",   9) ? 0 : 1))	{ s += i =  9; }

		if (i == 0 || *s != 0 || *(s - 1) == '+')
			return (cmd);

		cmd.type = S9xPointer;
	}
	else
	if (!strncmp(name, "ButtonToPointer ", 16))
	{
		if (name[16] < '1' || name[16] > '8')
			return (cmd);

		cmd.button.pointer.idx = name[16] - '1';
		s = name + 17;
		i = 0;

		if ((cmd.button.pointer.UD = (*s == 'u' ? -1 : (*s == 'd' ? 1 : 0))))	s += i = 1;
		if ((cmd.button.pointer.LR = (*s == 'l' ? -1 : (*s == 'r' ? 1 : 0))))	s += i = 1;

		if (i == 0 || *(s++) != ' ')
			return (cmd);

		for (i = 0; i < 4; i++)
			if (!strcmp(s, speed_names[i]))
				break;
		if (i > 3)
			return (cmd);

		cmd.button.pointer.speed_type = i;

		cmd.type = S9xButtonPseudopointer;
	}
	else
	if (!strncmp(name, "AxisToPointer ", 14))
	{
		if (name[14] < '1' || name[14] > '8')
			return (cmd);

		cmd.axis.pointer.idx = name[14] - '1';
		s= name + 15;
		i = 0;

		if (*s == 'h')
			cmd.axis.pointer.HV = 0;
		else if (*s == 'v')
			cmd.axis.pointer.HV = 1;
		else
			return (cmd);

		if (s[1] != ' ')
			return (cmd);

		s += 2;
		if ((cmd.axis.pointer.invert = *s == '-'))
			s++;

		for (i = 0; i < 4; i++)
			if (!strcmp(s, speed_names[i]))
				break;
		if (i > 3)
			return (cmd);

		cmd.axis.pointer.speed_type = i;

		cmd.type = S9xAxisPseudopointer;
	}
	else
	if (!strncmp(name, "AxisToButtons ", 14))
	{
		s = name + 14;

		if (s[0] == '0')
		{
			if (s[1] != '/')
				return (cmd);

			cmd.axis.button.negbutton = 0;
			s += 2;
		}
		else
		{
			i = 0;
			do
			{
				if (*s < '0' || *s > '9')
					return (cmd);

				i = i * 10 + *s - '0';
				if (i > 255)
					return (cmd);
			}
			while (*++s != '/');

			cmd.axis.button.negbutton = i;
			s++;
		}

		if (s[0] == '0')
		{
			if (s[1] != ' ')
				return (cmd);

			cmd.axis.button.posbutton = 0;
			s += 2;
		}
		else
		{
			i = 0;
			do
			{
				if (*s < '0' || *s > '9')
					return (cmd);

				i = i * 10 + *s - '0';
				if (i > 255)
					return (cmd);
			}
			while (*++s != ' ');

			cmd.axis.button.posbutton = i;
			s++;
		}

		i = get_threshold(&s);
		if (i < 0)
			return (cmd);
		cmd.axis.button.threshold = (i - 1) * 256 / 1000;

		cmd.type = S9xAxisPseudobuttons;
	}
	else
	if (!strncmp(name, "MULTI#", 6))
	{
		i = strtol(name + 6, (char **) &s, 10);
		if (s != NULL && *s != '\0')
			return (cmd);
		if (i >= (int) multis.size())
			return (cmd);

		cmd.button.multi_idx = i;
		cmd.type = S9xButtonMulti;
	}
	else
	if (((name[0] == '+' && name[1] == '{') || name[0] == '{') && name[strlen(name) - 1] == '}')
	{
		if (multis.size() > 2147483640)
		{
			fprintf(stderr, "Too many multis!");
			return (cmd);
		}

		string	x;
		int		n;

		j = 2;
		for (i = (name[0] == '+') ? 2 : 1; name[i] != '\0'; i++)
		{
			if (name[i] == ',' || name[i] == ';')
			{
				if (name[i] == ';')
					j++;
				if (++j > 2147483640)
				{
					fprintf(stderr, "Multi too long!");
					return (cmd);
				}
			}

			if (name[i] == '{')
				return (cmd);
		}

		s9xcommand_t	*c = (s9xcommand_t *) calloc(j, sizeof(s9xcommand_t));
		if (c == NULL)
		{
			perror("malloc error while parsing multi");
			return (cmd);
		}

		n = 0;
		i = (name[0] == '+') ? 2 : 1;

		do
		{
			if (name[i] == ';')
			{
				c[n].type         = S9xNoMapping;
				c[n].multi_press  = 0;
				c[n].button_norpt = 0;

				j = i;
			}
			else if (name[i] == ',')
			{
				free(c);
				return (cmd);
			}
			else
			{
				uint8	press = 0;

				if (name[0] == '+')
				{
					if (name[i] == '+')
						press = 1;
					else if (name[i] == '-')
						press = 2;
					else
					{
						free(c);
						return (cmd);
					}

					i++;
				}

				for (j = i; name[j] != ';' && name[j] != ',' && name[j] != '}'; j++) ;

				x.assign(name + i, j - i);
				c[n] = S9xGetCommandT(x.c_str());
				c[n].multi_press = press;

				if (maptype(c[n].type) != MAP_BUTTON)
				{
					free(c);
					return (cmd);
				}

				if (name[j] == ';')
					j--;
			}

			i = j + 1;
			n++;
		}
		while (name[i] != '\0');

		c[n].type        = S9xNoMapping;
		c[n].multi_press = 3;

		multis.push_back(c);

		cmd.button.multi_idx = multis.size() - 1;
		cmd.type = S9xButtonMulti;
	}
	else
	{
		i = findstr(name, command_names, LAST_COMMAND);
		if (i < 0)
			return (cmd);

		cmd.type = S9xButtonCommand;
		cmd.button.command = i;
	}

	return (cmd);
}

const char ** S9xGetAllSnes9xCommands (void)
{
	return (command_names);
}

s9xcommand_t S9xGetMapping (uint32 id)
{
	if (keymap.count(id) == 0)
	{
		s9xcommand_t	cmd;
		cmd.type = S9xNoMapping;
		return (cmd);
	}
	else
		return (keymap[id]);
}

static const char * maptypename (int t)
{
	switch (t)
	{
		case MAP_NONE:		return ("unmapped");
		case MAP_BUTTON:	return ("button");
		case MAP_AXIS:		return ("axis");
		case MAP_POINTER:	return ("pointer");
		default:			return ("unknown");
	}
}

void S9xUnmapID (uint32 id)
{
	for (int i = 0; i < NUMCTLS + 1; i++)
		pollmap[i].erase(id);

	if (mouse[0].ID     == id)	mouse[0].ID     = InvalidControlID;
	if (mouse[1].ID     == id)	mouse[1].ID     = InvalidControlID;
	if (superscope.ID   == id)	superscope.ID   = InvalidControlID;
	if (justifier.ID[0] == id)	justifier.ID[0] = InvalidControlID;
	if (justifier.ID[1] == id)	justifier.ID[1] = InvalidControlID;
	if (macsrifle.ID    == id)	macsrifle.ID    = InvalidControlID;

	if (id >= PseudoPointerBase)
		pseudopointer[id - PseudoPointerBase].mapped = false;

	keymap.erase(id);
}

bool S9xMapButton (uint32 id, s9xcommand_t mapping, bool poll)
{
	int	t;

	if (id == InvalidControlID)
	{
		fprintf(stderr, "Cannot map InvalidControlID\n");
		return (false);
	}

	t = maptype(mapping.type);

	if (t == MAP_NONE)
	{
		S9xUnmapID(id);
		return (true);
	}

	if (t != MAP_BUTTON)
		return (false);

	t = maptype(S9xGetMapping(id).type);

	if (t != MAP_NONE && t != MAP_BUTTON)
		fprintf(stderr, "WARNING: Remapping ID 0x%08x from %s to button\n", id, maptypename(t));

	if (id >= PseudoPointerBase)
	{
		fprintf(stderr, "ERROR: Refusing to map pseudo-pointer #%d as a button\n", id - PseudoPointerBase);
		return (false);
	}

	t = -1;

	if (poll)
	{
		if (id >= PseudoButtonBase)
			fprintf(stderr, "INFO: Ignoring attempt to set pseudo-button #%d to polling\n", id - PseudoButtonBase);
		else
		{
			switch (mapping.type)
			{
				case S9xButtonJoypad:
					t = JOYPAD0 + mapping.button.joypad.idx;
					break;

				case S9xButtonMouse:
					t = MOUSE0 + mapping.button.mouse.idx;
					break;

				case S9xButtonSuperscope:
					t = SUPERSCOPE;
					break;

				case S9xButtonJustifier:
					t = ONE_JUSTIFIER + mapping.button.justifier.idx;
					break;

				case S9xButtonMacsRifle:
					t = MACSRIFLE;
					break;

				case S9xButtonCommand:
				case S9xButtonPseudopointer:
				case S9xButtonPort:
				case S9xButtonMulti:
					t = POLL_ALL;
					break;
			}
		}
	}

	S9xUnmapID(id);

	keymap[id] = mapping;

	if (t >= 0)
		pollmap[t].insert(id);

	return (true);
}

void S9xReportButton (uint32 id, bool pressed)
{
	if (keymap.count(id) == 0)
		return;

	if (keymap[id].type == S9xNoMapping)
		return;

	if (maptype(keymap[id].type) != MAP_BUTTON)
	{
		fprintf(stderr, "ERROR: S9xReportButton called on %s ID 0x%08x\n", maptypename(maptype(keymap[id].type)), id);
		return;
	}

	if (keymap[id].type == S9xButtonCommand)	// skips the "already-pressed check" unless it's a command, as a hack to work around the following problem:
		if (keymap[id].button_norpt == pressed)	// FIXME: this makes the controls "stick" after loading a savestate while recording a movie and holding any button
			return;

	keymap[id].button_norpt = pressed;

	S9xApplyCommand(keymap[id], pressed, 0);
}

bool S9xMapPointer (uint32 id, s9xcommand_t mapping, bool poll)
{
	int	t;

	if (id == InvalidControlID)
	{
		fprintf(stderr, "Cannot map InvalidControlID\n");
		return (false);
	}

	t = maptype(mapping.type);

	if (t == MAP_NONE)
	{
		S9xUnmapID(id);
		return (true);
	}

	if (t != MAP_POINTER)
		return (false);

	t = maptype(S9xGetMapping(id).type);

	if (t != MAP_NONE && t != MAP_POINTER)
		fprintf(stderr, "WARNING: Remapping ID 0x%08x from %s to pointer\n", id, maptypename(t));

	if (id < PseudoPointerBase && id >= PseudoButtonBase)
	{
		fprintf(stderr, "ERROR: Refusing to map pseudo-button #%d as a pointer\n", id - PseudoButtonBase);
		return (false);
	}

	if (mapping.type == S9xPointer)
	{
		if (mapping.pointer.aim_mouse0 && mouse[0].ID != InvalidControlID && mouse[0].ID != id)
		{
			fprintf(stderr, "ERROR: Rejecting attempt to control Mouse1 with two pointers\n");
			return (false);
		}

		if (mapping.pointer.aim_mouse1 && mouse[1].ID != InvalidControlID && mouse[1].ID != id)
		{
			fprintf(stderr, "ERROR: Rejecting attempt to control Mouse2 with two pointers\n");
			return (false);
		}

		if (mapping.pointer.aim_scope && superscope.ID != InvalidControlID && superscope.ID != id)
		{
			fprintf(stderr, "ERROR: Rejecting attempt to control SuperScope with two pointers\n");
			return (false);
		}

		if (mapping.pointer.aim_justifier0 && justifier.ID[0] != InvalidControlID && justifier.ID[0] != id)
		{
			fprintf(stderr, "ERROR: Rejecting attempt to control Justifier1 with two pointers\n");
			return (false);
		}

		if (mapping.pointer.aim_justifier1 && justifier.ID[1] != InvalidControlID && justifier.ID[1] != id)
		{
			fprintf(stderr, "ERROR: Rejecting attempt to control Justifier2 with two pointers\n");
			return (false);
		}

		if (mapping.pointer.aim_macsrifle && macsrifle.ID != InvalidControlID && macsrifle.ID != id)
		{
			fprintf(stderr, "ERROR: Rejecting attempt to control M.A.C.S. Rifle with two pointers\n");
			return (false);
		}
	}

	S9xUnmapID(id);

	if (poll)
	{
		if (id >= PseudoPointerBase)
			fprintf(stderr, "INFO: Ignoring attempt to set pseudo-pointer #%d to polling\n", id - PseudoPointerBase);
		else
		{
			switch (mapping.type)
			{
				case S9xPointer:
					if (mapping.pointer.aim_mouse0    )	pollmap[MOUSE0        ].insert(id);
					if (mapping.pointer.aim_mouse1    )	pollmap[MOUSE1        ].insert(id);
					if (mapping.pointer.aim_scope     )	pollmap[SUPERSCOPE    ].insert(id);
					if (mapping.pointer.aim_justifier0)	pollmap[ONE_JUSTIFIER ].insert(id);
					if (mapping.pointer.aim_justifier1)	pollmap[TWO_JUSTIFIERS].insert(id);
					if (mapping.pointer.aim_macsrifle )	pollmap[MACSRIFLE     ].insert(id);
					break;

				case S9xPointerPort:
					pollmap[POLL_ALL].insert(id);
					break;
			}
		}
	}

	if (id >= PseudoPointerBase)
		pseudopointer[id - PseudoPointerBase].mapped = true;

	keymap[id] = mapping;

	if (mapping.pointer.aim_mouse0    )	mouse[0].ID     = id;
	if (mapping.pointer.aim_mouse1    )	mouse[1].ID     = id;
	if (mapping.pointer.aim_scope     )	superscope.ID   = id;
	if (mapping.pointer.aim_justifier0)	justifier.ID[0] = id;
	if (mapping.pointer.aim_justifier1)	justifier.ID[1] = id;
	if (mapping.pointer.aim_macsrifle )	macsrifle.ID    = id;

	return (true);
}

void S9xReportPointer (uint32 id, int16 x, int16 y)
{
	if (keymap.count(id) == 0)
		return;

	if (keymap[id].type == S9xNoMapping)
		return;

	if (maptype(keymap[id].type) != MAP_POINTER)
	{
		fprintf(stderr, "ERROR: S9xReportPointer called on %s ID 0x%08x\n", maptypename(maptype(keymap[id].type)), id);
		return;
	}

	S9xApplyCommand(keymap[id], x, y);
}

bool S9xMapAxis (uint32 id, s9xcommand_t mapping, bool poll)
{
	int	t;

	if (id == InvalidControlID)
	{
		fprintf(stderr, "Cannot map InvalidControlID\n");
		return (false);
	}

	t = maptype(mapping.type);

	if (t == MAP_NONE)
	{
		S9xUnmapID(id);
		return (true);
	}

	if (t != MAP_AXIS)
		return (false);

	t = maptype(S9xGetMapping(id).type);

	if (t != MAP_NONE && t != MAP_AXIS)
		fprintf(stderr, "WARNING: Remapping ID 0x%08x from %s to axis\n", id, maptypename(t));

	if (id >= PseudoPointerBase)
	{
		fprintf(stderr, "ERROR: Refusing to map pseudo-pointer #%d as an axis\n", id - PseudoPointerBase);
		return (false);
	}

	t = -1;

	if (poll)
	{
		switch (mapping.type)
		{
			case S9xAxisJoypad:
				t = JOYPAD0 + mapping.axis.joypad.idx;
				break;

			case S9xAxisPseudopointer:
			case S9xAxisPseudobuttons:
			case S9xAxisPort:
				t=POLL_ALL;
				break;
		}
	}

	S9xUnmapID(id);

	keymap[id] = mapping;

	if (t >= 0)
		pollmap[t].insert(id);

	return (true);
}

void S9xReportAxis (uint32 id, int16 value)
{
	if (keymap.count(id) == 0)
		return;

	if (keymap[id].type == S9xNoMapping)
		return;

	if (maptype(keymap[id].type) != MAP_AXIS)
	{
		fprintf(stderr, "ERROR: S9xReportAxis called on %s ID 0x%08x\n", maptypename(maptype(keymap[id].type)), id);
		return;
	}

	S9xApplyCommand(keymap[id], value, 0);
}

static int32 ApplyMulti (s9xcommand_t *multi, int32 pos, int16 data1)
{
	while (1)
	{
		if (multi[pos].multi_press == 3)
			return (-1);

		if (multi[pos].type == S9xNoMapping)
			break;

		if (multi[pos].multi_press)
			S9xApplyCommand(multi[pos], multi[pos].multi_press == 1, 0);
		else
			S9xApplyCommand(multi[pos], data1, 0);

		pos++;
	}

	return (pos + 1);
}

void S9xApplyCommand (s9xcommand_t cmd, int16 data1, int16 data2)
{
	int	i;

	switch (cmd.type)
	{
		case S9xNoMapping:
			return;

		case S9xButtonJoypad:
			if (cmd.button.joypad.toggle)
			{
				if (!data1)
					return;

				uint16	r = cmd.button.joypad.buttons;

				if (cmd.button.joypad.turbo)	joypad[cmd.button.joypad.idx].toggleturbo ^= r;
				if (cmd.button.joypad.sticky)	joypad[cmd.button.joypad.idx].togglestick ^= r;
			}
			else
			{
				uint16	r, s, t, st;

				r = cmd.button.joypad.buttons;
				st = r & joypad[cmd.button.joypad.idx].togglestick & joypad[cmd.button.joypad.idx].toggleturbo;
				r ^= st;
				t  = r & joypad[cmd.button.joypad.idx].toggleturbo;
				r ^= t;
				s  = r & joypad[cmd.button.joypad.idx].togglestick;
				r ^= s;

				if (cmd.button.joypad.turbo && cmd.button.joypad.sticky)
				{
					uint16	x = r; r = st; st = x;
					x = s; s = t; t = x;
				}
				else if (cmd.button.joypad.turbo)
				{
					uint16	x = r; r = t; t = x;
					x = s; s = st; st = x;
				}
				else if (cmd.button.joypad.sticky)
				{
					uint16	x = r; r = s; s = x;
					x = t; t = st; st = x;
				}

				if (data1)
				{
					if (!Settings.UpAndDown && !S9xMoviePlaying()) // if up+down isn't allowed AND we are NOT playing a movie,
					{
						if (cmd.button.joypad.buttons & (SNES_LEFT_MASK | SNES_RIGHT_MASK))
						{
							// if we're pressing left or right, then unpress and unturbo them both first
							// so we don't end up hittnig left AND right accidentally.
							// Note though that the user can still do it on purpose, if Settings.UpAndDown = true.
							// This is a feature, look up glitches in tLoZ:aLttP to find out why.
							joypad[cmd.button.joypad.idx].buttons &= ~(SNES_LEFT_MASK | SNES_RIGHT_MASK);
							joypad[cmd.button.joypad.idx].turbos  &= ~(SNES_LEFT_MASK | SNES_RIGHT_MASK);
						}

						if (cmd.button.joypad.buttons & (SNES_UP_MASK | SNES_DOWN_MASK))
						{
							// and ditto for up/down
							joypad[cmd.button.joypad.idx].buttons &= ~(SNES_UP_MASK | SNES_DOWN_MASK);
							joypad[cmd.button.joypad.idx].turbos  &= ~(SNES_UP_MASK | SNES_DOWN_MASK);
						}
					}

					joypad[cmd.button.joypad.idx].buttons |= r;
					joypad[cmd.button.joypad.idx].turbos  |= t;
					joypad[cmd.button.joypad.idx].buttons ^= s;
					joypad[cmd.button.joypad.idx].buttons &= ~(joypad[cmd.button.joypad.idx].turbos & st);
					joypad[cmd.button.joypad.idx].turbos  ^= st;
				}
				else
				{
					joypad[cmd.button.joypad.idx].buttons &= ~r;
					joypad[cmd.button.joypad.idx].buttons &= ~(joypad[cmd.button.joypad.idx].turbos & t);
					joypad[cmd.button.joypad.idx].turbos  &= ~t;
				}
			}

			return;

		case S9xButtonMouse:
			i = 0;
			if (cmd.button.mouse.left )	i |= 0x40;
			if (cmd.button.mouse.right)	i |= 0x80;

			if (data1)
				mouse[cmd.button.mouse.idx].buttons |=  i;
			else
				mouse[cmd.button.mouse.idx].buttons &= ~i;

			return;

		case S9xButtonSuperscope:
			i = 0;
			if (cmd.button.scope.fire         )	i |= SUPERSCOPE_FIRE;
			if (cmd.button.scope.cursor       )	i |= SUPERSCOPE_CURSOR;
			if (cmd.button.scope.pause        )	i |= SUPERSCOPE_PAUSE;
			if (cmd.button.scope.aim_offscreen)	i |= SUPERSCOPE_OFFSCREEN;

			if (data1)
			{
				superscope.phys_buttons |= i;

				if (cmd.button.scope.turbo)
				{
					superscope.phys_buttons ^= SUPERSCOPE_TURBO;

					if (superscope.phys_buttons & SUPERSCOPE_TURBO)
						superscope.next_buttons |= superscope.phys_buttons & (SUPERSCOPE_FIRE | SUPERSCOPE_CURSOR);
					else
						superscope.next_buttons &= ~(SUPERSCOPE_FIRE | SUPERSCOPE_CURSOR);
				}

				superscope.next_buttons |= i & (SUPERSCOPE_FIRE | SUPERSCOPE_CURSOR | SUPERSCOPE_PAUSE);

				if (!S9xMovieActive()) // PPU modification during non-recordable command screws up movie synchronization
					if ((superscope.next_buttons & (SUPERSCOPE_FIRE | SUPERSCOPE_CURSOR)) && curcontrollers[1] == SUPERSCOPE && !(superscope.phys_buttons & SUPERSCOPE_OFFSCREEN))
						DoGunLatch(superscope.x, superscope.y);
			}
			else
			{
				superscope.phys_buttons &= ~i;
				superscope.next_buttons &= SUPERSCOPE_OFFSCREEN | ~i;
			}

			return;

		case S9xButtonJustifier:
			i = 0;
			if (cmd.button.justifier.trigger)	i |= JUSTIFIER_TRIGGER;
			if (cmd.button.justifier.start  )	i |= JUSTIFIER_START;
			if (cmd.button.justifier.aim_offscreen)	justifier.offscreen[cmd.button.justifier.idx] = data1 ? 1 : 0;
			i >>= cmd.button.justifier.idx;

			if (data1)
				justifier.buttons |=  i;
			else
				justifier.buttons &= ~i;

			return;

		case S9xButtonMacsRifle:
			i = 0;
			if (cmd.button.macsrifle.trigger) i |= MACSRIFLE_TRIGGER;

			if(data1)
				macsrifle.buttons |= i;
			else
				macsrifle.buttons &= ~i;

			return;

		case S9xButtonCommand:
			if (((enum command_numbers) cmd.button.command) >= LAST_COMMAND)
			{
				fprintf(stderr, "Unknown command %04x\n", cmd.button.command);
				return;
			}

			if (!data1)
			{
				switch (i = cmd.button.command)
				{
					case EmuTurbo:
						Settings.TurboMode = FALSE;
						break;
				}
			}
			else
			{
				switch ((enum command_numbers) (i = cmd.button.command))
				{
					case ExitEmu:
						S9xExit();
						break;

					case Reset:
						S9xReset();
						break;

					case SoftReset:
						S9xMovieUpdateOnReset();
						if (S9xMoviePlaying())
							S9xMovieStop(TRUE);
						S9xSoftReset();
						break;

					case EmuTurbo:
						Settings.TurboMode = TRUE;
						break;

					case ToggleEmuTurbo:
						Settings.TurboMode = !Settings.TurboMode;
						DisplayStateChange("Turbo mode", Settings.TurboMode);
						break;

					case ClipWindows:
						Settings.DisableGraphicWindows = !Settings.DisableGraphicWindows;
						DisplayStateChange("Graphic clip windows", !Settings.DisableGraphicWindows);
						break;

					case Debugger:
					#ifdef DEBUGGER
						CPU.Flags |= DEBUG_MODE_FLAG;
					#endif
						break;

					case IncFrameRate:
						if (Settings.SkipFrames == AUTO_FRAMERATE)
							Settings.SkipFrames = 1;
						else
						if (Settings.SkipFrames < 10)
							Settings.SkipFrames++;

						if (Settings.SkipFrames == AUTO_FRAMERATE)
							S9xSetInfoString("Auto frame skip");
						else
						{
							sprintf(buf, "Frame skip: %d", Settings.SkipFrames - 1);
							S9xSetInfoString(buf);
						}

						break;

					case DecFrameRate:
						if (Settings.SkipFrames <= 1)
							Settings.SkipFrames = AUTO_FRAMERATE;
						else
						if (Settings.SkipFrames != AUTO_FRAMERATE)
							Settings.SkipFrames--;

						if (Settings.SkipFrames == AUTO_FRAMERATE)
							S9xSetInfoString("Auto frame skip");
						else
						{
							sprintf(buf, "Frame skip: %d", Settings.SkipFrames - 1);
							S9xSetInfoString(buf);
						}

						break;

					case IncEmuTurbo:
						if (Settings.TurboSkipFrames < 20)
							Settings.TurboSkipFrames += 1;
						else
						if (Settings.TurboSkipFrames < 200)
							Settings.TurboSkipFrames += 5;
						sprintf(buf, "Turbo frame skip: %d", Settings.TurboSkipFrames);
						S9xSetInfoString(buf);
						break;

					case DecEmuTurbo:
						if (Settings.TurboSkipFrames > 20)
							Settings.TurboSkipFrames -= 5;
						else
						if (Settings.TurboSkipFrames > 0)
							Settings.TurboSkipFrames -= 1;
						sprintf(buf, "Turbo frame skip: %d", Settings.TurboSkipFrames);
						S9xSetInfoString(buf);
						break;

					case IncFrameTime: // Increase emulated frame time by 1ms
						Settings.FrameTime += 1000;
						sprintf(buf, "Emulated frame time: %dms", Settings.FrameTime / 1000);
						S9xSetInfoString(buf);
						break;

					case DecFrameTime: // Decrease emulated frame time by 1ms
						if (Settings.FrameTime >= 1000)
							Settings.FrameTime -= 1000;
						sprintf(buf, "Emulated frame time: %dms", Settings.FrameTime / 1000);
						S9xSetInfoString(buf);
						break;

					case IncTurboSpeed:
						if (turbo_time >= 120)
							break;
						turbo_time++;
						sprintf(buf, "Turbo speed: %d", turbo_time);
						S9xSetInfoString(buf);
						break;

					case DecTurboSpeed:
						if (turbo_time <= 1)
							break;
						turbo_time--;
						sprintf(buf, "Turbo speed: %d", turbo_time);
						S9xSetInfoString(buf);
						break;

					case LoadFreezeFile:
						break;

					case SaveFreezeFile:
						break;

					case LoadOopsFile:
					{
						std::string filename = S9xGetFilename("oops", SNAPSHOT_DIR);

						if (S9xUnfreezeGame(filename.c_str()))
						{
							snprintf(buf, 256, "%.240s.oops loaded", S9xBasename(Memory.ROMFilename).c_str());
							S9xSetInfoString(buf);
						}
						else
							S9xMessage(S9X_ERROR, S9X_FREEZE_FILE_NOT_FOUND, "Oops file not found");

						break;
					}

					case Pause:
						Settings.Paused = !Settings.Paused;
						DisplayStateChange("Pause", Settings.Paused);
					#if defined(NETPLAY_SUPPORT) && !defined(__WIN32__)
						S9xNPSendPause(Settings.Paused);
					#endif
						break;

					case QuickLoad000:
					case QuickLoad001:
					case QuickLoad002:
					case QuickLoad003:
					case QuickLoad004:
					case QuickLoad005:
					case QuickLoad006:
					case QuickLoad007:
					case QuickLoad008:
					case QuickLoad009:
					case QuickLoad010:
					{
						std::string ext = std::to_string(i - QuickLoad000);
						while (ext.length() < 3)
							ext = '0' + ext;

						auto filename = S9xGetFilename(ext, SNAPSHOT_DIR);

						if (S9xUnfreezeGame(filename.c_str()))
						{
							snprintf(buf, 256, "%s loaded", S9xBasename(filename).c_str());
							S9xSetInfoString(buf);
						}
						else
							S9xMessage(S9X_ERROR, S9X_FREEZE_FILE_NOT_FOUND, "Freeze file not found");

						break;
					}

					case QuickSave000:
					case QuickSave001:
					case QuickSave002:
					case QuickSave003:
					case QuickSave004:
					case QuickSave005:
					case QuickSave006:
					case QuickSave007:
					case QuickSave008:
					case QuickSave009:
					case QuickSave010:
					{
						std::string ext = std::to_string(i - QuickSave000);
						while (ext.length() < 3)
							ext = '0' + ext;

						auto filename = S9xGetFilename(ext, SNAPSHOT_DIR);

						snprintf(buf, 256, "%s saved", S9xBasename(filename).c_str());
						S9xSetInfoString(buf);

						S9xFreezeGame(filename.c_str());
						break;
					}

					case SaveSPC:
						S9xDumpSPCSnapshot();
						break;

					case Screenshot:
						Settings.TakeScreenshot = TRUE;
						break;

					case SoundChannel0:
					case SoundChannel1:
					case SoundChannel2:
					case SoundChannel3:
					case SoundChannel4:
					case SoundChannel5:
					case SoundChannel6:
					case SoundChannel7:
						S9xToggleSoundChannel(i - SoundChannel0);
						sprintf(buf, "Sound channel %d toggled", i - SoundChannel0);
						S9xSetInfoString(buf);
						break;

					case SoundChannelsOn:
						S9xToggleSoundChannel(8);
						S9xSetInfoString("All sound channels on");
						break;

					case ToggleBackdrop:
						switch (Settings.ForcedBackdrop)
						{
						case 0:
							Settings.ForcedBackdrop = 0xf81f;
							break;
						case 0xf81f:
							Settings.ForcedBackdrop = 0x07e0;
							break;
						case 0x07e0:
							Settings.ForcedBackdrop = 0x07ff;
							break;
						default:
							Settings.ForcedBackdrop = 0;
							break;
						}
						sprintf(buf, "Setting backdrop to 0x%04x", Settings.ForcedBackdrop);
						S9xSetInfoString(buf);
						break;

					case ToggleBG0:
						Settings.BG_Forced ^= 1;
						DisplayStateChange("BG#0", !(Settings.BG_Forced & 1));
						break;

					case ToggleBG1:
						Settings.BG_Forced ^= 2;
						DisplayStateChange("BG#1", !(Settings.BG_Forced & 2));
						break;

					case ToggleBG2:
						Settings.BG_Forced ^= 4;
						DisplayStateChange("BG#2", !(Settings.BG_Forced & 4));
						break;

					case ToggleBG3:
						Settings.BG_Forced ^= 8;
						DisplayStateChange("BG#3", !(Settings.BG_Forced & 8));
						break;

					case ToggleSprites:
						Settings.BG_Forced ^= 16;
						DisplayStateChange("Sprites", !(Settings.BG_Forced & 16));
						break;

					case ToggleTransparency:
						Settings.Transparency = !Settings.Transparency;
						DisplayStateChange("Transparency effects", Settings.Transparency);
						break;

					case BeginRecordingMovie:
						// if (S9xMovieActive())
						// 	S9xMovieStop(FALSE);
						// S9xMovieCreate(S9xChooseMovieFilename(FALSE), 0xFF, MOVIE_OPT_FROM_RESET, NULL, 0);
						break;

					case LoadMovie:
						// if (S9xMovieActive())
						// 	S9xMovieStop(FALSE);
						// S9xMovieOpen(S9xChooseMovieFilename(TRUE), FALSE);
						break;

					case EndRecordingMovie:
						if (S9xMovieActive())
							S9xMovieStop(FALSE);
						break;

					case SwapJoypads:
						if ((curcontrollers[0] != NONE && !(curcontrollers[0] >= JOYPAD0 && curcontrollers[0] <= JOYPAD7)))
						{
							S9xSetInfoString("Cannot swap pads: port 1 is not a joypad");
							break;
						}

						if ((curcontrollers[1] != NONE && !(curcontrollers[1] >= JOYPAD0 && curcontrollers[1] <= JOYPAD7)))
						{
							S9xSetInfoString("Cannot swap pads: port 2 is not a joypad");
							break;
						}

#ifdef NETPLAY_SUPPORT
						if (Settings.NetPlay && data2 != 1) { //data2 == 1 means it's sent by the netplay code
							if (Settings.NetPlayServer) {
								S9xNPSendJoypadSwap();
							} else {
								S9xSetInfoString("Netplay Client cannot swap pads.");
								break;
							}
						}
#endif

						newcontrollers[1] = curcontrollers[0];
						newcontrollers[0] = curcontrollers[1];

						strcpy(buf, "Swap pads: P1=");
						i = 14;
						if (newcontrollers[0] == NONE)
						{
							strcpy(buf + i, "<none>");
							i += 6;
						}
						else
						{
							sprintf(buf + i, "Joypad%d", newcontrollers[0] - JOYPAD0 + 1);
							i += 7;
						}

						strcpy(buf + i, " P2=");
						i += 4;
						if (newcontrollers[1] == NONE)
							strcpy(buf + i, "<none>");
						else
							sprintf(buf + i, "Joypad%d", newcontrollers[1] - JOYPAD0 + 1);

						S9xSetInfoString(buf);
						break;

					case SeekToFrame:
						if (S9xMovieActive())
						{
							sprintf(buf, "Select frame number (current: %d)", S9xMovieGetFrameCounter());
							const char	*frameno = S9xStringInput(buf);
							if (!frameno)
								return;

							int	frameDest = atoi(frameno);
							if (frameDest > 0 && frameDest > (int) S9xMovieGetFrameCounter())
							{
								int	distance = frameDest - S9xMovieGetFrameCounter();
								Settings.HighSpeedSeek = distance;
							}
						}

						break;

					case LAST_COMMAND:
						break;
				}
			}

			return;

		case S9xPointer:
			if (cmd.pointer.aim_mouse0)
			{
				mouse[0].cur_x = data1;
				mouse[0].cur_y = data2;
			}

			if (cmd.pointer.aim_mouse1)
			{
				mouse[1].cur_x = data1;
				mouse[1].cur_y = data2;
			}

			if (cmd.pointer.aim_scope)
			{
				superscope.x   = data1;
				superscope.y   = data2;
			}

			if (cmd.pointer.aim_justifier0)
			{
				justifier.x[0] = data1;
				justifier.y[0] = data2;
			}

			if (cmd.pointer.aim_justifier1)
			{
				justifier.x[1] = data1;
				justifier.y[1] = data2;
			}

			if (cmd.pointer.aim_macsrifle)
			{
				macsrifle.x = data1;
				macsrifle.y = data2;
			}

			return;

		case S9xButtonPseudopointer:
			if (data1)
			{
				if (cmd.button.pointer.UD)
				{
					if (!pseudopointer[cmd.button.pointer.idx].V_adj)
						pseudopointer[cmd.button.pointer.idx].V_adj = cmd.button.pointer.UD * ptrspeeds[cmd.button.pointer.speed_type];
					pseudopointer[cmd.button.pointer.idx].V_var = (cmd.button.pointer.speed_type == 0);
				}

				if (cmd.button.pointer.LR)
				{
					if (!pseudopointer[cmd.button.pointer.idx].H_adj)
						pseudopointer[cmd.button.pointer.idx].H_adj = cmd.button.pointer.LR * ptrspeeds[cmd.button.pointer.speed_type];
					pseudopointer[cmd.button.pointer.idx].H_var = (cmd.button.pointer.speed_type == 0);
				}
			}
			else
			{
				if (cmd.button.pointer.UD)
				{
					pseudopointer[cmd.button.pointer.idx].V_adj = 0;
					pseudopointer[cmd.button.pointer.idx].V_var = false;
				}

				if (cmd.button.pointer.LR)
				{
					pseudopointer[cmd.button.pointer.idx].H_adj = 0;
					pseudopointer[cmd.button.pointer.idx].H_var = false;
				}
			}

			return;

		case S9xAxisJoypad:
		{
			uint16	pos, neg;

			switch (cmd.axis.joypad.axis)
			{
				case 0: neg = SNES_LEFT_MASK;	pos = SNES_RIGHT_MASK;	break;
				case 1: neg = SNES_UP_MASK;		pos = SNES_DOWN_MASK;	break;
				case 2: neg = SNES_Y_MASK;		pos = SNES_A_MASK;		break;
				case 3: neg = SNES_X_MASK;		pos = SNES_B_MASK;		break;
				case 4: neg = SNES_TL_MASK;		pos = SNES_TR_MASK;		break;
				default: return;
			}

			if (cmd.axis.joypad.invert)
				data1 = -data1;

			uint16	p, r;

			p = r = 0;
			if (data1 >  ((cmd.axis.joypad.threshold + 1) *  127))
				p |= pos;
			else
				r |= pos;

			if (data1 <= ((cmd.axis.joypad.threshold + 1) * -127))
				p |= neg;
			else
				r |= neg;

			joypad[cmd.axis.joypad.idx].buttons |= p;
			joypad[cmd.axis.joypad.idx].buttons &= ~r;
			joypad[cmd.axis.joypad.idx].turbos  &= ~(p | r);

			return;
		}

		case S9xAxisPseudopointer:
			if (data1 == 0)
			{
				if (cmd.axis.pointer.HV)
				{
					pseudopointer[cmd.axis.pointer.idx].V_adj = 0;
					pseudopointer[cmd.axis.pointer.idx].V_var = false;
				}
				else
				{
					pseudopointer[cmd.axis.pointer.idx].H_adj = 0;
					pseudopointer[cmd.axis.pointer.idx].H_var = false;
				}
			}
			else
			{
				if (cmd.axis.pointer.invert)
					data1 = -data1;

				if (cmd.axis.pointer.HV)
				{
					if (!pseudopointer[cmd.axis.pointer.idx].V_adj)
						pseudopointer[cmd.axis.pointer.idx].V_adj = (int16) ((int32) data1 * ptrspeeds[cmd.axis.pointer.speed_type] / 32767);
					pseudopointer[cmd.axis.pointer.idx].V_var = (cmd.axis.pointer.speed_type == 0);
				}
				else
				{
					if (!pseudopointer[cmd.axis.pointer.idx].H_adj)
						pseudopointer[cmd.axis.pointer.idx].H_adj = (int16) ((int32) data1 * ptrspeeds[cmd.axis.pointer.speed_type] / 32767);
					pseudopointer[cmd.axis.pointer.idx].H_var = (cmd.axis.pointer.speed_type == 0);
				}
			}

			return;

		case S9xAxisPseudobuttons:
			if (data1 >  ((cmd.axis.button.threshold + 1) *  127))
			{
				if (!pseudobuttons[cmd.axis.button.posbutton])
				{
					pseudobuttons[cmd.axis.button.posbutton] = 1;
					S9xReportButton(PseudoButtonBase + cmd.axis.button.posbutton, true);
				}
			}
			else
			{
				if (pseudobuttons[cmd.axis.button.posbutton])
				{
					pseudobuttons[cmd.axis.button.posbutton] = 0;
					S9xReportButton(PseudoButtonBase + cmd.axis.button.posbutton, false);
				}
			}

			if (data1 <= ((cmd.axis.button.threshold + 1) * -127))
			{
				if (!pseudobuttons[cmd.axis.button.negbutton])
				{
					pseudobuttons[cmd.axis.button.negbutton] = 1;
					S9xReportButton(PseudoButtonBase + cmd.axis.button.negbutton, true);
				}
			}
			else
			{
				if (pseudobuttons[cmd.axis.button.negbutton])
				{
					pseudobuttons[cmd.axis.button.negbutton] = 0;
					S9xReportButton(PseudoButtonBase + cmd.axis.button.negbutton, false);
				}
			}

			return;

		case S9xButtonPort:
		case S9xAxisPort:
		case S9xPointerPort:
			S9xHandlePortCommand(cmd, data1, data2);
			return;

		case S9xButtonMulti:
			if (cmd.button.multi_idx >= (int) multis.size())
				return;

			if (multis[cmd.button.multi_idx]->multi_press && !data1)
				return;

			i = ApplyMulti(multis[cmd.button.multi_idx], 0, data1);
			if (i >= 0)
			{
				struct exemulti	*e = new struct exemulti;
				e->pos    = i;
				e->data1  = data1 != 0;
				e->script = multis[cmd.button.multi_idx];
				exemultis.insert(e);
			}

			return;

		default:
			fprintf(stderr, "WARNING: Unknown command type %d\n", cmd.type);
			return;
	}
}

static void do_polling (int mp)
{
	set<uint32>::iterator	itr;

	if (S9xMoviePlaying())
		return;

	if (pollmap[mp].empty())
		return;

	for (itr = pollmap[mp].begin(); itr != pollmap[mp].end(); itr++)
	{
		switch (maptype(keymap[*itr].type))
		{
			case MAP_BUTTON:
			{
				bool	pressed = false;
				if (S9xPollButton(*itr, &pressed))
					S9xReportButton(*itr, pressed);
				break;
			}

			case MAP_AXIS:
			{
				int16	value = 0;
				if (S9xPollAxis(*itr, &value))
					S9xReportAxis(*itr, value);
				break;
			}

			case MAP_POINTER:
			{
				int16	x = 0, y = 0;
				if (S9xPollPointer(*itr, &x, &y))
					S9xReportPointer(*itr, x, y);
				break;
			}

			default:
				break;
		}
	}
}

static void UpdatePolledMouse (int i)
{
	int16	j;

	j = mouse[i - MOUSE0].cur_x - mouse[i - MOUSE0].old_x;

	if (j < -127)
	{
		mouse[i - MOUSE0].delta_x = 0xff;
		mouse[i - MOUSE0].old_x -= 127;
	}
	else if (j < 0)
	{
		mouse[i - MOUSE0].delta_x = 0x80 | -j;
		mouse[i - MOUSE0].old_x = mouse[i - MOUSE0].cur_x;
	}
	else if (j > 127)
	{
		mouse[i - MOUSE0].delta_x = 0x7f;
		mouse[i - MOUSE0].old_x += 127;
	}
	else
	{
		mouse[i - MOUSE0].delta_x = (uint8) j;
		mouse[i - MOUSE0].old_x = mouse[i - MOUSE0].cur_x;
	}

	j = mouse[i - MOUSE0].cur_y - mouse[i - MOUSE0].old_y;

	if (j < -127)
	{
		mouse[i - MOUSE0].delta_y = 0xff;
		mouse[i - MOUSE0].old_y -= 127;
	}
	else if (j < 0)
	{
		mouse[i - MOUSE0].delta_y = 0x80 | -j;
		mouse[i - MOUSE0].old_y = mouse[i - MOUSE0].cur_y;
	}
	else if (j > 127)
	{
		mouse[i - MOUSE0].delta_y = 0x7f;
		mouse[i - MOUSE0].old_y += 127;
	}
	else
	{
		mouse[i - MOUSE0].delta_y = (uint8) j;
		mouse[i - MOUSE0].old_y = mouse[i - MOUSE0].cur_y;
	}
}

void S9xSetJoypadLatch (bool latch)
{
	if (!latch && FLAG_LATCH)
	{
		// 1 written, 'plug in' new controllers now
		curcontrollers[0] = newcontrollers[0];
		curcontrollers[1] = newcontrollers[1];
	}

	if (latch && !FLAG_LATCH)
	{
		int	i;

		for (int n = 0; n < 2; n++)
		{
			for (int j = 0; j < 2; j++)
				read_idx[n][j] = 0;

			switch (i = curcontrollers[n])
			{
				case MP5:
					for (int j = 0, k; j < 4; ++j)
					{
						k = mp5[n].pads[j];
						if (k == NONE)
							continue;
						do_polling(k);
					}

					break;

				case JOYPAD0:
				case JOYPAD1:
				case JOYPAD2:
				case JOYPAD3:
				case JOYPAD4:
				case JOYPAD5:
				case JOYPAD6:
				case JOYPAD7:
					do_polling(i);
					break;

				case MOUSE0:
				case MOUSE1:
					do_polling(i);
					if (!S9xMoviePlaying())
						UpdatePolledMouse(i);
					break;

				case SUPERSCOPE:
					if (superscope.next_buttons & SUPERSCOPE_FIRE)
					{
						superscope.next_buttons &= ~SUPERSCOPE_TURBO;
						superscope.next_buttons |= superscope.phys_buttons & SUPERSCOPE_TURBO;
					}

					if (superscope.next_buttons & (SUPERSCOPE_FIRE | SUPERSCOPE_CURSOR))
					{
						superscope.next_buttons &= ~SUPERSCOPE_OFFSCREEN;
						superscope.next_buttons |= superscope.phys_buttons & SUPERSCOPE_OFFSCREEN;
					}

					superscope.read_buttons = superscope.next_buttons;

					superscope.next_buttons &= ~SUPERSCOPE_PAUSE;
					if (!(superscope.phys_buttons & SUPERSCOPE_TURBO))
						superscope.next_buttons &= ~(SUPERSCOPE_CURSOR | SUPERSCOPE_FIRE);

					do_polling(i);
					break;

				case TWO_JUSTIFIERS:
					do_polling(TWO_JUSTIFIERS);
					// fall through

				case ONE_JUSTIFIER:
					justifier.buttons ^= JUSTIFIER_SELECT;
					do_polling(ONE_JUSTIFIER);
					break;

				case MACSRIFLE:
					do_polling(i);
					break;

				default:
					break;
			}
		}
	}

	FLAG_LATCH = latch;
}

// prevent read_idx from overflowing (only latching resets it)
// otherwise $4016/7 reads will start returning input data again
static inline uint8 IncreaseReadIdxPost(uint8 &var)
{
	uint8 oldval = var;
	if (var < 255)
		var++;
	return oldval;
}

uint8 S9xReadJOYSERn (int n)
{
	int	i, j, r;

	if (n > 1)
		n -= 0x4016;
	assert(n == 0 || n == 1);

	uint8	bits = (OpenBus & ~3) | ((n == 1) ? 0x1c : 0);

	if (FLAG_LATCH)
	{
		switch (i = curcontrollers[n])
		{
			case MP5:
				return (bits | 2);

			case JOYPAD0:
			case JOYPAD1:
			case JOYPAD2:
			case JOYPAD3:
			case JOYPAD4:
			case JOYPAD5:
			case JOYPAD6:
			case JOYPAD7:
				return (bits | ((joypad[i - JOYPAD0].buttons & 0x8000) ? 1 : 0));

			case MOUSE0:
			case MOUSE1:
				mouse[i - MOUSE0].buttons += 0x10;
				if ((mouse[i - MOUSE0].buttons & 0x30) == 0x30)
					mouse[i - MOUSE0].buttons &= 0xcf;
				return (bits);

			case SUPERSCOPE:
				return (bits | ((superscope.read_buttons & 0x80) ? 1 : 0));

			case ONE_JUSTIFIER:
			case TWO_JUSTIFIERS:
				return (bits);

			case MACSRIFLE:
				do_polling(i);
				return (bits | ((macsrifle.buttons & 0x01) ? 1 : 0));

			default:
				return (bits);
		}
	}
	else
	{
		switch (i = curcontrollers[n])
		{
			case MP5:
				r = IncreaseReadIdxPost(read_idx[n][FLAG_IOBIT(n) ? 0 : 1]);
				j = FLAG_IOBIT(n) ? 0 : 2;

				for (i = 0; i < 2; i++, j++)
				{
					if (mp5[n].pads[j] == NONE)
						continue;
					if (r >= 16)
						bits |= 1 << i;
					else
						bits |= ((joypad[mp5[n].pads[j] - JOYPAD0].buttons & (0x8000 >> r)) ? 1 : 0) << i;
				}

				return (bits);

			case JOYPAD0:
			case JOYPAD1:
			case JOYPAD2:
			case JOYPAD3:
			case JOYPAD4:
			case JOYPAD5:
			case JOYPAD6:
			case JOYPAD7:
				if (read_idx[n][0] >= 16)
				{
					IncreaseReadIdxPost(read_idx[n][0]);
					return (bits | 1);
				}
				else
					return (bits | ((joypad[i - JOYPAD0].buttons & (0x8000 >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));

			case MOUSE0:
			case MOUSE1:
				if (read_idx[n][0] < 8)
				{
					IncreaseReadIdxPost(read_idx[n][0]);
					return (bits);
				}
				else
				if (read_idx[n][0] < 16)
					return (bits | ((mouse[i - MOUSE0].buttons & (0x8000     >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));
				else
				if (read_idx[n][0] < 24)
					return (bits | ((mouse[i - MOUSE0].delta_y & (0x800000   >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));
				else
				if (read_idx[n][0] < 32)
					return (bits | ((mouse[i - MOUSE0].delta_x & (0x80000000 >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));
				else
				{
					IncreaseReadIdxPost(read_idx[n][0]);
					return (bits | 1);
				}

			case SUPERSCOPE:
				if (read_idx[n][0] < 8)
					return (bits | ((superscope.read_buttons & (0x80 >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));
				else
				{
					IncreaseReadIdxPost(read_idx[n][0]);
					return (bits | 1);
				}

			case ONE_JUSTIFIER:
				if (read_idx[n][0] < 24)
					return (bits | ((0xaa7000 >> IncreaseReadIdxPost(read_idx[n][0])) & 1));
				else
				if (read_idx[n][0] < 32)
					return (bits | ((justifier.buttons & (JUSTIFIER_TRIGGER | JUSTIFIER_START | JUSTIFIER_SELECT) & (0x80000000 >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));
				else
				{
					IncreaseReadIdxPost(read_idx[n][0]);
					return (bits | 1);
				}

			case TWO_JUSTIFIERS:
				if (read_idx[n][0] < 24)
					return (bits | ((0xaa7000 >> IncreaseReadIdxPost(read_idx[n][0])) & 1));
				else
				if (read_idx[n][0] < 32)
					return (bits | ((justifier.buttons & (0x80000000 >> IncreaseReadIdxPost(read_idx[n][0]))) ? 1 : 0));
				else
				{
					IncreaseReadIdxPost(read_idx[n][0]);
					return (bits | 1);
				}

			case MACSRIFLE:
				do_polling(i);
				return (bits | ((macsrifle.buttons & 0x01) ? 1 : 0));

			default:
				IncreaseReadIdxPost(read_idx[n][0]);
				return (bits);
		}
	}
}

void S9xDoAutoJoypad (void)
{
	int	i, j;

	S9xSetJoypadLatch(1);
	S9xSetJoypadLatch(0);

	S9xMovieUpdate(false);

	for (int n = 0; n < 2; n++)
	{
		switch (i = curcontrollers[n])
		{
			case MP5:
				j = FLAG_IOBIT(n) ? 0 : 2;
				for (i = 0; i < 2; i++, j++)
				{
					if (mp5[n].pads[j] == NONE)
						WRITE_WORD(Memory.FillRAM + 0x4218 + n * 2 + i * 4, 0);
					else
						WRITE_WORD(Memory.FillRAM + 0x4218 + n * 2 + i * 4, joypad[mp5[n].pads[j] - JOYPAD0].buttons);
				}

				read_idx[n][FLAG_IOBIT(n) ? 0 : 1] = 16;
				break;

			case JOYPAD0:
			case JOYPAD1:
			case JOYPAD2:
			case JOYPAD3:
			case JOYPAD4:
			case JOYPAD5:
			case JOYPAD6:
			case JOYPAD7:
				read_idx[n][0] = 16;
				WRITE_WORD(Memory.FillRAM + 0x4218 + n * 2, joypad[i - JOYPAD0].buttons);
				WRITE_WORD(Memory.FillRAM + 0x421c + n * 2, 0);
				break;

			case MOUSE0:
			case MOUSE1:
				read_idx[n][0] = 16;
				WRITE_WORD(Memory.FillRAM + 0x4218 + n * 2, mouse[i - MOUSE0].buttons);
				WRITE_WORD(Memory.FillRAM + 0x421c + n * 2, 0);
				break;

			case SUPERSCOPE:
				read_idx[n][0] = 16;
				Memory.FillRAM[0x4218 + n * 2] = 0xff;
				Memory.FillRAM[0x4219 + n * 2] = superscope.read_buttons;
				WRITE_WORD(Memory.FillRAM + 0x421c + n * 2, 0);
				break;

			case ONE_JUSTIFIER:
			case TWO_JUSTIFIERS:
				read_idx[n][0] = 16;
				WRITE_WORD(Memory.FillRAM + 0x4218 + n * 2, 0x000e);
				WRITE_WORD(Memory.FillRAM + 0x421c + n * 2, 0);
				break;

			case MACSRIFLE:
				read_idx[n][0] = 16;
				Memory.FillRAM[0x4218 + n * 2] = 0xff;
				Memory.FillRAM[0x4219 + n * 2] = macsrifle.buttons;
				WRITE_WORD(Memory.FillRAM + 0x421c + n * 2, 0);
				break;

			default:
				WRITE_WORD(Memory.FillRAM + 0x4218 + n * 2, 0);
				WRITE_WORD(Memory.FillRAM + 0x421c + n * 2, 0);
				break;
		}
	}
}

void S9xControlEOF (void)
{
	struct crosshair	*c;
	int					i, j;

	PPU.GunVLatch = 1000; // i.e., never latch
	PPU.GunHLatch = 0;

	for (int n = 0; n < 2; n++)
	{
		switch (i = curcontrollers[n])
		{
			case MP5:
				for (j = 0; j < 4; ++j)
				{
					i = mp5[n].pads[j];
					if (i == NONE)
						continue;

					if (++joypad[i - JOYPAD0].turbo_ct >= turbo_time)
					{
						joypad[i - JOYPAD0].turbo_ct = 0;
						joypad[i - JOYPAD0].buttons ^= joypad[i - JOYPAD0].turbos;
					}
				}

				break;

			case JOYPAD0:
			case JOYPAD1:
			case JOYPAD2:
			case JOYPAD3:
			case JOYPAD4:
			case JOYPAD5:
			case JOYPAD6:
			case JOYPAD7:
				if (++joypad[i - JOYPAD0].turbo_ct >= turbo_time)
				{
					joypad[i - JOYPAD0].turbo_ct = 0;
					joypad[i - JOYPAD0].buttons ^= joypad[i - JOYPAD0].turbos;
				}

				break;

			case MOUSE0:
			case MOUSE1:
				c = &mouse[i - MOUSE0].crosshair;
				if (IPPU.RenderThisFrame)
					S9xDrawCrosshair(S9xGetCrosshair(c->img), c->fg, c->bg, mouse[i - MOUSE0].cur_x, mouse[i - MOUSE0].cur_y);
				break;

			case SUPERSCOPE:
				if (n == 1 && !(superscope.phys_buttons & SUPERSCOPE_OFFSCREEN))
				{
					if (superscope.next_buttons & (SUPERSCOPE_FIRE | SUPERSCOPE_CURSOR))
						DoGunLatch(superscope.x, superscope.y);

					c = &superscope.crosshair;
					if (IPPU.RenderThisFrame)
						S9xDrawCrosshair(S9xGetCrosshair(c->img), c->fg, c->bg, superscope.x, superscope.y);
				}

				break;

			case TWO_JUSTIFIERS:
				if (n == 1 && !justifier.offscreen[1])
				{
					c = &justifier.crosshair[1];
					if (IPPU.RenderThisFrame)
						S9xDrawCrosshair(S9xGetCrosshair(c->img), c->fg, c->bg, justifier.x[1], justifier.y[1]);
				}

				i = (justifier.buttons & JUSTIFIER_SELECT) ?  1 : 0;
				goto do_justifier;

			case ONE_JUSTIFIER:
				i = (justifier.buttons & JUSTIFIER_SELECT) ? -1 : 0;

			do_justifier:
				if (n == 1)
				{
					if (i >= 0 && !justifier.offscreen[i])
						DoGunLatch(justifier.x[i], justifier.y[i]);

					if (!justifier.offscreen[0])
					{
						c = &justifier.crosshair[0];
						if (IPPU.RenderThisFrame)
							S9xDrawCrosshair(S9xGetCrosshair(c->img), c->fg, c->bg, justifier.x[0], justifier.y[0]);
					}
				}

				break;

			case MACSRIFLE:
				if (n == 1)
				{
					DoMacsRifleLatch(macsrifle.x, macsrifle.y);

					c = &macsrifle.crosshair;
					if (IPPU.RenderThisFrame)
						S9xDrawCrosshair(S9xGetCrosshair(c->img), c->fg, c->bg, macsrifle.x, macsrifle.y);
				}

				break;

			default:
				break;
		}
	}

	for (int n = 0; n < 8; n++)
	{
		if (!pseudopointer[n].mapped)
			continue;

		if (pseudopointer[n].H_adj)
		{
			pseudopointer[n].x += pseudopointer[n].H_adj;
			if (pseudopointer[n].x < 0)
				pseudopointer[n].x = 0;
			else if (pseudopointer[n].x > 255)
				pseudopointer[n].x = 255;

			if (pseudopointer[n].H_var)
			{
				if (pseudopointer[n].H_adj < 0)
				{
					if (pseudopointer[n].H_adj > -ptrspeeds[3])
						pseudopointer[n].H_adj--;
				}
				else
				{
					if (pseudopointer[n].H_adj <  ptrspeeds[3])
						pseudopointer[n].H_adj++;
				}
			}
		}

		if (pseudopointer[n].V_adj)
		{
			pseudopointer[n].y += pseudopointer[n].V_adj;
			if (pseudopointer[n].y < 0)
				pseudopointer[n].y = 0;
			else if (pseudopointer[n].y > PPU.ScreenHeight - 1)
				pseudopointer[n].y = PPU.ScreenHeight - 1;

			if (pseudopointer[n].V_var)
			{
				if (pseudopointer[n].V_adj < 0)
				{
					if (pseudopointer[n].V_adj > -ptrspeeds[3])
						pseudopointer[n].V_adj--;
				}
				else
				{
					if (pseudopointer[n].V_adj <  ptrspeeds[3])
						pseudopointer[n].V_adj++;
				}
			}
		}

		S9xReportPointer(PseudoPointerBase + n, pseudopointer[n].x, pseudopointer[n].y);
	}

	set<struct exemulti *>::iterator	it, jt;

	for (it = exemultis.begin(); it != exemultis.end(); it++)
	{
		i = ApplyMulti((*it)->script, (*it)->pos, (*it)->data1);

		if (i >= 0)
			(*it)->pos = i;
		else
		{
			jt = it;
			it--;
			delete *jt;
			exemultis.erase(jt);
		}
	}

	do_polling(POLL_ALL);

	pad_read_last = pad_read;
	pad_read      = false;
}

void S9xSetControllerCrosshair (enum crosscontrols ctl, int8 idx, const char *fg, const char *bg)
{
	struct crosshair	*c;
	int8				fgcolor = -1, bgcolor = -1;
	int					i, j;

	if (idx < -1 || idx > 31)
	{
		fprintf(stderr, "S9xSetControllerCrosshair() called with invalid index\n");
		return;
	}

	switch (ctl)
	{
		case X_MOUSE1:		c = &mouse[0].crosshair;		break;
		case X_MOUSE2:		c = &mouse[1].crosshair;		break;
		case X_SUPERSCOPE:	c = &superscope.crosshair;		break;
		case X_JUSTIFIER1:	c = &justifier.crosshair[0];	break;
		case X_JUSTIFIER2:	c = &justifier.crosshair[1];	break;
		case X_MACSRIFLE:	c = &macsrifle.crosshair;		break;
		default:
			fprintf(stderr, "S9xSetControllerCrosshair() called with an invalid controller ID %d\n", ctl);
			return;
	}

	if (fg)
	{
		fgcolor = 0;
		if (*fg == 't')
		{
			fg++;
			fgcolor = 16;
		}

		for (i = 0; i < 16; i++)
		{
			for (j = 0; color_names[i][j] && fg[j] == color_names[i][j]; j++) ;

			if (isalnum(fg[j]))
				continue;

			if (!color_names[i][j])
				break;
		}

		fgcolor |= i;
		if (i > 15 || fgcolor == 16)
		{
			fprintf(stderr, "S9xSetControllerCrosshair() called with invalid fgcolor\n");
			return;
		}
	}

	if (bg)
	{
		bgcolor = 0;
		if (*bg == 't')
		{
			bg++;
			bgcolor = 16;
		}

		for (i = 0; i < 16; i++)
		{
			for (j = 0; color_names[i][j] && bg[j] == color_names[i][j]; j++) ;

			if (isalnum(bg[j]))
				continue;

			if (!color_names[i][j])
				break;
		}

		bgcolor |= i;
		if (i > 15 || bgcolor == 16)
		{
			fprintf(stderr, "S9xSetControllerCrosshair() called with invalid bgcolor\n");
			return;
		}
	}

	if (idx != -1)
	{
		c->set |= 1;
		c->img = idx;
	}

	if (fgcolor != -1)
	{
		c->set |= 2;
		c->fg = fgcolor;
	}

	if (bgcolor != -1)
	{
		c->set |= 4;
		c->bg = bgcolor;
	}
}

void S9xGetControllerCrosshair (enum crosscontrols ctl, int8 *idx, const char **fg, const char **bg)
{
	struct crosshair	*c;

	switch (ctl)
	{
		case X_MOUSE1:		c = &mouse[0].crosshair;		break;
		case X_MOUSE2:		c = &mouse[1].crosshair;		break;
		case X_SUPERSCOPE:	c = &superscope.crosshair;		break;
		case X_JUSTIFIER1:	c = &justifier.crosshair[0];	break;
		case X_JUSTIFIER2:	c = &justifier.crosshair[1];	break;
		case X_MACSRIFLE:	c = &macsrifle.crosshair;		break;
		default:
			fprintf(stderr, "S9xGetControllerCrosshair() called with an invalid controller ID %d\n", ctl);
			return;
	}

	if (idx)
		*idx = c->img;

	if (fg)
		*fg = color_names[c->fg];

	if (bg)
		*bg = color_names[c->bg];
}

void S9xControlPreSaveState (struct SControlSnapshot *s)
{
	memset(s, 0, sizeof(*s));
	s->ver = 4;

	for (int j = 0; j < 2; j++)
	{
		s->port1_read_idx[j] = read_idx[0][j];
		s->port2_read_idx[j] = read_idx[1][j];
	}

	for (int j = 0; j < 2; j++)
		s->mouse_speed[j] = (mouse[j].buttons & 0x30) >> 4;

	s->justifier_select = ((justifier.buttons & JUSTIFIER_SELECT) ? 1 : 0);

#define COPY(x)	{ memcpy((char *) s->internal + i, &(x), sizeof(x)); i += sizeof(x); }

	int	i = 0;

	for (int j = 0; j < 8; j++)
		COPY(joypad[j].buttons);

	for (int j = 0; j < 2; j++)
	{
		COPY(mouse[j].delta_x);
		COPY(mouse[j].delta_y);
		COPY(mouse[j].old_x);
		COPY(mouse[j].old_y);
		COPY(mouse[j].cur_x);
		COPY(mouse[j].cur_y);
		COPY(mouse[j].buttons);
	}

	COPY(superscope.x);
	COPY(superscope.y);
	COPY(superscope.phys_buttons);
	COPY(superscope.next_buttons);
	COPY(superscope.read_buttons);

	for (int j = 0; j < 2; j++)
		COPY(justifier.x[j]);
	for (int j = 0; j < 2; j++)
		COPY(justifier.y[j]);
	COPY(justifier.buttons);
	for (int j = 0; j < 2; j++)
		COPY(justifier.offscreen[j]);

	for (int j = 0; j < 2; j++)
		for (int k = 0; k < 2; k++)
			COPY(mp5[j].pads[k]);

	assert(i == sizeof(s->internal));

	#undef COPY
	#define COPY(x)	{ memcpy((char *) s->internal_macs + i, &(x), sizeof(x)); i += sizeof(x); }
	i = 0;

	COPY(macsrifle.x);
	COPY(macsrifle.y);
	COPY(macsrifle.buttons);

	assert(i == sizeof(s->internal_macs));

#undef COPY

	s->pad_read      = pad_read;
	s->pad_read_last = pad_read_last;
}

void S9xControlPostLoadState (struct SControlSnapshot *s)
{
	if (curcontrollers[0] == MP5 && s->ver < 1)
	{
		// Crap. Old snes9x didn't support this.
		S9xMessage(S9X_WARNING, S9X_FREEZE_FILE_INFO, "Old savestate has no support for MP5 in port 1.");
		newcontrollers[0] = curcontrollers[0];
		curcontrollers[0] = mp5[0].pads[0];
	}

	for (int j = 0; j < 2; j++)
	{
		read_idx[0][j] = s->port1_read_idx[j];
		read_idx[1][j] = s->port2_read_idx[j];
	}

	for (int j = 0; j < 2; j++)
		mouse[j].buttons |= (s->mouse_speed[j] & 3) << 4;

	if (s->justifier_select & 1)
		justifier.buttons |=  JUSTIFIER_SELECT;
	else
		justifier.buttons &= ~JUSTIFIER_SELECT;

	FLAG_LATCH = (Memory.FillRAM[0x4016] & 1) == 1;

	if (s->ver > 1)
	{
	#define COPY(x)	{ memcpy(&(x), (char *) s->internal + i, sizeof(x)); i += sizeof(x); }

		int	i = 0;

		for (int j = 0; j < 8; j++)
			COPY(joypad[j].buttons);

		for (int j = 0; j < 2; j++)
		{
			COPY(mouse[j].delta_x);
			COPY(mouse[j].delta_y);
			COPY(mouse[j].old_x);
			COPY(mouse[j].old_y);
			COPY(mouse[j].cur_x);
			COPY(mouse[j].cur_y);
			COPY(mouse[j].buttons);
		}

		COPY(superscope.x);
		COPY(superscope.y);
		COPY(superscope.phys_buttons);
		COPY(superscope.next_buttons);
		COPY(superscope.read_buttons);

		for (int j = 0; j < 2; j++)
			COPY(justifier.x[j]);
		for (int j = 0; j < 2; j++)
			COPY(justifier.y[j]);
		COPY(justifier.buttons);
		for (int j = 0; j < 2; j++)
			COPY(justifier.offscreen[j]);
		for (int j = 0; j < 2; j++)
			for (int k = 0; k < 2; k++)
				COPY(mp5[j].pads[k]);

		assert(i == sizeof(s->internal));

		if (s->ver > 3)
		{
			#undef COPY
			#define COPY(x)	{ memcpy(&(x), (char *) s->internal_macs + i, sizeof(x)); i += sizeof(x); }
			i = 0;

			COPY(macsrifle.x);
			COPY(macsrifle.y);
			COPY(macsrifle.buttons);

			assert(i == sizeof(s->internal_macs));
		}

	#undef COPY
	}

	if (s->ver > 2)
	{
		pad_read      = s->pad_read;
		pad_read_last = s->pad_read_last;
	}
}

uint16 MovieGetJoypad (int i)
{
	if (i < 0 || i > 7)
		return (0);

	return (joypad[i].buttons);
}

void MovieSetJoypad (int i, uint16 buttons)
{
	if (i < 0 || i > 7)
		return;

	joypad[i].buttons = buttons;
}

bool MovieGetMouse (int i, uint8 out[5])
{
	if (i < 0 || i > 1 || (curcontrollers[i] != MOUSE0 && curcontrollers[i] != MOUSE1))
		return (false);

	int		n = curcontrollers[i] - MOUSE0;
	uint8	*ptr = out;

	WRITE_WORD(ptr, mouse[n].cur_x); ptr += 2;
	WRITE_WORD(ptr, mouse[n].cur_y); ptr += 2;
	*ptr = mouse[n].buttons;

	return (true);
}

void MovieSetMouse (int i, uint8 in[5], bool inPolling)
{
	if (i < 0 || i > 1 || (curcontrollers[i] != MOUSE0 && curcontrollers[i] != MOUSE1))
		return;

	int		n = curcontrollers[i] - MOUSE0;
	uint8	*ptr = in;

	mouse[n].cur_x = READ_WORD(ptr); ptr += 2;
	mouse[n].cur_y = READ_WORD(ptr); ptr += 2;
	mouse[n].buttons = *ptr;

	if (inPolling)
		UpdatePolledMouse(curcontrollers[i]);
}

bool MovieGetScope (int i, uint8 out[6])
{
	if (i < 0 || i > 1 || curcontrollers[i] != SUPERSCOPE)
		return (false);

	uint8	*ptr = out;

	WRITE_WORD(ptr, superscope.x); ptr += 2;
	WRITE_WORD(ptr, superscope.y); ptr += 2;
	*ptr++ = superscope.phys_buttons;
	*ptr   = superscope.next_buttons;

	return (true);
}

void MovieSetScope (int i, uint8 in[6])
{
	if (i < 0 || i > 1 || curcontrollers[i] != SUPERSCOPE)
		return;

	uint8	*ptr = in;

	superscope.x = READ_WORD(ptr); ptr += 2;
	superscope.y = READ_WORD(ptr); ptr += 2;
	superscope.phys_buttons = *ptr++;
	superscope.next_buttons = *ptr;
}

bool MovieGetJustifier (int i, uint8 out[11])
{
	if (i < 0 || i > 1 || (curcontrollers[i] != ONE_JUSTIFIER && curcontrollers[i] != TWO_JUSTIFIERS))
		return (false);

	uint8	*ptr = out;

	WRITE_WORD(ptr, justifier.x[0]); ptr += 2;
	WRITE_WORD(ptr, justifier.x[1]); ptr += 2;
	WRITE_WORD(ptr, justifier.y[0]); ptr += 2;
	WRITE_WORD(ptr, justifier.y[1]); ptr += 2;
	*ptr++ = justifier.buttons;
	*ptr++ = justifier.offscreen[0];
	*ptr   = justifier.offscreen[1];

	return (true);
}

void MovieSetJustifier (int i, uint8 in[11])
{
	if (i < 0 || i > 1 || (curcontrollers[i] != ONE_JUSTIFIER && curcontrollers[i] != TWO_JUSTIFIERS))
		return;

	uint8	*ptr = in;

	justifier.x[0] = READ_WORD(ptr); ptr += 2;
	justifier.x[1] = READ_WORD(ptr); ptr += 2;
	justifier.y[0] = READ_WORD(ptr); ptr += 2;
	justifier.y[1] = READ_WORD(ptr); ptr += 2;
	justifier.buttons      = *ptr++;
	justifier.offscreen[0] = *ptr++;
	justifier.offscreen[1] = *ptr;
}

bool MovieGetMacsRifle (int i, uint8 out[5])
{
	if (i < 0 || i > 1 || curcontrollers[i] != MACSRIFLE)
		return (false);

	uint8	*ptr = out;

	WRITE_WORD(ptr, macsrifle.x); ptr += 2;
	WRITE_WORD(ptr, macsrifle.y); ptr += 2;
	*ptr = macsrifle.buttons;

	return (true);
}

void MovieSetMacsRifle (int i, uint8 in[5])
{
	if (i < 0 || i > 1 || curcontrollers[i] != MACSRIFLE)
		return;

	uint8	*ptr = in;

	macsrifle.x = READ_WORD(ptr); ptr += 2;
	macsrifle.y = READ_WORD(ptr); ptr += 2;
	macsrifle.buttons = *ptr;
}

