Harden serial comm interface (#1847)
* Harden serial interface * Fixed build errors * Minor tweaks * Automatically attached A->A, B->B, etc * Interfaces go in decending order * Do a modulo in case I decide to move interface numbers * Do specifically mod 4 * Call SDL_JoystickGetDeviceInstanceID which seems to help with a Windows bug * Made MapleDevices smart pointers so they can freely be swapped out without leaking * Fixed issues some issues reported by kosekmi * Added missing bracket * Fixed another compile issue * Fixed a missing else * If device had no serial number, fall down to checking name * Use new DreamPort firmware dynamics * Added missing parens * Fixed screen blanking issue * Added const * Reset VMU Screen on game/emulator exit * Added gameTermination() hook to hardware gamepad * Send SW port on game termination * Added checks so port is only sent if data is valid * Fixed bug: wait for write to complete in sendCmd * Fixed bug leading to multiple pointers to VMU and Rumble pack --------- Co-authored-by: Mike Kosek <mike@kosek.de>
This commit is contained in:
parent
0414a90500
commit
4e74619d9e
|
@ -213,8 +213,8 @@ bool maple_atomiswave_coin_chute(int slot)
|
|||
|
||||
static void mcfg_Create(MapleDeviceType type, u32 bus, u32 port, s32 player_num = -1)
|
||||
{
|
||||
delete MapleDevices[bus][port];
|
||||
maple_device* dev = maple_Create(type);
|
||||
MapleDevices[bus][port].reset();
|
||||
std::shared_ptr<maple_device> dev = maple_Create(type);
|
||||
dev->Setup(bus, port, player_num);
|
||||
}
|
||||
|
||||
|
@ -364,7 +364,7 @@ static void vmuDigest()
|
|||
for (int i = 0; i < MAPLE_PORTS; i++)
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
const maple_device* device = MapleDevices[i][j];
|
||||
std::shared_ptr<const maple_device> device = MapleDevices[i][j];
|
||||
if (device != nullptr)
|
||||
{
|
||||
size_t size;
|
||||
|
@ -414,8 +414,7 @@ void mcfg_DestroyDevices(bool full)
|
|||
if (MapleDevices[i][j] != nullptr
|
||||
&& (full || MapleDevices[i][j]->get_device_type() != MDT_NaomiJamma))
|
||||
{
|
||||
delete MapleDevices[i][j];
|
||||
MapleDevices[i][j] = nullptr;
|
||||
MapleDevices[i][j].reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -435,7 +434,7 @@ void mcfg_SerializeDevices(Serializer& ser)
|
|||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
u8 deviceType = MDT_None;
|
||||
maple_device* device = MapleDevices[i][j];
|
||||
std::shared_ptr<maple_device> device = MapleDevices[i][j];
|
||||
if (device != nullptr)
|
||||
deviceType = device->get_device_type();
|
||||
ser << deviceType;
|
||||
|
@ -490,9 +489,9 @@ void mcfg_DeserializeDevices(Deserializer& deser)
|
|||
memcpy(EEPROM, eeprom, sizeof(eeprom));
|
||||
}
|
||||
|
||||
maple_naomi_jamma *getMieDevice()
|
||||
std::shared_ptr<maple_naomi_jamma> getMieDevice()
|
||||
{
|
||||
if (MapleDevices[0][5] == nullptr || MapleDevices[0][5]->get_device_type() != MDT_NaomiJamma)
|
||||
return nullptr;
|
||||
return (maple_naomi_jamma *)MapleDevices[0][5];
|
||||
return std::static_pointer_cast<maple_naomi_jamma>(MapleDevices[0][5]);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "types.h"
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
enum MapleDeviceType
|
||||
{
|
||||
|
@ -123,4 +124,4 @@ const u8 *getRfidCardData(int playerNum);
|
|||
void setRfidCardData(int playerNum, u8 *data);
|
||||
|
||||
struct maple_naomi_jamma;
|
||||
maple_naomi_jamma *getMieDevice();
|
||||
std::shared_ptr<maple_naomi_jamma> getMieDevice();
|
||||
|
|
|
@ -44,7 +44,7 @@ void maple_device::Setup(u32 bus, u32 port, int playerNum)
|
|||
|
||||
config = new MapleConfigMap(this);
|
||||
OnSetup();
|
||||
MapleDevices[bus][port] = this;
|
||||
MapleDevices[bus][port] = shared_from_this();
|
||||
}
|
||||
maple_device::~maple_device()
|
||||
{
|
||||
|
@ -368,7 +368,7 @@ struct maple_sega_vmu: maple_base
|
|||
}
|
||||
fullSaveNeeded = true;
|
||||
}
|
||||
|
||||
|
||||
bool fullSave()
|
||||
{
|
||||
if (file == nullptr)
|
||||
|
@ -402,7 +402,7 @@ struct maple_sega_vmu: maple_base
|
|||
{
|
||||
memset(flash_data, 0, sizeof(flash_data));
|
||||
memset(lcd_data, 0, sizeof(lcd_data));
|
||||
|
||||
|
||||
// Load existing vmu file if found
|
||||
std::string rpath = hostfs::getVmuPath(logical_port, false);
|
||||
// this might be a storage url
|
||||
|
@ -2047,56 +2047,56 @@ struct RFIDReaderWriter : maple_base
|
|||
|
||||
void insertRfidCard(int playerNum)
|
||||
{
|
||||
maple_device *mapleDev = MapleDevices[1 + playerNum][5];
|
||||
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
|
||||
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
|
||||
((RFIDReaderWriter *)mapleDev)->insertCard();
|
||||
std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->insertCard();
|
||||
}
|
||||
|
||||
void setRfidCardData(int playerNum, u8 *data)
|
||||
{
|
||||
maple_device *mapleDev = MapleDevices[1 + playerNum][5];
|
||||
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
|
||||
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
|
||||
((RFIDReaderWriter *)mapleDev)->setCardData(data);
|
||||
std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->setCardData(data);
|
||||
}
|
||||
|
||||
const u8 *getRfidCardData(int playerNum)
|
||||
{
|
||||
maple_device *mapleDev = MapleDevices[1 + playerNum][5];
|
||||
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
|
||||
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
|
||||
return ((RFIDReaderWriter *)mapleDev)->getCardData();
|
||||
return std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->getCardData();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
maple_device* maple_Create(MapleDeviceType type)
|
||||
std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case MDT_SegaController:
|
||||
if (!settings.platform.isAtomiswave())
|
||||
return new maple_sega_controller();
|
||||
return std::make_shared<maple_sega_controller>();
|
||||
else
|
||||
return new maple_atomiswave_controller();
|
||||
case MDT_Microphone: return new maple_microphone();
|
||||
case MDT_SegaVMU: return new maple_sega_vmu();
|
||||
case MDT_PurupuruPack: return new maple_sega_purupuru();
|
||||
case MDT_Keyboard: return new maple_keyboard();
|
||||
case MDT_Mouse: return new maple_mouse();
|
||||
return std::make_shared<maple_atomiswave_controller>();
|
||||
case MDT_Microphone: return std::make_shared<maple_microphone>();
|
||||
case MDT_SegaVMU: return std::make_shared<maple_sega_vmu>();
|
||||
case MDT_PurupuruPack: return std::make_shared<maple_sega_purupuru>();
|
||||
case MDT_Keyboard: return std::make_shared<maple_keyboard>();
|
||||
case MDT_Mouse: return std::make_shared<maple_mouse>();
|
||||
case MDT_LightGun:
|
||||
if (!settings.platform.isAtomiswave())
|
||||
return new maple_lightgun();
|
||||
return std::make_shared<maple_lightgun>();
|
||||
else
|
||||
return new atomiswave_lightgun();
|
||||
case MDT_NaomiJamma: return new maple_naomi_jamma();
|
||||
case MDT_TwinStick: return new maple_sega_twinstick();
|
||||
case MDT_AsciiStick: return new maple_ascii_stick();
|
||||
case MDT_MaracasController: return new maple_maracas_controller();
|
||||
case MDT_FishingController: return new maple_fishing_controller();
|
||||
case MDT_PopnMusicController: return new maple_popnmusic_controller();
|
||||
case MDT_RacingController: return new maple_racing_controller();
|
||||
case MDT_DenshaDeGoController: return new maple_densha_controller();
|
||||
case MDT_SegaControllerXL: return new FullController();
|
||||
case MDT_RFIDReaderWriter: return new RFIDReaderWriter();
|
||||
return std::make_shared<atomiswave_lightgun>();
|
||||
case MDT_NaomiJamma: return std::make_shared<maple_naomi_jamma>();
|
||||
case MDT_TwinStick: return std::make_shared<maple_sega_twinstick>();
|
||||
case MDT_AsciiStick: return std::make_shared<maple_ascii_stick>();
|
||||
case MDT_MaracasController: return std::make_shared<maple_maracas_controller>();
|
||||
case MDT_FishingController: return std::make_shared<maple_fishing_controller>();
|
||||
case MDT_PopnMusicController: return std::make_shared<maple_popnmusic_controller>();
|
||||
case MDT_RacingController: return std::make_shared<maple_racing_controller>();
|
||||
case MDT_DenshaDeGoController: return std::make_shared<maple_densha_controller>();
|
||||
case MDT_SegaControllerXL: return std::make_shared<FullController>();
|
||||
case MDT_RFIDReaderWriter: return std::make_shared<RFIDReaderWriter>();
|
||||
|
||||
default:
|
||||
ERROR_LOG(MAPLE, "Invalid device type %d", type);
|
||||
|
@ -2108,6 +2108,8 @@ maple_device* maple_Create(MapleDeviceType type)
|
|||
|
||||
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP) && defined(USE_SDL) && !defined(LIBRETRO)
|
||||
#include "sdl/dreamconn.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
struct DreamConnVmu : public maple_sega_vmu
|
||||
{
|
||||
|
@ -2131,7 +2133,7 @@ struct DreamConnVmu : public maple_sega_vmu
|
|||
return maple_sega_vmu::dma(cmd);
|
||||
}
|
||||
|
||||
void copy(maple_sega_vmu *other)
|
||||
void copyIn(std::shared_ptr<maple_sega_vmu> other)
|
||||
{
|
||||
memcpy(flash_data, other->flash_data, sizeof(flash_data));
|
||||
memcpy(lcd_data, other->lcd_data, sizeof(lcd_data));
|
||||
|
@ -2139,6 +2141,14 @@ struct DreamConnVmu : public maple_sega_vmu
|
|||
fullSaveNeeded = other->fullSaveNeeded;
|
||||
}
|
||||
|
||||
void copyOut(std::shared_ptr<maple_sega_vmu> other)
|
||||
{
|
||||
memcpy(other->flash_data, flash_data, sizeof(other->flash_data));
|
||||
memcpy(other->lcd_data, lcd_data, sizeof(other->lcd_data));
|
||||
memcpy(other->lcd_data_decoded, lcd_data_decoded, sizeof(other->lcd_data_decoded));
|
||||
other->fullSaveNeeded = fullSaveNeeded;
|
||||
}
|
||||
|
||||
void updateScreen()
|
||||
{
|
||||
MapleMsg msg;
|
||||
|
@ -2170,32 +2180,111 @@ struct DreamConnPurupuru : public maple_sega_purupuru
|
|||
}
|
||||
};
|
||||
|
||||
static std::list<std::shared_ptr<DreamConnVmu>> dreamConnVmus;
|
||||
static std::list<std::shared_ptr<DreamConnPurupuru>> dreamConnPurupurus;
|
||||
|
||||
void createDreamConnDevices(std::shared_ptr<DreamConn> dreamconn, bool gameStart)
|
||||
{
|
||||
const int bus = dreamconn->getBus();
|
||||
|
||||
bool vmuFound = false;
|
||||
bool rumbleFound = false;
|
||||
|
||||
if (dreamconn->hasVmu())
|
||||
{
|
||||
maple_device *dev = MapleDevices[bus][0];
|
||||
std::shared_ptr<DreamConnVmu> vmu;
|
||||
for (const std::shared_ptr<DreamConnVmu>& vmuIter : dreamConnVmus)
|
||||
{
|
||||
if (vmuIter->dreamconn.get() == dreamconn.get())
|
||||
{
|
||||
vmuFound = true;
|
||||
vmu = vmuIter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<maple_device> dev = MapleDevices[bus][0];
|
||||
if (gameStart || (dev != nullptr && dev->get_device_type() == MDT_SegaVMU))
|
||||
{
|
||||
DreamConnVmu *vmu = new DreamConnVmu(dreamconn);
|
||||
vmu->Setup(bus, 0);
|
||||
if (!gameStart) {
|
||||
// if loading a state, copy data from the regular vmu and send a screen update
|
||||
vmu->copy(static_cast<maple_sega_vmu*>(dev));
|
||||
vmu->updateScreen();
|
||||
bool vmuCreated = false;
|
||||
if (!vmu)
|
||||
{
|
||||
vmu = std::make_shared<DreamConnVmu>(dreamconn);
|
||||
vmuCreated = true;
|
||||
}
|
||||
delete dev;
|
||||
|
||||
vmu->Setup(bus, 0);
|
||||
|
||||
if ((!gameStart || !vmuCreated) && dev) {
|
||||
// if loading a state or DreamConnVmu existed, copy data from the regular vmu and send a screen update
|
||||
vmu->copyIn(std::static_pointer_cast<maple_sega_vmu>(dev));
|
||||
if (!gameStart) {
|
||||
vmu->updateScreen();
|
||||
}
|
||||
}
|
||||
|
||||
if (!vmuFound) dreamConnVmus.push_back(vmu);
|
||||
}
|
||||
}
|
||||
if (dreamconn->hasRumble())
|
||||
{
|
||||
maple_device *dev = MapleDevices[bus][1];
|
||||
std::shared_ptr<DreamConnPurupuru> rumble;
|
||||
for (const std::shared_ptr<DreamConnPurupuru>& purupuru : dreamConnPurupurus)
|
||||
{
|
||||
if (purupuru->dreamconn.get() == dreamconn.get())
|
||||
{
|
||||
rumbleFound = true;
|
||||
rumble = purupuru;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<maple_device> dev = MapleDevices[bus][1];
|
||||
if (gameStart || (dev != nullptr && dev->get_device_type() == MDT_PurupuruPack))
|
||||
{
|
||||
delete dev;
|
||||
DreamConnPurupuru *rumble = new DreamConnPurupuru(dreamconn);
|
||||
if (!rumble)
|
||||
{
|
||||
rumble = std::make_shared<DreamConnPurupuru>(dreamconn);
|
||||
}
|
||||
rumble->Setup(bus, 1);
|
||||
|
||||
if (!rumbleFound) dreamConnPurupurus.push_back(rumble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tearDownDreamConnDevices(std::shared_ptr<DreamConn> dreamconn)
|
||||
{
|
||||
const int bus = dreamconn->getBus();
|
||||
for (std::list<std::shared_ptr<DreamConnVmu>>::const_iterator iter = dreamConnVmus.begin();
|
||||
iter != dreamConnVmus.end();)
|
||||
{
|
||||
if ((*iter)->dreamconn.get() == dreamconn.get())
|
||||
{
|
||||
std::shared_ptr<maple_device> dev = maple_Create(MDT_SegaVMU);
|
||||
dev->Setup(bus, 0);
|
||||
(*iter)->copyOut(std::static_pointer_cast<maple_sega_vmu>(dev));
|
||||
iter = dreamConnVmus.erase(iter);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
for (std::list<std::shared_ptr<DreamConnPurupuru>>::const_iterator iter = dreamConnPurupurus.begin();
|
||||
iter != dreamConnPurupurus.end();)
|
||||
{
|
||||
if ((*iter)->dreamconn.get() == dreamconn.get())
|
||||
{
|
||||
std::shared_ptr<maple_device> dev = maple_Create(MDT_PurupuruPack);
|
||||
dev->Setup(bus, 1);
|
||||
iter = dreamConnPurupurus.erase(iter);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ enum AWAVE_KEYS
|
|||
AWAVE_TRIGGER_KEY = 1 << 17,
|
||||
};
|
||||
|
||||
struct maple_device
|
||||
struct maple_device : public std::enable_shared_from_this<maple_device>
|
||||
{
|
||||
u8 maple_port; //raw maple port
|
||||
u8 bus_port; //0 .. 5
|
||||
|
@ -150,7 +150,7 @@ struct maple_device
|
|||
virtual const void *getData(size_t& size) const { size = 0; return nullptr; }
|
||||
};
|
||||
|
||||
maple_device* maple_Create(MapleDeviceType type);
|
||||
std::shared_ptr<maple_device> maple_Create(MapleDeviceType type);
|
||||
|
||||
#define MAPLE_PORTS 4
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
u32 maple_GetAttachedDevices(u32 bus)
|
||||
{
|
||||
verify(MapleDevices[bus][5]!=0);
|
||||
verify(MapleDevices[bus][5]!=nullptr);
|
||||
|
||||
u32 rv=0;
|
||||
|
||||
|
||||
for (int i=0;i<5;i++)
|
||||
rv|=(MapleDevices[bus][i]!=0?1:0)<<i;
|
||||
rv|=(MapleDevices[bus][i]!=nullptr?1:0)<<i;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "network/ggpo.h"
|
||||
#include "hw/naomi/card_reader.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
enum MaplePattern
|
||||
{
|
||||
MP_Start,
|
||||
|
@ -17,7 +19,7 @@ enum MaplePattern
|
|||
MP_NOP = 7
|
||||
};
|
||||
|
||||
maple_device* MapleDevices[MAPLE_PORTS][6];
|
||||
std::shared_ptr<maple_device> MapleDevices[MAPLE_PORTS][6];
|
||||
|
||||
int maple_schid;
|
||||
|
||||
|
@ -202,13 +204,13 @@ static void maple_DoDma()
|
|||
}
|
||||
const u32 frame_header = swap_msb ? SWAP32(p_data[0]) : p_data[0];
|
||||
|
||||
//Command code
|
||||
//Command code
|
||||
u32 command = frame_header & 0xFF;
|
||||
//Recipient address
|
||||
//Recipient address
|
||||
u32 reci = (frame_header >> 8) & 0xFF;//0-5;
|
||||
//Sender address
|
||||
//Sender address
|
||||
//u32 send = (frame_header >> 16) & 0xFF;
|
||||
//Number of additional words in frame
|
||||
//Number of additional words in frame
|
||||
u32 inlen = (frame_header >> 24) & 0xFF;
|
||||
|
||||
u32 port = getPort(reci);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
#include "maple_devs.h"
|
||||
#include <memory>
|
||||
|
||||
extern maple_device* MapleDevices[MAPLE_PORTS][6];
|
||||
extern std::shared_ptr<maple_device> MapleDevices[MAPLE_PORTS][6];
|
||||
|
||||
void maple_Init();
|
||||
void maple_Reset(bool Manual);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <list>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
#if defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))
|
||||
#include <dirent.h>
|
||||
|
@ -42,40 +43,40 @@
|
|||
#endif
|
||||
|
||||
void createDreamConnDevices(std::shared_ptr<DreamConn> dreamconn, bool gameStart);
|
||||
void tearDownDreamConnDevices(std::shared_ptr<DreamConn> dreamconn);
|
||||
|
||||
class DreamcastControllerConnection
|
||||
{
|
||||
protected:
|
||||
//! The maple bus index [0,3]
|
||||
const int bus;
|
||||
private:
|
||||
MapleMsg connection_msg;
|
||||
|
||||
public:
|
||||
DreamcastControllerConnection(const DreamcastControllerConnection&) = delete;
|
||||
DreamcastControllerConnection() = delete;
|
||||
|
||||
explicit DreamcastControllerConnection(int bus) : bus(bus)
|
||||
{}
|
||||
DreamcastControllerConnection() = default;
|
||||
~DreamcastControllerConnection() = default;
|
||||
|
||||
std::optional<MapleMsg> connect(){
|
||||
if (!establishConnection()) {
|
||||
std::optional<MapleMsg> connect(int bus){
|
||||
bool result = establishConnection(bus);
|
||||
|
||||
if (!result) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Now get the controller configuration
|
||||
MapleMsg msg;
|
||||
msg.command = MDCF_GetCondition;
|
||||
msg.destAP = (bus << 6) | 0x20;
|
||||
msg.originAP = bus << 6;
|
||||
msg.setData(MFID_0_Input);
|
||||
connection_msg.command = MDCF_GetCondition;
|
||||
connection_msg.destAP = (bus << 6) | 0x20;
|
||||
connection_msg.originAP = bus << 6;
|
||||
connection_msg.setData(MFID_0_Input);
|
||||
|
||||
asio::error_code ec = sendMsg(msg);
|
||||
asio::error_code ec = sendMsg(connection_msg);
|
||||
if (ec)
|
||||
{
|
||||
WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str());
|
||||
disconnect();
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!receiveMsg(msg)) {
|
||||
if (!receiveMsg(connection_msg)) {
|
||||
WARN_LOG(INPUT, "DreamcastController[%d] read timeout", bus);
|
||||
disconnect();
|
||||
return std::nullopt;
|
||||
|
@ -83,34 +84,24 @@ public:
|
|||
|
||||
onConnectComplete();
|
||||
|
||||
return msg;
|
||||
return connection_msg;
|
||||
}
|
||||
|
||||
virtual void disconnect() = 0;
|
||||
virtual asio::error_code sendMsg(const MapleMsg& msg) = 0;
|
||||
virtual bool receiveMsg(MapleMsg& msg) = 0;
|
||||
virtual std::string getName() = 0;
|
||||
virtual int getDefaultBus() {
|
||||
// Value of -1 means to use enumeration order
|
||||
return -1;
|
||||
}
|
||||
virtual void gameTermination() {
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool establishConnection() = 0;
|
||||
virtual bool establishConnection(int bus) = 0;
|
||||
virtual void onConnectComplete() = 0;
|
||||
|
||||
std::string msgToString(const MapleMsg& msg, const std::string& delim = " ")
|
||||
{
|
||||
std::ostringstream s;
|
||||
s.fill('0');
|
||||
|
||||
s << std::hex << std::uppercase
|
||||
<< std::setw(2) << (u32)msg.command << " "
|
||||
<< std::setw(2) << (u32)msg.destAP << " "
|
||||
<< std::setw(2) << (u32)msg.originAP << " "
|
||||
<< std::setw(2) << (u32)msg.size;
|
||||
const u32 sz = msg.getDataSize();
|
||||
for (u32 i = 0; i < sz; i++)
|
||||
s << " " << std::setw(2) << (u32)msg.data[i];
|
||||
s << "\r\n";
|
||||
|
||||
return s.str();
|
||||
}
|
||||
};
|
||||
|
||||
class DreamConnConnection : public DreamcastControllerConnection
|
||||
|
@ -126,16 +117,14 @@ public:
|
|||
|
||||
public:
|
||||
DreamConnConnection(const DreamConnConnection&) = delete;
|
||||
DreamConnConnection() = delete;
|
||||
|
||||
explicit DreamConnConnection(int bus) : DreamcastControllerConnection(bus)
|
||||
{}
|
||||
DreamConnConnection() = default;
|
||||
|
||||
~DreamConnConnection() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
bool establishConnection() override {
|
||||
bool establishConnection(int bus) override {
|
||||
#if !defined(_WIN32)
|
||||
WARN_LOG(INPUT, "DreamcastController[%d] connection failed: DreamConn+ / DreamConn S Controller supported on Windows only", bus);
|
||||
return false;
|
||||
|
@ -155,29 +144,32 @@ public:
|
|||
iostream.expires_from_now(std::chrono::duration<u32>::max()); // don't use a 64-bit based duration to avoid overflow
|
||||
}
|
||||
|
||||
void disconnect() override
|
||||
{
|
||||
void disconnect() override {
|
||||
if (iostream) {
|
||||
iostream.close();
|
||||
}
|
||||
}
|
||||
|
||||
asio::error_code sendMsg(const MapleMsg& msg) override
|
||||
{
|
||||
const std::string msgStr = msgToString(msg);
|
||||
asio::error_code ec;
|
||||
asio::error_code sendMsg(const MapleMsg& msg) override {
|
||||
std::ostringstream s;
|
||||
s.fill('0');
|
||||
s << std::hex << std::uppercase
|
||||
<< std::setw(2) << (u32)msg.command << " "
|
||||
<< std::setw(2) << (u32)msg.destAP << " "
|
||||
<< std::setw(2) << (u32)msg.originAP << " "
|
||||
<< std::setw(2) << (u32)msg.size;
|
||||
const u32 sz = msg.getDataSize();
|
||||
for (u32 i = 0; i < sz; i++)
|
||||
s << " " << std::setw(2) << (u32)msg.data[i];
|
||||
s << "\r\n";
|
||||
|
||||
if (!iostream) {
|
||||
return asio::error::not_connected;
|
||||
}
|
||||
asio::ip::tcp::socket& sock = static_cast<asio::ip::tcp::socket&>(iostream.socket());
|
||||
asio::write(sock, asio::buffer(msgStr), ec);
|
||||
|
||||
asio::error_code ec;
|
||||
asio::write(sock, asio::buffer(s.str()), ec);
|
||||
return ec;
|
||||
}
|
||||
|
||||
bool receiveMsg(MapleMsg& msg) override
|
||||
{
|
||||
bool receiveMsg(MapleMsg& msg) override {
|
||||
std::string response;
|
||||
|
||||
if (!std::getline(iostream, response))
|
||||
|
@ -191,16 +183,19 @@ public:
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string getName() override {
|
||||
return "DreamConn+ / DreamConn S Controller";
|
||||
}
|
||||
};
|
||||
|
||||
//! See: https://github.com/OrangeFox86/DreamcastControllerUsbPico
|
||||
class DreamcastControllerUsbPicoConnection : public DreamcastControllerConnection
|
||||
class DreamPortSerialHandler
|
||||
{
|
||||
//! Asynchronous context for serial_handler
|
||||
asio::io_context io_context;
|
||||
//! Output buffer data for serial_handler
|
||||
std::string serial_out_data;
|
||||
//! Handles communication to DreamcastControllerUsbPico
|
||||
//! Handles communication to DreamPort
|
||||
asio::serial_port serial_handler{io_context};
|
||||
//! Set to true while an async write is in progress with serial_handler
|
||||
bool serial_write_in_progress = false;
|
||||
|
@ -218,29 +213,9 @@ class DreamcastControllerUsbPicoConnection : public DreamcastControllerConnectio
|
|||
std::condition_variable read_cv;
|
||||
//! Mutex for read_cv and serializes access to read_queue
|
||||
std::mutex read_cv_mutex;
|
||||
//! Current timeout in milliseconds
|
||||
std::chrono::milliseconds timeout_ms;
|
||||
|
||||
public:
|
||||
//! Dreamcast Controller USB VID:1209 PID:2f07
|
||||
static constexpr const char* VID_PID_GUID = "09120000072f0000";
|
||||
|
||||
public:
|
||||
DreamcastControllerUsbPicoConnection(const DreamcastControllerUsbPicoConnection&) = delete;
|
||||
DreamcastControllerUsbPicoConnection() = delete;
|
||||
|
||||
explicit DreamcastControllerUsbPicoConnection(int bus) : DreamcastControllerConnection(bus)
|
||||
{}
|
||||
|
||||
~DreamcastControllerUsbPicoConnection(){
|
||||
disconnect();
|
||||
}
|
||||
|
||||
bool establishConnection() override {
|
||||
asio::error_code ec;
|
||||
|
||||
// Timeout is 1 second while establishing connection
|
||||
timeout_ms = std::chrono::seconds(1);
|
||||
DreamPortSerialHandler() {
|
||||
|
||||
// the serial port isn't ready at this point, so we need to sleep briefly
|
||||
// we probably should have a better way to handle this
|
||||
|
@ -252,38 +227,163 @@ public:
|
|||
std::string serial_device = "";
|
||||
|
||||
// use user-configured serial device if available, fallback to first available
|
||||
if (cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "default") != "default") {
|
||||
serial_device = cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "default");
|
||||
NOTICE_LOG(INPUT, "DreamcastController[%d] connecting to user-configured serial device: %s", bus, serial_device.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
serial_device = getFirstSerialDevice();
|
||||
NOTICE_LOG(INPUT, "DreamcastController[%d] connecting to autoselected serial device: %s", bus, serial_device.c_str());
|
||||
serial_device = cfgLoadStr("input", "DreamPortSerialDevice", "");
|
||||
if (serial_device.empty()) {
|
||||
serial_device = cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "");
|
||||
if (!serial_device.empty()) {
|
||||
WARN_LOG(INPUT, "DreamcastControllerUsbSerialDevice config is deprecated; use DreamPortSerialDevice instead");
|
||||
}
|
||||
}
|
||||
|
||||
if (!serial_device.empty())
|
||||
{
|
||||
NOTICE_LOG(INPUT, "DreamPort connecting to user-configured serial device: %s", serial_device.c_str());
|
||||
} else {
|
||||
serial_device = getFirstSerialDevice();
|
||||
NOTICE_LOG(INPUT, "DreamPort connecting to autoselected serial device: %s", serial_device.c_str());
|
||||
}
|
||||
|
||||
asio::error_code ec;
|
||||
serial_handler.open(serial_device, ec);
|
||||
|
||||
if (ec || !serial_handler.is_open()) {
|
||||
WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str());
|
||||
WARN_LOG(INPUT, "DreamPort serial connection failed: %s", ec.message().c_str());
|
||||
disconnect();
|
||||
return false;
|
||||
} else {
|
||||
NOTICE_LOG(INPUT, "DreamPort serial connection successful!");
|
||||
}
|
||||
|
||||
// This must be done before the io_context is run because it will keep io_context from returning immediately
|
||||
startSerialRead();
|
||||
|
||||
io_context_thread = std::make_unique<std::thread>([this](){contextThreadEnty();});
|
||||
}
|
||||
|
||||
~DreamPortSerialHandler() {
|
||||
disconnect();
|
||||
io_context_thread->join();
|
||||
}
|
||||
|
||||
bool is_open() const {
|
||||
return serial_handler.is_open();
|
||||
}
|
||||
|
||||
asio::error_code sendCmd(const std::string& cmd, std::chrono::milliseconds timeout_ms) {
|
||||
asio::error_code ec;
|
||||
|
||||
if (!serial_handler.is_open()) {
|
||||
return asio::error::not_connected;
|
||||
}
|
||||
|
||||
// Wait for last write to complete
|
||||
std::unique_lock<std::mutex> lock(write_cv_mutex);
|
||||
const std::chrono::steady_clock::time_point expiration = std::chrono::steady_clock::now() + timeout_ms;
|
||||
if (!write_cv.wait_until(lock, expiration, [this](){return (!serial_write_in_progress || !serial_handler.is_open());}))
|
||||
{
|
||||
return asio::error::timed_out;
|
||||
}
|
||||
|
||||
// Check again before continuing
|
||||
if (!serial_handler.is_open()) {
|
||||
return asio::error::not_connected;
|
||||
}
|
||||
|
||||
serial_out_data = cmd;
|
||||
|
||||
// Clear out the read buffer before writing next command
|
||||
read_queue.clear();
|
||||
serial_write_in_progress = true;
|
||||
asio::async_write(
|
||||
serial_handler,
|
||||
asio::buffer(serial_out_data),
|
||||
asio::transfer_exactly(serial_out_data.size()),
|
||||
[this](const asio::error_code& error, size_t bytes_transferred)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(write_cv_mutex);
|
||||
if (error) {
|
||||
try
|
||||
{
|
||||
serial_handler.cancel();
|
||||
}
|
||||
catch(const asio::system_error&)
|
||||
{
|
||||
// Ignore cancel errors
|
||||
}
|
||||
}
|
||||
serial_write_in_progress = false;
|
||||
write_cv.notify_all();
|
||||
}
|
||||
);
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
asio::error_code sendMsg(const MapleMsg& msg, int hardware_bus, std::chrono::milliseconds timeout_ms) {
|
||||
// Build serial_out_data string
|
||||
// Need to message the hardware bus instead of the software bus
|
||||
u8 hwDestAP = (hardware_bus << 6) | (msg.destAP & 0x3F);
|
||||
u8 hwOriginAP = (hardware_bus << 6) | (msg.originAP & 0x3F);
|
||||
|
||||
std::ostringstream s;
|
||||
s << "X "; // 'X' prefix triggers flycast command parser
|
||||
s.fill('0');
|
||||
s << std::hex << std::uppercase
|
||||
<< std::setw(2) << (u32)msg.command
|
||||
<< std::setw(2) << (u32)hwDestAP // override dest
|
||||
<< std::setw(2) << (u32)hwOriginAP // override origin
|
||||
<< std::setw(2) << (u32)msg.size;
|
||||
const u32 sz = msg.getDataSize();
|
||||
for (u32 i = 0; i < sz; i++) {
|
||||
s << std::setw(2) << (u32)msg.data[i];
|
||||
}
|
||||
s << "\n";
|
||||
|
||||
return sendCmd(s.str(), timeout_ms);
|
||||
}
|
||||
|
||||
bool receiveCmd(std::string& cmd, std::chrono::milliseconds timeout_ms)
|
||||
{
|
||||
// Wait for at least 2 lines to be received (first line is echo back)
|
||||
std::unique_lock<std::mutex> lock(read_cv_mutex);
|
||||
const std::chrono::steady_clock::time_point expiration = std::chrono::steady_clock::now() + timeout_ms;
|
||||
if (!read_cv.wait_until(lock, expiration, [this](){return ((read_queue.size() >= 2) || !serial_handler.is_open());}))
|
||||
{
|
||||
// Timeout
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read_queue.size() < 2) {
|
||||
// Connection was closed before data could be received
|
||||
return false;
|
||||
}
|
||||
|
||||
// discard the first message as we are interested in the second only which returns the controller configuration
|
||||
cmd = std::move(read_queue.back());
|
||||
read_queue.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void onConnectComplete() override {
|
||||
// Timeout is extended to 5 seconds for all other communication after connection
|
||||
timeout_ms = std::chrono::seconds(5);
|
||||
bool receiveMsg(MapleMsg& msg, std::chrono::milliseconds timeout_ms)
|
||||
{
|
||||
std::string response;
|
||||
if (!receiveCmd(response, timeout_ms)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sscanf(response.c_str(), "%hhx %hhx %hhx %hhx", &msg.command, &msg.destAP, &msg.originAP, &msg.size);
|
||||
|
||||
if (serial_handler.is_open()) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void disconnect() override
|
||||
private:
|
||||
void disconnect()
|
||||
{
|
||||
io_context.stop();
|
||||
|
||||
|
@ -313,89 +413,6 @@ public:
|
|||
// This context should never exit until disconnect due to read handler automatically rearming
|
||||
io_context.run();
|
||||
}
|
||||
|
||||
asio::error_code sendMsg(const MapleMsg& msg) override
|
||||
{
|
||||
asio::error_code ec;
|
||||
|
||||
if (!serial_handler.is_open()) {
|
||||
return asio::error::not_connected;
|
||||
}
|
||||
|
||||
// Wait for last write to complete
|
||||
std::unique_lock<std::mutex> lock(write_cv_mutex);
|
||||
const std::chrono::steady_clock::time_point expiration = std::chrono::steady_clock::now() + timeout_ms;
|
||||
if (!write_cv.wait_until(lock, expiration, [this](){return (!serial_write_in_progress || !serial_handler.is_open());}))
|
||||
{
|
||||
return asio::error::timed_out;
|
||||
}
|
||||
|
||||
// Check again before continuing
|
||||
if (!serial_handler.is_open()) {
|
||||
return asio::error::not_connected;
|
||||
}
|
||||
|
||||
// Clear out the read buffer before writing next command
|
||||
read_queue.clear();
|
||||
|
||||
serial_write_in_progress = true;
|
||||
// Messages to Dreamcast Controller USB need to be prefixed to trigger the correct parser
|
||||
serial_out_data = std::string("X ") + msgToString(msg);
|
||||
asio::async_write(serial_handler, asio::buffer(serial_out_data), asio::transfer_exactly(serial_out_data.size()), [this](const asio::error_code& error, size_t bytes_transferred)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(write_cv_mutex);
|
||||
if (error) {
|
||||
try
|
||||
{
|
||||
serial_handler.cancel();
|
||||
}
|
||||
catch(const asio::system_error&)
|
||||
{
|
||||
// Ignore cancel errors
|
||||
}
|
||||
}
|
||||
serial_write_in_progress = false;
|
||||
write_cv.notify_all();
|
||||
});
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
bool receiveMsg(MapleMsg& msg) override
|
||||
{
|
||||
std::string response;
|
||||
|
||||
// Wait for at least 2 lines to be received (first line is echo back)
|
||||
std::unique_lock<std::mutex> lock(read_cv_mutex);
|
||||
const std::chrono::steady_clock::time_point expiration = std::chrono::steady_clock::now() + timeout_ms;
|
||||
if (!read_cv.wait_until(lock, expiration, [this](){return ((read_queue.size() >= 2) || !serial_handler.is_open());}))
|
||||
{
|
||||
// Timeout
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read_queue.size() < 2) {
|
||||
// Connection was closed before data could be received
|
||||
return false;
|
||||
}
|
||||
|
||||
// discard the first message as we are interested in the second only which returns the controller configuration
|
||||
response = std::move(read_queue.back());
|
||||
read_queue.clear();
|
||||
|
||||
sscanf(response.c_str(), "%hhx %hhx %hhx %hhx", &msg.command, &msg.destAP, &msg.originAP, &msg.size);
|
||||
|
||||
if (serial_handler.is_open()) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string getFirstSerialDevice() {
|
||||
|
||||
// On Windows, we get the first serial device matching our VID/PID
|
||||
|
@ -566,29 +583,288 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
DreamConn::DreamConn(int bus, int dreamcastControllerType, const std::string& name) :
|
||||
bus(bus), dreamcastControllerType(dreamcastControllerType), name(name)
|
||||
//! See: https://github.com/OrangeFox86/DreamPort
|
||||
class DreamPortConnection : public DreamcastControllerConnection
|
||||
{
|
||||
change_bus(bus);
|
||||
//! The one and only serial port
|
||||
static std::unique_ptr<DreamPortSerialHandler> serial;
|
||||
//! Number of devices using the above serial
|
||||
static std::atomic<std::uint32_t> connected_dev_count;
|
||||
//! Current timeout in milliseconds
|
||||
std::chrono::milliseconds timeout_ms;
|
||||
//! The bus ID dictated by flycast
|
||||
int software_bus = -1;
|
||||
//! The bus index of the hardware connection which will differ from the software bus
|
||||
int hardware_bus = -1;
|
||||
//! true iff only a single devices was found when enumerating devices
|
||||
bool is_single_device = true;
|
||||
//! True when initial enumeration failed
|
||||
bool is_hardware_bus_implied = true;
|
||||
//! True once connection is established
|
||||
bool connection_established = false;
|
||||
|
||||
public:
|
||||
//! Dreamcast Controller USB VID:1209 PID:2f07
|
||||
static constexpr const std::uint16_t VID = 0x1209;
|
||||
static constexpr const std::uint16_t PID = 0x2f07;
|
||||
static constexpr const char* VID_PID_GUID = "09120000072f0000";
|
||||
|
||||
public:
|
||||
DreamPortConnection(const DreamPortConnection&) = delete;
|
||||
DreamPortConnection() = delete;
|
||||
|
||||
DreamPortConnection(int joystick_idx, SDL_Joystick* sdl_joystick) :
|
||||
DreamcastControllerConnection()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// Workaround: Getting the instance ID here fixes some sort of L/R trigger bug in Windows dinput for some reason
|
||||
(void)SDL_JoystickGetDeviceInstanceID(joystick_idx);
|
||||
#endif
|
||||
determineHardwareBus(joystick_idx, sdl_joystick);
|
||||
}
|
||||
|
||||
~DreamPortConnection(){
|
||||
disconnect();
|
||||
}
|
||||
|
||||
int hardwareBus() const {
|
||||
return hardware_bus;
|
||||
}
|
||||
|
||||
bool isHardwareBusImplied() const {
|
||||
return is_hardware_bus_implied;
|
||||
}
|
||||
|
||||
bool isSingleDevice() const {
|
||||
return is_single_device;
|
||||
}
|
||||
|
||||
bool establishConnection(int bus) override {
|
||||
// Timeout is 1 second while establishing connection
|
||||
timeout_ms = std::chrono::seconds(1);
|
||||
|
||||
software_bus = bus;
|
||||
|
||||
if (connection_established && serial) {
|
||||
if (serial->is_open()) {
|
||||
// This equipment is fixed to the hardware bus - the software bus isn't relevant
|
||||
sendPort();
|
||||
return true;
|
||||
} else {
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
++connected_dev_count;
|
||||
connection_established = true;
|
||||
if (!serial) {
|
||||
serial = std::make_unique<DreamPortSerialHandler>();
|
||||
}
|
||||
|
||||
if (serial && serial->is_open()) {
|
||||
sendPort();
|
||||
return true;
|
||||
} else {
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void sendPort() {
|
||||
if (connection_established && software_bus >= 0 && software_bus <= 3 && hardware_bus >=0 && hardware_bus <= 3) {
|
||||
// This will update the displayed port letter on the screen
|
||||
std::ostringstream s;
|
||||
s << "XP "; // XP is flycast "set port" command
|
||||
s << hardware_bus << " " << software_bus << "\n";
|
||||
serial->sendCmd(s.str(), timeout_ms);
|
||||
// Don't really care about the response, just want to ensure it gets fully processed before continuing
|
||||
std::string buffer;
|
||||
serial->receiveCmd(buffer, timeout_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void onConnectComplete() override {
|
||||
// Timeout is extended to 5 seconds for all other communication after connection
|
||||
timeout_ms = std::chrono::seconds(5);
|
||||
}
|
||||
|
||||
void disconnect() override {
|
||||
if (connection_established) {
|
||||
connection_established = false;
|
||||
if (--connected_dev_count == 0) {
|
||||
// serial is no longer needed
|
||||
serial.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asio::error_code sendMsg(const MapleMsg& msg) override {
|
||||
if (serial) {
|
||||
return serial->sendMsg(msg, hardware_bus, timeout_ms);
|
||||
}
|
||||
|
||||
return asio::error::not_connected;
|
||||
}
|
||||
|
||||
bool receiveMsg(MapleMsg& msg) override {
|
||||
if (serial) {
|
||||
return serial->receiveMsg(msg, timeout_ms);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string getName() override {
|
||||
std::string name = "DreamPort";
|
||||
if (!is_hardware_bus_implied && !is_single_device) {
|
||||
const char portChar = ('A' + hardware_bus);
|
||||
name += " " + std::string(1, portChar);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
int getDefaultBus() override {
|
||||
if (!is_hardware_bus_implied && !is_single_device) {
|
||||
return hardware_bus;
|
||||
} else {
|
||||
// Value of -1 means to use enumeration order
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void gameTermination() override {
|
||||
// Reset screen to selected port
|
||||
sendPort();
|
||||
}
|
||||
|
||||
private:
|
||||
void determineHardwareBus(int joystick_idx, SDL_Joystick* sdl_joystick) {
|
||||
// This function determines what bus index to use when communicating with the hardware.
|
||||
#if defined(_WIN32)
|
||||
// This only works in Windows because the joystick_path is not given in other OSes
|
||||
const char* joystick_name = SDL_JoystickName(sdl_joystick);
|
||||
const char* joystick_path = SDL_JoystickPath(sdl_joystick);
|
||||
|
||||
struct SDL_hid_device_info* devs = SDL_hid_enumerate(VID, PID);
|
||||
if (devs) {
|
||||
if (!devs->next) {
|
||||
// Only single device found, so this is simple (host-1p firmware used)
|
||||
hardware_bus = 0;
|
||||
is_hardware_bus_implied = false;
|
||||
is_single_device = true;
|
||||
} else {
|
||||
struct SDL_hid_device_info* it = devs;
|
||||
struct SDL_hid_device_info* my_dev = nullptr;
|
||||
|
||||
if (joystick_path)
|
||||
{
|
||||
while (it)
|
||||
{
|
||||
// Note: hex characters will be differing case, so case-insensitive cmp is needed
|
||||
if (it->path && 0 == SDL_strcasecmp(it->path, joystick_path)) {
|
||||
my_dev = it;
|
||||
break;
|
||||
}
|
||||
it = it->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (my_dev) {
|
||||
it = devs;
|
||||
int count = 0;
|
||||
if (my_dev->serial_number) {
|
||||
while (it) {
|
||||
if (it->serial_number &&
|
||||
0 == wcscmp(it->serial_number, my_dev->serial_number))
|
||||
{
|
||||
++count;
|
||||
}
|
||||
it = it->next;
|
||||
}
|
||||
|
||||
if (count == 1) {
|
||||
// Single device of this serial found
|
||||
is_single_device = true;
|
||||
hardware_bus = 0;
|
||||
is_hardware_bus_implied = false;
|
||||
} else {
|
||||
is_single_device = false;
|
||||
if (my_dev->release_number < 0x0102) {
|
||||
// Interfaces go in decending order
|
||||
hardware_bus = (count - (my_dev->interface_number % 4) - 1);
|
||||
is_hardware_bus_implied = false;
|
||||
} else {
|
||||
// Version 1.02 of interface will make interfaces in ascending order
|
||||
hardware_bus = (my_dev->interface_number % 4);
|
||||
is_hardware_bus_implied = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_hid_free_enumeration(devs);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hardware_bus < 0) {
|
||||
// The number of buttons gives a clue as to what index the controller is
|
||||
int nbuttons = SDL_JoystickNumButtons(sdl_joystick);
|
||||
|
||||
if (nbuttons >= 32 || nbuttons <= 27) {
|
||||
// Older version of firmware or single player
|
||||
hardware_bus = 0;
|
||||
is_hardware_bus_implied = true;
|
||||
is_single_device = true;
|
||||
}
|
||||
else {
|
||||
hardware_bus = 31 - nbuttons;
|
||||
is_hardware_bus_implied = false;
|
||||
is_single_device = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Define the static instances here
|
||||
std::unique_ptr<DreamPortSerialHandler> DreamPortConnection::serial;
|
||||
std::atomic<std::uint32_t> DreamPortConnection::connected_dev_count = 0;
|
||||
|
||||
DreamConn::DreamConn(int bus, int dreamcastControllerType, int joystick_idx, SDL_Joystick* sdl_joystick) :
|
||||
bus(bus), dreamcastControllerType(dreamcastControllerType)
|
||||
{
|
||||
switch (dreamcastControllerType)
|
||||
{
|
||||
case TYPE_DREAMCONN:
|
||||
dcConnection = std::make_unique<DreamConnConnection>();
|
||||
break;
|
||||
|
||||
case TYPE_DREAMPORT:
|
||||
dcConnection = std::make_unique<DreamPortConnection>(joystick_idx, sdl_joystick);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DreamConn::~DreamConn() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void DreamConn::change_bus(int bus) {
|
||||
disconnect();
|
||||
dcConnection.reset();
|
||||
switch (dreamcastControllerType)
|
||||
{
|
||||
case TYPE_DREAMCONN:
|
||||
dcConnection = std::make_unique<DreamConnConnection>(bus);
|
||||
break;
|
||||
|
||||
case TYPE_DREAMCASTCONTROLLERUSB:
|
||||
dcConnection = std::make_unique<DreamcastControllerUsbPicoConnection>(bus);
|
||||
break;
|
||||
int DreamConn::getDefaultBus() {
|
||||
if (dcConnection) {
|
||||
return dcConnection->getDefaultBus();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void DreamConn::changeBus(int newBus) {
|
||||
bus = newBus;
|
||||
}
|
||||
|
||||
std::string DreamConn::getName() {
|
||||
if (dcConnection) {
|
||||
return dcConnection->getName();
|
||||
}
|
||||
return "Unknown DreamConn";
|
||||
}
|
||||
|
||||
void DreamConn::connect()
|
||||
|
@ -604,7 +880,7 @@ void DreamConn::connect()
|
|||
return;
|
||||
}
|
||||
|
||||
std::optional<MapleMsg> msg = dcConnection->connect();
|
||||
std::optional<MapleMsg> msg = dcConnection->connect(bus);
|
||||
if (!msg)
|
||||
{
|
||||
return;
|
||||
|
@ -617,7 +893,7 @@ void DreamConn::connect()
|
|||
|
||||
if (hasVmu() || hasRumble())
|
||||
{
|
||||
NOTICE_LOG(INPUT, "Connected to DreamcastController[%d]: Type:%s, VMU:%d, Rumble Pack:%d", bus, name.c_str(), hasVmu(), hasRumble());
|
||||
NOTICE_LOG(INPUT, "Connected to DreamcastController[%d]: Type:%s, VMU:%d, Rumble Pack:%d", bus, getName().c_str(), hasVmu(), hasRumble());
|
||||
maple_io_connected = true;
|
||||
}
|
||||
else
|
||||
|
@ -662,6 +938,13 @@ bool DreamConn::send(const MapleMsg& msg)
|
|||
return true;
|
||||
}
|
||||
|
||||
void DreamConn::gameTermination()
|
||||
{
|
||||
if (dcConnection) {
|
||||
dcConnection->gameTermination();
|
||||
}
|
||||
}
|
||||
|
||||
bool DreamConnGamepad::isDreamcastController(int deviceIndex)
|
||||
{
|
||||
char guid_str[33] {};
|
||||
|
@ -673,7 +956,7 @@ bool DreamConnGamepad::isDreamcastController(int deviceIndex)
|
|||
// DreamConn VID:4457 PID:4443
|
||||
// Dreamcast Controller USB VID:1209 PID:2f07
|
||||
if (memcmp(DreamConnConnection::VID_PID_GUID, guid_str + 8, 16) == 0 ||
|
||||
memcmp(DreamcastControllerUsbPicoConnection::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
memcmp(DreamPortConnection::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
{
|
||||
NOTICE_LOG(INPUT, "Dreamcast controller found!");
|
||||
return true;
|
||||
|
@ -692,22 +975,34 @@ DreamConnGamepad::DreamConnGamepad(int maple_port, int joystick_idx, SDL_Joystic
|
|||
// Dreamcast Controller USB VID:1209 PID:2f07
|
||||
if (memcmp(DreamConnConnection::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
{
|
||||
_name = "DreamConn+ / DreamConn S Controller";
|
||||
dreamconn = std::make_shared<DreamConn>(maple_port, TYPE_DREAMCONN, _name);
|
||||
dreamconn = std::make_shared<DreamConn>(maple_port, TYPE_DREAMCONN, joystick_idx, sdl_joystick);
|
||||
}
|
||||
else if (memcmp(DreamcastControllerUsbPicoConnection::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
else if (memcmp(DreamPortConnection::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
{
|
||||
_name = "Dreamcast Controller USB";
|
||||
dreamconn = std::make_shared<DreamConn>(maple_port, TYPE_DREAMCASTCONTROLLERUSB, _name);
|
||||
dreamconn = std::make_shared<DreamConn>(maple_port, TYPE_DREAMPORT, joystick_idx, sdl_joystick);
|
||||
}
|
||||
|
||||
if (dreamconn) {
|
||||
_name = dreamconn->getName();
|
||||
int defaultBus = dreamconn->getDefaultBus();
|
||||
if (defaultBus >= 0 && defaultBus < 4) {
|
||||
set_maple_port(defaultBus);
|
||||
}
|
||||
}
|
||||
|
||||
EventManager::listen(Event::Start, handleEvent, this);
|
||||
EventManager::listen(Event::LoadState, handleEvent, this);
|
||||
EventManager::listen(Event::Terminate, handleEvent, this);
|
||||
}
|
||||
|
||||
DreamConnGamepad::~DreamConnGamepad() {
|
||||
EventManager::unlisten(Event::Start, handleEvent, this);
|
||||
EventManager::unlisten(Event::LoadState, handleEvent, this);
|
||||
EventManager::unlisten(Event::Terminate, handleEvent, this);
|
||||
if (dreamconn) {
|
||||
tearDownDreamConnDevices(dreamconn);
|
||||
dreamconn.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void DreamConnGamepad::set_maple_port(int port)
|
||||
|
@ -717,7 +1012,7 @@ void DreamConnGamepad::set_maple_port(int port)
|
|||
dreamconn->disconnect();
|
||||
}
|
||||
else if (dreamconn->getBus() != port) {
|
||||
dreamconn->change_bus(port);
|
||||
dreamconn->changeBus(port);
|
||||
if (is_registered()) {
|
||||
dreamconn->connect();
|
||||
}
|
||||
|
@ -739,6 +1034,11 @@ void DreamConnGamepad::handleEvent(Event event, void *arg)
|
|||
DreamConnGamepad *gamepad = static_cast<DreamConnGamepad*>(arg);
|
||||
if (gamepad->dreamconn != nullptr)
|
||||
createDreamConnDevices(gamepad->dreamconn, event == Event::Start);
|
||||
|
||||
if (gamepad->dreamconn != nullptr && event == Event::Terminate)
|
||||
{
|
||||
gamepad->dreamconn->gameTermination();
|
||||
}
|
||||
}
|
||||
|
||||
bool DreamConnGamepad::gamepad_btn_input(u32 code, bool pressed)
|
||||
|
@ -788,6 +1088,8 @@ void DreamConn::connect() {
|
|||
}
|
||||
void DreamConn::disconnect() {
|
||||
}
|
||||
void DreamConn::gameTermination() {
|
||||
}
|
||||
|
||||
bool DreamConnGamepad::isDreamcastController(int deviceIndex) {
|
||||
return false;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP)
|
||||
#define USE_DREAMCASTCONTROLLER 1
|
||||
#define TYPE_DREAMCONN 1
|
||||
#define TYPE_DREAMCASTCONTROLLERUSB 2
|
||||
#define TYPE_DREAMPORT 2
|
||||
#include <asio.hpp>
|
||||
#endif
|
||||
#include <memory>
|
||||
|
@ -52,7 +52,6 @@ class DreamConn
|
|||
{
|
||||
int bus = -1;
|
||||
const int dreamcastControllerType;
|
||||
const std::string name;
|
||||
#ifdef USE_DREAMCASTCONTROLLER
|
||||
std::unique_ptr<class DreamcastControllerConnection> dcConnection;
|
||||
#endif
|
||||
|
@ -60,12 +59,15 @@ class DreamConn
|
|||
u8 expansionDevs = 0;
|
||||
|
||||
public:
|
||||
DreamConn(int bus, int dreamcastControllerType, const std::string& name);
|
||||
DreamConn(int bus, int dreamcastControllerType, int joystick_idx, SDL_Joystick* sdl_joystick);
|
||||
|
||||
~DreamConn();
|
||||
|
||||
bool send(const MapleMsg& msg);
|
||||
|
||||
// When called, do teardown stuff like reset screen
|
||||
void gameTermination();
|
||||
|
||||
int getBus() const {
|
||||
return bus;
|
||||
}
|
||||
|
@ -76,7 +78,11 @@ public:
|
|||
return expansionDevs & 2;
|
||||
}
|
||||
|
||||
void change_bus(int bus);
|
||||
int getDefaultBus();
|
||||
|
||||
void changeBus(int newBus);
|
||||
|
||||
std::string getName();
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
|
|
|
@ -271,9 +271,9 @@ void input_sdl_init()
|
|||
// Linux mappings are OK by default
|
||||
// Can be removed once mapping is merged into SDL, see https://github.com/libsdl-org/SDL/pull/12039
|
||||
#if (defined(__APPLE__) && defined(TARGET_OS_MAC))
|
||||
SDL_GameControllerAddMapping("0300000009120000072f000000010000,OrangeFox86 Dreamcast Controller USB,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:a2,righttrigger:a5,start:b11");
|
||||
SDL_GameControllerAddMapping("0300000009120000072f000000010000,OrangeFox86 DreamPort,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:a2,righttrigger:a5,start:b11");
|
||||
#elif defined(_WIN32)
|
||||
SDL_GameControllerAddMapping("0300000009120000072f000000000000,OrangeFox86 Dreamcast Controller USB,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:-a2,righttrigger:-a5,start:b11");
|
||||
SDL_GameControllerAddMapping("0300000009120000072f000000000000,OrangeFox86 DreamPort,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:-a2,righttrigger:-a5,start:b11");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue