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 "Joystick.h"
#include "SoundCore.h"
#include "SNESMAX.h"
#include "ParallelPrinter.h"
#include "Interface.h"
@ -48,6 +49,7 @@ bool g_bHookSystemKey = true;
bool g_bHookAltTab = false;
bool g_bHookAltGrControl = false;
static LPSTR GetCurrArg(LPSTR lpCmdLine)
{
if (*lpCmdLine == '\"')
@ -549,6 +551,20 @@ bool ProcessCmdLine(LPSTR lpCmdLine)
{
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)
{
lpCmdLine = GetCurrArg(lpNextArg);

View File

@ -43,11 +43,10 @@
Bit 6 = Controller 2, Active Low
Bit 7 = Controller 1, Active Low
Once data is read, button presses (for each controller) should be 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
Once data is read, button presses (for each controller) are stored in the following structure:
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
*/
@ -57,6 +56,16 @@
#include "Memory.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)
{
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);
if (result == JOYERR_NOERROR)
{
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 = pCard->GetControllerButtons(JOYSTICKID1, infoEx, pCard->m_altControllerType[0]);
controller1Buttons = ~controller1Buttons;
result = joyGetPosEx(JOYSTICKID2, &infoEx);
if (result == JOYERR_NOERROR)
{
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 = pCard->GetControllerButtons(JOYSTICKID2, infoEx, pCard->m_altControllerType[1]);
controller2Buttons = ~controller2Buttons;
break;
@ -202,11 +133,126 @@ BYTE __stdcall SNESMAXCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value,
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)
{
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;

View File

@ -31,10 +31,17 @@ public:
virtual void SaveSnapshot(YamlSaveHelper& yamlSaveHelper);
virtual bool LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version);
static bool ParseControllerMappingFile(UINT joyNum, const char* pathname, std::string& errorMsg);
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_controller1Buttons;
UINT m_controller2Buttons;
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)
{
if (version == 0 || version > UNIT_APPLE2_VER)
@ -376,10 +349,10 @@ static void Snapshot_LoadState_v2(void)
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);
if (ParseFileHdr() != SS_FILE_VER)
if (yamlHelper.ParseFileHdr(SS_YAML_VALUE_AWSS) != SS_FILE_VER)
throw std::runtime_error("Version mismatch");
//

View File

@ -58,6 +58,31 @@ void YamlHelper::FinaliseParser(void)
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)
{
yaml_event_delete(&m_newEvent);

View File

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