/*****************************************************************************\ 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 #include #include #include #include #include #include #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 exemultis; static set pollmap[NUMCTLS + 1]; static map keymap; static vector 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(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::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, ". "); 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, " . "); 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: S9xUnfreezeGame(S9xChooseFilename(TRUE)); break; case SaveFreezeFile: S9xFreezeGame(S9xChooseFilename(FALSE)); break; case LoadOopsFile: { char filename[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], def[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(Memory.ROMFilename, drive, dir, def, ext); snprintf(filename, PATH_MAX + 1, "%s%s%s.%.*s", S9xGetDirectory(SNAPSHOT_DIR), SLASH_STR, def, _MAX_EXT - 1, "oops"); if (S9xUnfreezeGame(filename)) { snprintf(buf, 256, "%s.%.*s loaded", def, _MAX_EXT - 1, "oops"); 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: { char filename[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], def[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(Memory.ROMFilename, drive, dir, def, ext); snprintf(filename, PATH_MAX + 1, "%s%s%s.%03d", S9xGetDirectory(SNAPSHOT_DIR), SLASH_STR, def, i - QuickLoad000); if (S9xUnfreezeGame(filename)) { snprintf(buf, 256, "%s.%03d loaded", def, i - QuickLoad000); 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: { char filename[PATH_MAX + 1]; char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], def[_MAX_FNAME + 1], ext[_MAX_EXT + 1]; _splitpath(Memory.ROMFilename, drive, dir, def, ext); snprintf(filename, PATH_MAX + 1, "%s%s%s.%03d", S9xGetDirectory(SNAPSHOT_DIR), SLASH_STR, def, i - QuickSave000); snprintf(buf, 256, "%s.%03d saved", def, i - QuickSave000); S9xSetInfoString(buf); S9xFreezeGame(filename); 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 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, ""); 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, ""); 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::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::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; }