mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-07 17:11:09 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eadfb388e | ||
|
|
83f6d818b5 | ||
|
|
bbae3de9a4 | ||
|
|
c2f66018d8 | ||
|
|
82ca2db61a | ||
|
|
b4d83c0d12 |
No files matched your search
+14
-8
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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."));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user