AppleWin/source/SNESMAX.cpp

280 lines
8.8 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
SNESMAX.CPP
Emulate a SNES MAX controller serial interface card
2 ports (one for each of the two possible joysticks)
Latch = write any data to C0n0
Clock = write any data to C0n1
Data = read from C0n0
n = 8 + slot number
Data
Bit 0 = Not Used
Bit 1 = Not Used
Bit 2 = Not Used
Bit 3 = Not Used
Bit 4 = Not Used
Bit 5 = Not Used
Bit 6 = Controller 2, Active Low
Bit 7 = Controller 1, Active Low
Once data is read, button presses (for each controller) are stored in the following structure:
bit# 16 / 11 10 9 8 / 7 6 5 4 3 2 1 0
m_controllerXButtons: *1 x:x:x:x:Fr:Fl:X:A / R:L:D:U:St:Sl:Y:B
*1 = Controller plugged in status
Alex Lukacz Aug 2021
*/
#include "StdAfx.h"
#include "Joystick.h"
#include "SNESMAX.h"
#include "Memory.h"
#include "YamlHelper.h"
// Default to Sony DS4 / DualSense:
// b11,..,b0: St,Sl / -,-,R,L,X,A,B,Y
//
// infoEx.dwButtons bit definitions:
const UINT SNESMAXCard::m_mainControllerButtons[NUM_BUTTONS] =
{
Y,B,A,X,LB,RB,UNUSED,UNUSED, SELECT,START,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED // bit0 -> Y, bit1 -> B, bit2 -> A, etc
};
// AltControllerType defaults to "8BitDo NES30 PRO" (can be reconfigured via command line + yaml mapping file)
// b11,..,b0: St,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,UNUSED,UNUSED,UNUSED,UNUSED}, // bit0 -> A, bit1 -> B, bit2 -> unused, etc
{A,B,UNUSED,X,Y,UNUSED,UNUSED,UNUSED, LB,RB,SELECT,START,UNUSED,UNUSED,UNUSED,UNUSED}
};
BYTE __stdcall SNESMAXCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
const UINT slot = ((addr & 0xff) >> 4) - 8;
SNESMAXCard* pCard = (SNESMAXCard*)MemGetSlotParameters(slot);
BYTE output = MemReadFloatingBus(nExecutedCycles);
switch (addr & 0xF)
{
case 0:
output = (output & 0x3F) | ((pCard->m_controller2Buttons & 0x1) << 6) | ((pCard->m_controller1Buttons & 0x1) << 7);
break;
default:
break;
}
return output;
}
BYTE __stdcall SNESMAXCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE value, ULONG nExecutedCycles)
{
const UINT slot = ((addr & 0xff) >> 4) - 8;
SNESMAXCard* pCard = (SNESMAXCard*)MemGetSlotParameters(slot);
UINT xAxis = 0;
UINT yAxis = 0;
JOYINFOEX infoEx;
infoEx.dwSize = sizeof(infoEx);
infoEx.dwFlags = JOY_RETURNPOV | JOY_RETURNBUTTONS;
UINT& controller1Buttons = pCard->m_controller1Buttons; // ref to object's controller1Buttons
UINT& controller2Buttons = pCard->m_controller2Buttons; // ref to object's controller2Buttons
switch (addr & 0xF)
{
case 0: // Latch
pCard->m_buttonIndex = 0;
controller1Buttons = 0;
controller2Buttons = 0;
if (GetJoystick1() >= 0 && joyGetPosEx(GetJoystick1(), &infoEx) == JOYERR_NOERROR)
controller1Buttons = pCard->GetControllerButtons(JOYSTICKID1, infoEx, pCard->m_altControllerType[0]);
controller1Buttons = ~controller1Buttons;
if (GetJoystick2() >= 0 && joyGetPosEx(GetJoystick2(), &infoEx) == JOYERR_NOERROR)
controller2Buttons = pCard->GetControllerButtons(JOYSTICKID2, infoEx, pCard->m_altControllerType[1]);
controller2Buttons = ~controller2Buttons;
break;
case 1: // Clock
if (pCard->m_buttonIndex <= NUM_BUTTONS) // NB. 17 bits (where last bit is: Controller plugged in status)
{
pCard->m_buttonIndex++;
controller1Buttons = controller1Buttons >> 1;
controller2Buttons = controller2Buttons >> 1;
}
break;
default:
break;
}
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
UINT* controllerButtonMappings = !altControllerType ? (UINT*)m_mainControllerButtons : (UINT*)(&m_altControllerButtons[joyNum][0]);
for (UINT i = 0; i < NUM_BUTTONS; i++)
{
if (infoEx.dwButtons & (1 << i))
{
if (controllerButtonMappings[i] != UNUSED)
controllerButtons |= (1 << controllerButtonMappings[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;
#define SS_YAML_KEY_BUTTON_INDEX "Button Index"
const std::string& SNESMAXCard::GetSnapshotCardName(void)
{
static const std::string name("SNES MAX");
return name;
}
void SNESMAXCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION);
YamlSaveHelper::Label unit(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
yamlSaveHelper.SaveUint(SS_YAML_KEY_BUTTON_INDEX, m_buttonIndex);
}
bool SNESMAXCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
if (version < 1 || version > kUNIT_VERSION)
ThrowErrorInvalidVersion(version);
m_buttonIndex = yamlLoadHelper.LoadUint(SS_YAML_KEY_BUTTON_INDEX);
// Initialise with no buttons pressed (loaded state maybe part way through reading the buttons)
m_controller1Buttons = 0x1ffff;
m_controller2Buttons = 0x1ffff;
return true;
}