447 lines
9.4 KiB
C++
447 lines
9.4 KiB
C++
/*****************************************************************************\
|
|
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 "snes9x.h"
|
|
#include "memory.h"
|
|
#include "apu.h"
|
|
//#include "snapshot.h"
|
|
#include "controls.h"
|
|
|
|
static s9xcommand_t keymap[MaxControlID + 1];
|
|
static int FLAG_LATCH = FALSE;
|
|
static struct {
|
|
uint32 read_idx;
|
|
uint32 buttons;
|
|
} joypads[2];
|
|
|
|
// Note: these should be in asciibetical order!
|
|
#define THE_COMMANDS \
|
|
S(Debugger), \
|
|
S(DecFrameRate), \
|
|
S(EmuTurbo), \
|
|
S(ExitEmu), \
|
|
S(IncFrameRate), \
|
|
S(LoadFreezeFile), \
|
|
S(Pause), \
|
|
S(Reset), \
|
|
S(SaveFreezeFile), \
|
|
S(SoftReset), \
|
|
S(SoundChannel0), \
|
|
S(SoundChannel1), \
|
|
S(SoundChannel2), \
|
|
S(SoundChannel3), \
|
|
S(SoundChannel4), \
|
|
S(SoundChannel5), \
|
|
S(SoundChannel6), \
|
|
S(SoundChannel7), \
|
|
S(SoundChannelsOn), \
|
|
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 *str, bool8 on)
|
|
{
|
|
snprintf(String, sizeof(String), "%s: %s", str, on ? "on":"off");
|
|
S9xMessage(S9X_INFO, 0, String);
|
|
}
|
|
|
|
void S9xControlsReset (void)
|
|
{
|
|
memset(&joypads, 0, sizeof(joypads));
|
|
FLAG_LATCH = FALSE;
|
|
}
|
|
|
|
void S9xUnmapAllControls (void)
|
|
{
|
|
memset(&keymap, 0, sizeof(keymap));
|
|
S9xControlsReset();
|
|
}
|
|
|
|
s9xcommand_t S9xGetCommandT (const char *name)
|
|
{
|
|
s9xcommand_t cmd ;
|
|
cmd.type = S9xBadMapping;
|
|
cmd.button_norpt = 0;
|
|
cmd.command = 0;
|
|
//s9xcommand_t cmd = {
|
|
// .type = S9xBadMapping,
|
|
// .button_norpt = 0,
|
|
// .command = 0,
|
|
//};
|
|
|
|
if (!name)
|
|
cmd.type = S9xBadMapping;
|
|
else
|
|
if (!strcmp(name, "None"))
|
|
cmd.type = S9xNoMapping;
|
|
else
|
|
if (!strncmp(name, "Joypad", 6))
|
|
{
|
|
if (((int)name[6] - '1') <= 0)
|
|
cmd.type = S9xButtonJoypad0;
|
|
else
|
|
cmd.type = S9xButtonJoypad1;
|
|
|
|
const char *s = name + 7;
|
|
int i = 0;
|
|
|
|
while (s[0] == ' ')
|
|
s++;
|
|
|
|
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.buttons = i;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < LAST_COMMAND; i++)
|
|
{
|
|
if (strcasecmp(command_names[i], name) == 0)
|
|
{
|
|
cmd.type = S9xButtonCommand;
|
|
cmd.command = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (cmd);
|
|
}
|
|
|
|
s9xcommand_t S9xGetMapping (uint32 id)
|
|
{
|
|
if (id > MaxControlID)
|
|
{
|
|
s9xcommand_t dummy = {0};
|
|
return dummy;
|
|
}
|
|
|
|
return (keymap[id]);
|
|
}
|
|
|
|
void S9xUnmapButton (uint32 id)
|
|
{
|
|
if (id > MaxControlID)
|
|
{
|
|
printf("WARNING: Invalid Control ID %d, should be between 0 and %d\n", id, MaxControlID);
|
|
return;
|
|
}
|
|
|
|
memset(&keymap[id], 0, sizeof(s9xcommand_t));
|
|
}
|
|
|
|
bool S9xMapButtonT (uint32 id, const char *command)
|
|
{
|
|
s9xcommand_t cmd = S9xGetCommandT(command);
|
|
|
|
if (cmd.type == S9xBadMapping)
|
|
{
|
|
printf("WARNING: Invalid command '%s'\n", command);
|
|
return (false);
|
|
}
|
|
|
|
return S9xMapButton(id, cmd);
|
|
}
|
|
|
|
bool S9xMapButton (uint32 id, s9xcommand_t mapping)
|
|
{
|
|
if (id > MaxControlID)
|
|
{
|
|
printf("WARNING: Invalid Control ID %d, should be between 0 and %d\n", id, MaxControlID);
|
|
return (false);
|
|
}
|
|
|
|
if (mapping.type == S9xNoMapping)
|
|
{
|
|
S9xUnmapButton(id);
|
|
return (true);
|
|
}
|
|
|
|
if (keymap[id].type != S9xNoMapping)
|
|
fprintf(stderr, "WARNING: Remapping ID %d\n", id);
|
|
|
|
keymap[id] = mapping;
|
|
|
|
return (true);
|
|
}
|
|
|
|
void S9xReportButton (uint32 id, bool pressed)
|
|
{
|
|
if (id > MaxControlID)
|
|
return;
|
|
|
|
// skips the "already-pressed check" unless it's a command, as a hack to work around the following problem:
|
|
// FIXME: this makes the controls "stick" after loading a savestate while recording a movie and holding any button
|
|
if (keymap[id].type == S9xButtonCommand && keymap[id].button_norpt)
|
|
return;
|
|
|
|
keymap[id].button_norpt = pressed;
|
|
|
|
S9xApplyCommand(keymap[id], pressed);
|
|
}
|
|
|
|
void S9xApplyCommand (s9xcommand_t cmd, int data1)
|
|
{
|
|
switch (cmd.type)
|
|
{
|
|
case S9xNoMapping:
|
|
return;
|
|
|
|
case S9xButtonJoypad0:
|
|
if (data1)
|
|
joypads[0].buttons |= cmd.buttons;
|
|
else
|
|
joypads[0].buttons &= ~cmd.buttons;
|
|
return;
|
|
|
|
case S9xButtonJoypad1:
|
|
if (data1)
|
|
joypads[1].buttons |= cmd.buttons;
|
|
else
|
|
joypads[1].buttons &= ~cmd.buttons;
|
|
|
|
return;
|
|
|
|
case S9xButtonCommand:
|
|
if (!data1)
|
|
{
|
|
switch (cmd.command)
|
|
{
|
|
case EmuTurbo:
|
|
Settings.TurboMode = FALSE;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown command %04x\n", cmd.command);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (cmd.command)
|
|
{
|
|
case ExitEmu:
|
|
S9xExit();
|
|
break;
|
|
|
|
case Reset:
|
|
S9xReset();
|
|
break;
|
|
|
|
case SoftReset:
|
|
S9xSoftReset();
|
|
break;
|
|
|
|
case EmuTurbo:
|
|
Settings.TurboMode = TRUE;
|
|
break;
|
|
|
|
case ToggleEmuTurbo:
|
|
Settings.TurboMode = !Settings.TurboMode;
|
|
DisplayStateChange("Turbo mode", Settings.TurboMode);
|
|
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)
|
|
S9xMessage(S9X_INFO, 0, "Auto frame skip");
|
|
else
|
|
{
|
|
sprintf(String, "Frame skip: %d", Settings.SkipFrames - 1);
|
|
S9xMessage(S9X_INFO, 0, String);
|
|
}
|
|
|
|
break;
|
|
|
|
case DecFrameRate:
|
|
if (Settings.SkipFrames <= 1)
|
|
Settings.SkipFrames = AUTO_FRAMERATE;
|
|
else
|
|
if (Settings.SkipFrames != AUTO_FRAMERATE)
|
|
Settings.SkipFrames--;
|
|
|
|
if (Settings.SkipFrames == AUTO_FRAMERATE)
|
|
S9xMessage(S9X_INFO, 0, "Auto frame skip");
|
|
else
|
|
{
|
|
sprintf(String, "Frame skip: %d", Settings.SkipFrames - 1);
|
|
S9xMessage(S9X_INFO, 0, String);
|
|
}
|
|
|
|
break;
|
|
|
|
case LoadFreezeFile:
|
|
// S9xUnfreezeGame(S9xChooseFilename(TRUE));
|
|
break;
|
|
|
|
case SaveFreezeFile:
|
|
// S9xFreezeGame(S9xChooseFilename(FALSE));
|
|
break;
|
|
|
|
case Pause:
|
|
Settings.Paused = !Settings.Paused;
|
|
DisplayStateChange("Pause", Settings.Paused);
|
|
break;
|
|
|
|
case SoundChannel0:
|
|
case SoundChannel1:
|
|
case SoundChannel2:
|
|
case SoundChannel3:
|
|
case SoundChannel4:
|
|
case SoundChannel5:
|
|
case SoundChannel6:
|
|
case SoundChannel7:
|
|
S9xToggleSoundChannel((int)cmd.command - SoundChannel0);
|
|
sprintf(String, "Sound channel %d toggled", (int)cmd.command - SoundChannel0);
|
|
S9xMessage(S9X_INFO, 0, String);
|
|
break;
|
|
|
|
case SoundChannelsOn:
|
|
S9xToggleSoundChannel(8);
|
|
S9xMessage(S9X_INFO, 0, "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 LAST_COMMAND:
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Unknown command %04x\n", cmd.command);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
default:
|
|
fprintf(stderr, "WARNING: Unknown command type %d\n", cmd.type);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void S9xSetJoypadLatch (bool latch)
|
|
{
|
|
if (latch && !FLAG_LATCH)
|
|
{
|
|
joypads[0].read_idx = 0;
|
|
joypads[1].read_idx = 0;
|
|
}
|
|
|
|
FLAG_LATCH = latch;
|
|
}
|
|
|
|
uint8 S9xReadJOYSERn (int n)
|
|
{
|
|
n &= 1;
|
|
|
|
int bits = (OpenBus & ~3) | ((n == 1) ? 0x1c : 0);
|
|
|
|
if (FLAG_LATCH)
|
|
{
|
|
return (bits | ((joypads[n].buttons >> 15) & 1));
|
|
}
|
|
else
|
|
{
|
|
if (joypads[n].read_idx < 16)
|
|
{
|
|
return (bits | ((joypads[n].buttons >> (15 - joypads[n].read_idx++)) & 1));
|
|
}
|
|
else
|
|
{
|
|
return (bits | 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void S9xDoAutoJoypad (void)
|
|
{
|
|
S9xSetJoypadLatch(1);
|
|
S9xSetJoypadLatch(0);
|
|
|
|
for (int n = 0; n < 2; n++)
|
|
{
|
|
joypads[n].read_idx = 16;
|
|
WRITE_WORD(Memory.CPU_IO + 0x218 + n * 2, joypads[n].buttons);
|
|
WRITE_WORD(Memory.CPU_IO + 0x21c + n * 2, 0);
|
|
}
|
|
}
|