controls.cpp
1 /*****************************************************************************\ 2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. 3 This file is licensed under the Snes9x License. 4 For further information, consult the LICENSE file in the root directory. 5 \*****************************************************************************/ 6 7 #include "snes9x.h" 8 #include "memory.h" 9 #include "apu.h" 10 //#include "snapshot.h" 11 #include "controls.h" 12 13 static s9xcommand_t keymap[MaxControlID + 1]; 14 static int FLAG_LATCH = FALSE; 15 static struct { 16 uint32 read_idx; 17 uint32 buttons; 18 } joypads[2]; 19 20 // Note: these should be in asciibetical order! 21 #define THE_COMMANDS \ 22 S(Debugger), \ 23 S(DecFrameRate), \ 24 S(EmuTurbo), \ 25 S(ExitEmu), \ 26 S(IncFrameRate), \ 27 S(LoadFreezeFile), \ 28 S(Pause), \ 29 S(Reset), \ 30 S(SaveFreezeFile), \ 31 S(SoftReset), \ 32 S(SoundChannel0), \ 33 S(SoundChannel1), \ 34 S(SoundChannel2), \ 35 S(SoundChannel3), \ 36 S(SoundChannel4), \ 37 S(SoundChannel5), \ 38 S(SoundChannel6), \ 39 S(SoundChannel7), \ 40 S(SoundChannelsOn), \ 41 S(ToggleBG0), \ 42 S(ToggleBG1), \ 43 S(ToggleBG2), \ 44 S(ToggleBG3), \ 45 S(ToggleEmuTurbo), \ 46 S(ToggleSprites), \ 47 S(ToggleTransparency) \ 48 49 #define S(x) x 50 51 enum command_numbers 52 { 53 THE_COMMANDS, 54 LAST_COMMAND 55 }; 56 57 #undef S 58 #define S(x) #x 59 60 static const char *command_names[LAST_COMMAND + 1] = 61 { 62 THE_COMMANDS, 63 NULL 64 }; 65 66 #undef S 67 #undef THE_COMMANDS 68 69 static void DisplayStateChange (const char *str, bool8 on) 70 { 71 snprintf(String, sizeof(String), "%s: %s", str, on ? "on":"off"); 72 S9xMessage(S9X_INFO, 0, String); 73 } 74 75 void S9xControlsReset (void) 76 { 77 memset(&joypads, 0, sizeof(joypads)); 78 FLAG_LATCH = FALSE; 79 } 80 81 void S9xUnmapAllControls (void) 82 { 83 memset(&keymap, 0, sizeof(keymap)); 84 S9xControlsReset(); 85 } 86 87 s9xcommand_t S9xGetCommandT (const char *name) 88 { 89 s9xcommand_t cmd ; 90 cmd.type = S9xBadMapping; 91 cmd.button_norpt = 0; 92 cmd.command = 0; 93 //s9xcommand_t cmd = { 94 // .type = S9xBadMapping, 95 // .button_norpt = 0, 96 // .command = 0, 97 //}; 98 99 if (!name) 100 cmd.type = S9xBadMapping; 101 else 102 if (!strcmp(name, "None")) 103 cmd.type = S9xNoMapping; 104 else 105 if (!strncmp(name, "Joypad", 6)) 106 { 107 if (((int)name[6] - '1') <= 0) 108 cmd.type = S9xButtonJoypad0; 109 else 110 cmd.type = S9xButtonJoypad1; 111 112 const char *s = name + 7; 113 int i = 0; 114 115 while (s[0] == ' ') 116 s++; 117 118 if (!strncmp(s, "Up", 2)) { i |= SNES_UP_MASK; s += 2; if (*s == '+') s++; } 119 if (!strncmp(s, "Down", 4)) { i |= SNES_DOWN_MASK; s += 4; if (*s == '+') s++; } 120 if (!strncmp(s, "Left", 4)) { i |= SNES_LEFT_MASK; s += 4; if (*s == '+') s++; } 121 if (!strncmp(s, "Right", 5)) { i |= SNES_RIGHT_MASK; s += 5; if (*s == '+') s++; } 122 123 if (*s == 'A') { i |= SNES_A_MASK; s++; if (*s == '+') s++; } 124 if (*s == 'B') { i |= SNES_B_MASK; s++; if (*s == '+') s++; } 125 if (*s == 'X') { i |= SNES_X_MASK; s++; if (*s == '+') s++; } 126 if (*s == 'Y') { i |= SNES_Y_MASK; s++; if (*s == '+') s++; } 127 if (*s == 'L') { i |= SNES_TL_MASK; s++; if (*s == '+') s++; } 128 if (*s == 'R') { i |= SNES_TR_MASK; s++; if (*s == '+') s++; } 129 130 if (!strncmp(s, "Start", 5)) { i |= SNES_START_MASK; s += 5; if (*s == '+') s++; } 131 if (!strncmp(s, "Select", 6)) { i |= SNES_SELECT_MASK; s += 6; } 132 133 if (i == 0 || *s != 0 || *(s - 1) == '+') 134 return (cmd); 135 136 cmd.buttons = i; 137 } 138 else 139 { 140 for (int i = 0; i < LAST_COMMAND; i++) 141 { 142 if (strcasecmp(command_names[i], name) == 0) 143 { 144 cmd.type = S9xButtonCommand; 145 cmd.command = i; 146 break; 147 } 148 } 149 } 150 151 return (cmd); 152 } 153 154 s9xcommand_t S9xGetMapping (uint32 id) 155 { 156 if (id > MaxControlID) 157 { 158 s9xcommand_t dummy = {0}; 159 return dummy; 160 } 161 162 return (keymap[id]); 163 } 164 165 void S9xUnmapButton (uint32 id) 166 { 167 if (id > MaxControlID) 168 { 169 printf("WARNING: Invalid Control ID %d, should be between 0 and %d\n", id, MaxControlID); 170 return; 171 } 172 173 memset(&keymap[id], 0, sizeof(s9xcommand_t)); 174 } 175 176 bool S9xMapButtonT (uint32 id, const char *command) 177 { 178 s9xcommand_t cmd = S9xGetCommandT(command); 179 180 if (cmd.type == S9xBadMapping) 181 { 182 printf("WARNING: Invalid command '%s'\n", command); 183 return (false); 184 } 185 186 return S9xMapButton(id, cmd); 187 } 188 189 bool S9xMapButton (uint32 id, s9xcommand_t mapping) 190 { 191 if (id > MaxControlID) 192 { 193 printf("WARNING: Invalid Control ID %d, should be between 0 and %d\n", id, MaxControlID); 194 return (false); 195 } 196 197 if (mapping.type == S9xNoMapping) 198 { 199 S9xUnmapButton(id); 200 return (true); 201 } 202 203 if (keymap[id].type != S9xNoMapping) 204 fprintf(stderr, "WARNING: Remapping ID %d\n", id); 205 206 keymap[id] = mapping; 207 208 return (true); 209 } 210 211 void S9xReportButton (uint32 id, bool pressed) 212 { 213 if (id > MaxControlID) 214 return; 215 216 // skips the "already-pressed check" unless it's a command, as a hack to work around the following problem: 217 // FIXME: this makes the controls "stick" after loading a savestate while recording a movie and holding any button 218 if (keymap[id].type == S9xButtonCommand && keymap[id].button_norpt) 219 return; 220 221 keymap[id].button_norpt = pressed; 222 223 S9xApplyCommand(keymap[id], pressed); 224 } 225 226 void S9xApplyCommand (s9xcommand_t cmd, int data1) 227 { 228 switch (cmd.type) 229 { 230 case S9xNoMapping: 231 return; 232 233 case S9xButtonJoypad0: 234 if (data1) 235 joypads[0].buttons |= cmd.buttons; 236 else 237 joypads[0].buttons &= ~cmd.buttons; 238 return; 239 240 case S9xButtonJoypad1: 241 if (data1) 242 joypads[1].buttons |= cmd.buttons; 243 else 244 joypads[1].buttons &= ~cmd.buttons; 245 246 return; 247 248 case S9xButtonCommand: 249 if (!data1) 250 { 251 switch (cmd.command) 252 { 253 case EmuTurbo: 254 Settings.TurboMode = FALSE; 255 break; 256 default: 257 fprintf(stderr, "Unknown command %04x\n", cmd.command); 258 } 259 } 260 else 261 { 262 switch (cmd.command) 263 { 264 case ExitEmu: 265 S9xExit(); 266 break; 267 268 case Reset: 269 S9xReset(); 270 break; 271 272 case SoftReset: 273 S9xSoftReset(); 274 break; 275 276 case EmuTurbo: 277 Settings.TurboMode = TRUE; 278 break; 279 280 case ToggleEmuTurbo: 281 Settings.TurboMode = !Settings.TurboMode; 282 DisplayStateChange("Turbo mode", Settings.TurboMode); 283 break; 284 285 case Debugger: 286 #ifdef DEBUGGER 287 CPU.Flags |= DEBUG_MODE_FLAG; 288 #endif 289 break; 290 291 case IncFrameRate: 292 if (Settings.SkipFrames == AUTO_FRAMERATE) 293 Settings.SkipFrames = 1; 294 else 295 if (Settings.SkipFrames < 10) 296 Settings.SkipFrames++; 297 298 if (Settings.SkipFrames == AUTO_FRAMERATE) 299 S9xMessage(S9X_INFO, 0, "Auto frame skip"); 300 else 301 { 302 sprintf(String, "Frame skip: %d", Settings.SkipFrames - 1); 303 S9xMessage(S9X_INFO, 0, String); 304 } 305 306 break; 307 308 case DecFrameRate: 309 if (Settings.SkipFrames <= 1) 310 Settings.SkipFrames = AUTO_FRAMERATE; 311 else 312 if (Settings.SkipFrames != AUTO_FRAMERATE) 313 Settings.SkipFrames--; 314 315 if (Settings.SkipFrames == AUTO_FRAMERATE) 316 S9xMessage(S9X_INFO, 0, "Auto frame skip"); 317 else 318 { 319 sprintf(String, "Frame skip: %d", Settings.SkipFrames - 1); 320 S9xMessage(S9X_INFO, 0, String); 321 } 322 323 break; 324 325 case LoadFreezeFile: 326 // S9xUnfreezeGame(S9xChooseFilename(TRUE)); 327 break; 328 329 case SaveFreezeFile: 330 // S9xFreezeGame(S9xChooseFilename(FALSE)); 331 break; 332 333 case Pause: 334 Settings.Paused = !Settings.Paused; 335 DisplayStateChange("Pause", Settings.Paused); 336 break; 337 338 case SoundChannel0: 339 case SoundChannel1: 340 case SoundChannel2: 341 case SoundChannel3: 342 case SoundChannel4: 343 case SoundChannel5: 344 case SoundChannel6: 345 case SoundChannel7: 346 S9xToggleSoundChannel((int)cmd.command - SoundChannel0); 347 sprintf(String, "Sound channel %d toggled", (int)cmd.command - SoundChannel0); 348 S9xMessage(S9X_INFO, 0, String); 349 break; 350 351 case SoundChannelsOn: 352 S9xToggleSoundChannel(8); 353 S9xMessage(S9X_INFO, 0, "All sound channels on"); 354 break; 355 356 case ToggleBG0: 357 Settings.BG_Forced ^= 1; 358 DisplayStateChange("BG#0", !(Settings.BG_Forced & 1)); 359 break; 360 361 case ToggleBG1: 362 Settings.BG_Forced ^= 2; 363 DisplayStateChange("BG#1", !(Settings.BG_Forced & 2)); 364 break; 365 366 case ToggleBG2: 367 Settings.BG_Forced ^= 4; 368 DisplayStateChange("BG#2", !(Settings.BG_Forced & 4)); 369 break; 370 371 case ToggleBG3: 372 Settings.BG_Forced ^= 8; 373 DisplayStateChange("BG#3", !(Settings.BG_Forced & 8)); 374 break; 375 376 case ToggleSprites: 377 Settings.BG_Forced ^= 16; 378 DisplayStateChange("Sprites", !(Settings.BG_Forced & 16)); 379 break; 380 381 case ToggleTransparency: 382 Settings.Transparency = !Settings.Transparency; 383 DisplayStateChange("Transparency effects", Settings.Transparency); 384 break; 385 386 case LAST_COMMAND: 387 break; 388 389 default: 390 fprintf(stderr, "Unknown command %04x\n", cmd.command); 391 } 392 } 393 394 return; 395 396 default: 397 fprintf(stderr, "WARNING: Unknown command type %d\n", cmd.type); 398 return; 399 } 400 } 401 402 void S9xSetJoypadLatch (bool latch) 403 { 404 if (latch && !FLAG_LATCH) 405 { 406 joypads[0].read_idx = 0; 407 joypads[1].read_idx = 0; 408 } 409 410 FLAG_LATCH = latch; 411 } 412 413 uint8 S9xReadJOYSERn (int n) 414 { 415 n &= 1; 416 417 int bits = (OpenBus & ~3) | ((n == 1) ? 0x1c : 0); 418 419 if (FLAG_LATCH) 420 { 421 return (bits | ((joypads[n].buttons >> 15) & 1)); 422 } 423 else 424 { 425 if (joypads[n].read_idx < 16) 426 { 427 return (bits | ((joypads[n].buttons >> (15 - joypads[n].read_idx++)) & 1)); 428 } 429 else 430 { 431 return (bits | 1); 432 } 433 } 434 } 435 436 void S9xDoAutoJoypad (void) 437 { 438 S9xSetJoypadLatch(1); 439 S9xSetJoypadLatch(0); 440 441 for (int n = 0; n < 2; n++) 442 { 443 joypads[n].read_idx = 16; 444 WRITE_WORD(Memory.CPU_IO + 0x218 + n * 2, joypads[n].buttons); 445 WRITE_WORD(Memory.CPU_IO + 0x21c + n * 2, 0); 446 } 447 }