Enable physical VMU memory access (#1881)
This commit is contained in:
parent
ea6d5f8732
commit
2631706fe2
|
@ -227,7 +227,7 @@ else()
|
|||
add_executable(${PROJECT_NAME} core/emulator.cpp)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
CMAKE_CXX_STANDARD 20
|
||||
CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
@ -481,6 +481,10 @@ if(NOT LIBRETRO)
|
|||
core/sdl/sdl_gamepad.h
|
||||
core/sdl/sdl_keyboard.h
|
||||
core/sdl/sdl_keyboard_mac.h
|
||||
core/sdl/dreamlink.cpp
|
||||
core/sdl/dreamlink.h
|
||||
core/sdl/dreampicoport.cpp
|
||||
core/sdl/dreampicoport.h
|
||||
core/sdl/dreamconn.cpp
|
||||
core/sdl/dreamconn.h)
|
||||
|
||||
|
@ -523,7 +527,7 @@ if(NOT WITH_SYSTEM_ZLIB)
|
|||
# help libzip find the package
|
||||
set(ZLIB_FOUND TRUE)
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${ZLIB_RELATIVE_PATH}" "${CMAKE_CURRENT_BINARY_DIR}/${ZLIB_RELATIVE_PATH}")
|
||||
|
||||
|
||||
cmake_policy(SET CMP0026 OLD)
|
||||
get_target_property(ZLIB_LIBRARY_RELEASE zlibstatic LOCATION)
|
||||
get_target_property(ZLIB_LIBRARY_DEBUG zlibstatic LOCATION_Debug)
|
||||
|
|
|
@ -203,6 +203,7 @@ Option<bool> PerGameVmu("PerGameVmu", false, "config");
|
|||
#ifdef _WIN32
|
||||
Option<bool, false> UseRawInput("RawInput", false, "input");
|
||||
#endif
|
||||
Option<bool> UsePhysicalVmuMemory("UsePhysicalVmuMemory", false);
|
||||
|
||||
#ifdef USE_LUA
|
||||
Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
|
||||
|
|
|
@ -543,6 +543,7 @@ extern Option<bool, false> UseRawInput;
|
|||
#else
|
||||
constexpr bool UseRawInput = false;
|
||||
#endif
|
||||
extern Option<bool> UsePhysicalVmuMemory;
|
||||
|
||||
#ifdef USE_LUA
|
||||
extern Option<std::string, false> LuaFileName;
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <zlib.h>
|
||||
#include <cerrno>
|
||||
#include <ctime>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
const char* maple_sega_controller_name = "Dreamcast Controller";
|
||||
const char* maple_sega_vmu_name = "Visual Memory";
|
||||
|
@ -369,7 +371,7 @@ struct maple_sega_vmu: maple_base
|
|||
fullSaveNeeded = true;
|
||||
}
|
||||
|
||||
bool fullSave()
|
||||
virtual bool fullSave()
|
||||
{
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
@ -2107,29 +2109,215 @@ std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
|
|||
}
|
||||
|
||||
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP) && defined(USE_SDL) && !defined(LIBRETRO)
|
||||
#include "sdl/dreamconn.h"
|
||||
#include "sdl/dreamlink.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
struct DreamConnVmu : public maple_sega_vmu
|
||||
struct DreamLinkVmu : public maple_sega_vmu
|
||||
{
|
||||
std::shared_ptr<DreamConn> dreamconn;
|
||||
bool running = true;
|
||||
|
||||
DreamConnVmu(std::shared_ptr<DreamConn> dreamconn) : dreamconn(dreamconn) {
|
||||
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();})
|
||||
{
|
||||
// Initialize useRealVmuMemory with our config setting
|
||||
useRealVmuMemory = config::UsePhysicalVmuMemory;
|
||||
}
|
||||
|
||||
virtual ~DreamLinkVmu() {
|
||||
running = false;
|
||||
|
||||
// Entering lock context
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(writeMutex);
|
||||
writeCv.notify_all();
|
||||
}
|
||||
|
||||
writeThread.join();
|
||||
}
|
||||
|
||||
void OnSetup() override
|
||||
{
|
||||
// Update useRealVmuMemory in case config changed
|
||||
useRealVmuMemory = config::UsePhysicalVmuMemory;
|
||||
|
||||
// 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));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
maple_sega_vmu::OnSetup();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return maple_sega_vmu::fullSave();
|
||||
}
|
||||
}
|
||||
|
||||
u32 dma(u32 cmd) override
|
||||
{
|
||||
// Physical VMU logic
|
||||
if (dma_count_in >= 4)
|
||||
{
|
||||
const u32 functionId = *(u32 *)dma_buffer_in;
|
||||
if ((cmd == MDCF_BlockWrite && functionId == MFID_2_LCD) // LCD screen
|
||||
|| (cmd == MDCF_SetCondition && functionId == MFID_3_Clock)) // Buzzer
|
||||
const u32 functionId = *(u32*)dma_buffer_in;
|
||||
const MapleMsg* msg = reinterpret_cast<const MapleMsg*>(dma_buffer_in - 4);
|
||||
|
||||
if (functionId == MFID_1_Storage)
|
||||
{
|
||||
const MapleMsg *msg = reinterpret_cast<const MapleMsg*>(dma_buffer_in - 4);
|
||||
dreamconn->send(*msg);
|
||||
if (useRealVmuMemory)
|
||||
{
|
||||
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("ATTENTION: Loading from a physical VMU", 6000,
|
||||
"Game data is being loaded from your physical VMU");
|
||||
lastNotifyTime = currentTime;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (functionId == MFID_2_LCD)
|
||||
{
|
||||
if (cmd == MDCF_BlockWrite)
|
||||
{
|
||||
dreamlink->send(*msg);
|
||||
}
|
||||
}
|
||||
else if (functionId == MFID_3_Clock)
|
||||
{
|
||||
if (cmd == MDCF_SetCondition)
|
||||
{
|
||||
dreamlink->send(*msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If made it here, call base's dma to handle return value
|
||||
return maple_sega_vmu::dma(cmd);
|
||||
}
|
||||
|
||||
|
@ -2143,10 +2331,14 @@ struct DreamConnVmu : public maple_sega_vmu
|
|||
|
||||
void copyOut(std::shared_ptr<maple_sega_vmu> other)
|
||||
{
|
||||
memcpy(other->flash_data, flash_data, sizeof(other->flash_data));
|
||||
memcpy(other->lcd_data, lcd_data, sizeof(other->lcd_data));
|
||||
memcpy(other->lcd_data_decoded, lcd_data_decoded, sizeof(other->lcd_data_decoded));
|
||||
other->fullSaveNeeded = fullSaveNeeded;
|
||||
// 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()
|
||||
|
@ -2156,130 +2348,301 @@ struct DreamConnVmu : public maple_sega_vmu
|
|||
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#
|
||||
*(u32*)&msg.data[0] = MFID_2_LCD;
|
||||
*(u32*)&msg.data[4] = 0; // PT, phase, block#
|
||||
memcpy(&msg.data[8], lcd_data, sizeof(lcd_data));
|
||||
dreamconn->send(msg);
|
||||
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("ATTENTION: You are saving to a physical VMU", 6000,
|
||||
"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("ATTENTION: Write to VMU failed", 6000);
|
||||
lastErrorNotifyTime = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DreamConnPurupuru : public maple_sega_purupuru
|
||||
{
|
||||
std::shared_ptr<DreamConn> dreamconn;
|
||||
u64 DreamLinkVmu::lastNotifyTime = 0;
|
||||
u64 DreamLinkVmu::lastErrorNotifyTime = 0;
|
||||
|
||||
DreamConnPurupuru(std::shared_ptr<DreamConn> dreamconn) : dreamconn(dreamconn) {
|
||||
struct DreamLinkPurupuru : public maple_sega_purupuru
|
||||
{
|
||||
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);
|
||||
dreamconn->send(*msg);
|
||||
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<DreamConnVmu>> dreamConnVmus;
|
||||
static std::list<std::shared_ptr<DreamConnPurupuru>> dreamConnPurupurus;
|
||||
static std::list<std::shared_ptr<DreamLinkVmu>> dreamLinkVmus[2];
|
||||
static std::list<std::shared_ptr<DreamLinkPurupuru>> dreamLinkPurupurus;
|
||||
|
||||
void createDreamConnDevices(std::shared_ptr<DreamConn> dreamconn, bool gameStart)
|
||||
static void disablePhysicalVmuMemoryOption()
|
||||
{
|
||||
const int bus = dreamconn->getBus();
|
||||
|
||||
bool vmuFound = false;
|
||||
bool rumbleFound = false;
|
||||
|
||||
if (dreamconn->hasVmu())
|
||||
// Make setting read only
|
||||
config::UsePhysicalVmuMemory.override(config::UsePhysicalVmuMemory);
|
||||
}
|
||||
|
||||
static void enablePhysicalVmuMemoryOption()
|
||||
{
|
||||
// Remove read-only setting and preserve current value
|
||||
bool val = config::UsePhysicalVmuMemory;
|
||||
config::UsePhysicalVmuMemory.reset();
|
||||
config::UsePhysicalVmuMemory.set(val);
|
||||
}
|
||||
|
||||
void createDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink, bool gameStart, bool gameEnd)
|
||||
{
|
||||
const int bus = dreamlink->getBus();
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::shared_ptr<DreamConnVmu> vmu;
|
||||
for (const std::shared_ptr<DreamConnVmu>& vmuIter : dreamConnVmus)
|
||||
std::shared_ptr<maple_device> dev = MapleDevices[bus][i];
|
||||
|
||||
if ((dreamlink->getFunctionCode(i + 1) & MFID_1_Storage) || (dev != nullptr && dev->get_device_type() == MDT_SegaVMU))
|
||||
{
|
||||
if (vmuIter->dreamconn.get() == dreamconn.get())
|
||||
bool vmuFound = false;
|
||||
std::shared_ptr<DreamLinkVmu> vmu;
|
||||
for (const std::shared_ptr<DreamLinkVmu>& vmuIter : dreamLinkVmus[i])
|
||||
{
|
||||
vmuFound = true;
|
||||
vmu = vmuIter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<maple_device> dev = MapleDevices[bus][0];
|
||||
if (gameStart || (dev != nullptr && dev->get_device_type() == MDT_SegaVMU))
|
||||
{
|
||||
bool vmuCreated = false;
|
||||
if (!vmu)
|
||||
{
|
||||
vmu = std::make_shared<DreamConnVmu>(dreamconn);
|
||||
vmuCreated = true;
|
||||
}
|
||||
|
||||
vmu->Setup(bus, 0);
|
||||
|
||||
if ((!gameStart || !vmuCreated) && dev) {
|
||||
// if loading a state or DreamConnVmu existed, copy data from the regular vmu and send a screen update
|
||||
vmu->copyIn(std::static_pointer_cast<maple_sega_vmu>(dev));
|
||||
if (!gameStart) {
|
||||
vmu->updateScreen();
|
||||
if (vmuIter->dreamlink.get() == dreamlink.get())
|
||||
{
|
||||
vmuFound = true;
|
||||
vmu = vmuIter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vmuFound) dreamConnVmus.push_back(vmu);
|
||||
}
|
||||
}
|
||||
if (dreamconn->hasRumble())
|
||||
{
|
||||
std::shared_ptr<DreamConnPurupuru> rumble;
|
||||
for (const std::shared_ptr<DreamConnPurupuru>& purupuru : dreamConnPurupurus)
|
||||
{
|
||||
if (purupuru->dreamconn.get() == dreamconn.get())
|
||||
if (gameStart || !vmuFound)
|
||||
{
|
||||
rumbleFound = true;
|
||||
rumble = purupuru;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!vmu)
|
||||
{
|
||||
vmu = std::make_shared<DreamLinkVmu>(dreamlink);
|
||||
}
|
||||
|
||||
std::shared_ptr<maple_device> dev = MapleDevices[bus][1];
|
||||
if (gameStart || (dev != nullptr && dev->get_device_type() == MDT_PurupuruPack))
|
||||
{
|
||||
if (!rumble)
|
||||
{
|
||||
rumble = std::make_shared<DreamConnPurupuru>(dreamconn);
|
||||
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)
|
||||
{
|
||||
disablePhysicalVmuMemoryOption();
|
||||
}
|
||||
else if (gameEnd)
|
||||
{
|
||||
enablePhysicalVmuMemoryOption();
|
||||
}
|
||||
}
|
||||
else if (i == 1 && ((dreamlink->getFunctionCode(i + 1) & MFID_8_Vibration) || (dev != nullptr && dev->get_device_type() == MDT_PurupuruPack)))
|
||||
{
|
||||
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 || !rumbleFound)
|
||||
{
|
||||
if (!rumble)
|
||||
{
|
||||
rumble = std::make_shared<DreamLinkPurupuru>(dreamlink);
|
||||
}
|
||||
rumble->Setup(bus, i);
|
||||
|
||||
if (!rumbleFound) dreamLinkPurupurus.push_back(rumble);
|
||||
}
|
||||
rumble->Setup(bus, 1);
|
||||
|
||||
if (!rumbleFound) dreamConnPurupurus.push_back(rumble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tearDownDreamConnDevices(std::shared_ptr<DreamConn> dreamconn)
|
||||
void tearDownDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink)
|
||||
{
|
||||
const int bus = dreamconn->getBus();
|
||||
for (std::list<std::shared_ptr<DreamConnVmu>>::const_iterator iter = dreamConnVmus.begin();
|
||||
iter != dreamConnVmus.end();)
|
||||
const int bus = dreamlink->getBus();
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
if ((*iter)->dreamconn.get() == dreamconn.get())
|
||||
for (std::list<std::shared_ptr<DreamLinkVmu>>::const_iterator iter = dreamLinkVmus[i].begin();
|
||||
iter != dreamLinkVmus[i].end();)
|
||||
{
|
||||
std::shared_ptr<maple_device> dev = maple_Create(MDT_SegaVMU);
|
||||
dev->Setup(bus, 0);
|
||||
(*iter)->copyOut(std::static_pointer_cast<maple_sega_vmu>(dev));
|
||||
iter = dreamConnVmus.erase(iter);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
if ((*iter)->dreamlink.get() == dreamlink.get())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::list<std::shared_ptr<DreamConnPurupuru>>::const_iterator iter = dreamConnPurupurus.begin();
|
||||
iter != dreamConnPurupurus.end();)
|
||||
|
||||
std::size_t dreamLinkVmuCount = 0;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
if ((*iter)->dreamconn.get() == dreamconn.get())
|
||||
dreamLinkVmuCount += dreamLinkVmus[i].size();
|
||||
}
|
||||
|
||||
if (dreamLinkVmuCount == 0)
|
||||
{
|
||||
enablePhysicalVmuMemoryOption();
|
||||
}
|
||||
|
||||
for (std::list<std::shared_ptr<DreamLinkPurupuru>>::const_iterator iter = dreamLinkPurupurus.begin();
|
||||
iter != dreamLinkPurupurus.end();)
|
||||
{
|
||||
if ((*iter)->dreamlink.get() == dreamlink.get())
|
||||
{
|
||||
std::shared_ptr<maple_device> dev = maple_Create(MDT_PurupuruPack);
|
||||
dev->Setup(bus, 1);
|
||||
iter = dreamConnPurupurus.erase(iter);
|
||||
iter = dreamLinkPurupurus.erase(iter);
|
||||
break;
|
||||
}
|
||||
else
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,95 +17,68 @@
|
|||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include "emulator.h"
|
||||
#include "sdl_gamepad.h"
|
||||
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP)
|
||||
#define USE_DREAMCASTCONTROLLER 1
|
||||
#define TYPE_DREAMCONN 1
|
||||
#define TYPE_DREAMPICOPORT 2
|
||||
#include <asio.hpp>
|
||||
#endif
|
||||
#include <memory>
|
||||
#include "dreamlink.h"
|
||||
|
||||
struct MapleMsg
|
||||
{
|
||||
u8 command;
|
||||
u8 destAP;
|
||||
u8 originAP;
|
||||
u8 size;
|
||||
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;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(MapleMsg) == 1028);
|
||||
|
||||
class DreamConn
|
||||
{
|
||||
int bus = -1;
|
||||
const int dreamcastControllerType;
|
||||
#ifdef USE_DREAMCASTCONTROLLER
|
||||
std::unique_ptr<class DreamcastControllerConnection> dcConnection;
|
||||
#endif
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <mutex>
|
||||
|
||||
class DreamConn : public DreamLink
|
||||
{
|
||||
//! Base port of communication to DreamConn
|
||||
static constexpr u16 BASE_PORT = 37393;
|
||||
|
||||
int bus = -1;
|
||||
bool maple_io_connected = false;
|
||||
u8 expansionDevs = 0;
|
||||
asio::ip::tcp::iostream iostream;
|
||||
std::mutex send_mutex;
|
||||
|
||||
public:
|
||||
DreamConn(int bus, int dreamcastControllerType, int joystick_idx, SDL_Joystick* sdl_joystick);
|
||||
//! DreamConn VID:4457 PID:4443
|
||||
static constexpr const char* VID_PID_GUID = "5744000043440000";
|
||||
|
||||
public:
|
||||
DreamConn(int bus);
|
||||
|
||||
~DreamConn();
|
||||
|
||||
bool send(const MapleMsg& msg);
|
||||
bool send(const MapleMsg& msg) override;
|
||||
|
||||
// When called, do teardown stuff like reset screen
|
||||
void gameTermination();
|
||||
bool send(const MapleMsg& txMsg, MapleMsg& rxMsg) override;
|
||||
|
||||
int getBus() const {
|
||||
int getBus() const override {
|
||||
return bus;
|
||||
}
|
||||
bool hasVmu() {
|
||||
|
||||
u32 getFunctionCode(int forPort) const override {
|
||||
if (forPort == 1 && hasVmu()) {
|
||||
return 0x0E000000;
|
||||
}
|
||||
else if (forPort == 2 && hasRumble()) {
|
||||
return 0x00010000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool hasVmu() const {
|
||||
return expansionDevs & 1;
|
||||
}
|
||||
bool hasRumble() {
|
||||
|
||||
bool hasRumble() const {
|
||||
return expansionDevs & 2;
|
||||
}
|
||||
|
||||
int getDefaultBus();
|
||||
void changeBus(int newBus) override;
|
||||
|
||||
void changeBus(int newBus);
|
||||
std::string getName() const override {
|
||||
return "DreamConn+ / DreamConn S Controller";
|
||||
}
|
||||
|
||||
std::string getName();
|
||||
void connect() override;
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
void disconnect() override;
|
||||
};
|
||||
|
||||
class DreamConnGamepad : public SDLGamepad
|
||||
{
|
||||
public:
|
||||
DreamConnGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
|
||||
~DreamConnGamepad();
|
||||
|
||||
void set_maple_port(int port) override;
|
||||
void registered() override;
|
||||
bool gamepad_btn_input(u32 code, bool pressed) override;
|
||||
bool gamepad_axis_input(u32 code, int value) override;
|
||||
static bool isDreamcastController(int deviceIndex);
|
||||
|
||||
private:
|
||||
static void handleEvent(Event event, void *arg);
|
||||
void checkKeyCombo();
|
||||
|
||||
std::shared_ptr<DreamConn> dreamconn;
|
||||
bool ltrigPressed = false;
|
||||
bool rtrigPressed = false;
|
||||
bool startPressed = false;
|
||||
};
|
||||
#endif // USE_DREAMCASTCONTROLLER
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
Copyright 2024 flyinghead
|
||||
|
||||
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 "dreamlink.h"
|
||||
|
||||
#ifdef USE_DREAMCASTCONTROLLER
|
||||
|
||||
#include "dreamconn.h"
|
||||
#include "dreampicoport.h"
|
||||
|
||||
#include "hw/maple/maple_devs.h"
|
||||
#include "ui/gui.h"
|
||||
#include <cfg/option.h>
|
||||
#include <SDL.h>
|
||||
#include <asio.hpp>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
#if defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <setupapi.h>
|
||||
#endif
|
||||
|
||||
void createDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink, bool gameStart, bool gameEnd);
|
||||
void tearDownDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink);
|
||||
|
||||
bool DreamLinkGamepad::isDreamcastController(int deviceIndex)
|
||||
{
|
||||
char guid_str[33] {};
|
||||
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(deviceIndex), guid_str, sizeof(guid_str));
|
||||
NOTICE_LOG(INPUT, "GUID: %s VID:%c%c%c%c PID:%c%c%c%c", guid_str,
|
||||
guid_str[10], guid_str[11], guid_str[8], guid_str[9],
|
||||
guid_str[18], guid_str[19], guid_str[16], guid_str[17]);
|
||||
|
||||
// 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!");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DreamLinkGamepad::DreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
|
||||
: SDLGamepad(maple_port, joystick_idx, sdl_joystick)
|
||||
{
|
||||
char guid_str[33] {};
|
||||
|
||||
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(joystick_idx), guid_str, sizeof(guid_str));
|
||||
|
||||
// DreamConn VID:4457 PID:4443
|
||||
// Dreamcast Controller USB VID:1209 PID:2f07
|
||||
if (memcmp(DreamConn::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
{
|
||||
dreamlink = std::make_shared<DreamConn>(maple_port);
|
||||
}
|
||||
else if (memcmp(DreamPicoPort::VID_PID_GUID, guid_str + 8, 16) == 0)
|
||||
{
|
||||
dreamlink = std::make_shared<DreamPicoPort>(maple_port, joystick_idx, sdl_joystick);
|
||||
}
|
||||
|
||||
if (dreamlink) {
|
||||
_name = dreamlink->getName();
|
||||
int defaultBus = dreamlink->getDefaultBus();
|
||||
if (defaultBus >= 0 && defaultBus < 4) {
|
||||
set_maple_port(defaultBus);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EventManager::listen(Event::Start, handleEvent, this);
|
||||
EventManager::listen(Event::LoadState, handleEvent, this);
|
||||
EventManager::listen(Event::Terminate, handleEvent, this);
|
||||
}
|
||||
|
||||
DreamLinkGamepad::~DreamLinkGamepad() {
|
||||
EventManager::unlisten(Event::Start, handleEvent, this);
|
||||
EventManager::unlisten(Event::LoadState, handleEvent, this);
|
||||
EventManager::unlisten(Event::Terminate, handleEvent, this);
|
||||
if (dreamlink) {
|
||||
tearDownDreamLinkDevices(dreamlink);
|
||||
dreamlink.reset();
|
||||
|
||||
// Make sure settings are open in case disconnection happened mid-game
|
||||
if (!gui_is_open()) {
|
||||
gui_open_settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DreamLinkGamepad::set_maple_port(int port)
|
||||
{
|
||||
if (dreamlink) {
|
||||
if (port < 0 || port >= 4) {
|
||||
dreamlink->disconnect();
|
||||
}
|
||||
else if (dreamlink->getBus() != port) {
|
||||
dreamlink->changeBus(port);
|
||||
if (is_registered()) {
|
||||
dreamlink->connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
SDLGamepad::set_maple_port(port);
|
||||
}
|
||||
|
||||
void DreamLinkGamepad::registered()
|
||||
{
|
||||
if (dreamlink)
|
||||
{
|
||||
dreamlink->connect();
|
||||
|
||||
// Create DreamLink Maple Devices here just in case game is already running
|
||||
createDreamLinkDevices(dreamlink, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void DreamLinkGamepad::handleEvent(Event event, void *arg)
|
||||
{
|
||||
DreamLinkGamepad *gamepad = static_cast<DreamLinkGamepad*>(arg);
|
||||
if (gamepad->dreamlink != nullptr && event != Event::Terminate) {
|
||||
createDreamLinkDevices(gamepad->dreamlink, event == Event::Start, event == Event::Terminate);
|
||||
}
|
||||
|
||||
if (gamepad->dreamlink != nullptr && event == Event::Terminate)
|
||||
{
|
||||
gamepad->dreamlink->gameTermination();
|
||||
}
|
||||
}
|
||||
|
||||
bool DreamLinkGamepad::gamepad_btn_input(u32 code, bool pressed)
|
||||
{
|
||||
if (!is_detecting_input() && input_mapper)
|
||||
{
|
||||
DreamcastKey key = input_mapper->get_button_id(0, code);
|
||||
if (key == DC_BTN_START) {
|
||||
startPressed = pressed;
|
||||
checkKeyCombo();
|
||||
}
|
||||
}
|
||||
else {
|
||||
startPressed = false;
|
||||
}
|
||||
return SDLGamepad::gamepad_btn_input(code, pressed);
|
||||
}
|
||||
|
||||
bool DreamLinkGamepad::gamepad_axis_input(u32 code, int value)
|
||||
{
|
||||
if (!is_detecting_input())
|
||||
{
|
||||
if (code == leftTrigger) {
|
||||
ltrigPressed = value > 0;
|
||||
checkKeyCombo();
|
||||
}
|
||||
else if (code == rightTrigger) {
|
||||
rtrigPressed = value > 0;
|
||||
checkKeyCombo();
|
||||
}
|
||||
}
|
||||
else {
|
||||
ltrigPressed = false;
|
||||
rtrigPressed = false;
|
||||
}
|
||||
return SDLGamepad::gamepad_axis_input(code, value);
|
||||
}
|
||||
|
||||
void DreamLinkGamepad::checkKeyCombo() {
|
||||
if (ltrigPressed && rtrigPressed && startPressed)
|
||||
gui_open_settings();
|
||||
}
|
||||
|
||||
#else // USE_DREAMCASTCONTROLLER
|
||||
|
||||
bool DreamLinkGamepad::isDreamcastController(int deviceIndex) {
|
||||
return false;
|
||||
}
|
||||
DreamLinkGamepad::DreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick)
|
||||
: SDLGamepad(maple_port, joystick_idx, sdl_joystick) {
|
||||
}
|
||||
DreamLinkGamepad::~DreamLinkGamepad() {
|
||||
}
|
||||
void DreamLinkGamepad::set_maple_port(int port) {
|
||||
SDLGamepad::set_maple_port(port);
|
||||
}
|
||||
void DreamLinkGamepad::registered() {
|
||||
}
|
||||
bool DreamLinkGamepad::gamepad_btn_input(u32 code, bool pressed) {
|
||||
return SDLGamepad::gamepad_btn_input(code, pressed);
|
||||
}
|
||||
bool DreamLinkGamepad::gamepad_axis_input(u32 code, int value) {
|
||||
return SDLGamepad::gamepad_axis_input(code, value);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
Copyright 2024 flyinghead
|
||||
|
||||
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
|
||||
|
||||
// This file contains abstraction layer for access to different kinds of physical controllers
|
||||
|
||||
#include "types.h"
|
||||
#include "emulator.h"
|
||||
#include "sdl_gamepad.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP)
|
||||
#define USE_DREAMCASTCONTROLLER 1
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
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 physical controller implementations
|
||||
class DreamLink
|
||||
{
|
||||
public:
|
||||
DreamLink() = default;
|
||||
|
||||
virtual ~DreamLink() = default;
|
||||
|
||||
//! 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 (1 or 2)
|
||||
//! @return the device type for the given port
|
||||
virtual u32 getFunctionCode(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;
|
||||
}
|
||||
|
||||
//! @return the selected bus number of the controller
|
||||
virtual int getBus() const = 0;
|
||||
|
||||
//! 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;
|
||||
|
||||
//! Attempt connection to the hardware controller
|
||||
virtual void connect() = 0;
|
||||
|
||||
//! Disconnect from the hardware controller
|
||||
virtual void disconnect() = 0;
|
||||
};
|
||||
|
||||
class DreamLinkGamepad : public SDLGamepad
|
||||
{
|
||||
public:
|
||||
DreamLinkGamepad(int maple_port, int joystick_idx, SDL_Joystick* sdl_joystick);
|
||||
~DreamLinkGamepad();
|
||||
|
||||
void set_maple_port(int port) override;
|
||||
void registered() override;
|
||||
bool gamepad_btn_input(u32 code, bool pressed) override;
|
||||
bool gamepad_axis_input(u32 code, int value) override;
|
||||
static bool isDreamcastController(int deviceIndex);
|
||||
|
||||
private:
|
||||
static void handleEvent(Event event, void *arg);
|
||||
void checkKeyCombo();
|
||||
|
||||
std::shared_ptr<DreamLink> dreamlink;
|
||||
bool ltrigPressed = false;
|
||||
bool rtrigPressed = false;
|
||||
bool startPressed = false;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2024 flyinghead
|
||||
|
||||
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 "dreamlink.h"
|
||||
|
||||
#ifdef USE_DREAMCASTCONTROLLER
|
||||
|
||||
#include <asio.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
// Forward declaration of underlying serial connection
|
||||
class DreamPicoPortSerialHandler;
|
||||
|
||||
//! See: https://github.com/OrangeFox86/DreamPicoPort
|
||||
class DreamPicoPort : public DreamLink
|
||||
{
|
||||
u8 expansionDevs = 0;
|
||||
|
||||
//! The one and only serial port
|
||||
static std::unique_ptr<DreamPicoPortSerialHandler> serial;
|
||||
//! Number of devices using the above serial
|
||||
static std::atomic<std::uint32_t> connected_dev_count;
|
||||
//! Current timeout in milliseconds
|
||||
std::chrono::milliseconds timeout_ms;
|
||||
//! The bus ID dictated by flycast
|
||||
int software_bus = -1;
|
||||
//! The bus index of the hardware connection which will differ from the software bus
|
||||
int hardware_bus = -1;
|
||||
//! true iff only a single devices was found when enumerating devices
|
||||
bool is_single_device = true;
|
||||
//! True when initial enumeration failed
|
||||
bool is_hardware_bus_implied = true;
|
||||
//! True once connection is established
|
||||
bool connection_established = false;
|
||||
//! The queried interface version
|
||||
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;
|
||||
|
||||
public:
|
||||
//! Dreamcast Controller USB VID:1209 PID:2f07
|
||||
static constexpr const std::uint16_t VID = 0x1209;
|
||||
static constexpr const std::uint16_t PID = 0x2f07;
|
||||
static constexpr const char* VID_PID_GUID = "09120000072f0000";
|
||||
|
||||
public:
|
||||
DreamPicoPort(int bus, int joystick_idx, SDL_Joystick* sdl_joystick);
|
||||
|
||||
virtual ~DreamPicoPort();
|
||||
|
||||
bool send(const MapleMsg& msg) override;
|
||||
|
||||
bool send(const MapleMsg& txMsg, MapleMsg& rxMsg) override;
|
||||
|
||||
void gameTermination() override;
|
||||
|
||||
int getBus() const override;
|
||||
|
||||
u32 getFunctionCode(int forPort) const override;
|
||||
|
||||
int getDefaultBus() const override;
|
||||
|
||||
void changeBus(int newBus);
|
||||
|
||||
std::string getName() const override;
|
||||
|
||||
void connect() override;
|
||||
|
||||
void disconnect() override;
|
||||
|
||||
void sendPort();
|
||||
|
||||
int hardwareBus() const;
|
||||
|
||||
bool isHardwareBusImplied() const;
|
||||
|
||||
bool isSingleDevice() const;
|
||||
|
||||
private:
|
||||
asio::error_code sendCmd(const std::string& cmd);
|
||||
asio::error_code sendMsg(const MapleMsg& msg);
|
||||
asio::error_code receiveCmd(std::string& cmd);
|
||||
asio::error_code receiveMsg(MapleMsg& msg);
|
||||
void determineHardwareBus(int joystick_idx, SDL_Joystick* sdl_joystick);
|
||||
bool queryInterfaceVersion();
|
||||
bool queryPeripherals();
|
||||
};
|
||||
|
||||
#endif // USE_DREAMCASTCONTROLLER
|
|
@ -30,7 +30,7 @@
|
|||
#include "nswitch.h"
|
||||
#include "switch_gamepad.h"
|
||||
#endif
|
||||
#include "dreamconn.h"
|
||||
#include "dreamlink.h"
|
||||
#include <unordered_map>
|
||||
|
||||
static SDL_Window* window = NULL;
|
||||
|
@ -83,8 +83,8 @@ static void sdl_open_joystick(int index)
|
|||
std::shared_ptr<SDLGamepad> gamepad = std::make_shared<SwitchGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
|
||||
#else
|
||||
std::shared_ptr<SDLGamepad> gamepad;
|
||||
if (DreamConnGamepad::isDreamcastController(index))
|
||||
gamepad = std::make_shared<DreamConnGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
|
||||
if (DreamLinkGamepad::isDreamcastController(index))
|
||||
gamepad = std::make_shared<DreamLinkGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
|
||||
else
|
||||
gamepad = std::make_shared<SDLGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
|
||||
#endif
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
#include "hw/pvr/Renderer_if.h"
|
||||
#if defined(USE_SDL)
|
||||
#include "sdl/sdl.h"
|
||||
#include "sdl/dreamlink.h"
|
||||
#endif
|
||||
|
||||
#include "vgamepad.h"
|
||||
|
@ -434,7 +435,7 @@ static void gui_newFrame()
|
|||
io.AddKeyEvent(ImGuiKey_GamepadDpadRight, ((kcode[0] & DC_DPAD_RIGHT) == 0));
|
||||
io.AddKeyEvent(ImGuiKey_GamepadDpadUp, ((kcode[0] & DC_DPAD_UP) == 0));
|
||||
io.AddKeyEvent(ImGuiKey_GamepadDpadDown, ((kcode[0] & DC_DPAD_DOWN) == 0));
|
||||
|
||||
|
||||
float analog;
|
||||
analog = joyx[0] < 0 ? -(float)joyx[0] / 32768.f : 0.f;
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, analog > 0.1f, analog);
|
||||
|
@ -820,8 +821,8 @@ const char *maple_device_types[] =
|
|||
// "Dreameye",
|
||||
};
|
||||
|
||||
const char *maple_expansion_device_types[] =
|
||||
{
|
||||
const char *maple_expansion_device_types[] =
|
||||
{
|
||||
"None",
|
||||
"Sega VMU",
|
||||
"Vibration Pack",
|
||||
|
@ -2032,6 +2033,10 @@ static void gui_settings_controls(bool& maple_devices_changed)
|
|||
#if defined(_WIN32) && !defined(TARGET_UWP)
|
||||
OptionCheckbox("Use Raw Input", config::UseRawInput, "Supports multiple pointing devices (mice, light guns) and keyboards");
|
||||
#endif
|
||||
#ifdef USE_DREAMCASTCONTROLLER
|
||||
OptionCheckbox("Use Physical VMU Memory", config::UsePhysicalVmuMemory,
|
||||
"Enables direct read/write access to physical VMU memory via DreamPicoPort/DreamConn.");
|
||||
#endif
|
||||
|
||||
ImGui::Spacing();
|
||||
header("Dreamcast Devices");
|
||||
|
@ -2848,7 +2853,6 @@ static void gui_settings_advanced()
|
|||
}
|
||||
OptionCheckbox("Dump Textures", config::DumpTextures,
|
||||
"Dump all textures into data/texdump/<game id>");
|
||||
|
||||
bool logToFile = cfgLoadBool("log", "LogToFile", false);
|
||||
if (ImGui::Checkbox("Log to File", &logToFile))
|
||||
cfgSaveBool("log", "LogToFile", logToFile);
|
||||
|
@ -3268,7 +3272,7 @@ static void gui_display_content()
|
|||
const int itemsPerLine = std::max<int>(totalWidth / (uiScaled(150) + ImGui::GetStyle().ItemSpacing.x), 1);
|
||||
const float responsiveBoxSize = totalWidth / itemsPerLine - ImGui::GetStyle().FramePadding.x * 2;
|
||||
const ImVec2 responsiveBoxVec2 = ImVec2(responsiveBoxSize, responsiveBoxSize);
|
||||
|
||||
|
||||
if (config::BoxartDisplayMode)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
|
||||
else
|
||||
|
@ -3737,7 +3741,7 @@ void gui_display_profiler()
|
|||
ImGui::Unindent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const fc_profiler::ProfileThread* profileThread : fc_profiler::ProfileThread::s_allThreads)
|
||||
{
|
||||
fc_profiler::drawGraph(*profileThread);
|
||||
|
|
Loading…
Reference in New Issue