6 Commits
Author SHA1 Message Date
Flyinghead 5eadfb388e dreampotato: change timeout values. disconnect on receive error
initial RTT timeout is 500 ms then 100 ms
2026-01-16 13:12:03 +01:00
Flyinghead 83f6d818b5 maplelink: disable savestates. Change some options default. Bug fix
Disable load/save state if at least one MapleLink with storage enabled.
Change UsePhysicalMemory default to false.
Change Per Game VMU A1 default to true.
Fix DreamLink infinite loop on exit.
dreampotato, dreamconn: don't use stringstream for output
dreampotato: libretro support (no VMU storage yet)
2026-01-16 13:08:54 +01:00
Flyinghead bbae3de9a4 switch: build fix 2026-01-15 17:23:08 +01:00
Flyinghead c2f66018d8 lr: build fix 2026-01-15 16:17:00 +01:00
Flyinghead 82ca2db61a !win32 build fix 2026-01-15 16:01:26 +01:00
Flyinghead b4d83c0d12 DreamLink refactoring
Introduce MapleLink base interface and DreamPotato implementation.
2026-01-15 15:45:14 +01:00
33 changed files with 1083 additions and 1407 deletions

No files matched your search

+14 -8
View File
@@ -214,18 +214,24 @@ std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices {{
Option<MapleDeviceType>("device4.2", MDT_None, "input")}},
}};
std::array<Option<bool>, 4> UseNetworkExpansionDevices{
Option<bool>("device1.UseNetworkExpansionDevices", false, "input"),
Option<bool>("device2.UseNetworkExpansionDevices", false, "input"),
Option<bool>("device3.UseNetworkExpansionDevices", false, "input"),
Option<bool>("device4.UseNetworkExpansionDevices", false, "input"),
};
std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices{{
{{Option<int>("device1.1.net", 0, "input"),
Option<int>("device1.2.net", 0, "input")}},
Option<bool> PerGameVmu("PerGameVmu", false, "config");
{{Option<int>("device2.1.net", 0, "input"),
Option<int>("device2.2.net", 0, "input")}},
{{Option<int>("device3.1.net", 0, "input"),
Option<int>("device3.2.net", 0, "input")}},
{{Option<int>("device4.1.net", 0, "input"),
Option<int>("device4.2.net", 0, "input")}},
}};
Option<bool> PerGameVmu("PerGameVmu", true, "config");
#ifdef _WIN32
Option<bool, false> UseRawInput("RawInput", false, "input");
#endif
Option<bool> UsePhysicalVmuMemory("UsePhysicalVmuMemory", true);
Option<bool> UsePhysicalVmuMemory("UsePhysicalVmuMemory", false);
#ifdef USE_LUA
Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
+1 -1
View File
@@ -551,7 +551,7 @@ extern Option<int> VirtualGamepadVibration;
extern Option<int> VirtualGamepadTransparency;
extern std::array<Option<MapleDeviceType>, 4> MapleMainDevices;
extern std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices;
extern std::array<Option<bool>, 4> UseNetworkExpansionDevices;
extern std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices;
extern Option<bool> PerGameVmu;
#ifdef _WIN32
extern Option<bool, false> UseRawInput;
+35 -36
View File
@@ -50,9 +50,6 @@
#endif
#include "hw/sh4/sh4_interpreter.h"
#include "hw/sh4/dyna/ngen.h"
#ifdef USE_DREAMLINK_DEVICES
#include "sdl/dreamlink.h"
#endif
#include "oslib/i18n.h"
settings_t settings;
@@ -639,11 +636,6 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
}
if (!settings.naomi.slave)
{
#ifdef USE_DREAMLINK_DEVICES
// Note: ordinarily we would like to use the 'events' system for this step.
// However, this call can change 'config::MapleExpansionDevices', and therefore needs to run before 'mcfg_CreateDevices'.
reconnectDreamLinks();
#endif
mcfg_DestroyDevices();
mcfg_CreateDevices();
if (settings.platform.isNaomi())
@@ -668,6 +660,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
loadGameSpecificSettings();
NetworkHandshake::init();
settings.input.fastForwardMode = false;
EventManager::event(Event::Start);
if (!settings.content.path.empty())
{
#ifndef LIBRETRO
@@ -677,12 +670,11 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
dc_loadstate(config::SavestateSlot);
#endif
}
EventManager::event(Event::Start);
if (progress)
{
#ifdef GDB_SERVER
if(config::GDBWaitForConnection)
if (config::GDB && config::GDBWaitForConnection)
progress->label = "Waiting for debugger...";
else
#endif
@@ -698,34 +690,40 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
void Emulator::runInternal()
{
if (singleStep)
{
getSh4Executor()->Step();
singleStep = false;
}
else if (stepRangeTo != 0)
{
while (Sh4cntx.pc >= stepRangeFrom && Sh4cntx.pc < stepRangeTo)
runner.init();
try {
if (singleStep)
{
getSh4Executor()->Step();
singleStep = false;
}
else if (stepRangeTo != 0)
{
while (Sh4cntx.pc >= stepRangeFrom && Sh4cntx.pc < stepRangeTo)
getSh4Executor()->Step();
stepRangeFrom = 0;
stepRangeTo = 0;
}
else
{
do {
resetRequested = false;
stepRangeFrom = 0;
stepRangeTo = 0;
}
else
{
do {
resetRequested = false;
getSh4Executor()->Run();
getSh4Executor()->Run();
if (resetRequested)
{
nvmem::saveFiles();
dc_reset(false);
if (!restartCpu())
resetRequested = false;
}
} while (resetRequested);
if (resetRequested)
{
nvmem::saveFiles();
dc_reset(false);
if (!restartCpu())
resetRequested = false;
}
} while (resetRequested);
}
} catch (...) {
runner.term();
throw;
}
}
@@ -956,13 +954,13 @@ void Emulator::run()
startTime = sh4_sched_now64();
renderTimeout = false;
if (!singleStep && stepRangeTo == 0)
getSh4Executor()->Start();
getSh4Executor()->Start();
try {
runInternal();
if (ggpo::active())
ggpo::nextFrame();
} catch (const std::exception& e) {
ERROR_LOG(COMMON, "Exception: %s\n", e.what());
ERROR_LOG(COMMON, "Exception: %s", e.what());
setNetworkState(false);
state = Error;
getSh4Executor()->Stop();
@@ -1079,6 +1077,7 @@ bool Emulator::render()
void Emulator::vblank()
{
EventManager::event(Event::VBlank);
runner.execTasks();
// Time out if a frame hasn't been rendered for 50 ms
if (sh4_sched_now64() - startTime <= 10000000)
return;
+7
View File
@@ -20,6 +20,7 @@
*/
#pragma once
#include "types.h"
#include "stdclass.h"
#include <atomic>
#include <future>
@@ -39,6 +40,7 @@ void dc_savestate(int index = 0, const u8 *pngData = nullptr, u32 pngSize = 0);
void dc_loadstate(int index = 0);
time_t dc_getStateCreationDate(int index);
void dc_getStateScreenshot(int index, std::vector<u8>& pngData);
bool dc_savestateAllowed();
enum class Event {
Start,
@@ -185,6 +187,10 @@ public:
Sh4Executor *getSh4Executor();
void run(std::function<void()> func) {
runner.runOnThread(func);
}
void dc_reset(bool hard); // for tests only
private:
@@ -212,6 +218,7 @@ private:
std::mutex mutex;
Sh4Executor *interpreter = nullptr;
Sh4Executor *recompiler = nullptr;
ThreadRunner runner;
};
extern Emulator emu;
+9
View File
@@ -6,6 +6,7 @@
#include "cfg/option.h"
#include "stdclass.h"
#include "serialize.h"
#include "input/maplelink.h"
MapleInputState mapleInputState[4];
extern bool maple_ddt_pending_reset;
@@ -214,6 +215,14 @@ bool maple_atomiswave_coin_chute(int slot)
static void mcfg_Create(MapleDeviceType type, u32 bus, u32 port, s32 player_num = -1)
{
MapleDevices[bus][port].reset();
if (type == MDT_SegaVMU)
{
MapleLink::Ptr link = MapleLink::GetMapleLink(bus, port);
if (link != nullptr && link->storageEnabled()) {
createMapleLinkVmu(bus, port);
return;
}
}
std::shared_ptr<maple_device> dev = maple_Create(type);
dev->Setup(bus, port, player_num);
}
+17
View File
@@ -117,6 +117,23 @@ void mcfg_DestroyDevices(bool full = true);
void mcfg_SerializeDevices(Serializer& ser);
void mcfg_DeserializeDevices(Deserializer& deser);
constexpr int maple_getPortCount(MapleDeviceType type)
{
switch (type)
{
case MDT_SegaController:
case MDT_SegaControllerXL:
return 2;
case MDT_LightGun:
case MDT_TwinStick:
case MDT_AsciiStick:
case MDT_RacingController:
return 1;
default:
return 0;
}
}
bool maple_atomiswave_coin_chute(int slot);
void push_vmu_screen(int bus_id, int bus_port, u8* buffer);
void insertRfidCard(int playerNum);
+132 -575
View File
@@ -9,6 +9,7 @@
#include "oslib/i18n.h"
#include "hw/aica/sgc_if.h"
#include "cfg/option.h"
#include "input/maplelink.h"
#include <zlib.h>
#include <cerrno>
#include <ctime>
@@ -54,6 +55,15 @@ maple_device::~maple_device()
delete config;
}
bool maple_base::relayMapleLink()
{
auto link = MapleLink::GetMapleLink(bus_id, bus_port);
if (link == nullptr)
return true;
else
return link->send(*inMsg);
}
static inline void mutualExclusion(u32& keycode, u32 mask)
{
if ((keycode & mask) == 0)
@@ -367,11 +377,28 @@ struct maple_sega_vmu: maple_base
if (b != 0)
{
config->SetImage(lcd_data_decoded);
updateMapleLinkScreen();
break;
}
fullSaveNeeded = true;
}
void updateMapleLinkScreen()
{
auto link = MapleLink::GetMapleLink(bus_id, bus_port);
if (link == nullptr)
return;
MapleMsg msg;
msg.command = MDCF_BlockWrite;
msg.destAP = maple_port;
msg.originAP = bus_id << 6;
msg.pushData(MFID_2_LCD);
msg.pushData(0); // PT, phase, block#
msg.pushData(lcd_data);
link->send(msg);
}
virtual bool fullSave()
{
if (file == nullptr)
@@ -451,6 +478,8 @@ struct maple_sega_vmu: maple_base
{
if (file != nullptr)
std::fclose(file);
memset(lcd_data, 0, sizeof(lcd_data));
updateMapleLinkScreen();
}
u32 dma(u32 cmd) override
@@ -681,9 +710,6 @@ struct maple_sega_vmu: maple_base
return MDRE_FileError; // I/O error
}
}
else {
WARN_LOG(MAPLE, "Failed to save VMU %s data", logical_port);
}
return MDRS_DeviceReply;
}
@@ -710,6 +736,7 @@ struct maple_sega_vmu: maple_base
}
}
config->SetImage(lcd_data_decoded);
relayMapleLink();
return MDRS_DeviceReply;
}
@@ -744,14 +771,16 @@ struct maple_sega_vmu: maple_base
switch(r32())
{
case MFID_3_Clock:
{
u8 alw = r8();
u8 ald = r8();
r16(); // Alarm 2
INFO_LOG(MAPLE, "BEEP: %d/%d", alw, ald);
aica::sgc::vmuBeep(alw, ald);
}
{
u8 alw = r8();
u8 ald = r8();
r16(); // Alarm 2
INFO_LOG(MAPLE, "BEEP: %d/%d", alw, ald);
aica::sgc::vmuBeep(alw, ald);
relayMapleLink();
return MDRS_DeviceReply;
}
default:
INFO_LOG(MAPLE, "VMU: command MDCF_SetCondition -> Bad function used, returning MDRE_UnknownFunction");
@@ -1061,6 +1090,7 @@ struct maple_sega_purupuru : maple_base
//Auto-stop time
AST = dma_buffer_in[10];
AST_ms = AST * 250 + 250;
relayMapleLink();
return MDRS_DeviceReply;
@@ -1092,6 +1122,8 @@ struct maple_sega_purupuru : maple_base
else
inclination = FREQ / (1000.0 * INC * std::max(POW_POS, POW_NEG));
config->SetVibration(power, inclination, duration_ms);
relayMapleLink();
}
return MDRS_DeviceReply;
@@ -1716,7 +1748,7 @@ struct RFIDReaderWriter : maple_base
// Surprisingly recipient and sender aren't swapped in the response so we override RawDma for this reason
// vf4tuned and mushiking do care
u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) override
u32 RawDma(const u32 *buffer_in, u32 buffer_in_len, u32* buffer_out) override
{
u32 command=buffer_in[0] &0xFF;
//Recipient address
@@ -2110,86 +2142,74 @@ std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
return nullptr;
}
#if defined(USE_DREAMLINK_DEVICES)
#include "sdl/dreamlink.h"
#include <list>
#include <memory>
struct DreamLinkVmu : public maple_sega_vmu
struct MapleLinkVmu : public maple_sega_vmu
{
bool running = true;
std::shared_ptr<DreamLink> dreamlink;
bool useRealVmuMemory; //!< Set this to true to use physical VMU memory, false for virtual memory
std::chrono::time_point<std::chrono::system_clock> lastWriteTime;
bool mirroredBlocks[256]; //!< Set to true for block that has been loaded/written
s16 lastWriteBlock = -1;
std::list<u8> blocksToWrite;
std::mutex writeMutex;
std::condition_variable writeCv;
std::thread writeThread;
static u64 lastNotifyTime;
static u64 lastErrorNotifyTime;
DreamLinkVmu(std::shared_ptr<DreamLink> dreamlink) :
dreamlink(dreamlink),
writeThread([this](){writeEntrypoint();})
{
useRealVmuMemory = config::UsePhysicalVmuMemory && dreamlink->isPhysicalVMUMemorySupported();
}
virtual ~DreamLinkVmu() {
running = false;
// Entering lock context
{
std::unique_lock<std::mutex> lock(writeMutex);
writeCv.notify_all();
}
writeThread.join();
}
bool cachedBlocks[256]; //!< Set to true for block that has been loaded/written
void OnSetup() override
{
// All data must be re-read
memset(mirroredBlocks, 0, sizeof(mirroredBlocks));
if (useRealVmuMemory)
{
// Ensure file is not being used
if (file != nullptr)
{
std::fclose(file);
file = nullptr;
}
memset(flash_data, 0, sizeof(flash_data));
memset(lcd_data, 0, sizeof(lcd_data));
memset(cachedBlocks, 0, sizeof(cachedBlocks));
// Ensure file is not being used
if (file != nullptr) {
std::fclose(file);
file = nullptr;
}
else
{
maple_sega_vmu::OnSetup();
}
memset(flash_data, 0, sizeof(flash_data));
memset(lcd_data, 0, sizeof(lcd_data));
}
bool fullSave() override
{
if (useRealVmuMemory)
{
// Skip virtual save when using physical VMU
//DEBUG_LOG(MAPLE, "Not saving because this is a real vmu");
NOTICE_LOG(MAPLE, "Saving to physical VMU");
// Skip virtual save when using MapleLink VMU
DEBUG_LOG(MAPLE, "Full save ignored for MapleLink VMU");
return true;
}
return true;
void serialize(Serializer& ser) const override {
throw Serializer::Exception("Can't save linked VMU data");
}
void deserialize(Deserializer& deser) override
{
// Ignore the VMU data from the loaded state
u8 savedData[sizeof(flash_data)];
memcpy(savedData, flash_data, sizeof(savedData));
maple_sega_vmu::deserialize(deser);
memcpy(flash_data, savedData, sizeof(savedData));
}
MapleDeviceRV readBlock(unsigned block)
{
if (cachedBlocks[block])
return MDRS_JVSNone;
auto link = MapleLink::GetMapleLink(bus_id, bus_port);
if (link == nullptr) {
ERROR_LOG(MAPLE, "MapleLinkVmu[%s]::readBlock: MapleLink is null", logical_port);
return MDRS_JVSNone;
}
else
MapleMsg txMsg;
txMsg.command = MDCF_BlockRead;
txMsg.originAP = inMsg->originAP;
txMsg.destAP = inMsg->destAP;
txMsg.pushData(MFID_1_Storage);
txMsg.pushData<u32>(block << 24); // (BE) partition #, phase, block #
MapleMsg rxMsg;
if (link->sendReceive(txMsg, rxMsg) && rxMsg.size == 130)
{
return maple_sega_vmu::fullSave();
DEBUG_LOG(MAPLE, "MapleLinkVmu[%s]: read block %d", logical_port, block);
memcpy(&flash_data[block * 4 * 128], &rxMsg.data[8], 4 * 128);
cachedBlocks[block] = true;
}
else {
ERROR_LOG(MAPLE, "Failed to read VMU %s: I/O error", logical_port);
return MDRE_FileError; // I/O error
}
return MDRS_JVSNone;
}
u32 dma(u32 cmd) override
@@ -2197,521 +2217,58 @@ struct DreamLinkVmu : public maple_sega_vmu
// Physical VMU logic
if (dma_count_in >= 4)
{
const u32 functionId = *(u32*)dma_buffer_in;
const MapleMsg* msg = reinterpret_cast<const MapleMsg*>(dma_buffer_in - 4);
const u32 functionId = inMsg->readData<u32>(0);
if (functionId == MFID_1_Storage)
{
if (useRealVmuMemory)
switch (cmd)
{
const u64 currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
// Only show notification once every 6 seconds to avoid spam
if (cmd == MDCF_BlockRead &&
(currentTime - lastNotifyTime) > 4000 &&
(currentTime - lastErrorNotifyTime) > 4000)
{
// This is a read operation (loading)
os_notify(i18n::T("ATTENTION: Loading from a physical VMU"), 6000,
i18n::T("Game data is being loaded from your physical VMU"));
lastNotifyTime = currentTime;
case MDCF_BlockWrite:
{
auto link = MapleLink::GetMapleLink(bus_id, bus_port);
if (link == nullptr) {
ERROR_LOG(MAPLE, "MapleLinkVmu[%s]::BlockWrite: MapleLink is null", logical_port);
return MDRE_FileError;
}
switch (cmd)
{
case MDCF_BlockWrite:
{
// Throw away function
r32();
// Save the write to RAM
u32 bph=r32();
u32 Block = lastWriteBlock = (SWAP32(bph))&0xffff;
u32 Phase = ((SWAP32(bph))>>16)&0xff;
u32 write_adr=Block*512+Phase*(512/4);
u32 write_len=r_count();
DEBUG_LOG(MAPLE, "VMU %s block write: Block %d Phase %d addr %x len %d", logical_port, Block, Phase, write_adr, write_len);
if (write_adr + write_len > sizeof(flash_data))
{
INFO_LOG(MAPLE, "Failed to write VMU %s: overflow", logical_port);
skip(write_len);
return MDRE_FileError; //invalid params
}
rptr(&flash_data[write_adr],write_len);
// All done - wait until GetLastError to queue the write
return MDRS_DeviceReply;
}
case MDCF_GetLastError:
{
mirroredBlocks[lastWriteBlock] = true;
// Entering lock context
{
std::unique_lock<std::mutex> lock(writeMutex);
if (std::find(blocksToWrite.begin(), blocksToWrite.end(), lastWriteBlock) == blocksToWrite.end())
{
blocksToWrite.push_back(lastWriteBlock);
writeCv.notify_all();
}
}
lastWriteTime = std::chrono::system_clock::now();
return MDRS_DeviceReply;
}
case MDCF_BlockRead:
{
u8 requestBlock = msg->data[7];
if (!mirroredBlocks[requestBlock]) {
// Try up to 4 times to read
bool readSuccess = false;
for (u32 i = 0; i < 4; ++i) {
if (i > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(30));
}
MapleMsg rcvMsg;
if (dreamlink->send(*msg, rcvMsg) && rcvMsg.size == 130) {
// Something read!
u8 block = rcvMsg.data[7];
memcpy(&flash_data[block * 4 * 128], &rcvMsg.data[8], 4 * 128);
mirroredBlocks[block] = true;
readSuccess = true;
break;
}
}
if (!readSuccess) {
ERROR_LOG(MAPLE, "Failed to read VMU %s: I/O error", logical_port);
return MDRE_FileError; // I/O error
}
}
break;
}
default:
// do nothing
break;
MapleMsg rxMsg;
if (!link->sendReceive(*inMsg, rxMsg)) {
ERROR_LOG(MAPLE, "Failed to write VMU %s: I/O error", logical_port);
return MDRE_FileError;
}
if (rxMsg.command != MDRS_DeviceReply)
return rxMsg.command;
cachedBlocks[inMsg->data[7]] = true;
DEBUG_LOG(MAPLE, "MapleLinkVmu[%s]: write block %d", logical_port, inMsg->data[7]);
break;
}
}
else if (functionId == MFID_2_LCD)
{
if (cmd == MDCF_BlockWrite)
case MDCF_BlockRead:
{
dreamlink->send(*msg);
u8 block = inMsg->data[7];
MapleDeviceRV rc = readBlock(block);
if (rc != MDRS_JVSNone)
return rc;
break;
}
}
else if (functionId == MFID_3_Clock)
{
if (cmd == MDCF_SetCondition)
{
dreamlink->send(*msg);
case MDCF_GetMediaInfo:
// block 255 contains the media info
readBlock(255);
break;
default:
// do nothing
break;
}
}
}
// If made it here, call base's dma to handle return value
return maple_sega_vmu::dma(cmd);
}
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));
memcpy(lcd_data_decoded, other->lcd_data_decoded, sizeof(lcd_data_decoded));
fullSaveNeeded = other->fullSaveNeeded;
}
void copyOut(std::shared_ptr<maple_sega_vmu> other)
{
// Never copy data to virtual VMU if physical VMU is enabled
if (!config::UsePhysicalVmuMemory && !useRealVmuMemory)
{
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;
msg.command = MDCF_BlockWrite;
msg.destAP = maple_port;
msg.originAP = bus_id << 6;
msg.size = 2 + sizeof(lcd_data) / 4;
*(u32*)&msg.data[0] = MFID_2_LCD;
*(u32*)&msg.data[4] = 0; // PT, phase, block#
memcpy(&msg.data[8], lcd_data, sizeof(lcd_data));
dreamlink->send(msg);
}
private:
//! Thread entrypoint for write operations
void writeEntrypoint()
{
while (true)
{
u8 block = 0;
// Entering lock context
{
std::unique_lock<std::mutex> lock(writeMutex);
writeCv.wait(lock, [this](){ return (!running || !blocksToWrite.empty()); });
if (!running)
{
break;
}
block = blocksToWrite.front();
blocksToWrite.pop_front();
}
const u64 currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
// Only show notification once every 6 seconds to avoid spam
if ((currentTime - lastNotifyTime) > 4000 && (currentTime - lastErrorNotifyTime) > 4000)
{
// This is a write operation (saving)
os_notify(i18n::T("ATTENTION: You are saving to a physical VMU"), 6000,
i18n::T("Do not disconnect the VMU or close the game"));
lastNotifyTime = currentTime;
}
bool writeSuccess = true;
const u8* blockData = &flash_data[block * 4 * 128];
std::chrono::milliseconds delay(10);
const std::chrono::milliseconds delayInc(5);
// Try up to 4 times to write
for (u32 i = 0; i < 4; ++i) {
if (i > 0) {
// Slow down writes to avoid overloading the VMU
delay += delayInc;
}
writeSuccess = true;
// 4 write phases per block
for (u32 phase = 0; phase < 4; ++phase) {
MapleMsg writeMsg;
writeMsg.command = MDCF_BlockWrite;
writeMsg.destAP = (bus_id << 6) | (1 << bus_port);
writeMsg.originAP = (bus_id << 6);
writeMsg.size = 34;
writeMsg.setWord(MFID_1_Storage, 0);
const u32 locationWord = (block << 24) | (phase << 8);
writeMsg.setWord(locationWord, 1);
memcpy(&writeMsg.data[8], &blockData[phase * 128], 128);
// Delay before writing
std::this_thread::sleep_for(delay);
MapleMsg rcvMsg;
if (!dreamlink->send(writeMsg, rcvMsg) || rcvMsg.command != MDRS_DeviceReply) {
// Not acknowledged
writeSuccess = false;
break;
}
}
if (writeSuccess) {
// Delay before committing
std::this_thread::sleep_for(delay);
// Send the GetLastError command to commit the data
MapleMsg writeMsg;
writeMsg.command = MDCF_GetLastError;
writeMsg.destAP = (bus_id << 6) | (1 << bus_port);
writeMsg.originAP = (bus_id << 6);
writeMsg.size = 2;
writeMsg.setWord(MFID_1_Storage, 0);
const u32 phase = 4;
const u32 locationWord = (block << 24) | (phase << 8);
writeMsg.setWord(locationWord, 1);
MapleMsg rcvMsg;
if (dreamlink->send(writeMsg, rcvMsg) && rcvMsg.command == MDRS_DeviceReply) {
// Acknowledged
break;
}
}
// else: continue to retry
}
if (!writeSuccess) {
ERROR_LOG(MAPLE, "Failed to save VMU %s: I/O error", logical_port);
const u64 currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
if ((currentTime - lastErrorNotifyTime) > 4000)
{
os_notify(i18n::T("ATTENTION: Write to VMU failed"), 6000);
lastErrorNotifyTime = currentTime;
}
}
}
}
};
u64 DreamLinkVmu::lastNotifyTime = 0;
u64 DreamLinkVmu::lastErrorNotifyTime = 0;
struct DreamLinkPurupuru : public maple_sega_purupuru
void createMapleLinkVmu(int bus, int port)
{
std::shared_ptr<DreamLink> dreamlink;
//! Number of consecutive stop conditions sent
u32 stopSendCount = 0;
DreamLinkPurupuru(std::shared_ptr<DreamLink> dreamlink) : dreamlink(dreamlink) {
}
u32 dma(u32 cmd) override
{
if (cmd == MDCF_BlockWrite || cmd == MDCF_SetCondition) {
const MapleMsg *msg = reinterpret_cast<const MapleMsg*>(dma_buffer_in - 4);
const u32 functionId = *(u32*)dma_buffer_in;
const u32 condition = *(u32*)(dma_buffer_in + 4);
if (functionId == MFID_8_Vibration && condition == 0x00000010) {
++stopSendCount;
} else {
stopSendCount = 0;
}
// Only send 2 consecutive stop commands; ignore the rest to avoid unnecessary communications
if (stopSendCount <= 2) {
dreamlink->send(*msg);
}
}
return maple_sega_purupuru::dma(cmd);
}
};
static std::list<std::shared_ptr<DreamLinkVmu>> dreamLinkVmus[2];
static std::list<std::shared_ptr<DreamLinkPurupuru>> dreamLinkPurupurus;
bool reconnectDreamLinks()
{
auto& useNetworkExpansionDevices = config::UseNetworkExpansionDevices;
bool anyNewConnection = false;
for (int i = 0; i < DreamLink::NUM_PORTS; i++)
{
const auto& dreamlink = DreamLink::activeDreamLinks[i];
if (!useNetworkExpansionDevices[i] && dreamlink && !dreamlink->isForPhysicalController())
{
// This bus is not using network expansion devices.
// Dispose of the dreamlink for the bus, unless it is for a physical controller (and therefore not managed by this setting).
dreamlink->disconnect();
DreamLink::activeDreamLinks[i] = nullptr;
}
if (dreamlink && !dreamlink->isConnected())
{
dreamlink->connect();
if (dreamlink->isConnected())
{
anyNewConnection = true;
}
}
}
return anyNewConnection;
INFO_LOG(MAPLE, "MapleLinkVmu created on %d,%d", bus, port);
auto vmu = std::make_shared<MapleLinkVmu>();
vmu->Setup(bus, port);
}
// Checks for and handles a message from server telling us to refresh the expansion devices.
void refreshDreamLinksIfNeeded()
{
bool anyNeedsRefresh = false;
for (auto& dreamlink : DreamLink::activeDreamLinks)
{
if (dreamlink && dreamlink->needsRefresh())
anyNeedsRefresh = true;
}
if (!anyNeedsRefresh)
return;
for (auto& dreamlink : DreamLink::activeDreamLinks)
{
if (dreamlink)
tearDownDreamLinkDevices(dreamlink.get());
}
maple_ReconnectDevices();
}
void createAllDreamLinkDevices()
{
for (int i = 0; i < DreamLink::NUM_PORTS; i++)
{
const auto& dreamlink = DreamLink::activeDreamLinks[i];
if (dreamlink && dreamlink->isConnected())
{
bool gameStart = false;
bool stateLoaded = false;
createDreamLinkDevices(dreamlink, gameStart, stateLoaded);
}
}
}
// Replaces certain ordinary MapleDevices entries with specialized devices that communicate thru a DreamLink.
void createDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink, bool gameStart, bool stateLoaded)
{
const int bus = dreamlink->getBus();
for (int i = 0; i < 2; ++i)
{
std::shared_ptr<maple_device> dev = MapleDevices[bus][i];
const bool isStorage = ((dreamlink->getFunctionCode(i) & MFID_1_Storage) != 0);
const bool isVibration = ((dreamlink->getFunctionCode(i) & MFID_8_Vibration) != 0);
if (i == 1 && isVibration)
{
bool rumbleFound = false;
std::shared_ptr<DreamLinkPurupuru> rumble;
for (const std::shared_ptr<DreamLinkPurupuru>& purupuru : dreamLinkPurupurus)
{
if (purupuru->dreamlink.get() == dreamlink.get())
{
rumbleFound = true;
rumble = purupuru;
break;
}
}
if (gameStart || stateLoaded || !rumbleFound)
{
if (!rumble)
{
rumble = std::make_shared<DreamLinkPurupuru>(dreamlink);
}
rumble->Setup(bus, i);
if (!rumbleFound) dreamLinkPurupurus.push_back(rumble);
}
}
else if (isStorage)
{
bool vmuFound = false;
std::shared_ptr<DreamLinkVmu> vmu;
for (const std::shared_ptr<DreamLinkVmu>& vmuIter : dreamLinkVmus[i])
{
if (vmuIter->dreamlink.get() == dreamlink.get())
{
vmuFound = true;
vmu = vmuIter;
break;
}
}
if (gameStart || stateLoaded || !vmuFound)
{
if (!vmu)
{
vmu = std::make_shared<DreamLinkVmu>(dreamlink);
}
if (gameStart)
{
// Update useRealVmuMemory in case config changed
vmu->useRealVmuMemory = config::UsePhysicalVmuMemory && dreamlink->isPhysicalVMUMemorySupported();
}
else if (stateLoaded)
{
if (vmu->useRealVmuMemory)
{
// Disconnect from real VMU memory when a state is loaded
vmu->useRealVmuMemory = false;
os_notify(i18n::T("WARNING: Disconnected from physical VMU memory due to load state"), 6000,
i18n::T("Reconnect manually to resume using physical VMU memory"));
}
}
vmu->Setup(bus, i);
if (!vmuFound && dev && dev->get_device_type() == MDT_SegaVMU && !vmu->useRealVmuMemory) {
// Only copy data from virtual VMU if Physical VMU Only is disabled
vmu->copyIn(std::static_pointer_cast<maple_sega_vmu>(dev));
if (!gameStart) {
vmu->updateScreen();
}
}
if (!vmuFound) {
dreamLinkVmus[i].push_back(vmu);
}
if (gameStart && vmu->useRealVmuMemory)
{
dreamlink->sendGameId(i, settings.content.gameId);
}
}
}
}
}
// Replaces DreamLink devices associated with 'dreamlink' with ordinary Maple devices.
void tearDownDreamLinkDevices(DreamLink* dreamlink)
{
const int bus = dreamlink->getBus();
for (int i = 0; i < 2; ++i)
{
for (std::list<std::shared_ptr<DreamLinkVmu>>::const_iterator iter = dreamLinkVmus[i].begin();
iter != dreamLinkVmus[i].end();)
{
if ((*iter)->dreamlink.get() == dreamlink)
{
DEBUG_LOG(MAPLE, "VMU teardown - Physical VMU: %s", (*iter)->useRealVmuMemory ? "true" : "false");
std::shared_ptr<maple_device> dev = maple_Create(MDT_SegaVMU);
dev->Setup(bus, 0);
if (!(*iter)->useRealVmuMemory)
{
(*iter)->copyOut(std::static_pointer_cast<maple_sega_vmu>(dev));
}
DEBUG_LOG(MAPLE, "VMU teardown - Copy completed");
iter = dreamLinkVmus[i].erase(iter);
break;
}
else
{
++iter;
}
}
}
std::size_t dreamLinkVmuCount = 0;
for (int i = 0; i < 2; ++i)
{
dreamLinkVmuCount += dreamLinkVmus[i].size();
}
for (std::list<std::shared_ptr<DreamLinkPurupuru>>::const_iterator iter = dreamLinkPurupurus.begin();
iter != dreamLinkPurupurus.end();)
{
if ((*iter)->dreamlink.get() == dreamlink)
{
std::shared_ptr<maple_device> dev = maple_Create(MDT_PurupuruPack);
dev->Setup(bus, 1);
iter = dreamLinkPurupurus.erase(iter);
break;
}
else
{
++iter;
}
}
}
#endif
+53 -11
View File
@@ -121,6 +121,42 @@ enum AWAVE_KEYS
AWAVE_TRIGGER_KEY = 1 << 17,
};
struct MapleMsg
{
u8 command = 0;
u8 destAP = 0;
u8 originAP = 0;
u8 size = 0;
u8 data[1020];
u32 getDataSize() const {
return size * 4;
}
template<typename T>
void pushData(const T& p) {
static_assert(sizeof(T) >= 4);
pushData(&p, sizeof(T));
}
void pushData(const void *p, u32 size)
{
u32 offset = this->size * 4;
memcpy(&data[offset], p, size);
offset += size;
this->size = (offset + 3) / 4;
}
template<typename T>
T readData(int index = 0) const
{
T v;
memcpy(&v, &data[index * sizeof(T)], sizeof(T));
return v;
}
};
static_assert(sizeof(MapleMsg) == 1024);
struct maple_device : public std::enable_shared_from_this<maple_device>
{
u8 maple_port; //raw maple port
@@ -136,7 +172,7 @@ struct maple_device : public std::enable_shared_from_this<maple_device>
virtual void OnSetup() {};
virtual ~maple_device();
virtual u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) = 0;
virtual u32 RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out) = 0;
virtual void serialize(Serializer& ser) const {
ser << player_num;
@@ -181,7 +217,8 @@ struct maple_base: maple_device
u8* dma_buffer_out;
u32* dma_count_out;
u8* dma_buffer_in;
const MapleMsg *inMsg;
const u8* dma_buffer_in;
u32 dma_count_in;
void w8(u8 data) { *(u8*)dma_buffer_out = data; dma_buffer_out += 1; dma_count_out[0] += 1; }
@@ -207,9 +244,9 @@ struct maple_base: maple_device
w8(' ');
}
u8 r8() { u8 rv = *(u8*)dma_buffer_in; dma_buffer_in += 1; dma_count_in -= 1; return rv; }
u16 r16() { u16 rv = *(u16*)dma_buffer_in; dma_buffer_in += 2; dma_count_in -= 2; return rv; }
u32 r32() { u32 rv = *(u32*)dma_buffer_in; dma_buffer_in += 4; dma_count_in -= 4; return rv; }
u8 r8() { u8 rv = *dma_buffer_in; dma_buffer_in += 1; dma_count_in -= 1; return rv; }
u16 r16() { u16 rv = *(const u16 *)dma_buffer_in; dma_buffer_in += 2; dma_count_in -= 2; return rv; }
u32 r32() { u32 rv = *(const u32 *)dma_buffer_in; dma_buffer_in += 4; dma_count_in -= 4; return rv; }
void skip(u32 len) {
dma_buffer_in += len;
dma_count_in -= len;
@@ -223,20 +260,21 @@ struct maple_base: maple_device
}
u32 r_count() { return dma_count_in; }
u32 Dma(u32 Command, u32* buffer_in, u32 buffer_in_len, u32* buffer_out, u32& buffer_out_len)
u32 Dma(u32 Command, const u32* buffer_in, u32 buffer_in_len, u32* buffer_out, u32& buffer_out_len)
{
dma_buffer_out = (u8*)buffer_out;
dma_count_out = &buffer_out_len;
dma_buffer_in = (u8*)buffer_in;
dma_buffer_in = (const u8 *)buffer_in;
dma_count_in = buffer_in_len;
return dma(Command);
}
virtual u32 dma(u32 cmd) = 0;
u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) override
u32 RawDma(const u32* buffer_in, u32 buffer_in_len, u32* buffer_out) override
{
inMsg = (const MapleMsg *)buffer_in;
u32 command=buffer_in[0] &0xFF;
//Recipient address
u32 reci = (buffer_in[0] >> 8) & 0xFF;
@@ -253,8 +291,12 @@ struct maple_base: maple_device
return outlen + 4;
}
bool relayMapleLink();
};
void createMapleLinkVmu(int bus, int port);
class jvs_io_board;
struct maple_naomi_jamma : maple_base, SerialPort
@@ -284,13 +326,13 @@ struct maple_naomi_jamma : maple_base, SerialPort
return last_node ? 0x8E : 0x8F;
}
void send_jvs_message(u32 node_id, u32 channel, u32 length, u8 *data);
void send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, u8 *data, bool repeat_first);
void send_jvs_message(u32 node_id, u32 channel, u32 length, const u8 *data);
void send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, const u8 *data, bool repeat_first);
bool receive_jvs_messages(u32 channel);
void handle_86_subcommand();
u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) override;
u32 RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out) override;
u32 dma(u32 cmd) override { return 0; }
void serialize(Serializer& ser) const override;
+4 -23
View File
@@ -8,10 +8,6 @@
#include "network/ggpo.h"
#include "hw/naomi/card_reader.h"
#ifdef USE_DREAMLINK_DEVICES
#include "sdl/dreamlink.h"
#endif
#include <memory>
enum MaplePattern
@@ -51,10 +47,6 @@ bool SDCKBOccupied;
void maple_vblank()
{
#if USE_DREAMLINK_DEVICES
refreshDreamLinksIfNeeded();
#endif
if (SB_MDEN & 1)
{
if (SB_MDTSEL == 1)
@@ -65,7 +57,7 @@ void maple_vblank()
}
else
{
DEBUG_LOG(MAPLE, "DDT vblank");
//DEBUG_LOG(MAPLE, "DDT vblank");
SB_MDST = 1;
maple_DoDma();
// if trigger reset is manual, mark it as pending
@@ -356,12 +348,10 @@ void maple_Init()
#endif
maple_schid = sh4_sched_register(0, maple_schd);
#if defined(USE_DREAMLINK_DEVICES)
registerDreamLinkEvents();
#endif
}
static u64 reconnect_time;
void maple_Reset(bool hard)
{
maple_ddt_pending_reset = false;
@@ -373,6 +363,7 @@ void maple_Reset(bool hard)
SB_MDAPRO = 0x00007F00;
SB_MMSEL = 1;
mapleDmaOut.clear();
reconnect_time = 0;
}
void maple_Term()
@@ -380,14 +371,8 @@ void maple_Term()
mcfg_DestroyDevices();
sh4_sched_unregister(maple_schid);
maple_schid = -1;
#if defined(USE_DREAMLINK_DEVICES)
unregisterDreamLinkEvents();
#endif
}
static u64 reconnect_time;
void maple_ReconnectDevices()
{
mcfg_DestroyDevices();
@@ -400,9 +385,5 @@ static void maple_handle_reconnect()
{
reconnect_time = 0;
mcfg_CreateDevices();
#if defined(USE_DREAMLINK_DEVICES)
createAllDreamLinkDevices();
#endif
}
}
+8 -8
View File
@@ -157,7 +157,7 @@ public:
}
virtual ~jvs_io_board() = default;
u32 handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_out);
u32 handle_jvs_message(const u8 *buffer_in, u32 length_in, u8 *buffer_out);
virtual void serialize(Serializer& ser) const;
virtual void deserialize(Deserializer& deser);
@@ -229,7 +229,7 @@ protected:
}
}
virtual void write_digital_out(int count, u8 *data)
virtual void write_digital_out(int count, const u8 *data)
{
u32 newOutput = digOutput;
for (int i = 0; i < count && i < 4; i++)
@@ -613,7 +613,7 @@ protected:
v[1] &= ~NAOMI_BTN2_KEY;
}
void write_digital_out(int count, u8 *data) override
void write_digital_out(int count, const u8 *data) override
{
if (count != 3)
return;
@@ -1382,7 +1382,7 @@ maple_naomi_jamma::~maple_naomi_jamma()
EEPROM = nullptr;
}
void maple_naomi_jamma::send_jvs_message(u32 node_id, u32 channel, u32 length, u8 *data)
void maple_naomi_jamma::send_jvs_message(u32 node_id, u32 channel, u32 length, const u8 *data)
{
if (node_id - 1 < io_boards.size())
{
@@ -1413,7 +1413,7 @@ void maple_naomi_jamma::send_jvs_message(u32 node_id, u32 channel, u32 length, u
}
}
void maple_naomi_jamma::send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, u8 *data, bool repeat_first)
void maple_naomi_jamma::send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, const u8 *data, bool repeat_first)
{
u8 temp_buffer[256];
if (data)
@@ -1519,7 +1519,7 @@ void maple_naomi_jamma::handle_86_subcommand()
}
}
u8 node_id = 0;
u8 *cmd = NULL;
const u8 *cmd = NULL;
u32 len = 0;
u8 channel = 0;
if (dma_count_in >= 3)
@@ -1798,7 +1798,7 @@ void maple_naomi_jamma::handle_86_subcommand()
}
}
u32 maple_naomi_jamma::RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out)
u32 maple_naomi_jamma::RawDma(const u32* buffer_in, u32 buffer_in_len, u32* buffer_out)
{
#ifdef DUMP_JVS
printf("JVS IN: ");
@@ -2020,7 +2020,7 @@ u16 jvs_io_board::read_analog_axis(int player_num, int player_axis, bool inverte
#define JVS_OUT(b) buffer_out[length++] = b
#define JVS_STATUS1() JVS_OUT(1)
u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_out)
u32 jvs_io_board::handle_jvs_message(const u8 *buffer_in, u32 length_in, u8 *buffer_out)
{
u8 jvs_cmd = buffer_in[0];
if (jvs_cmd == 0xF0) // JVS reset
+5
View File
@@ -1,3 +1,8 @@
target_sources(${PROJECT_NAME} PRIVATE
dreampotato.cpp
dreampotato.h
maplelink.cpp
maplelink.h)
if(NOT LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
gamepad.h
+208
View File
@@ -0,0 +1,208 @@
/*
This file is part of Flycast.
Flycast 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.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "types.h"
#include <asio.hpp> // Must be included first to avoid winsock issues on Windows
#include "dreampotato.h"
#include "maplelink.h"
#include "emulator.h"
#include "cfg/option.h"
#include <memory>
#include <array>
#include <iomanip>
asio::error_code sendMsg(const MapleMsg& msg, asio::ip::tcp::iostream& stream)
{
static char buffer[1024 * 3 + 2];
char *p = buffer;
p += sprintf(p, "%02X %02X %02X %02X", msg.command, msg.destAP, msg.originAP, msg.size);
const u32 sz = msg.getDataSize();
for (u32 i = 0; i < sz; i++)
p += sprintf(p, " %02X", msg.data[i]);
strcpy(p, "\r\n");
p += 2;
asio::ip::tcp::socket& sock = static_cast<asio::ip::tcp::socket&>(stream.socket());
asio::error_code ec;
asio::write(sock, asio::buffer(buffer, p - buffer), ec);
return ec;
}
bool receiveMsg(MapleMsg& msg, std::istream& stream)
{
std::string response;
if (!std::getline(stream, response))
return false;
sscanf(response.c_str(), "%hhx %hhx %hhx %hhx", &msg.command, &msg.destAP, &msg.originAP, &msg.size);
if ((msg.getDataSize() - 1) * 3 + 13 >= response.length())
return false;
for (unsigned i = 0; i < msg.getDataSize(); i++)
sscanf(&response[i * 3 + 12], "%hhx", &msg.data[i]);
return !stream.fail();
}
namespace dreampotato
{
class DreamPotato : public BaseMapleLink
{
int bus;
asio::ip::tcp::iostream iostream;
bool connected = false;
static constexpr u16 BASE_PORT = 37393;
public:
DreamPotato(int bus)
: BaseMapleLink(true), bus(bus)
{}
bool send(const MapleMsg& msg) override;
bool sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg) override;
bool isConnected() override {
return connected;
}
void connect();
void disconnect();
void init();
void term();
};
static std::array<std::shared_ptr<DreamPotato>, 4> Potatoes;
void DreamPotato::init()
{
connect();
registerLink(bus, 0);
}
void DreamPotato::term()
{
disconnect();
unregisterLink(bus, 0);
}
void DreamPotato::connect()
{
if (connected)
return;
iostream = asio::ip::tcp::iostream("::1", std::to_string(BASE_PORT + bus), asio::ip::resolver_base::flags::numeric_host);
if (!iostream)
iostream = asio::ip::tcp::iostream("127.0.0.1", std::to_string(BASE_PORT + bus), asio::ip::resolver_base::flags::numeric_host);
if (!iostream) {
WARN_LOG(INPUT, "DreamPotato[%d] connection failed: %s", bus, iostream.error().message().c_str());
disconnect();
return;
}
connected = true;
iostream.expires_from_now(std::chrono::milliseconds(500));
MapleMsg txMsg;
txMsg.command = MDC_DeviceRequest;
txMsg.destAP = (bus << 6) | 1;
txMsg.originAP = bus << 6;
MapleMsg rxMsg;
// Note: connected *must* be true or infinite recursion loop
if (!sendReceive(txMsg, rxMsg))
// error has been logged and disconnect() called
return;
const u32 fnCode = rxMsg.readData<u32>();
if ((fnCode & MFID_1_Storage) == 0)
{
WARN_LOG(INPUT, "Unrecognized response from DreamPotato");
disconnect();
return;
}
NOTICE_LOG(INPUT, "Connected to DreamPotato[%d]", bus);
}
void DreamPotato::disconnect()
{
if (!connected)
return;
if (storageEnabled())
disableStorage();
connected = false;
iostream.close();
NOTICE_LOG(INPUT, "Disconnected from DreamPotato[%d]", bus);
}
bool DreamPotato::send(const MapleMsg& msg)
{
connect();
if (!connected)
return false;
iostream.expires_from_now(std::chrono::milliseconds(100));
asio::error_code ec = sendMsg(msg, iostream);
if (ec)
{
WARN_LOG(INPUT, "DreamPotato[%d] send failed: %s", bus, ec.message().c_str());
disconnect();
return false;
}
return true;
}
bool DreamPotato::sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg)
{
if (!send(txMsg))
return false;
if (!receiveMsg(rxMsg, iostream)) {
WARN_LOG(INPUT, "DreamPotato[%d] receive failed", bus);
disconnect();
return false;
}
return true;
}
// Instantiate DreamPotato devices where needed and delete the others
void update()
{
for (unsigned bus = 0; bus < Potatoes.size(); bus++)
{
auto& potato = Potatoes[bus];
if (config::NetworkExpansionDevices[bus][0] == 1
&& config::MapleExpansionDevices[bus][0] == MDT_SegaVMU
&& maple_getPortCount(config::MapleMainDevices[bus]) >= 1)
{
if (potato == nullptr) {
potato = std::make_shared<DreamPotato>(bus);
potato->init();
}
}
else if (potato != nullptr) {
potato->term();
potato = nullptr;
}
}
}
void term()
{
for (unsigned bus = 0; bus < Potatoes.size(); bus++)
{
auto& potato = Potatoes[bus];
if (potato != nullptr) {
potato->term();
potato = nullptr;
}
}
}
} // namespace dreampotato
+23
View File
@@ -0,0 +1,23 @@
/*
This file is part of Flycast.
Flycast 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.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
namespace dreampotato
{
void update();
void term();
}
+2 -1
View File
@@ -160,6 +160,8 @@ protected:
return _input_detected != nullptr;
}
virtual void registered() {}
std::string _name;
std::string _unique_id;
std::shared_ptr<InputMapping> input_mapper;
@@ -168,7 +170,6 @@ protected:
int rumblePower = 100;
private:
virtual void registered() {}
bool handleButtonInput(int port, DreamcastKey key, bool pressed);
bool handleButtonInputDef(const InputMapping::InputDef& inputDef, bool pressed);
std::string make_mapping_filename(bool instance, int system, bool perGame = false);
+127
View File
@@ -0,0 +1,127 @@
/*
This file is part of Flycast.
Flycast 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.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "maplelink.h"
#include "cfg/option.h"
#include "hw/maple/maple_if.h"
std::array<std::array<MapleLink::Ptr, 2>, 4> MapleLink::Links;
std::mutex MapleLink::Mutex;
namespace
{
struct GameState
{
GameState()
{
EventManager::listen(Event::Start, [](Event, void*) {
started = true;
});
EventManager::listen(Event::Terminate, [](Event, void*) {
started = false;
});
}
static bool started;
static GameState instance;
};
bool GameState::started;
GameState GameState::instance;
}
bool MapleLink::isGameStarted() const {
return GameState::started;
}
void MapleLink::registerLink(int bus, int port)
{
if (bus >= 0 && bus < (int)Links.size()
&& port >= 0 && port < (int)Links[0].size())
{
std::lock_guard<std::mutex> _(Mutex);
Links[bus][port] = shared_from_this();
}
}
void MapleLink::unregisterLink(int bus, int port)
{
if (bus >= 0 && bus < (int)Links.size()
&& port >= 0 && port < (int)Links[0].size())
{
std::lock_guard<std::mutex> _(Mutex);
Ptr& existing = Links[bus][port];
if (existing == shared_from_this())
existing = nullptr;
}
}
bool MapleLink::StorageEnabled()
{
std::lock_guard<std::mutex> _(Mutex);
for (const auto& ports : Links)
{
for (auto link : ports)
if (link != nullptr && link->storageEnabled())
return true;
}
return false;
}
BaseMapleLink::BaseMapleLink(bool storageSupported)
: storageSupported(storageSupported)
{
EventManager::listen(Event::LoadState, eventLoadState, this);
EventManager::listen(Event::Start, eventStart, this);
if (isGameStarted())
vmuStorage = storageSupported && config::UsePhysicalVmuMemory;
}
BaseMapleLink::~BaseMapleLink()
{
EventManager::unlisten(Event::LoadState, eventLoadState, this);
EventManager::unlisten(Event::Start, eventStart, this);
}
void BaseMapleLink::eventStart(Event event, void *p)
{
BaseMapleLink *self = (BaseMapleLink *)p;
self->vmuStorage = self->storageSupported && config::UsePhysicalVmuMemory && self->isConnected();
}
void BaseMapleLink::eventLoadState(Event event, void *p)
{
BaseMapleLink *self = (BaseMapleLink *)p;
if (self->vmuStorage) {
WARN_LOG(INPUT, "State loaded but VMU has storage enabled");
self->disableStorage();
}
}
void BaseMapleLink::disableStorage()
{
if (!vmuStorage)
return;
vmuStorage = false;
if (isGameStarted())
emu.run(maple_ReconnectDevices);
}
bool BaseMapleLink::storageEnabled()
{
if (!isConnected())
return false;
if (!isGameStarted())
return storageSupported && config::UsePhysicalVmuMemory;
else
return vmuStorage;
}
+76
View File
@@ -0,0 +1,76 @@
/*
This file is part of Flycast.
Flycast 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.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "hw/maple/maple_devs.h"
#include "emulator.h"
#include <array>
#include <memory>
#include <mutex>
// parent interface of DreamLink and DreamPotato for use by hw/maple
class MapleLink : public std::enable_shared_from_this<MapleLink>
{
public:
using Ptr = std::shared_ptr<MapleLink>;
virtual ~MapleLink() = default;
//! Sends a message to the controller, not expecting a response
virtual bool send(const MapleMsg& msg) = 0;
//! Sends a message to the controller and waits for a response
virtual bool sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg) = 0;
//! True if VMU reads and writes should be sent to the device
virtual bool storageEnabled() = 0;
//! True if the link is operational
virtual bool isConnected() = 0;
//! Returns the maple link at the given location if any
static Ptr GetMapleLink(int bus, int port) {
std::lock_guard<std::mutex> _(Mutex);
return Links[bus][port];
}
//! True if any maple link currently has storage enabled
static bool StorageEnabled();
protected:
//! True if a game has been started
bool isGameStarted() const;
void registerLink(int bus, int port);
void unregisterLink(int bus, int port);
private:
static std::mutex Mutex;
static std::array<std::array<Ptr, 2>, 4> Links;
};
class BaseMapleLink : public MapleLink
{
public:
~BaseMapleLink();
bool storageEnabled() override;
protected:
BaseMapleLink(bool storageSupported);
void disableStorage(); // Disable VMU storage for this link
static void eventStart(Event event, void *p);
static void eventLoadState(Event event, void *p);
const bool storageSupported;
bool vmuStorage = false;
};
+8 -2
View File
@@ -17,6 +17,7 @@
#include "stdclass.h"
#include "serialize.h"
#include "oslib/i18n.h"
#include "input/maplelink.h"
#include <time.h>
#ifdef TARGET_UWP
#include <winrt/Windows.System.h>
@@ -155,9 +156,14 @@ void flycast_term()
os_TermInput();
}
bool dc_savestateAllowed() {
return !settings.content.path.empty() && !settings.network.online
&& !settings.naomi.multiboard && !MapleLink::StorageEnabled();
}
void dc_savestate(int index, const u8 *pngData, u32 pngSize)
{
if (settings.network.online || settings.content.fileName.empty())
if (!dc_savestateAllowed())
return;
lastStateFile.clear();
@@ -225,7 +231,7 @@ fail:
void dc_loadstate(int index)
{
if (settings.raHardcoreMode)
if (!dc_savestateAllowed() || settings.raHardcoreMode)
return;
u32 total_size = 0;
+3
View File
@@ -41,6 +41,7 @@
#endif
#include "profiler/fc_profiler.h"
#include "input/gamepad_device.h"
#include "input/dreampotato.h"
#include "i18n.h"
namespace hostfs
@@ -460,10 +461,12 @@ void os_SetupInput()
if (config::UseRawInput)
rawinput::init();
#endif
dreampotato::update();
}
void os_TermInput()
{
dreampotato::term();
#if defined(USE_SDL)
input_sdl_quit();
#else
+69 -213
View File
@@ -26,136 +26,63 @@
#include <cfg/option.h>
#include "oslib/i18n.h"
#include <SDL.h>
#include <iomanip>
#include <sstream>
#include <locale>
#include <mutex>
#ifdef USE_DREAMCONN
static asio::error_code sendMsg(const MapleMsg& msg, asio::ip::tcp::iostream& stream)
{
std::ostringstream s;
s.imbue(std::locale::classic());
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";
asio::ip::tcp::socket& sock = static_cast<asio::ip::tcp::socket&>(stream.socket());
asio::error_code ec;
asio::write(sock, asio::buffer(s.str()), ec);
return ec;
}
static bool receiveMsg(MapleMsg& msg, std::istream& stream)
{
std::string response;
if (!std::getline(stream, response))
return false;
sscanf(response.c_str(), "%hhx %hhx %hhx %hhx", &msg.command, &msg.destAP, &msg.originAP, &msg.size);
if ((msg.getDataSize() - 1) * 3 + 13 >= response.length())
return false;
for (unsigned i = 0; i < msg.getDataSize(); i++)
sscanf(&response[i * 3 + 12], "%hhx", &msg.data[i]);
return !stream.fail();
}
asio::error_code sendMsg(const MapleMsg& msg, asio::ip::tcp::iostream& stream);
bool receiveMsg(MapleMsg& msg, std::istream& stream);
//! DreamConn implementation class
//! This is here mainly so asio.hpp can be included in this source file instead of the header.
class DreamConnImp : public DreamConn
class DreamConn : public DreamLink
{
int bus = -1;
bool maple_io_connected = false;
std::array<MapleDeviceType, 2> expansionDevs{};
asio::ip::tcp::iostream iostream;
std::mutex send_mutex;
//! Base port of communication to DreamConn
static constexpr u16 BASE_PORT = 37393;
public:
DreamConnImp(int bus) :
DreamConn(),
bus(bus)
DreamConn(int bus)
: DreamLink(false), bus(bus)
{}
~DreamConnImp() {
disconnect();
}
bool isForPhysicalController() override {
return true;
}
bool isPhysicalVMUMemorySupported() override {
bool storageEnabled() override {
// DreamConn controllers don't support physical VMU memory access
return false;
}
bool send(const MapleMsg& msg) override {
std::lock_guard<std::mutex> lock(send_mutex); // Ensure thread safety for send operations
return send_no_lock(msg);
}
bool send(const MapleMsg& txMsg, MapleMsg& rxMsg) override {
std::lock_guard<std::mutex> lock(send_mutex); // Ensure thread safety for send operations
return send_no_lock(txMsg, rxMsg);
}
private:
bool send_no_lock(const MapleMsg& msg) {
bool send(const MapleMsg& msg) override
{
if (!maple_io_connected)
return false;
auto ec = sendMsg(msg, iostream);
if (ec) {
WARN_LOG(INPUT, "DreamcastController[%d] send failed: %s", bus, ec.message().c_str());
WARN_LOG(INPUT, "DreamConn[%d] send failed: %s", bus, ec.message().c_str());
disconnect();
return false;
}
return true;
}
bool send_no_lock(const MapleMsg& txMsg, MapleMsg& rxMsg) {
if (!send_no_lock(txMsg)) {
bool sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg) override
{
if (!send(txMsg))
return false;
if (!receiveMsg(rxMsg, iostream)) {
WARN_LOG(INPUT, "DreamConn[%d] receive failed", bus);
disconnect();
return false;
}
return receiveMsg(rxMsg, iostream);
return true;
}
public:
int getBus() const override {
return bus;
}
u32 getFunctionCode(int forPort) const override {
MapleDeviceType deviceType = expansionDevs.at(forPort);
if (deviceType == MDT_SegaVMU) {
return 0x0E000000;
}
else if (deviceType == MDT_PurupuruPack) {
return 0x00010000;
}
return 0;
}
std::array<u32, 3> getFunctionDefinitions(int forPort) const override {
MapleDeviceType deviceType = expansionDevs.at(forPort);
if (deviceType == MDT_SegaVMU)
// For clock, LCD, storage
return std::array<u32, 3>{0x403f7e7e, 0x00100500, 0x00410f00};
else if (deviceType == MDT_PurupuruPack)
return std::array<u32, 3>{0x00000101, 0, 0};
return std::array<u32, 3>{0, 0, 0};
}
void changeBus(int newBus) override {
void changeBus(int newBus) override
{
if (newBus != bus) {
// A different TCP port is used depending on the bus. We'll need to disconnect from the current port.
// The caller will call connect() again if appropriate.
@@ -164,48 +91,46 @@ public:
}
}
std::string getName() const override {
return "DreamConn+ / DreamConn S Controller";
}
bool needsRefresh() override {
return false;
}
bool isConnected() override {
if (!maple_io_connected)
return false;
if (isSocketDisconnected()) {
NOTICE_LOG(INPUT, "DreamLink server disconnected bus[%d]", bus);
disconnect();
return false;
}
return true;
return maple_io_connected;
}
void connect() override
{
maple_io_connected = false;
if (!DreamLink::isValidPort(bus))
return;
iostream = asio::ip::tcp::iostream("localhost", std::to_string(DreamConn::BASE_PORT + bus));
iostream = asio::ip::tcp::iostream("localhost", std::to_string(BASE_PORT + bus));
if (!iostream) {
WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, iostream.error().message().c_str());
WARN_LOG(INPUT, "DreamConn[%d] connection failed: %s", bus, iostream.error().message().c_str());
disconnect();
return;
}
// Optimistically assume we are connected to the maple server. If a send fails we will just set this flag back to false.
maple_io_connected = true;
iostream.expires_from_now(std::chrono::seconds(1));
if (!updateExpansionDevs_no_lock())
// Now get the controller configuration
MapleMsg msg;
msg.command = MDCF_GetCondition;
msg.destAP = (bus << 6) | 0x20;
msg.originAP = bus << 6;
msg.pushData(MFID_0_Input);
MapleMsg rxMsg;
if (!sendReceive(msg, rxMsg))
return;
iostream.expires_from_now(std::chrono::duration<u32>::max()); // don't use a 64-bit based duration to avoid overflow
// Remain connected even if no devices were found, so that connecting a device later will be detected
NOTICE_LOG(INPUT, "Connected to DreamcastController[%d]: Type:%s, Slot 1: %s, Slot 2: %s", bus, getName().c_str(), deviceDescription(expansionDevs[0]), deviceDescription(expansionDevs[1]));
config::MapleExpansionDevices[bus][0] = expansionDevs[0] = rxMsg.originAP & 1 ? MDT_SegaVMU : MDT_None;
config::MapleExpansionDevices[bus][1] = expansionDevs[1] = rxMsg.originAP & 2 ? MDT_PurupuruPack : MDT_None;
if (expansionDevs[0] == MDT_SegaVMU)
registerLink(bus, 0);
if (expansionDevs[1] == MDT_PurupuruPack)
registerLink(bus, 1);
NOTICE_LOG(INPUT, "Connected to DreamConn[%d]: Slot 1: %s, Slot 2: %s", bus,
deviceDescription(expansionDevs[0]), deviceDescription(expansionDevs[1]));
}
static const char* deviceDescription(MapleDeviceType deviceType) {
@@ -217,112 +142,43 @@ public:
}
}
void disconnect() override {
void disconnect() override
{
// Already disconnected
if (!maple_io_connected)
return;
maple_io_connected = false;
unregisterLink(bus, 0);
unregisterLink(bus, 1);
if (iostream)
iostream.close();
// Notify the user of the disconnect
NOTICE_LOG(INPUT, "Disconnected from DreamcastController[%d]", bus);
NOTICE_LOG(INPUT, "Disconnected from DreamConn[%d]", bus);
char buf[128];
snprintf(buf, sizeof(buf), i18n::T("WARNING: DreamLink disconnected from port %c"), 'A' + bus);
snprintf(buf, sizeof(buf), i18n::T("WARNING: DreamConn disconnected from port %c"), 'A' + bus);
os_notify(buf, 6000);
tearDownDreamLinkDevices(this);
}
void gameTermination() override {
clearScreen(0);
clearScreen(1);
}
private:
void clearScreen(int deviceIndex) {
if (expansionDevs[deviceIndex] != MDT_SegaVMU)
return;
// Clear the remote VMU screen
MapleMsg msg;
msg.command = MDCF_BlockWrite;
msg.destAP = (bus << 6) | 0x20 | (1 << deviceIndex);
msg.originAP = bus << 6;
u32 localData[0x32];
memset(localData, 0, sizeof(localData));
localData[0] = MFID_2_LCD;
msg.setData(localData);
send(msg);
}
bool updateExpansionDevs_no_lock() {
// 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);
auto ec = sendMsg(msg, iostream);
if (ec)
{
WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str());
disconnect();
return false;
}
if (!receiveMsg(msg, iostream)) {
WARN_LOG(INPUT, "DreamcastController[%d] read timeout", bus);
disconnect();
return false;
}
u8 portFlags = msg.originAP & 0x1f;
config::MapleExpansionDevices[bus][0] = expansionDevs[0] = getDevice_no_lock(portFlags, 1 << 0);
config::MapleExpansionDevices[bus][1] = expansionDevs[1] = getDevice_no_lock(portFlags, 1 << 1);
return true;
}
MapleDeviceType getDevice_no_lock(u8 portFlags, u8 portFlag)
{
if (!(portFlags & portFlag)) {
// No device connected to this slot
return MDT_None;
}
// Assume that a device in slot 1 is a VMU and a device in slot 2 is a purupuru.
return portFlag == (1 << 0) ? MDT_SegaVMU : MDT_PurupuruPack;
}
bool isSocketDisconnected() {
std::lock_guard<std::mutex> lock(send_mutex);
// A socket was disconnected if 'select()' says the socket is ready to read, and a subsequent 'recv()' fails or says 0 bytes available to read.
auto& sock = static_cast<asio::ip::tcp::socket&>(iostream.socket());
auto nativeHandle = sock.native_handle();
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(nativeHandle, &readfds);
timeval timeout = { 0, 0 };
// nfds should be set to the highest-numbered file descriptor plus 1.
// See https://www.man7.org/linux/man-pages/man2/select.2.html
int nfds = nativeHandle + 1;
int nReady = select(nfds, &readfds, nullptr, nullptr, &timeout);
bool socketIsReady = nReady > 0 && FD_ISSET(nativeHandle, &readfds);
if (!socketIsReady)
return false;
char dest;
int len = recv(nativeHandle, &dest, sizeof(dest), MSG_PEEK);
return len <= 0;
}
};
std::shared_ptr<DreamConn> DreamConn::create_shared(int bus) {
return std::make_shared<DreamConnImp>(bus);
bool DreamConnGamepad::identify(int deviceIndex)
{
char guid_str[33] {};
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(deviceIndex), guid_str, sizeof(guid_str));
// DreamConn VID:4457 PID:4443
const char* pid_vid_guid_str = guid_str + 8;
if (memcmp(VID_PID_GUID, pid_vid_guid_str, 16) == 0) {
NOTICE_LOG(INPUT, "DreamConn controller found!");
return true;
}
return false;
}
DreamConnGamepad::DreamConnGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
: DreamLinkGamepad(std::make_shared<DreamConn>(maple_port), maple_port, joystick_idx, sdl_joystick)
{
_name = "DreamConn+ / DreamConn S Controller";
}
#endif // USE_DREAMCONN
+6 -12
View File
@@ -25,22 +25,16 @@
#include "dreamlink.h"
#include <memory>
class DreamConn : public DreamLink
class DreamConnGamepad : public DreamLinkGamepad
{
public:
//! Base port of communication to DreamConn
static constexpr u16 BASE_PORT = 37393;
DreamConnGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
static bool identify(int deviceIndex);
private:
//! DreamConn VID:4457 PID:4443
static constexpr const char* VID_PID_GUID = "5744000043440000";
protected:
DreamConn() = default;
virtual ~DreamConn() = default;
public:
static std::shared_ptr<DreamConn> create_shared(int bus);
};
#endif // WIN32 && !UWP
+36 -156
View File
@@ -45,40 +45,23 @@
#include <setupapi.h>
#endif
void handleEvent(Event event, void* arg)
DreamLink::DreamLink(bool storageSupported)
: BaseMapleLink(storageSupported)
{
for (auto& dreamlink : DreamLink::activeDreamLinks)
{
if (dreamlink != nullptr)
{
if (event != Event::Terminate)
{
createDreamLinkDevices(dreamlink, event == Event::Start, event == Event::LoadState);
}
else
{
dreamlink->gameTermination();
}
}
}
EventManager::listen(Event::Terminate, DreamLink::eventTerminate, this);
}
void registerDreamLinkEvents()
DreamLink::~DreamLink()
{
EventManager::listen(Event::Start, handleEvent, nullptr);
EventManager::listen(Event::LoadState, handleEvent, nullptr);
EventManager::listen(Event::Terminate, handleEvent, nullptr);
EventManager::unlisten(Event::Terminate, DreamLink::eventTerminate, this);
}
void unregisterDreamLinkEvents()
void DreamLink::eventTerminate(Event event, void *param)
{
EventManager::unlisten(Event::Start, handleEvent, nullptr);
EventManager::unlisten(Event::LoadState, handleEvent, nullptr);
EventManager::unlisten(Event::Terminate, handleEvent, nullptr);
DreamLink *self = static_cast<DreamLink *>(param);
self->gameTermination();
}
std::array<std::shared_ptr<DreamLink>, DreamLink::NUM_PORTS> DreamLink::activeDreamLinks;
bool DreamLinkGamepad::isDreamcastController(int deviceIndex)
{
char guid_str[33] {};
@@ -88,82 +71,36 @@ bool DreamLinkGamepad::isDreamcastController(int deviceIndex)
guid_str[18], guid_str[19], guid_str[16], guid_str[17]);
#ifdef USE_DREAMCONN
// DreamConn VID:4457 PID:4443
// Dreamcast Controller USB VID:1209 PID:2f07
const char* pid_vid_guid_str = guid_str + 8;
if (memcmp(DreamConn::VID_PID_GUID, pid_vid_guid_str, 16) == 0 ||
memcmp(DreamPicoPort::VID_PID_GUID, pid_vid_guid_str, 16) == 0)
{
NOTICE_LOG(INPUT, "Dreamcast controller found!");
if (DreamConnGamepad::identify(deviceIndex))
return true;
}
#endif
if (DreamPicoPortGamepad::identify(deviceIndex))
return true;
return false;
}
DreamLinkGamepad::DreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
: SDLGamepad(maple_port, joystick_idx, sdl_joystick)
DreamLinkGamepad::DreamLinkGamepad(std::shared_ptr<DreamLink> dreamlink, int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
: SDLGamepad(maple_port, joystick_idx, sdl_joystick), dreamlink(dreamlink)
{
char guid_str[33] {};
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(joystick_idx), guid_str, sizeof(guid_str));
device_guid = guid_str;
#ifdef USE_DREAMCONN
// DreamConn VID:4457 PID:4443
// Dreamcast Controller USB VID:1209 PID:2f07
if (memcmp(DreamConn::VID_PID_GUID, guid_str + 8, 16) == 0)
{
dreamlink = DreamConn::create_shared(maple_port);
}
else
#endif
if (memcmp(DreamPicoPort::VID_PID_GUID, guid_str + 8, 16) == 0)
{
dreamlink = DreamPicoPort::create_shared(maple_port, joystick_idx, sdl_joystick);
}
if (dreamlink) {
if (DreamLink::isValidPort(maple_port)) {
DreamLink::activeDreamLinks[maple_port] = dreamlink;
}
_name = dreamlink->getName();
int defaultBus = dreamlink->getDefaultBus();
if (DreamLink::isValidPort(defaultBus)) {
set_maple_port(defaultBus);
}
std::string uniqueId = dreamlink->getUniqueId();
if (!uniqueId.empty()) {
this->_unique_id = uniqueId;
}
}
loadMapping();
verify(dreamlink != nullptr);
}
DreamLinkGamepad::~DreamLinkGamepad() {
int port = maple_port();
if (dreamlink && DreamLink::isValidPort(port)) {
tearDownDreamLinkDevices(dreamlink.get());
void DreamLinkGamepad::close()
{
if (dreamlink != nullptr)
{
dreamlink->disconnect();
dreamlink.reset();
DreamLink::activeDreamLinks[port] = nullptr;
// Make sure settings are open in case disconnection happened mid-game
if (!gui_is_open()) {
if (!gui_is_open())
gui_open_settings();
}
}
SDLGamepad::close();
}
const char* DreamLinkGamepad::dreamLinkStatus()
{
using namespace i18n;
int port = maple_port();
if (!dreamlink || !DreamLink::isValidPort(port) || DreamLink::activeDreamLinks[port] != dreamlink)
return T("Inactive");
return dreamlink->isConnected() ? T("Connected") : T("Disconnected");
}
@@ -174,96 +111,28 @@ void DreamLinkGamepad::set_maple_port(int port)
return;
SDLGamepad::set_maple_port(port);
if (!dreamlink)
return;
dreamlink->changeBus(port);
if (DreamLink::isValidPort(oldPort) && DreamLink::activeDreamLinks[oldPort] == dreamlink) {
// 'dreamlink' is currently active in 'oldPort'.
// Remove this dreamlink from 'oldPort' and repopulate with the dreamlink for a different gamepad, if any.
DreamLink::activeDreamLinks[oldPort] = nullptr;
for (int i = 0; i < GamepadDevice::GetGamepadCount(); i++) {
DreamLinkGamepad* gamepad = dynamic_cast<DreamLinkGamepad*>(GamepadDevice::GetGamepad(i).get());
if (gamepad == nullptr || !gamepad->is_registered() || gamepad->maple_port() != oldPort)
continue;
// Found a DreamLinkGamepad for 'oldPort'
assert(gamepad != this);
DreamLink::activeDreamLinks[oldPort] = gamepad->dreamlink;
gamepad->dreamlink->connect();
}
}
if (!DreamLink::isValidPort(port)) {
// Moving to a port out of range.
// This usually means the gamepad is just not being used for any port.
// Just disconnect the dreamlink and no further action needed.
dreamlink->disconnect();
return;
}
auto& existingDreamLink = DreamLink::activeDreamLinks[port];
if (existingDreamLink) {
// A DreamLink was already active in 'port'. Disconnect it before making it inactive.
assert(existingDreamLink != dreamlink);
existingDreamLink->disconnect();
}
DreamLink::activeDreamLinks[port] = dreamlink;
if (is_registered()) {
dreamlink->connect();
}
}
void DreamLinkGamepad::registered()
{
if (dreamlink)
{
dreamlink->connect();
// Create DreamLink Maple Devices here just in case game is already running
createDreamLinkDevices(dreamlink, false, false);
}
SDLGamepad::registered();
dreamlink->connect();
}
void DreamLinkGamepad::resetMappingToDefault(bool arcade, bool gamepad) {
SDLGamepad::resetMappingToDefault(arcade, gamepad);
if (input_mapper) {
if (dreamlink) {
dreamlink->setDefaultMapping(input_mapper);
}
setCustomMapping(input_mapper);
setBaseDefaultMapping(input_mapper);
}
}
const char *DreamLinkGamepad::get_button_name(u32 code) {
if (dreamlink) {
const char* name = dreamlink->getButtonName(code);
if (name) {
return name;
}
}
return SDLGamepad::get_button_name(code);
}
const char *DreamLinkGamepad::get_axis_name(u32 code) {
if (dreamlink) {
const char* name = dreamlink->getAxisName(code);
if (name) {
return name;
}
}
return SDLGamepad::get_axis_name(code);
}
std::shared_ptr<InputMapping> DreamLinkGamepad::getDefaultMapping() {
std::shared_ptr<InputMapping> mapping = SDLGamepad::getDefaultMapping();
if (mapping) {
if (dreamlink) {
dreamlink->setDefaultMapping(mapping);
}
setCustomMapping(mapping);
setBaseDefaultMapping(mapping);
}
return mapping;
@@ -288,3 +157,14 @@ void DreamLinkGamepad::setBaseDefaultMapping(const std::shared_ptr<InputMapping>
});
}
}
std::shared_ptr<DreamLinkGamepad> createDreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
{
if (DreamPicoPortGamepad::identify(joystick_idx))
return std::make_shared<DreamPicoPortGamepad>(maple_port, joystick_idx, sdl_joystick);
#ifdef USE_DREAMCONN
else if (DreamConnGamepad::identify(joystick_idx))
return std::make_shared<DreamConnGamepad>(maple_port, joystick_idx, sdl_joystick);
#endif
return nullptr;
}
+17 -126
View File
@@ -26,54 +26,18 @@
#include "types.h"
#include "emulator.h"
#include "sdl_gamepad.h"
#include "input/maplelink.h"
#include <functional>
#include <memory>
#include <array>
struct MapleMsg
{
u8 command = 0;
u8 destAP = 0;
u8 originAP = 0;
u8 size = 0;
u8 data[1024];
u32 getDataSize() const {
return size * 4;
}
template<typename T>
void setData(const T& p) {
memcpy(data, &p, sizeof(T));
this->size = (sizeof(T) + 3) / 4;
}
void setWord(const u32& p, int index) {
if (index < 0 || index >= 256) {
return;
}
memcpy(&data[index * 4], &p, sizeof(u32));
if (this->size <= index) {
this->size = index + 1;
}
}
};
static_assert(sizeof(MapleMsg) == 1028);
// Abstract base class for communication with physical controllers and remote expansion devices
class DreamLink : public std::enable_shared_from_this<DreamLink>
// Abstract base class for communication with physical controllers
class DreamLink : public BaseMapleLink
{
public:
//! Number of physical dreamcast ports
static constexpr const int NUM_PORTS = 4;
// The active DreamLink, if any, for each port.
// Note that multiple gamepad DreamLinks may exist for a given port, in which case only one is active.
static std::array<std::shared_ptr<DreamLink>, NUM_PORTS> activeDreamLinks;
DreamLink() = default;
virtual ~DreamLink() = default;
static constexpr int NUM_PORTS = 4;
//! Check if a given port is valid
//! @param[in] port The dreamcast port index to test
@@ -82,72 +46,12 @@ public:
return (port >= 0 && port < NUM_PORTS);
}
virtual bool isForPhysicalController() = 0;
virtual bool isPhysicalVMUMemorySupported() = 0;
//! Sends a message to the controller, ignoring the response
//! @note The implementation shall be thread safe
virtual bool send(const MapleMsg& msg) = 0;
//! Sends a message to the controller and waits for a response
//! @note The implementation shall be thread safe
virtual bool send(const MapleMsg& txMsg, MapleMsg& rxMsg) = 0;
//! When called, do teardown stuff like reset screen
virtual inline void gameTermination() {}
//! @param[in] forPort The port number to get the function code of
//! @return the device type for the given port
virtual u32 getFunctionCode(int forPort) const = 0;
//! @param[in] forPort The port number to get the function definitions of
//! @return the 3 function definitions for the supported function codes
virtual std::array<u32, 3> getFunctionDefinitions(int forPort) const = 0;
//! @return the default bus number to select for this controller or -1 to not select a default
virtual int getDefaultBus() const {
return -1;
}
//! Allows a DreamLink device to dictate the default mapping
virtual void setDefaultMapping(const std::shared_ptr<InputMapping>& mapping) const {
}
//! Allows button names to be defined by a DreamLink device
//! @param[in] code The button code to retrieve name of
//! @return the button name for the given code to override what is defined by the gamepad
//! @return nullptr to fall back to gamepad definitions
virtual const char *getButtonName(u32 code) const {
return nullptr;
}
//! Allows axis names to be defined by a DreamLink device
//! @param[in] code The axis code to retrieve name of
//! @return the axis name for the given code to override what is defined by the gamepad
//! @return nullptr to fall back to gamepad definitions
virtual const char *getAxisName(u32 code) const {
return nullptr;
}
//! @return a unique ID for this DreamLink device or empty string to use default
virtual std::string getUniqueId() const {
return std::string();
}
//! @return the selected bus number of the controller
virtual int getBus() const = 0;
//! When called, do teardown stuff (vmu screen reset is handled by maple_sega_vmu)
virtual void gameTermination() {}
//! Changes the selected maple port is changed by the user
virtual void changeBus(int newBus) = 0;
//! @return the display name of the controller
virtual std::string getName() const = 0;
//! Fetch the latest remote device configuration and return 'true' if it changed.
//! Caller is responsible for recreating the actual devices.
virtual bool needsRefresh() = 0;
//! Returns true if connected to the hardware controller (TODO: "hardware controller or remote device" throughout?)
virtual bool isConnected() = 0;
@@ -157,47 +61,34 @@ public:
//! Disconnect from the hardware controller
virtual void disconnect() = 0;
//! Sends the current game id to a DreamLink backed expansion device if supported
virtual void sendGameId(int expansion, const std::string& gameId) {}
protected:
DreamLink(bool storageSupported = true);
~DreamLink();
private:
static void eventTerminate(Event event, void *param);
static void eventStartAndLoadState(Event event, void *param);
};
class DreamLinkGamepad : public SDLGamepad
{
public:
DreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
~DreamLinkGamepad();
const char* dreamLinkStatus();
void set_maple_port(int port) override;
void registered() override;
static bool isDreamcastController(int deviceIndex);
void resetMappingToDefault(bool arcade, bool gamepad) override;
const char *get_button_name(u32 code) override;
const char *get_axis_name(u32 code) override;
void close() override;
protected:
DreamLinkGamepad(std::shared_ptr<DreamLink> dreamlink, int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
std::shared_ptr<InputMapping> getDefaultMapping() override;
void setBaseDefaultMapping(const std::shared_ptr<InputMapping>& mapping) const;
virtual void setCustomMapping(const std::shared_ptr<InputMapping>& mapping) {}
std::shared_ptr<DreamLink> dreamlink;
bool ltrigPressed = false;
bool rtrigPressed = false;
bool startPressed = false;
std::string device_guid;
};
// Creates and destroys DreamLinks according to config settings.
// Attempts to connect/reconnect DreamLinks which are not connected.
// Returns true if any new connection was established.
// Note that this doesn't createDreamLinkDevices for DreamLinks created by this function. Caller is responsible for that.
bool reconnectDreamLinks();
void refreshDreamLinksIfNeeded();
void createAllDreamLinkDevices();
void createDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink, bool gameStart, bool stateLoaded);
void tearDownDreamLinkDevices(DreamLink* dreamlink);
void registerDreamLinkEvents();
void unregisterDreamLinkEvents();
std::shared_ptr<DreamLinkGamepad> createDreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
#endif // USE_DREAMLINK_DEVICES
+140 -105
View File
@@ -20,6 +20,7 @@
#include "dreampicoport.h"
#include "hw/maple/maple_devs.h"
#include "hw/maple/maple_if.h"
#include "ui/gui.h"
#include "cfg/option.h"
#include "oslib/i18n.h"
@@ -751,7 +752,7 @@ public:
msg.command = MDCF_GetCondition;
msg.destAP = (hardware_bus << 6) | 0x20;
msg.originAP = hardware_bus << 6;
msg.setData(MFID_0_Input);
msg.pushData(MFID_0_Input);
asio::error_code error = serial->sendMsg(msg, hardware_bus, msg, timeout_ms);
if (error)
@@ -1054,7 +1055,7 @@ public:
std::unordered_map<std::string, std::weak_ptr<dpp_api::DppDevice>> ApiDreamPicoPortComms::all_dpp_api_devices;
std::mutex ApiDreamPicoPortComms::all_dpp_api_devices_mutex;
class DreamPicoPortImp : public DreamPicoPort
class DreamPicoPort : public DreamLink
{
//! Implements communication interface to DreamPicoPort
std::unique_ptr<class DreamPicoPortComms> dpp_comms;
@@ -1066,6 +1067,9 @@ class DreamPicoPortImp : public DreamPicoPort
double interface_version = 0.0;
//! The queried peripherals; for each function, index 0 is function code and index 1 is the function definition
std::vector<std::vector<std::array<uint32_t, 2>>> peripherals;
//! Dreamcast Controller USB VID:1209 PID:2f07
static constexpr std::uint16_t VID = 0x1209;
static constexpr std::uint16_t PID = 0x2f07;
//! Static hardware information
struct HardwareInfo
@@ -1097,24 +1101,11 @@ class DreamPicoPortImp : public DreamPicoPort
const HardwareInfo hw_info;
public:
DreamPicoPortImp(int bus, int joystick_idx, SDL_Joystick* sdl_joystick) :
DreamPicoPort(),
DreamPicoPort(int bus, int joystick_idx, SDL_Joystick* sdl_joystick) :
software_bus(bus),
hw_info(parseHardwareInfo(joystick_idx, sdl_joystick))
{}
~DreamPicoPortImp() {
disconnect();
}
bool isForPhysicalController() override {
return true;
}
bool isPhysicalVMUMemorySupported() override {
return true;
}
bool send(const MapleMsg& msg) override {
if (!dpp_comms) {
return false;
@@ -1123,7 +1114,7 @@ public:
return dpp_comms->send(msg, timeout_ms);
}
bool send(const MapleMsg& txMsg, MapleMsg& rxMsg) override {
bool sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg) override {
if (!dpp_comms) {
return false;
}
@@ -1131,10 +1122,12 @@ public:
return dpp_comms->send(txMsg, rxMsg, timeout_ms);
}
void sendGameId(int expansion, const std::string& gameId) override {
//! Sends the current game id to a DreamLink backed expansion device if supported
void sendGameId(int expansion) {
if (!dpp_comms || hw_info.hardware_bus < 0)
return;
const std::string& gameId = settings.content.gameId;
if (gameId.empty() || expansion < 0 || expansion > 1)
return;
@@ -1142,11 +1135,8 @@ public:
msg.command = 33;
msg.destAP = (hw_info.hardware_bus << 6) | (1u << expansion);
msg.originAP = hw_info.hardware_bus << 6;
msg.setWord(MFID_1_Storage, 0);
const size_t idSize = 12;
const size_t copyLength = std::min(gameId.size(), idSize);
memcpy(&msg.data[4], gameId.data(), copyLength);
msg.pushData(MFID_1_Storage);
msg.pushData(gameId.data(), std::min<u32>(gameId.size() + 1, 12));
msg.size = 4;
dpp_comms->send(msg, timeout_ms);
@@ -1159,10 +1149,6 @@ public:
sendPort();
}
int getBus() const override {
return software_bus;
}
//! Transform flycast port index into DreamPicoPort port index
static int fcPortToDppPort(int forPort) {
// Flycast uses port index 5 for main peripheral and 0 is the first sub-peripheral slot
@@ -1174,7 +1160,7 @@ public:
}
}
u32 getFunctionCode(int forPort) const override {
u32 getFunctionCode(int forPort) const {
forPort = fcPortToDppPort(forPort);
u32 mask = 0;
if ((int)peripherals.size() > forPort) {
@@ -1186,7 +1172,7 @@ public:
return SWAP32(mask);
}
std::array<u32, 3> getFunctionDefinitions(int forPort) const override {
std::array<u32, 3> getFunctionDefinitions(int forPort) const {
forPort = fcPortToDppPort(forPort);
std::array<u32, 3> arr{0, 0, 0};
if ((int)peripherals.size() > forPort) {
@@ -1199,7 +1185,7 @@ public:
return arr;
}
int getDefaultBus() const override {
int getDefaultBus() const {
if (!hw_info.is_hardware_bus_implied && !hw_info.is_single_device) {
return hw_info.hardware_bus;
} else {
@@ -1208,61 +1194,69 @@ public:
}
}
void setDefaultMapping(const std::shared_ptr<InputMapping>& mapping) const override {
// Since this is a real DC controller, no deadzone adjustment is needed
mapping->dead_zone = 0.0f;
// Map the things not set by SDL
mapping->set_button(DC_BTN_C, 2);
mapping->set_button(DC_BTN_Z, 5);
mapping->set_button(DC_BTN_D, 10);
mapping->set_button(DC_DPAD2_UP, 9);
mapping->set_button(DC_DPAD2_DOWN, 8);
mapping->set_button(DC_DPAD2_LEFT, 7);
mapping->set_button(DC_DPAD2_RIGHT, 6);
}
const char *getButtonName(u32 code) const override {
using namespace i18n;
switch (code) {
// Coincides with buttons setup in setDefaultMapping
case 2: return "C";
case 5: return "Z";
case 10: return "D";
case 9: return T("DPad2 Up");
case 8: return T("DPad2 Down");
case 7: return T("DPad2 Left");
case 6: return T("DPad2 Right");
// These buttons are normally not physically accessible but are mapped on DreamPicoPort
case 12: return T("VMU1 A");
case 15: return T("VMU1 B");
case 16: return T("VMU1 Up");
case 17: return T("VMU1 Down");
case 18: return T("VMU1 Left");
case 19: return T("VMU1 Right");
default: return nullptr; // no override
}
}
std::string getUniqueId() const override {
std::string getUniqueId() const {
return hw_info.unique_id;
}
void changeBus(int newBus) override {
if (software_bus == newBus)
return;
unregisterLink(software_bus, 0);
unregisterLink(software_bus, 1);
software_bus = newBus;
setMapleDevices();
if (dpp_comms) {
dpp_comms->changeSoftwareBus(software_bus);
}
}
std::string getName() const override {
std::string getName() const {
return hw_info.getName();
}
bool needsRefresh() override {
// TODO: implementing this method may also help to support hot plugging of VMUs/jump packs here.
return false;
void setMapleDevices()
{
if (!DreamLink::isValidPort(software_bus))
return;
int portCount = maple_getPortCount(config::MapleMainDevices[software_bus]);
if (portCount == 0)
return;
bool needReconnect = false;
u32 portOneFn = getFunctionCode(0);
if (portOneFn & MFID_1_Storage) {
config::MapleExpansionDevices[software_bus][0] = MDT_SegaVMU;
registerLink(software_bus, 0);
if (storageEnabled()) {
sendGameId(0);
needReconnect = true;
}
}
else {
config::MapleExpansionDevices[software_bus][0] = MDT_None;
}
if (portCount >= 2)
{
u32 portTwoFn = getFunctionCode(1);
if (portTwoFn & MFID_8_Vibration) {
config::MapleExpansionDevices[software_bus][1] = MDT_PurupuruPack;
registerLink(software_bus, 1);
}
else if (portTwoFn & MFID_1_Storage) {
config::MapleExpansionDevices[software_bus][1] = MDT_SegaVMU;
registerLink(software_bus, 1);
if (storageEnabled()) {
sendGameId(1);
needReconnect = true;
}
}
else {
config::MapleExpansionDevices[software_bus][1] = MDT_None;
}
}
if (needReconnect && isGameStarted())
emu.run(maple_ReconnectDevices);
}
bool isConnected() override {
@@ -1293,7 +1287,7 @@ public:
NOTICE_LOG(INPUT, "Serial number for DreamPicoPort[%d] not found", software_bus);
}
#ifndef TARGET_UWP
#ifndef TARGET_UWP
if (!dpp_comms) {
NOTICE_LOG(
INPUT,
@@ -1305,7 +1299,7 @@ public:
dpp_comms.reset();
}
}
#endif
#endif
if (isConnected()) {
sendPort();
@@ -1322,45 +1316,23 @@ public:
// Timeout is extended to 5 seconds for all other communication after connection
timeout_ms = std::chrono::seconds(5);
int vmuCount = 0;
int vibrationCount = 0;
if (software_bus >= 0 && static_cast<std::size_t>(software_bus) < config::MapleExpansionDevices.size()) {
u32 portOneFn = getFunctionCode(0);
if (portOneFn & MFID_1_Storage) {
config::MapleExpansionDevices[software_bus][0] = MDT_SegaVMU;
++vmuCount;
}
else {
config::MapleExpansionDevices[software_bus][0] = MDT_None;
}
u32 portTwoFn = getFunctionCode(1);
if (portTwoFn & MFID_8_Vibration) {
config::MapleExpansionDevices[software_bus][1] = MDT_PurupuruPack;
++vibrationCount;
}
else if (portTwoFn & MFID_1_Storage) {
config::MapleExpansionDevices[software_bus][1] = MDT_SegaVMU;
++vmuCount;
}
else {
config::MapleExpansionDevices[software_bus][1] = MDT_None;
}
}
setMapleDevices();
NOTICE_LOG(
INPUT,
"Connected to DreamPicoPort[%d]: Type:%s, VMU:%d, Jump Pack:%d",
software_bus,
getName().c_str(),
vmuCount,
vibrationCount
(getFunctionCode(0) & MFID_1_Storage) != 0,
(getFunctionCode(1) & MFID_8_Vibration) != 0
);
}
void disconnect() override {
dpp_comms.reset();
unregisterLink(software_bus, 0);
unregisterLink(software_bus, 1);
disableStorage();
}
void sendPort() {
@@ -1420,7 +1392,6 @@ private:
#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);
@@ -1545,6 +1516,70 @@ private:
}
};
std::shared_ptr<DreamPicoPort> DreamPicoPort::create_shared(int bus, int joystick_idx, SDL_Joystick* sdl_joystick) {
return std::make_shared<DreamPicoPortImp>(bus, joystick_idx, sdl_joystick);
DreamPicoPortGamepad::DreamPicoPortGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
: DreamLinkGamepad(std::make_shared<DreamPicoPort>(maple_port, joystick_idx, sdl_joystick), maple_port, joystick_idx, sdl_joystick)
{
DreamPicoPort *picoPort = dynamic_cast<DreamPicoPort*>(dreamlink.get());
_name = picoPort->getName();
std::string uniqueId = picoPort->getUniqueId();
if (!uniqueId.empty()) {
_unique_id = uniqueId;
loadMapping();
}
int bus = picoPort->getDefaultBus();
if (DreamLink::isValidPort(bus))
set_maple_port(bus);
}
bool DreamPicoPortGamepad::identify(int deviceIndex)
{
char guid_str[33] {};
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(deviceIndex), guid_str, sizeof(guid_str));
// Dreamcast Controller USB VID:1209 PID:2f07
const char* pid_vid_guid_str = guid_str + 8;
if (memcmp(VID_PID_GUID, pid_vid_guid_str, 16) == 0) {
NOTICE_LOG(INPUT, "Dreamcast controller found!");
return true;
}
return false;
}
void DreamPicoPortGamepad::setCustomMapping(const std::shared_ptr<InputMapping>& mapping)
{
// Since this is a real DC controller, no deadzone adjustment is needed
mapping->dead_zone = 0.0f;
// Map the things not set by SDL
mapping->set_button(DC_BTN_C, 2);
mapping->set_button(DC_BTN_Z, 5);
mapping->set_button(DC_BTN_D, 10);
mapping->set_button(DC_DPAD2_UP, 9);
mapping->set_button(DC_DPAD2_DOWN, 8);
mapping->set_button(DC_DPAD2_LEFT, 7);
mapping->set_button(DC_DPAD2_RIGHT, 6);
}
const char *DreamPicoPortGamepad::get_button_name(u32 code)
{
using namespace i18n;
switch (code) {
// Coincides with buttons setup in setDefaultMapping
case 2: return "C";
case 5: return "Z";
case 10: return "D";
case 9: return T("DPad2 Up");
case 8: return T("DPad2 Down");
case 7: return T("DPad2 Left");
case 6: return T("DPad2 Right");
// These buttons are normally not physically accessible but are mapped on DreamPicoPort
case 12: return T("VMU1 A");
case 15: return T("VMU1 B");
case 16: return T("VMU1 Up");
case 17: return T("VMU1 Down");
case 18: return T("VMU1 Left");
case 19: return T("VMU1 Right");
default: return DreamLinkGamepad::get_button_name(code); // default name
}
}
+9 -11
View File
@@ -25,20 +25,18 @@
#include <memory>
//! See: https://github.com/OrangeFox86/DreamPicoPort
class DreamPicoPort : public DreamLink
class DreamPicoPortGamepad : public DreamLinkGamepad
{
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";
DreamPicoPortGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
const char *get_button_name(u32 code) override;
static bool identify(int deviceIndex);
protected:
DreamPicoPort() = default;
virtual ~DreamPicoPort() = default;
void setCustomMapping(const std::shared_ptr<InputMapping>& mapping) override;
public:
static std::shared_ptr<DreamPicoPort> create_shared(int bus, int joystick_idx, SDL_Joystick* sdl_joystick);
//! Dreamcast Controller USB VID:1209 PID:2f07
static constexpr const char* VID_PID_GUID = "09120000072f0000";
};
#endif // USE_DREAMCASTCONTROLLER
#endif // USE_DREAMLINK_DEVICES
+1 -1
View File
@@ -83,7 +83,7 @@ static void sdl_open_joystick(int index)
#elif defined(USE_DREAMLINK_DEVICES)
std::shared_ptr<SDLGamepad> gamepad;
if (DreamLinkGamepad::isDreamcastController(index))
gamepad = std::make_shared<DreamLinkGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
gamepad = createDreamLinkGamepad(index < MAPLE_PORTS ? index : -1, index, pJoystick);
else
gamepad = std::make_shared<SDLGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
#else
+1 -1
View File
@@ -34,7 +34,7 @@ public:
void setDamper(float param, float speed);
void stopHaptic();
void close();
virtual void close();
const char *get_button_name(u32 code) override;
const char *get_axis_name(u32 code) override;
+5 -1
View File
@@ -242,9 +242,13 @@ public:
void init() {
threadId = std::this_thread::get_id();
}
void term() {
threadId = {};
}
void runOnThread(std::function<void()> func)
{
if (threadId == std::this_thread::get_id()) {
if (threadId == std::thread::id{}
|| threadId == std::this_thread::get_id()) {
func();
}
else {
+5 -60
View File
@@ -48,7 +48,6 @@
#include "hw/maple/maple_if.h"
#if defined(USE_SDL)
#include "sdl/sdl.h"
#include "sdl/dreamlink.h"
#endif
#include "vgamepad.h"
#include "settings.h"
@@ -693,10 +692,6 @@ void gui_stop_game(const std::string& message)
}
}
static bool savestateAllowed() {
return !settings.content.path.empty() && !settings.network.online && !settings.naomi.multiboard;
}
static void appendVectorData(void *context, void *data, int size)
{
std::vector<u8>& v = *(std::vector<u8> *)context;
@@ -817,56 +812,6 @@ static void gui_display_commands()
card_reader::barcodeSetCard(cardBuf);
}
#if defined(USE_DREAMLINK_DEVICES)
// DreamLink connection status
bool hasAnyDreamLinks = false;
bool hasDisconnectedDreamLink = false;
for (auto& dreamlink : DreamLink::activeDreamLinks)
{
if (dreamlink && !dreamlink->isForPhysicalController())
{
hasAnyDreamLinks = true;
if (!dreamlink->isConnected())
hasDisconnectedDreamLink = true;
}
if (hasAnyDreamLinks && hasDisconnectedDreamLink)
break;
}
if (hasAnyDreamLinks)
{
if (hasDisconnectedDreamLink)
{
if (ImGui::Button(T("Connect DreamLink"), ScaledVec2(buttonWidth, 50)))
{
if (reconnectDreamLinks())
maple_ReconnectDevices();
}
}
else
{
if (ImGui::Button(T("Disconnect\nDreamLink"), ScaledVec2(buttonWidth, 50)))
{
for (auto& dreamlink : DreamLink::activeDreamLinks)
{
if (dreamlink)
dreamlink->disconnect();
}
}
}
for (int i = 0; i < 4; i++)
{
auto dreamlink = DreamLink::activeDreamLinks[i];
if (dreamlink)
{
ImGui::Text("%s %c: %s", T("Port"), 'A' + i, dreamlink->isConnected() ? T("Connected") : T("Disconnected"));
}
}
}
#endif
ImGui::NextColumn();
// Insert/Eject Disk
@@ -891,14 +836,14 @@ static void gui_display_commands()
ImGui::NextColumn();
{
DisabledScope _{!savestateAllowed()};
DisabledScope _{!dc_savestateAllowed()};
ImguiStateTexture savestatePic;
time_t savestateDate = dc_getStateCreationDate(config::SavestateSlot);
// Load State
{
DisabledScope _{settings.raHardcoreMode || savestateDate == 0};
if (IconButton(ICON_FA_CLOCK_ROTATE_LEFT, T("Load State"), ScaledVec2(buttonWidth, 50)).realize() && savestateAllowed())
if (IconButton(ICON_FA_CLOCK_ROTATE_LEFT, T("Load State"), ScaledVec2(buttonWidth, 50)).realize() && dc_savestateAllowed())
{
gui_setState(GuiState::Closed);
dc_loadstate(config::SavestateSlot);
@@ -906,7 +851,7 @@ static void gui_display_commands()
}
// Save State
if (IconButton(ICON_FA_DOWNLOAD, T("Save State"), ScaledVec2(buttonWidth, 50)).realize() && savestateAllowed())
if (IconButton(ICON_FA_DOWNLOAD, T("Save State"), ScaledVec2(buttonWidth, 50)).realize() && dc_savestateAllowed())
{
gui_setState(GuiState::Closed);
savestate();
@@ -1800,7 +1745,7 @@ void gui_error(const std::string& what) {
void gui_loadState()
{
const LockGuard lock(guiMutex);
if (gui_state == GuiState::Closed && savestateAllowed())
if (gui_state == GuiState::Closed && dc_savestateAllowed())
{
try {
emu.stop();
@@ -1815,7 +1760,7 @@ void gui_loadState()
void gui_saveState(bool stopRestart)
{
const LockGuard lock(guiMutex);
if ((gui_state == GuiState::Closed || !stopRestart) && savestateAllowed())
if ((gui_state == GuiState::Closed || !stopRestart) && dc_savestateAllowed())
{
try {
if (stopRestart)
+2 -7
View File
@@ -23,15 +23,12 @@
#include "log/LogManager.h"
#include "hw/maple/maple_if.h"
#include "imgui_stdlib.h"
#include "input/dreampotato.h"
#ifdef GDB_SERVER
#include "hw/mem/addrspace.h"
#endif
#if defined(USE_DREAMLINK_DEVICES)
#include "sdl/dreamlink.h"
#endif
static void gui_settings_advanced()
{
#if FEAT_SHREC != DYNAREC_NONE
@@ -228,11 +225,9 @@ void gui_display_settings()
if (maple_devices_changed)
{
maple_devices_changed = false;
dreampotato::update();
if (game_started && settings.platform.isConsole())
{
#if defined(USE_DREAMLINK_DEVICES)
reconnectDreamLinks();
#endif
maple_ReconnectDevices();
reset_vmus();
}
+32 -49
View File
@@ -56,12 +56,15 @@ static char *maple_device_types[] =
// Tnop("Dreameye"),
};
constexpr int MDT_DreamPotato = 100;
static char *maple_expansion_device_types[] =
{
Tnop("None"),
Tnop("Sega VMU"),
Tnop("Vibration Pack"),
Tnop("Microphone"),
Tnop("DreamPotato"),
};
static const char *maple_device_name(MapleDeviceType type)
@@ -138,7 +141,7 @@ static MapleDeviceType maple_device_type_from_index(int idx)
static const char *maple_expansion_device_name(MapleDeviceType type)
{
switch (type)
switch ((int)type)
{
case MDT_SegaVMU:
return maple_expansion_device_types[1];
@@ -146,6 +149,8 @@ static const char *maple_expansion_device_name(MapleDeviceType type)
return maple_expansion_device_types[2];
case MDT_Microphone:
return maple_expansion_device_types[3];
case MDT_DreamPotato:
return maple_expansion_device_types[4];
case MDT_None:
default:
return maple_expansion_device_types[0];
@@ -300,6 +305,8 @@ static MapleDeviceType maple_expansion_device_type_from_index(int idx)
return MDT_PurupuruPack;
case 3:
return MDT_Microphone;
case 4:
return (MapleDeviceType)MDT_DreamPotato;
case 0:
default:
return MDT_None;
@@ -988,7 +995,7 @@ void gui_settings_controls(bool& maple_devices_changed)
ImGui::TableSetColumnIndex(2);
DreamLinkGamepad* dreamLinkGamepad = dynamic_cast<DreamLinkGamepad*>(gamepad.get());
if (dreamLinkGamepad != nullptr) {
ImGui::Text(T("DreamLink status: %s"), dreamLinkGamepad->dreamLinkStatus());
ImGui::Text(T("DreamLink: %s"), dreamLinkGamepad->dreamLinkStatus());
}
#endif
@@ -1002,14 +1009,8 @@ void gui_settings_controls(bool& maple_devices_changed)
for (int j = -1; j < (int)std::size(maple_ports) - 1; j++)
{
bool is_selected = gamepad->maple_port() == j;
if (ImGui::Selectable(maple_ports[j + 1], &is_selected)) {
if (ImGui::Selectable(maple_ports[j + 1], &is_selected))
gamepad->set_maple_port(j);
#if defined(USE_DREAMLINK_DEVICES)
if (dreamLinkGamepad != nullptr) {
maple_devices_changed = true;
}
#endif
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
@@ -1055,14 +1056,6 @@ void gui_settings_controls(bool& maple_devices_changed)
#if defined(_WIN32) && !defined(TARGET_UWP)
OptionCheckbox(T("Use Raw Input"), config::UseRawInput, T("Supports multiple pointing devices (mice, light guns) and keyboards"));
#endif
#ifdef USE_DREAMLINK_DEVICES
{
DisabledScope scope(game_started);
OptionCheckbox(T("Use Physical VMU Memory"), config::UsePhysicalVmuMemory,
T("Enables direct read/write access to physical VMU memory via DreamPicoPort/DreamConn. "
"This is not compatible with load state events."));
}
#endif
ImGui::Spacing();
header(T("Dreamcast Devices"));
@@ -1101,34 +1094,32 @@ void gui_settings_controls(bool& maple_devices_changed)
}
ImGui::EndCombo();
}
int port_count = 0;
switch (config::MapleMainDevices[bus]) {
case MDT_SegaController:
case MDT_SegaControllerXL:
port_count = 2;
break;
case MDT_LightGun:
case MDT_TwinStick:
case MDT_AsciiStick:
case MDT_RacingController:
port_count = 1;
break;
default: break;
}
int port_count = maple_getPortCount(config::MapleMainDevices[bus]);
for (int port = 0; port < port_count; port++)
{
ImGui::TableSetColumnIndex(2 + port);
snprintf(device_name, sizeof(device_name), "##device%d.%d", bus, port + 1);
ImguiID _(device_name);
ImGui::SetNextItemWidth(expComboWidth);
if (ImGui::BeginCombo(device_name, maple_expansion_device_name(config::MapleExpansionDevices[bus][port]), ImGuiComboFlags_None))
int subtype = config::MapleExpansionDevices[bus][port];
if (subtype == MDT_SegaVMU && config::NetworkExpansionDevices[bus][port] == 1)
subtype = MDT_DreamPotato;
if (ImGui::BeginCombo(device_name, maple_expansion_device_name((MapleDeviceType)subtype), ImGuiComboFlags_None))
{
for (int i = 0; i < IM_ARRAYSIZE(maple_expansion_device_types); i++)
{
bool is_selected = config::MapleExpansionDevices[bus][port] == maple_expansion_device_type_from_index(i);
bool is_selected = subtype == maple_expansion_device_type_from_index(i);
if (ImGui::Selectable(maple_expansion_device_types[i], &is_selected))
{
config::MapleExpansionDevices[bus][port] = maple_expansion_device_type_from_index(i);
subtype = maple_expansion_device_type_from_index(i);
if (subtype == MDT_DreamPotato) {
config::MapleExpansionDevices[bus][port] = MDT_SegaVMU;
config::NetworkExpansionDevices[bus][port] = 1;
}
else {
config::MapleExpansionDevices[bus][port] = (MapleDeviceType)subtype;
config::NetworkExpansionDevices[bus][port] = 0;
}
maple_devices_changed = true;
}
if (is_selected)
@@ -1172,28 +1163,20 @@ void gui_settings_controls(bool& maple_devices_changed)
is_there_any_xhair |= enabled;
}
#ifdef USE_DREAMLINK_DEVICES
if (port_count > 0)
{
ImGui::SameLine();
ImGui::PushID(bus);
bool pressed = OptionCheckbox(T("Use Network Expansion Devices"), config::UseNetworkExpansionDevices[bus],
T("Connect to expansion devices such as VMUs over local TCP."));
ImGui::PopID();
if (pressed)
maple_devices_changed = true;
}
#endif
ImGui::PopItemWidth();
}
ImGui::EndTable();
}
OptionCheckbox(T("Per Game VMU A1"), config::PerGameVmu, T("When enabled, each game has its own VMU on port 1 of controller A."));
{
DisabledScope scope(game_started);
OptionCheckbox(T("Use Physical VMU Storage"), config::UsePhysicalVmuMemory,
T("Enables read and write access to physical VMU storage via DreamPicoPort or DreamPotato. "
"This is not compatible with load state events."));
}
{
DisabledScope scope(!is_there_any_xhair);
OptionSlider(T("Crosshair Size"), config::CrosshairSize, 10, 100);
}
OptionCheckbox(T("Per Game VMU A1"), config::PerGameVmu, T("When enabled, each game has its own VMU on port 1 of controller A."));
}
}
+9
View File
@@ -66,6 +66,7 @@
#include "oslib/oslib.h"
#include "rend/CustomTexture.h"
#include "oslib/i18n.h"
#include "input/dreampotato.h"
constexpr char slash = path_default_slash_c();
@@ -997,6 +998,7 @@ static void update_variables(bool first_startup)
}
snprintf(key, sizeof(key), CORE_OPTION_NAME "_device_port%d_slot%d", i + 1, slot + 1);
config::NetworkExpansionDevices[i][slot] = 0;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
@@ -1006,6 +1008,10 @@ static void update_variables(bool first_startup)
config::MapleExpansionDevices[i][slot] = MDT_PurupuruPack;
else if (!strcmp("None", var.value))
config::MapleExpansionDevices[i][slot] = MDT_None;
else if (!strcmp("DreamPotato", var.value)) {
config::MapleExpansionDevices[i][slot] = MDT_SegaVMU;
config::NetworkExpansionDevices[i][slot] = 1;
}
}
else if (slot == 0) // Default to VMU in case the above is false somehow
config::MapleExpansionDevices[i][0] = MDT_SegaVMU;
@@ -1186,6 +1192,7 @@ static void update_variables(bool first_startup)
}
// must *not* be changed once a game is started
config::EmulateBBA.override(emulateBba);
dreampotato::update();
}
}
@@ -2337,6 +2344,7 @@ bool retro_load_game(const struct retro_game_info *game)
setRotation();
haveCardReader = card_reader::readerAvailable();
dreampotato::update();
refresh_devices(true);
// System may have changed - have to update hidden core options
@@ -2354,6 +2362,7 @@ void retro_unload_game()
{
INFO_LOG(COMMON, "Flycast unloading game");
emu.unloadGame();
dreampotato::term();
game_data.clear();
disk_paths.clear();
disk_labels.clear();
+4
View File
@@ -1014,6 +1014,7 @@ struct retro_core_option_v2_definition option_defs_us[] = {
{
{ "VMU", NULL },
{ "Purupuru", "Vibration Pack" },
{ "DreamPotato", NULL },
{ "None", NULL },
{ NULL, NULL },
},
@@ -1044,6 +1045,7 @@ struct retro_core_option_v2_definition option_defs_us[] = {
{
{ "VMU", NULL },
{ "Purupuru", "Vibration Pack" },
{ "DreamPotato", NULL },
{ "None", NULL },
{ NULL, NULL },
},
@@ -1074,6 +1076,7 @@ struct retro_core_option_v2_definition option_defs_us[] = {
{
{ "VMU", NULL },
{ "Purupuru", "Vibration Pack" },
{ "DreamPotato", NULL },
{ "None", NULL },
{ NULL, NULL },
},
@@ -1104,6 +1107,7 @@ struct retro_core_option_v2_definition option_defs_us[] = {
{
{ "VMU", NULL },
{ "Purupuru", "Vibration Pack" },
{ "DreamPotato", NULL },
{ "None", NULL },
{ NULL, NULL },
},
+15
View File
@@ -154,4 +154,19 @@ std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices {
Option<MapleDeviceType>("", MDT_None),
};
std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices{{
{{Option<int>("", 0),
Option<int>("", 0)}},
{{Option<int>("", 0),
Option<int>("", 0)}},
{{Option<int>("", 0),
Option<int>("", 0)}},
{{Option<int>("", 0),
Option<int>("", 0)}},
}};
Option<bool> UsePhysicalVmuMemory(CORE_OPTION_NAME "_linked_vmu_storage", false);
} // namespace config