Support controller-button remapping file for SNES MAX (#1155)

SNES MAX: Support controller-button remapping (yaml) file from command line. (#1141, PR #1155)
This commit is contained in:
TomCh 2022-12-15 14:45:00 +00:00 committed by GitHub
parent 9358abbb28
commit 5c0f3d03ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 113 deletions

View File

@ -36,6 +36,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "Keyboard.h" #include "Keyboard.h"
#include "Joystick.h" #include "Joystick.h"
#include "SoundCore.h" #include "SoundCore.h"
#include "SNESMAX.h"
#include "ParallelPrinter.h" #include "ParallelPrinter.h"
#include "Interface.h" #include "Interface.h"
@ -48,6 +49,7 @@ bool g_bHookSystemKey = true;
bool g_bHookAltTab = false; bool g_bHookAltTab = false;
bool g_bHookAltGrControl = false; bool g_bHookAltGrControl = false;
static LPSTR GetCurrArg(LPSTR lpCmdLine) static LPSTR GetCurrArg(LPSTR lpCmdLine)
{ {
if (*lpCmdLine == '\"') if (*lpCmdLine == '\"')
@ -549,6 +551,20 @@ bool ProcessCmdLine(LPSTR lpCmdLine)
{ {
g_cmdLine.snesMaxAltControllerType[1] = true; g_cmdLine.snesMaxAltControllerType[1] = true;
} }
else if (strcmp(lpCmdLine, "-snes-max-user-joy1") == 0 || strcmp(lpCmdLine, "-snes-max-user-joy2") == 0)
{
const unsigned int joyNum = (strcmp(lpCmdLine, "-snes-max-user-joy1") == 0) ? 0 : 1;
lpCmdLine = GetCurrArg(lpNextArg);
lpNextArg = GetNextArg(lpNextArg);
std::string errorMsg;
if (!SNESMAXCard::ParseControllerMappingFile(joyNum, lpCmdLine, errorMsg))
{
LogFileOutput("%s", errorMsg.c_str());
GetFrame().FrameMessageBox(errorMsg.c_str(), TEXT("AppleWin Error"), MB_OK);
}
}
else if (strcmp(lpCmdLine, "-wav-speaker") == 0) else if (strcmp(lpCmdLine, "-wav-speaker") == 0)
{ {
lpCmdLine = GetCurrArg(lpNextArg); lpCmdLine = GetCurrArg(lpNextArg);

View File

@ -43,11 +43,10 @@
Bit 6 = Controller 2, Active Low Bit 6 = Controller 2, Active Low
Bit 7 = Controller 1, Active Low Bit 7 = Controller 1, Active Low
Once data is read, button presses (for each controller) should be stored in the following structure Once data is read, button presses (for each controller) are stored in the following structure:
Byte 0: B:Y:Sl:St:U:D:L:R
Byte 1: A:X:Fl:Fr:x:x:x:x
The variable controllerXButtons will stored in the reverse order. bit# 11 10 9 8 / 7 6 5 4 3 2 1 0
m_controllerXButtons: x:x:x:x:Fr:Fl:X:A / R:L:D:U:St:Sl:Y:B
Alex Lukacz Aug 2021 Alex Lukacz Aug 2021
*/ */
@ -57,6 +56,16 @@
#include "Memory.h" #include "Memory.h"
#include "YamlHelper.h" #include "YamlHelper.h"
// AltControllerType defaults to "8BitDo NES30 PRO"
// b11,..,b0: Sr,Sl,R,L / -,-,-,Y,X,-,B,A
//
// infoEx.dwButtons bit definitions:
UINT SNESMAXCard::m_altControllerButtons[2][NUM_BUTTONS] =
{
{A,B,UNUSED,X,Y,UNUSED,UNUSED,UNUSED, LB,RB,SELECT,START}, // bit0 -> A, bit1 -> B, bit2 -> unused, etc
{A,B,UNUSED,X,Y,UNUSED,UNUSED,UNUSED, LB,RB,SELECT,START}
};
BYTE __stdcall SNESMAXCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles) BYTE __stdcall SNESMAXCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{ {
const UINT slot = ((addr & 0xff) >> 4) - 8; const UINT slot = ((addr & 0xff) >> 4) - 8;
@ -100,90 +109,12 @@ BYTE __stdcall SNESMAXCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value,
result = joyGetPosEx(JOYSTICKID1, &infoEx); result = joyGetPosEx(JOYSTICKID1, &infoEx);
if (result == JOYERR_NOERROR) if (result == JOYERR_NOERROR)
{ controller1Buttons = pCard->GetControllerButtons(JOYSTICKID1, infoEx, pCard->m_altControllerType[0]);
xAxis = (infoEx.dwXpos >> 8) & 0xFF;
yAxis = (infoEx.dwYpos >> 8) & 0xFF;
controller1Buttons = controller1Buttons | ((yAxis < 103 || infoEx.dwPOV == 0 || infoEx.dwPOV == 4500 || infoEx.dwPOV == 31500) << 4); // U Button
controller1Buttons = controller1Buttons | ((yAxis > 153 || (infoEx.dwPOV >= 13500 && infoEx.dwPOV <= 22500)) << 5); // D Button
controller1Buttons = controller1Buttons | ((xAxis < 103 || (infoEx.dwPOV >= 22500 && infoEx.dwPOV <= 31500)) << 6); // L Button
controller1Buttons = controller1Buttons | ((xAxis > 153 || (infoEx.dwPOV >= 4500 && infoEx.dwPOV <= 13500)) << 7); // R Button
// controller1Buttons = controller1Buttons | 0 * 0x1000; // spare Button
// controller1Buttons = controller1Buttons | 0 * 0x2000; // spare Button
// controller1Buttons = controller1Buttons | 0 * 0x4000; // spare Button
// controller1Buttons = controller1Buttons | 0 * 0x8000; // spare Button
if (pCard->m_altControllerType[0])
{
// 8BitDo NES30 PRO
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0002) >> 1); // B Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0010) >> 3); // Y Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0400) >> 8); // Sl Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0800) >> 8); // St Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0001) << 8); // A Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0008) << 6); // X Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0100) << 2) | ((infoEx.dwButtons & 0x0040) << 4); // Fl Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0200) << 2) | ((infoEx.dwButtons & 0x0080) << 4); // Fr Button
}
else
{
// Logitech F310, Dualshock 4
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0002) >> 1); // B Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0001) << 1); // Y Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0100) >> 6); // Sl Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0200) >> 6); // St Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0004) << 6); // A Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0008) << 6); // X Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0010) << 6) | ((infoEx.dwButtons & 0x0040) << 4); // Fl Button
controller1Buttons = controller1Buttons | ((infoEx.dwButtons & 0x0020) << 6) | ((infoEx.dwButtons & 0x0080) << 4); // Fr Button
}
controller1Buttons = controller1Buttons | 0x10000; // Controller plugged in status.
}
controller1Buttons = ~controller1Buttons; controller1Buttons = ~controller1Buttons;
result = joyGetPosEx(JOYSTICKID2, &infoEx); result = joyGetPosEx(JOYSTICKID2, &infoEx);
if (result == JOYERR_NOERROR) if (result == JOYERR_NOERROR)
{ controller2Buttons = pCard->GetControllerButtons(JOYSTICKID2, infoEx, pCard->m_altControllerType[1]);
xAxis = (infoEx.dwXpos >> 8) & 0xFF;
yAxis = (infoEx.dwYpos >> 8) & 0xFF;
controller2Buttons = controller2Buttons | ((yAxis < 103 || infoEx.dwPOV == 0 || infoEx.dwPOV == 4500 || infoEx.dwPOV == 31500) << 4); // U Button
controller2Buttons = controller2Buttons | ((yAxis > 153 || (infoEx.dwPOV >= 13500 && infoEx.dwPOV <= 22500)) << 5); // D Button
controller2Buttons = controller2Buttons | ((xAxis < 103 || (infoEx.dwPOV >= 22500 && infoEx.dwPOV <= 31500)) << 6); // L Button
controller2Buttons = controller2Buttons | ((xAxis > 153 || (infoEx.dwPOV >= 4500 && infoEx.dwPOV <= 13500)) << 7); // R Button
// controller2Buttons = controller2Buttons | 0 * 0x1000; // spare Button
// controller2Buttons = controller2Buttons | 0 * 0x2000; // spare Button
// controller2Buttons = controller2Buttons | 0 * 0x4000; // spare Button
// controller2Buttons = controller2Buttons | 0 * 0x8000; // spare Button
if (pCard->m_altControllerType[1])
{
// 8BitDo NES30 PRO
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0002) >> 1); // B Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0010) >> 3); // Y Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0400) >> 8); // Sl Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0800) >> 8); // St Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0001) << 8); // A Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0008) << 6); // X Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0100) << 2) | ((infoEx.dwButtons & 0x0040) << 4); // Fl Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0200) << 2) | ((infoEx.dwButtons & 0x0080) << 4); // Fr Button
}
else
{
// Logitech F310, Dualshock 4
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0002) >> 1); // B Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0001) << 1); // Y Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0100) >> 6); // Sl Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0200) >> 6); // St Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0004) << 6); // A Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0008) << 6); // X Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0010) << 6) | ((infoEx.dwButtons & 0x0040) << 4); // Fl Button
controller2Buttons = controller2Buttons | ((infoEx.dwButtons & 0x0020) << 6) | ((infoEx.dwButtons & 0x0080) << 4); // Fr Button
}
controller2Buttons = controller2Buttons | 0x10000; // Controller plugged in status.
}
controller2Buttons = ~controller2Buttons; controller2Buttons = ~controller2Buttons;
break; break;
@ -202,11 +133,126 @@ BYTE __stdcall SNESMAXCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value,
return 0; return 0;
} }
UINT SNESMAXCard::GetControllerButtons(UINT joyNum, JOYINFOEX& infoEx, bool altControllerType)
{
UINT xAxis = (infoEx.dwXpos >> 8) & 0xFF;
UINT yAxis = (infoEx.dwYpos >> 8) & 0xFF;
UINT controllerButtons = 0;
controllerButtons |= ((yAxis < 103 || infoEx.dwPOV == 0 || infoEx.dwPOV == 4500 || infoEx.dwPOV == 31500) << 4); // U Button
controllerButtons |= ((yAxis > 153 || (infoEx.dwPOV >= 13500 && infoEx.dwPOV <= 22500)) << 5); // D Button
controllerButtons |= ((xAxis < 103 || (infoEx.dwPOV >= 22500 && infoEx.dwPOV <= 31500)) << 6); // L Button
controllerButtons |= ((xAxis > 153 || (infoEx.dwPOV >= 4500 && infoEx.dwPOV <= 13500)) << 7); // R Button
// controllerButtons |= 0 * 0x1000; // spare Button
// controllerButtons |= 0 * 0x2000; // spare Button
// controllerButtons |= 0 * 0x4000; // spare Button
// controllerButtons |= 0 * 0x8000; // spare Button
if (!altControllerType)
{
// Logitech F310, Sony DualShock 4
// b11,..,b0: -,-,St,Sl / -,-,R,L,X,A,B,Y
controllerButtons |= ((infoEx.dwButtons & 0x0002) >> 1); // B Button
controllerButtons |= ((infoEx.dwButtons & 0x0001) << 1); // Y Button
controllerButtons |= ((infoEx.dwButtons & 0x0100) >> 6); // Sl Button
controllerButtons |= ((infoEx.dwButtons & 0x0200) >> 6); // St Button
controllerButtons |= ((infoEx.dwButtons & 0x0004) << 6); // A Button
controllerButtons |= ((infoEx.dwButtons & 0x0008) << 6); // X Button
controllerButtons |= ((infoEx.dwButtons & 0x0010) << 6) | ((infoEx.dwButtons & 0x0040) << 4); // Fl Button
controllerButtons |= ((infoEx.dwButtons & 0x0020) << 6) | ((infoEx.dwButtons & 0x0080) << 4); // Fr Button
}
else
{
for (UINT i = 0; i < 12; i++)
{
if (infoEx.dwButtons & (1 << i))
{
if (m_altControllerButtons[joyNum][i] != UNUSED)
controllerButtons |= (1 << m_altControllerButtons[joyNum][i]);
}
}
}
return controllerButtons | 0x10000; // Controller plugged in status.
}
void SNESMAXCard::InitializeIO(LPBYTE pCxRomPeripheral) void SNESMAXCard::InitializeIO(LPBYTE pCxRomPeripheral)
{ {
RegisterIoHandler(m_slot, &SNESMAXCard::IORead, &SNESMAXCard::IOWrite, IO_Null, IO_Null, this, NULL); RegisterIoHandler(m_slot, &SNESMAXCard::IORead, &SNESMAXCard::IOWrite, IO_Null, IO_Null, this, NULL);
} }
bool SNESMAXCard::ParseControllerMappingFile(UINT joyNum, const char* pathname, std::string& errorMsg)
{
bool res = true;
YamlHelper yamlHelper;
try
{
if (!yamlHelper.InitParser(pathname))
throw std::runtime_error("Controller mapping file: Failed to initialize parser or open file");
if (yamlHelper.ParseFileHdr("AppleWin Controller Button Remapping") != 1)
throw std::runtime_error("Controller mapping file: Version mismatch");
std::string scalar;
while (yamlHelper.GetScalar(scalar))
{
if (scalar == SS_YAML_KEY_UNIT)
{
yamlHelper.GetMapStartEvent();
YamlLoadHelper yamlLoadHelper(yamlHelper);
std::string hid = yamlLoadHelper.LoadString("HID");
std::string desc = yamlLoadHelper.LoadString("Description");
for (UINT i = 0; i < SNESMAXCard::NUM_BUTTONS; i++)
{
char szButtonNum[3] = "00";
sprintf_s(szButtonNum, "%d", i + 1); // +1 as 1-based
std::string buttonNum = szButtonNum;
bool found = false;
std::string buttonStr = yamlLoadHelper.LoadString_NoThrow(buttonNum, found);
SNESMAXCard::Button button = SNESMAXCard::UNUSED;
if (found)
{
if (buttonStr == "A") button = SNESMAXCard::A;
else if (buttonStr == "B") button = SNESMAXCard::B;
else if (buttonStr == "X") button = SNESMAXCard::X;
else if (buttonStr == "Y") button = SNESMAXCard::Y;
else if (buttonStr == "LB") button = SNESMAXCard::LB;
else if (buttonStr == "RB") button = SNESMAXCard::RB;
else if (buttonStr == "SELECT") button = SNESMAXCard::SELECT;
else if (buttonStr == "START") button = SNESMAXCard::START;
else if (buttonStr == "") button = SNESMAXCard::UNUSED;
else throw std::runtime_error("Controller mapping file: Unknown button: " + buttonStr);
}
m_altControllerButtons[joyNum][i] = button;
}
}
else
{
throw std::runtime_error("Unknown top-level scalar: " + scalar);
}
break; // TODO: extend to support multiple controllers
}
}
catch (const std::exception& szMessage)
{
errorMsg = "Error with yaml file: ";
errorMsg += pathname;
errorMsg += "\n";
errorMsg += szMessage.what();
res = false;
}
yamlHelper.FinaliseParser();
if (res)
g_cmdLine.snesMaxAltControllerType[joyNum] = true; // Enable the alt controller
return res;
}
//=========================================================================== //===========================================================================
static const UINT kUNIT_VERSION = 1; static const UINT kUNIT_VERSION = 1;

View File

@ -31,10 +31,17 @@ public:
virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper); virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper);
virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version); virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version);
static bool ParseControllerMappingFile(UINT joyNum, const char* pathname, std::string& errorMsg);
private: private:
UINT GetControllerButtons(UINT joyNum, JOYINFOEX& infoEx, bool altControllerType);
enum Button { B, Y, SELECT, START, U, D, L, R, A, X, LB, RB, UNUSED1, UNUSED2, UNUSED3, UNUSED4, NUM_BUTTONS, UNUSED };
UINT m_buttonIndex; UINT m_buttonIndex;
UINT m_controller1Buttons; UINT m_controller1Buttons;
UINT m_controller2Buttons; UINT m_controller2Buttons;
bool m_altControllerType[2]; bool m_altControllerType[2];
static UINT m_altControllerButtons[2][NUM_BUTTONS];
}; };

View File

@ -237,33 +237,6 @@ static std::string GetApple2TypeAsString(void)
//--- //---
static UINT ParseFileHdr(void)
{
std::string scalar;
if (!yamlHelper.GetScalar(scalar))
throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Failed to find scalar");
if (scalar != SS_YAML_KEY_FILEHDR)
throw std::runtime_error("Failed to find file header");
yamlHelper.GetMapStartEvent();
YamlLoadHelper yamlLoadHelper(yamlHelper);
//
std::string value = yamlLoadHelper.LoadString(SS_YAML_KEY_TAG);
if (value != SS_YAML_VALUE_AWSS)
{
//printf("%s: Bad tag (%s) - expected %s\n", SS_YAML_KEY_FILEHDR, value.c_str(), SS_YAML_VALUE_AWSS);
throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Bad tag");
}
return yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION);
}
//---
static void ParseUnitApple2(YamlLoadHelper& yamlLoadHelper, UINT version) static void ParseUnitApple2(YamlLoadHelper& yamlLoadHelper, UINT version)
{ {
if (version == 0 || version > UNIT_APPLE2_VER) if (version == 0 || version > UNIT_APPLE2_VER)
@ -376,10 +349,10 @@ static void Snapshot_LoadState_v2(void)
try try
{ {
if (!yamlHelper.InitParser( g_strSaveStatePathname.c_str() )) if (!yamlHelper.InitParser(g_strSaveStatePathname.c_str()))
throw std::runtime_error("Failed to initialize parser or open file: " + g_strSaveStatePathname); throw std::runtime_error("Failed to initialize parser or open file: " + g_strSaveStatePathname);
if (ParseFileHdr() != SS_FILE_VER) if (yamlHelper.ParseFileHdr(SS_YAML_VALUE_AWSS) != SS_FILE_VER)
throw std::runtime_error("Version mismatch"); throw std::runtime_error("Version mismatch");
// //

View File

@ -58,6 +58,31 @@ void YamlHelper::FinaliseParser(void)
yaml_parser_delete(&m_parser); yaml_parser_delete(&m_parser);
} }
UINT YamlHelper::ParseFileHdr(const char* tag)
{
std::string scalar;
if (!GetScalar(scalar))
throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Failed to find scalar");
if (scalar != SS_YAML_KEY_FILEHDR)
throw std::runtime_error("Failed to find file header");
GetMapStartEvent();
YamlLoadHelper yamlLoadHelper(*this);
//
std::string value = yamlLoadHelper.LoadString(SS_YAML_KEY_TAG);
if (value != tag)
{
//printf("%s: Bad tag (%s) - expected %s\n", SS_YAML_KEY_FILEHDR, value.c_str(), tag);
throw std::runtime_error(SS_YAML_KEY_FILEHDR ": Bad tag");
}
return yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION);
}
void YamlHelper::GetNextEvent(void) void YamlHelper::GetNextEvent(void)
{ {
yaml_event_delete(&m_newEvent); yaml_event_delete(&m_newEvent);

View File

@ -44,6 +44,8 @@ public:
int InitParser(const char* pPathname); int InitParser(const char* pPathname);
void FinaliseParser(void); void FinaliseParser(void);
UINT ParseFileHdr(const char* tag);
int GetScalar(std::string& scalar); int GetScalar(std::string& scalar);
void GetMapStartEvent(void); void GetMapStartEvent(void);