flycast/core/hw/maple/maple_devs.cpp

2641 lines
66 KiB
C++
Executable File

#include "maple_devs.h"
#include "maple_cfg.h"
#include "maple_helper.h"
#include "maple_if.h"
#include "hw/pvr/spg.h"
#include "audio/audiostream.h"
#include "oslib/oslib.h"
#include "oslib/storage.h"
#include "hw/aica/sgc_if.h"
#include "cfg/option.h"
#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";
const char* maple_sega_kbd_name = "Emulated Dreamcast Keyboard";
const char* maple_sega_mouse_name = "Emulated Dreamcast Mouse";
const char* maple_sega_dreameye_name_1 = "Dreamcast Camera Flash Device";
const char* maple_sega_dreameye_name_2 = "Dreamcast Camera Flash LDevice";
const char* maple_sega_mic_name = "MicDevice for Dreameye";
const char* maple_sega_purupuru_name = "Puru Puru Pack";
const char* maple_sega_lightgun_name = "Dreamcast Gun";
const char* maple_sega_twinstick_name = "Twin Stick";
const char* maple_ascii_stick_name = "ASCII STICK";
const char* maple_maracas_controller_name = "Maracas Controller";
const char* maple_fishing_controller_name = "Dreamcast Fishing Controller";
const char* maple_popnmusic_controller_name = "pop'n music controller";
const char* maple_racing_controller_name = "Racing Controller";
const char* maple_densha_controller_name = "TAITO 001 Controller";
const char* maple_sega_brand = "Produced By or Under License From SEGA ENTERPRISES,LTD.";
//fill in the info
void maple_device::Setup(u32 bus, u32 port, int playerNum)
{
maple_port = (bus << 6) | (1 << port);
bus_port = port;
bus_id = bus;
logical_port[0] = 'A' + bus_id;
logical_port[1] = bus_port == 5 ? 'x' : '1' + bus_port;
logical_port[2] = 0;
player_num = playerNum == -1 ? bus_id : playerNum;
config = new MapleConfigMap(this);
OnSetup();
MapleDevices[bus][port] = shared_from_this();
}
maple_device::~maple_device()
{
delete config;
}
static inline void mutualExclusion(u32& keycode, u32 mask)
{
if ((keycode & mask) == 0)
keycode |= mask;
}
/*
Sega Dreamcast Controller
No error checking of any kind, but works just fine
*/
struct maple_sega_controller: maple_base
{
virtual u32 get_capabilities() {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xfe060f00; // 4 analog axes (0-3) X Y A B Start U D L R
}
virtual u16 getButtonState(const PlainJoystickState &pjs)
{
u32 kcode = pjs.kcode;
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
return kcode | 0xF901; // mask off DPad2, C, D and Z;
}
virtual u32 getAnalogAxis(int index, const PlainJoystickState &pjs)
{
if (index == 2 || index == 3)
{
// Limit the magnitude of the analog axes to 128
s8 xaxis = pjs.joy[PJAI_X1] - 128;
s8 yaxis = pjs.joy[PJAI_Y1] - 128;
limit_joystick_magnitude<128>(xaxis, yaxis);
if (index == 2)
return xaxis + 128;
else
return yaxis + 128;
}
else if (index == 0)
return pjs.trigger[PJTI_R]; // Right trigger
else if (index == 1)
return pjs.trigger[PJTI_L]; // Left trigger
else
return 0x80; // unused
}
MapleDeviceType get_device_type() override
{
return MDT_SegaController;
}
virtual const char *get_device_name()
{
return maple_sega_controller_name;
}
virtual const char *get_device_brand()
{
return maple_sega_brand;
}
virtual u32 get_device_current(int get_max_current)
{
return get_max_current ? 0x01F4 : 0x01AE; // Max. 50 mA, standby: 43 mA
}
u32 dma(u32 cmd) override
{
//printf("maple_sega_controller::dma Called 0x%X;Command %d\n", bus_id, cmd);
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
// Fixed Device Status
// (Device ID)
//caps
//4
w32(MFID_0_Input);
//struct data
//3*4
w32(get_capabilities());
w32(0);
w32(0);
//1 area code (Country specification)
w8(0xFF);
//1 direction (Connection method)
w8(0);
//30 (Model name)
wstr(get_device_name(), 30);
//60 (License)
wstr(get_device_brand(), 60);
//2 (Standby current consumption)
w16(get_device_current(0));
//2 (Maximum current consumption)
w16(get_device_current(1));
if (cmd == MDC_AllStatusReq)
{
const char *extra = "Version 1.010,1998/09/28,315-6211-AB ,Analog Module : The 4th Edition.5/8 +DF";
wptr(extra, strlen(extra));
return MDRS_DeviceStatusAll;
}
else {
return MDRS_DeviceStatus;
}
//controller condition
case MDCF_GetCondition:
{
PlainJoystickState pjs;
config->GetInput(&pjs);
//caps
//4
w32(MFID_0_Input);
//state data
//2 key code
w16(getButtonState(pjs));
// analog axes
for (int axis = 0; axis < 6; axis++)
w8(getAnalogAxis(axis, pjs));
}
return MDRS_DataTransfer;
case MDC_DeviceReset:
return MDRS_DeviceReply;
case MDC_DeviceKill:
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "maple_sega_controller: Unknown maple command %d", cmd);
return MDRE_UnknownCmd;
}
}
};
struct maple_atomiswave_controller: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xff663f00; // 6 analog axes, X Y L2/D2(?) A B C Start U D L R
}
u16 getButtonState(const PlainJoystickState &pjs) override
{
u32 kcode = pjs.kcode;
mutualExclusion(kcode, AWAVE_UP_KEY | AWAVE_DOWN_KEY);
mutualExclusion(kcode, AWAVE_LEFT_KEY | AWAVE_RIGHT_KEY);
return kcode | AWAVE_TRIGGER_KEY;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override {
if (index < 2 || index > 5)
return 0x80;
index -= 2;
return pjs.joy[index];
}
};
/*
Sega Twin Stick Controller
*/
struct maple_sega_twinstick: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xfefe0000; // no analog axes, X Y A B D Start U/D/L/R U2/D2/L2/R2
}
u16 getButtonState(const PlainJoystickState &pjs) override
{
u32 kcode = pjs.kcode;
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
mutualExclusion(kcode, DC_DPAD2_UP | DC_DPAD2_DOWN);
mutualExclusion(kcode, DC_DPAD2_LEFT | DC_DPAD2_RIGHT);
return kcode | 0x0101;
}
MapleDeviceType get_device_type() override {
return MDT_TwinStick;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override {
return 0x80;
}
const char *get_device_name() override {
return maple_sega_twinstick_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x012C : 0x00DC; // Max. 30 mA, standby: 22 mA
}
};
/*
Ascii Stick (Arcade/FT Stick)
*/
struct maple_ascii_stick: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xff070000; // no analog axes, X Y Z A B C Start U/D/L/R
}
u16 getButtonState(const PlainJoystickState &pjs) override
{
u32 kcode = pjs.kcode;
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
return kcode | 0xF800;
}
MapleDeviceType get_device_type() override {
return MDT_AsciiStick;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override {
return 0x80;
}
const char *get_device_name() override {
return maple_ascii_stick_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x0172 : 0x010E; // Max. 37 mA, standby: 27 mA
}
};
/*
Sega Dreamcast Visual Memory Unit
This is pretty much done (?)
*/
u8 vmu_default[] = {
0x78,0x9c,0xed,0xd2,0x31,0x4e,0x02,0x61,0x10,0x06,0xd0,0x8f,0x04,0x28,0x4c,0x2c,
0x28,0x2d,0x0c,0xa5,0x57,0xe0,0x16,0x56,0x16,0x76,0x14,0x1e,0xc4,0x03,0x50,0x98,
0x50,0x40,0x69,0xc1,0x51,0x28,0xbc,0x8e,0x8a,0x0a,0xeb,0xc2,0xcf,0x66,0x13,0x1a,
0x13,0xa9,0x30,0x24,0xe6,0xbd,0xc9,0x57,0xcc,0x4c,0x33,0xc5,0x2c,0xb3,0x48,0x6e,
0x67,0x01,0x00,0x00,0x00,0x00,0x00,0x4e,0xaf,0xdb,0xe4,0x7a,0xd2,0xcf,0x53,0x16,
0x6d,0x46,0x99,0xb6,0xc9,0x78,0x9e,0x3c,0x5f,0x9c,0xfb,0x3c,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x80,0x5f,0xd5,0x45,0xfd,0xef,0xaa,0xca,0x6b,0xde,0xf2,0x9e,0x55,
0x3e,0xf2,0x99,0xaf,0xac,0xb3,0x49,0x95,0xef,0xd4,0xa9,0x9a,0xdd,0xdd,0x0f,0x9d,
0x52,0xca,0xc3,0x91,0x7f,0xb9,0x9a,0x0f,0x6e,0x92,0xfb,0xee,0xa1,0x2f,0x6d,0x76,
0xe9,0x64,0x9b,0xcb,0xf4,0xf2,0x92,0x61,0x33,0x79,0xfc,0xeb,0xb7,0xe5,0x44,0xf6,
0x77,0x19,0x06,0xef,
};
struct maple_sega_vmu: maple_base
{
FILE *file = nullptr;
u8 flash_data[128_KB];
u8 lcd_data[192];
u8 lcd_data_decoded[48*32];
bool fullSaveNeeded = false;
MapleDeviceType get_device_type() override
{
return MDT_SegaVMU;
}
void serialize(Serializer& ser) const override
{
maple_base::serialize(ser);
ser << flash_data;
ser << lcd_data;
ser << lcd_data_decoded;
}
void deserialize(Deserializer& deser) override
{
maple_base::deserialize(deser);
deser >> flash_data;
deser >> lcd_data;
deser >> lcd_data_decoded;
for (u8 b : lcd_data)
if (b != 0)
{
config->SetImage(lcd_data_decoded);
break;
}
fullSaveNeeded = true;
}
virtual bool fullSave()
{
if (file == nullptr)
return false;
if (std::fseek(file, 0, SEEK_SET) != 0) {
ERROR_LOG(MAPLE, "VMU %s: I/O error", logical_port);
return false;
}
if (std::fwrite(flash_data, sizeof(flash_data), 1, file) != 1) {
ERROR_LOG(MAPLE, "Failed to write the VMU %s to disk", logical_port);
return false;
}
fullSaveNeeded = false;
return true;
}
void initializeVmu()
{
INFO_LOG(MAPLE, "Initialising empty VMU %s...", logical_port);
uLongf dec_sz = sizeof(flash_data);
int rv = uncompress(flash_data, &dec_sz, vmu_default, sizeof(vmu_default));
verify(rv == Z_OK);
verify(dec_sz == sizeof(flash_data));
fullSave();
}
void OnSetup() override
{
memset(flash_data, 0, sizeof(flash_data));
memset(lcd_data, 0, sizeof(lcd_data));
// Load existing vmu file if found
std::string rpath = hostfs::getVmuPath(logical_port, false);
// this might be a storage url
FILE *rfile = hostfs::storage().openFile(rpath, "rb");
if (rfile == nullptr) {
INFO_LOG(MAPLE, "Unable to open VMU file \"%s\", creating new file", rpath.c_str());
}
else
{
if (std::fread(flash_data, sizeof(flash_data), 1, rfile) != 1)
WARN_LOG(MAPLE, "Failed to read the VMU file \"%s\" from disk", rpath.c_str());
std::fclose(rfile);
}
// Open or create the vmu file to save to
std::string wpath = hostfs::getVmuPath(logical_port, true);
file = nowide::fopen(wpath.c_str(), "rb+");
if (file == nullptr)
{
file = nowide::fopen(wpath.c_str(), "wb+");
if (file == nullptr) {
ERROR_LOG(MAPLE, "Failed to create VMU save file \"%s\"", wpath.c_str());
}
else if (rfile != nullptr)
{
// VMU file is being renamed so save it fully now
// and delete the old file
if (fullSave())
nowide::remove(rpath.c_str());
}
}
u8 sum = 0;
for (u32 i = 0; i < sizeof(flash_data); i++)
sum |= flash_data[i];
if (sum == 0)
// This means the existing VMU file is completely empty and needs to be recreated
initializeVmu();
fullSaveNeeded = false;
}
~maple_sega_vmu() override
{
if (file != nullptr)
std::fclose(file);
}
u32 dma(u32 cmd) override
{
//printf("maple_sega_vmu::dma Called for port %d:%d, Command %d\n", bus_id, bus_port, cmd);
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
//caps
//4
w32(MFID_1_Storage | MFID_2_LCD | MFID_3_Clock);
//struct data
//3*4
w32( 0x403f7e7e); // for clock
w32( 0x00100500); // for LCD
w32( 0x00410f00); // for storage
//1 area code
w8(0xFF);
//1 direction
w8(0);
//30
wstr(maple_sega_vmu_name,30);
//60
wstr(maple_sega_brand,60);
//2
w16(0x007c); // 12.4 mA
//2
w16(0x0082); // 13 mA
if (cmd == MDC_AllStatusReq)
{
const char *extra = "Version 1.005,1999/04/15,315-6208-03,SEGA Visual Memory System BIOS Produced by ";
wptr(extra, strlen(extra));
return MDRS_DeviceStatusAll;
}
else {
return MDRS_DeviceStatus;
}
//in[0] is function used
//out[0] is function used
case MDCF_GetMediaInfo:
{
u32 function=r32();
switch(function)
{
case MFID_1_Storage:
DEBUG_LOG(MAPLE, "VMU %s GetMediaInfo storage", logical_port);
w32(MFID_1_Storage);
if (*(u16*)&flash_data[0xFF * 512 + 0x40] != 0xFF)
{
// Unformatted state: return predetermined media information
//total_size;
w16(0xff);
//partition_number;
w16(0);
//system_area_block;
w16(0xFF);
//fat_area_block;
w16(0xfe);
//number_fat_areas_block;
w16(1);
//file_info_block;
w16(0xfd);
//number_info_blocks;
w16(0xd);
//volume_icon;
w8(0);
//reserved1;
w8(0);
//save_area_block;
w16(0xc8);
//number_of_save_blocks;
w16(0x1f);
//reserverd0 (something for execution files?)
w32(0);
}
else
{
// Get data from the vmu system area (block 0xFF)
wptr(flash_data + 0xFF * 512 + 0x40, 24);
}
return MDRS_DataTransfer;//data transfer
case MFID_2_LCD:
{
u32 pt=r32();
if (pt!=0)
{
INFO_LOG(MAPLE, "VMU: MDCF_GetMediaInfo -> bad input |%08X|, returning MDRE_UnknownCmd", pt);
return MDRE_UnknownCmd;
}
else
{
DEBUG_LOG(MAPLE, "VMU %s GetMediaInfo LCD", logical_port);
w32(MFID_2_LCD);
w8(47); //X dots -1
w8(31); //Y dots -1
w8(((1)<<4) | (0)); //1 Color, 0 contrast levels
w8(2); //Padding
return MDRS_DataTransfer;
}
}
default:
INFO_LOG(MAPLE, "VMU: MDCF_GetMediaInfo -> Bad function used |%08X|, returning -2", function);
return MDRE_UnknownFunction;//bad function
}
}
break;
case MDCF_BlockRead:
{
u32 function=r32();
switch(function)
{
case MFID_1_Storage:
{
w32(MFID_1_Storage);
u32 xo=r32();
u32 Block = (SWAP32(xo))&0xffff;
w32(xo);
if (Block>255)
{
DEBUG_LOG(MAPLE, "Block read : %d", Block);
DEBUG_LOG(MAPLE, "BLOCK READ ERROR");
Block&=255;
}
else
DEBUG_LOG(MAPLE, "VMU %s block read: Block %d addr %x len %d", logical_port, Block, Block*512, 512);
wptr(flash_data+Block*512,512);
}
return MDRS_DataTransfer;//data transfer
case MFID_2_LCD:
DEBUG_LOG(MAPLE, "VMU %s read LCD", logical_port);
w32(MFID_2_LCD);
w32(r32()); // mnn ?
wptr(flash_data,192);
return MDRS_DataTransfer;//data transfer
case MFID_3_Clock:
if (r32()!=0)
{
INFO_LOG(MAPLE, "VMU: Block read: MFID_3_Clock : invalid params");
return MDRE_TransmitAgain; //invalid params
}
else
{
w32(MFID_3_Clock);
time_t now;
time(&now);
tm* timenow=localtime(&now);
u8* timebuf=dma_buffer_out;
w8((timenow->tm_year+1900)%256);
w8((timenow->tm_year+1900)/256);
w8(timenow->tm_mon+1);
w8(timenow->tm_mday);
w8(timenow->tm_hour);
w8(timenow->tm_min);
w8(timenow->tm_sec);
w8(0);
DEBUG_LOG(MAPLE, "VMU: CLOCK Read-> datetime is %04d/%02d/%02d ~ %02d:%02d:%02d!",
timebuf[0] + timebuf[1] * 256,
timebuf[2],
timebuf[3],
timebuf[4],
timebuf[5],
timebuf[6]);
return MDRS_DataTransfer;//transfer reply ...
}
default:
INFO_LOG(MAPLE, "VMU: cmd MDCF_BlockRead -> Bad function |%08X| used, returning -2", function);
return MDRE_UnknownFunction;//bad function
}
}
break;
case MDCF_BlockWrite:
{
u32 function = r32();
switch (function)
{
case MFID_1_Storage:
{
u32 bph=r32();
u32 Block = (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);
if (file != nullptr)
{
if (fullSaveNeeded) {
if (!fullSave())
return MDRE_FileError;
}
else if (std::fseek(file, write_adr, SEEK_SET) != 0
|| std::fwrite(&flash_data[write_adr], write_len, 1, file) != 1)
{
ERROR_LOG(MAPLE, "Failed to save VMU %s: I/O error", logical_port);
return MDRE_FileError; // I/O error
}
}
else {
WARN_LOG(MAPLE, "Failed to save VMU %s data", logical_port);
}
return MDRS_DeviceReply;
}
case MFID_2_LCD:
{
DEBUG_LOG(MAPLE, "VMU %s LCD write", logical_port);
r32(); // PT, phase, block#
rptr(lcd_data,192);
u8 white=0xff,black=0x00;
for(int y=0;y<32;++y)
{
u8* dst=lcd_data_decoded+y*48;
u8* src=lcd_data+6*y+5;
for(int x=0;x<6;++x)
{
u8 col=*src--;
for(int l=0;l<8;l++)
{
*dst++=col&1?black:white;
col>>=1;
}
}
}
config->SetImage(lcd_data_decoded);
return MDRS_DeviceReply;
}
case MFID_3_Clock:
if (r32()!=0 || r_count()!=8)
{
INFO_LOG(MAPLE, "VMU %s clock write invalid params: rcount %d", logical_port, r_count());
return MDRE_TransmitAgain; //invalid params ...
}
else
{
u8 timebuf[8];
rptr(timebuf,8);
DEBUG_LOG(MAPLE, "VMU: CLOCK Write-> datetime is %04d/%02d/%02d ~ %02d:%02d:%02d! Nothing set tho ...",
timebuf[0]+timebuf[1]*256,timebuf[2],timebuf[3],timebuf[4],timebuf[5],timebuf[6]);
return MDRS_DeviceReply;//ok !
}
default:
INFO_LOG(MAPLE, "VMU: command MDCF_BlockWrite -> Unknown function %x", function);
return MDRE_UnknownFunction;//bad function
}
}
break;
case MDCF_GetLastError:
return MDRS_DeviceReply;//just ko
case MDCF_SetCondition:
{
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);
}
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "VMU: command MDCF_SetCondition -> Bad function used, returning MDRE_UnknownFunction");
return MDRE_UnknownFunction; //bad function
}
}
break;
case MDC_DeviceReset:
aica::sgc::vmuBeep(0, 0);
return MDRS_DeviceReply;
case MDC_DeviceKill:
aica::sgc::vmuBeep(0, 0);
return MDRS_DeviceReply;
default:
DEBUG_LOG(MAPLE, "Unknown MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
const void *getData(size_t& size) const override
{
size = sizeof(flash_data);
return flash_data;
}
};
struct maple_microphone: maple_base
{
u32 gain;
bool sampling;
bool eight_khz;
~maple_microphone() override
{
if (sampling)
StopAudioRecording();
}
MapleDeviceType get_device_type() override
{
return MDT_Microphone;
}
void serialize(Serializer& ser) const override
{
maple_base::serialize(ser);
ser << gain;
ser << sampling;
ser << eight_khz;
}
void deserialize(Deserializer& deser) override
{
if (sampling)
StopAudioRecording();
maple_base::deserialize(deser);
deser >> gain;
deser >> sampling;
deser >> eight_khz;
deser.skip(480 - sizeof(u32) - sizeof(bool) * 2, Deserializer::V23);
if (sampling)
StartAudioRecording(eight_khz);
}
void OnSetup() override
{
gain = 0xf;
sampling = false;
eight_khz = false;
}
u32 dma(u32 cmd) override
{
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
DEBUG_LOG(MAPLE, "maple_microphone::dma MDC_DeviceRequest");
//caps
//4
w32(MFID_4_Mic);
//struct data
//3*4
w32(0xf0000000);
w32(0);
w32(0);
//1 area code
w8(0xFF);
//1 direction
w8(0);
//30
wstr(maple_sega_mic_name, 30);
//60
wstr(maple_sega_brand, 60);
//2
w16(0x012C); // 30 mA
//2
w16(0x012C); // 30 mA
return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll;
case MDC_DeviceReset:
DEBUG_LOG(MAPLE, "maple_microphone::dma MDC_DeviceReset");
if (sampling)
StopAudioRecording();
OnSetup();
return MDRS_DeviceReply;
case MDCF_MICControl:
{
u32 function=r32();
switch(function)
{
case MFID_4_Mic:
{
u32 subcommand = r8();
u32 dt1 = r8();
u16 dt23 = r16();
switch(subcommand)
{
case 0x01: // Get_Sampling_Data
{
w32(MFID_4_Mic);
u8 micdata[240 * 2];
u32 samples = RecordAudio(micdata, 240);
//32 bit header
//status: bit 7 6 5 4 3 2 1 0
// name EX_BIT SBFOV 0 14LSB1 14LSB0 SMPL ulaw Fs
w8((sampling << 2) | eight_khz);
w8(gain); // gain
w8(0); //(unused)
w8(samples); // sample count (max 240)
wptr(micdata, ((samples + 1) >> 1) << 2);
return MDRS_DataTransfer;
}
case 0x02: // Basic_Control
DEBUG_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl Basic_Control DT1 %02x", dt1);
eight_khz = ((dt1 >> 2) & 3) == 1;
if (((dt1 & 0x80) == 0x80) != sampling)
{
if (sampling)
StopAudioRecording();
else
StartAudioRecording(eight_khz);
sampling = (dt1 & 0x80) == 0x80;
}
return MDRS_DeviceReply;
case 0x03: // AMP_GAIN
gain = dt1;
DEBUG_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl set gain %x", gain);
return MDRS_DeviceReply;
case 0x04: // EXTU_BIT
DEBUG_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl EXTU_BIT %#010x", dt1);
return MDRS_DeviceReply;
case 0x05: // Volume_Mode
DEBUG_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl Volume_Mode %#010x", dt1);
return MDRS_DeviceReply;
case MDRE_TransmitAgain:
WARN_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl MDRE_TransmitAgain");
//apparently this doesnt matter
//wptr(micdata, SIZE_OF_MIC_DATA);
return MDRS_DeviceReply;//MDRS_DataTransfer;
default:
INFO_LOG(MAPLE, "maple_microphone::dma UNHANDLED DT1 %02x DT23 %04x", dt1, dt23);
return MDRE_UnknownFunction;
}
}
default:
INFO_LOG(MAPLE, "maple_microphone::dma UNHANDLED function %#010x", function);
return MDRE_UnknownFunction;
}
break;
}
case MDC_DeviceKill:
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "maple_microphone::dma UNHANDLED MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
};
struct maple_sega_purupuru : maple_base
{
u16 AST = 19, AST_ms = 5000;
u32 VIBSET;
MapleDeviceType get_device_type() override
{
return MDT_PurupuruPack;
}
void serialize(Serializer& ser) const override
{
maple_base::serialize(ser);
ser << AST;
ser << AST_ms;
ser << VIBSET;
}
void deserialize(Deserializer& deser) override
{
maple_base::deserialize(deser);
deser >> AST;
deser >> AST_ms;
deser >> VIBSET;
}
u32 dma(u32 cmd) override
{
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
//caps
//4
w32(MFID_8_Vibration);
//struct data
//3*4
w32(0x00000101);
w32(0);
w32(0);
//1 area code
w8(0xFF);
//1 direction
w8(0);
//30
wstr(maple_sega_purupuru_name, 30);
//60
wstr(maple_sega_brand, 60);
//2
w16(0x00C8); // 20 mA
//2
w16(0x0640); // 160 mA
if (cmd == MDC_AllStatusReq)
{
const char *extra = "Version 1.000,1998/11/10,315-6211-AH ,Vibration Motor:1,Fm:4 - 30Hz,Pow:7 ";
wptr(extra, strlen(extra));
return MDRS_DeviceStatusAll;
}
else {
return MDRS_DeviceStatus;
}
//get last vibration
case MDCF_GetCondition:
w32(MFID_8_Vibration);
w32(VIBSET);
return MDRS_DataTransfer;
case MDCF_GetMediaInfo:
w32(MFID_8_Vibration);
// PuruPuru vib specs
w32(0x3B07E010);
return MDRS_DataTransfer;
case MDCF_BlockRead:
w32(MFID_8_Vibration);
w32(0);
w16(2);
w16(AST);
return MDRS_DataTransfer;
case MDCF_BlockWrite:
//Auto-stop time
AST = dma_buffer_in[10];
AST_ms = AST * 250 + 250;
return MDRS_DeviceReply;
case MDCF_SetCondition:
VIBSET = *(u32*)&dma_buffer_in[4];
{
//Do the rumble thing!
u8 POW_POS = (VIBSET >> 8) & 0x7;
u8 POW_NEG = (VIBSET >> 12) & 0x7;
u8 FREQ = (VIBSET >> 16) & 0xFF;
s16 INC = (VIBSET >> 24) & 0xFF;
if (VIBSET & 0x8000) // INH
INC = -INC;
else if (!(VIBSET & 0x0800)) // EXH
INC = 0;
bool CNT = VIBSET & 1;
float power = std::min((POW_POS + POW_NEG) / 7.0, 1.0);
u32 duration_ms;
if (FREQ > 0 && (!CNT || INC))
duration_ms = std::min((int)(1000 * (INC ? abs(INC) * std::max(POW_POS, POW_NEG) : 1) / FREQ), (int)AST_ms);
else
duration_ms = AST_ms;
float inclination;
if (INC == 0 || power == 0)
inclination = 0.0;
else
inclination = FREQ / (1000.0 * INC * std::max(POW_POS, POW_NEG));
config->SetVibration(power, inclination, duration_ms);
}
return MDRS_DeviceReply;
case MDC_DeviceReset:
return MDRS_DeviceReply;
case MDC_DeviceKill:
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "UNKOWN MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
};
struct maple_keyboard : maple_base
{
MapleDeviceType get_device_type() override
{
return MDT_Keyboard;
}
u32 dma(u32 cmd) override
{
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
//caps
//4
w32(MFID_6_Keyboard);
//struct data
//3*4
w8((u8)settings.input.keyboardLangId);
switch (settings.input.keyboardLangId)
{
case KeyboardLayout::JP:
w8(2); // 92 keys
break;
case KeyboardLayout::US:
w8(5); // 104 keys
break;
default:
w8(6); // 105 keys
break;
}
w8(0);
w8(0x80); // keyboard-controlled LEDs
w32(0);
w32(0);
//1 area code
w8(0xFF);
//1 direction
w8(0);
// Product name (30)
wstr(maple_sega_kbd_name, 30);
// License (60)
wstr(maple_sega_brand, 60);
// Low-consumption standby current (2)
w16(0x01AE); // 43 mA
// Maximum current consumption (2)
w16(0x01F5); // 50.1 mA
return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll;
case MDCF_GetCondition:
{
u8 shift;
u8 keys[6];
config->GetKeyboardInput(shift, keys);
w32(MFID_6_Keyboard);
//struct data
//int8 shift ; shift keys pressed (bitmask) //1
w8(shift);
//int8 led ; leds currently lit //1
w8(0);
//int8 key[6] ; normal keys pressed //6
for (std::size_t i = 0; i < std::size(keys); i++)
w8(keys[i]);
}
return MDRS_DataTransfer;
case MDC_DeviceReset:
return MDRS_DeviceReply;
case MDC_DeviceKill:
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "Keyboard: unknown MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
};
struct maple_mouse : maple_base
{
MapleDeviceType get_device_type() override
{
return MDT_Mouse;
}
static u16 mo_cvt(int delta)
{
return (u16)std::min(0x3FF, std::max(0, delta + 0x200));
}
u32 dma(u32 cmd) override
{
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
//caps
//4
w32(MFID_9_Mouse);
//struct data
//3*4
w32(0x00070E00); // Mouse, 3 buttons, 3 axes
w32(0);
w32(0);
//1 area code
w8(0xFF);
//1 direction
w8(0);
// Product name (30)
wstr(maple_sega_mouse_name, 30);
// License (60)
wstr(maple_sega_brand, 60);
// Low-consumption standby current (2)
w16(0x0190); // 40 mA
// Maximum current consumption (2)
w16(0x01f4); // 50 mA
return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll;
case MDCF_GetCondition:
{
u8 buttons;
int x, y, wheel;
config->GetMouseInput(buttons, x, y, wheel);
w32(MFID_9_Mouse);
// buttons (RLDUSABC, where A is left btn, B is right, and S is middle/scrollwheel)
w8(buttons);
// options
w8(0);
// axes overflow
w8(0);
// reserved
w8(0);
//int16 axis1 ; horizontal movement (0-$3FF) (little endian)
w16(mo_cvt(x));
//int16 axis2 ; vertical movement (0-$3FF) (little endian)
w16(mo_cvt(y));
//int16 axis3 ; mouse wheel movement (0-$3FF) (little endian)
w16(mo_cvt(wheel));
//int16 axis4 ; ? movement (0-$3FF) (little endian)
w16(mo_cvt(0));
//int16 axis5 ; ? movement (0-$3FF) (little endian)
w16(mo_cvt(0));
//int16 axis6 ; ? movement (0-$3FF) (little endian)
w16(mo_cvt(0));
//int16 axis7 ; ? movement (0-$3FF) (little endian)
w16(mo_cvt(0));
//int16 axis8 ; ? movement (0-$3FF) (little endian)
w16(mo_cvt(0));
}
return MDRS_DataTransfer;
case MDC_DeviceReset:
return MDRS_DeviceReply;
case MDC_DeviceKill:
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "Mouse: unknown MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
};
struct maple_lightgun : maple_base
{
virtual u32 transform_kcode(u32 kcode)
{
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
if ((kcode & DC_BTN_RELOAD) == 0)
kcode &= ~DC_BTN_A; // trigger
return kcode | 0xFF01;
}
MapleDeviceType get_device_type() override
{
return MDT_LightGun;
}
u32 dma(u32 cmd) override
{
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
//caps
//4
w32(MFID_7_LightGun | MFID_0_Input);
//struct data
//3*4
w32(0); // Light gun
w32(0xFE000000); // Controller
w32(0);
//1 area code
w8(1); // FF: Worldwide, 01: North America
//1 direction
w8(0);
// Product name (30)
wstr(maple_sega_lightgun_name, 30);
// License (60)
wstr(maple_sega_brand, 60);
// Low-consumption standby current (2)
w16(0x0069); // 10.5 mA
// Maximum current consumption (2)
w16(0x0120); // 28.8 mA
return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll;
case MDCF_GetCondition:
{
PlainJoystickState pjs;
config->GetInput(&pjs);
//caps
//4
w32(MFID_0_Input);
//state data
//2 key code
w16(transform_kcode(pjs.kcode));
//6 analog (not used)
w16(0);
w32(0x80808080);
}
return MDRS_DataTransfer;
case MDC_DeviceReset:
return MDRS_DeviceReply;
case MDC_DeviceKill:
return MDRS_DeviceReply;
default:
INFO_LOG(MAPLE, "Light gun: unknown MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
bool get_lightgun_pos() override
{
PlainJoystickState pjs;
config->GetInput(&pjs);
int x, y;
config->GetAbsCoordinates(x, y);
if ((pjs.kcode & DC_BTN_RELOAD) == 0)
read_lightgun_position(-1, -1);
else
read_lightgun_position(x, y);
return true;
}
};
struct atomiswave_lightgun : maple_lightgun
{
u32 transform_kcode(u32 kcode) override {
mutualExclusion(kcode, AWAVE_UP_KEY | AWAVE_DOWN_KEY);
mutualExclusion(kcode, AWAVE_LEFT_KEY | AWAVE_RIGHT_KEY);
// No need for reload on AW
return (kcode & AWAVE_TRIGGER_KEY) == 0 ? ~AWAVE_BTN0_KEY : ~0;
}
};
struct maple_maracas_controller: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0x0f093c00; // 4 analog axes (2-5) A B C D Z Start
}
u16 getButtonState(const PlainJoystickState &pjs) override {
return pjs.kcode | 0xf6f0; // mask off DPad2, X, Y, DPad;
}
MapleDeviceType get_device_type() override {
return MDT_MaracasController;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override {
if (index < 2 || index > 5)
return 0;
return pjs.joy[index -2];
/* // This should be tested with real maracas to see if it is worth implementing or not
u8 maracas_saturation_reduction = 2;
s32 axis_val = (pjs.joy[index -2] - 0x80) / maracas_saturation_reduction + 0x80;
if (axis_val < 0) axis_val = 0;
else if (axis_val > 0xff) axis_val = 0xFF;
return axis_val; */
}
const char *get_device_name() override {
return maple_maracas_controller_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x0546 : 0x044C; // Max. 130 mA, standby: 100 mA
}
};
struct maple_fishing_controller: maple_sega_controller
{
u32 analogToDPad = ~0;
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0x0fe063f00; // Ra,La,Da,Ua,A,B,X,Y,Start,A1,A2,A3,A4,A5,A6
}
u16 getButtonState(const PlainJoystickState &pjs) override
{
// Analog to DPad handling
if (pjs.joy[PJAI_X1] < 0x30) {
analogToDPad &= ~DC_DPAD_LEFT;
analogToDPad |= DC_DPAD_RIGHT;
}
else if (pjs.joy[PJAI_X1] > 0xd0) {
analogToDPad &= ~DC_DPAD_RIGHT;
analogToDPad |= DC_DPAD_LEFT;
}
else
{
if (pjs.joy[PJAI_X1] >= 0x40)
analogToDPad |= DC_DPAD_LEFT;
if (pjs.joy[PJAI_X1] <= 0xc0)
analogToDPad |= DC_DPAD_RIGHT;
}
if (pjs.joy[PJAI_Y1] < 0x30) {
analogToDPad &= ~DC_DPAD_UP;
analogToDPad |= DC_DPAD_DOWN;
}
else if (pjs.joy[PJAI_Y1] > 0xd0) {
analogToDPad &= ~DC_DPAD_DOWN;
analogToDPad |= DC_DPAD_UP;
}
else
{
if (pjs.joy[PJAI_Y1] >= 0x40)
analogToDPad |= DC_DPAD_UP;
if (pjs.joy[PJAI_Y1] <= 0xc0)
analogToDPad |= DC_DPAD_DOWN;
}
u32 kcode = pjs.kcode & analogToDPad;
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
return kcode | 0xf901; // mask off DPad2, D, Z, C;
}
MapleDeviceType get_device_type() override {
return MDT_FishingController;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override
{
// In the XYZ axes, acceleration sensor outputs 80 ± 8H (home position)
// in the static state (± 0G), F0h or greater for maximum force (+10G)
// in the positive direction and 11h or less
// for the maximum force (-10G) applied in the negative direction
// From the perspective of the player operating the controller:
// X: Right is positive, left is negative
// Y: Down is positive, up is negative
// Z: Forward is positive, backward is negative
switch (index)
{
case 0:
return pjs.trigger[PJTI_R]; // A1: Reel handle output
case 1:
return pjs.joy[PJAI_X3]; // A2: acceleration sensor Z
case 2:
return pjs.joy[PJAI_X1]; // A3: analog stick X
case 3:
return pjs.joy[PJAI_Y1]; // A4: analog stick Y
case 4:
return pjs.joy[PJAI_X2]; // A5: acceleration sensor X
case 5:
return pjs.joy[PJAI_Y2]; // A6: acceleration sensor Y
default:
return 0x80;
}
}
const char *get_device_name() override {
return maple_fishing_controller_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x0960 : 0x0258; // Max. 240 mA, standby: 60 mA
}
};
struct maple_popnmusic_controller: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xff060000; // no analog axes, X Y A B C Start U/D/L/R
}
u16 getButtonState(const PlainJoystickState &pjs) override {
return pjs.kcode | 0xf100; // mask off DPad2 and Z
}
MapleDeviceType get_device_type() override {
return MDT_PopnMusicController;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override {
if (index == 0 || index == 1)
return 0; // Right and left triggers
return 0x80;
}
const char *get_device_name() override {
return maple_popnmusic_controller_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x012C : 0x00AA; // Max. 30 mA, standby: 17 mA
}
};
struct maple_racing_controller: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xfe000700; // Steering only: Ra,La,Da,Ua,A,B,Start,A1,A2,A3
}
u16 getButtonState(const PlainJoystickState &pjs) override
{
// Ra, La are ON when A3 threshold values (La: 40h, Ra: BEh) are exceeded
u32 kcode = pjs.kcode;
if (pjs.joy[PJAI_X1] < 0x40)
kcode &= ~DC_DPAD_LEFT;
else if (pjs.joy[PJAI_X1] > 0xBE)
kcode &= ~DC_DPAD_RIGHT;
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
return kcode | 0xff01; // mask off DPad2, D, X, Y, Z, C
}
MapleDeviceType get_device_type() override {
return MDT_RacingController;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override
{
switch (index)
{
case 0: return pjs.trigger[PJTI_R]; // A1: lever, 0 at rest
case 1: return pjs.trigger[PJTI_L]; // A2: lever, 0 at rest
case 2: return pjs.joy[PJAI_X1]; // A3: 0-0xff, 0x80 at rest
default: return 0x80; // unused
}
}
const char *get_device_name() override {
return maple_racing_controller_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x0226 : 0x01B8; // Max. 55 mA, standby: 44 mA
}
};
struct maple_densha_controller: maple_sega_controller
{
u32 get_capabilities() override {
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xff0f3f00; // Ra,La,Da,Ua A,B,C,D,X,Y,Z,Start Xa,Ya,Xb,Yb Analog levers R,L
}
u16 getButtonState(const PlainJoystickState &pjs) override {
// Ra,La,Da,Ua are used together, corresponding to the brake lever.
return pjs.kcode | 0xF000; // mask off DPad2
}
MapleDeviceType get_device_type() override {
return MDT_DenshaDeGoController;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override {
if (index == 2 || index == 3)
return 0;
if (index == 0 || index == 1 || index == 4 || index == 5)
return 0xff;
return 0xff;
}
const char *get_device_name() override {
return maple_densha_controller_name;
}
u32 get_device_current(int get_max_current) override {
return get_max_current ? 0x01F4 : 0x00DC; // Max. 50 mA, standby: 22 mA
}
};
struct FullController : maple_sega_controller
{
u32 get_capabilities() override
{
// byte 0: 0 0 0 0 0 0 0 0
// byte 1: 0 0 a5 a4 a3 a2 a1 a0
// byte 2: R2 L2 D2 U2 D X Y Z
// byte 3: R L D U St A B C
return 0xffff3f00; // 6 axes, all buttons
}
u16 getButtonState(const PlainJoystickState &pjs) override
{
u32 kcode = pjs.kcode;
mutualExclusion(kcode, DC_DPAD_UP | DC_DPAD_DOWN);
mutualExclusion(kcode, DC_DPAD_LEFT | DC_DPAD_RIGHT);
mutualExclusion(kcode, DC_DPAD2_UP | DC_DPAD2_DOWN);
mutualExclusion(kcode, DC_DPAD2_LEFT | DC_DPAD2_RIGHT);
return kcode;
}
u32 getAnalogAxis(int index, const PlainJoystickState &pjs) override
{
if (index == 4 || index == 5)
{
// Limit the magnitude of the analog axes to 128
s8 xaxis = pjs.joy[PJAI_X2] - 128;
s8 yaxis = pjs.joy[PJAI_Y2] - 128;
limit_joystick_magnitude<128>(xaxis, yaxis);
if (index == 4)
return xaxis + 128;
else
return yaxis + 128;
}
return maple_sega_controller::getAnalogAxis(index, pjs);
}
const char *get_device_name() override {
return "Dreamcast Controller XL";
}
MapleDeviceType get_device_type() override {
return MDT_SegaControllerXL;
}
};
// Emulates a 838-14245-92 maple to RS232 converter
// wired to a 838-14243 RFID reader/writer (apparently Saxa HW210)
struct RFIDReaderWriter : maple_base
{
u32 getStatus() const
{
// b0: !card switch
// b1: state=4 errors?
// b2: state=5
// b3: state=6
// b4: state=7
// b5: state=8
// b6: card lock
// when 0x40 trying to read the card
u32 status = 1;
if (cardInserted)
status &= ~1;
if (cardLocked)
status |= 0x40;
return status;
}
// 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 command=buffer_in[0] &0xFF;
//Recipient address
u32 reci = (buffer_in[0] >> 8) & 0xFF;
//Sender address
u32 send = (buffer_in[0] >> 16) & 0xFF;
u32 outlen = 0;
u32 resp = Dma(command, &buffer_in[1], buffer_in_len - 4, &buffer_out[1], outlen);
if (reci & 0x20)
reci |= maple_GetAttachedDevices(bus_id);
verify(u8(outlen / 4) * 4 == outlen);
buffer_out[0] = (resp << 0 ) | (reci << 8) | (send << 16) | ((outlen / 4) << 24);
return outlen + 4;
}
u32 dma(u32 cmd) override
{
switch (cmd)
{
case MDC_DeviceRequest:
case MDC_AllStatusReq:
// custom function
w32(0x00100000);
// function flags
w32(0);
w32(0);
w32(0);
//1 area code
w8(0xff); // FF: Worldwide, 01: North America
//1 direction
w8(0);
// Product name (totally made up)
wstr("MAPLE/232C CONVERT BD", 30);
// License (60)
wstr(maple_sega_brand, 60);
// Low-consumption standby current (2)
w16(0x0069); // 10.5 mA
// Maximum current consumption (2)
w16(0x0120); // 28.8 mA
return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll;
case MDCF_GetCondition:
w32(0x00100000); // custom function
return MDRS_DataTransfer;
case MDC_DeviceReset:
case MDC_DeviceKill:
return MDRS_DeviceReply;
// 90 get status
//
// read test:
// d0 ?
// 91 get last cmd status?
// a0 ?
// 91
// a1 read md5 in data
// or data itself if after D4 xx xx xx xx
// d4 in=d2 03 aa db
// 91
//
// d9 lock
// da unlock
//
// write test:
// D0
// 91
// B1 05 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (28 bytes)
// 91
// B1 0b 06 00 00 00 00 c6 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (28 bytes)
// 91
// B1 11 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (28 bytes)
// 91
// B1 17 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (28 bytes)
// 91
// B1 1d 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (16 bytes)
// 91
// C1: 00 00 00 00
// 91
case 0xD0:
d4Seen = false;
[[fallthrough]];
case 0x90:
case 0x91:
case 0xA0:
case 0xD4:
case 0xC1:
w32(getStatus());
if (cmd == 0xd4)
d4Seen = true;
return (MapleDeviceRV)0xfe;
case 0xA1: // read card data
DEBUG_LOG(MAPLE, "RFID card read (data? %d)", d4Seen);
w32(getStatus());
if (!d4Seen)
// serial0 and serial1 only
wptr(cardData, 8);
else
wptr(cardData, sizeof(cardData));
return (MapleDeviceRV)0xfe;
case 0xD9: // lock card
cardLocked = true;
w32(getStatus());
INFO_LOG(MAPLE, "RFID card %d locked", player_num);
return (MapleDeviceRV)0xfe;
case 0xDA: // unlock card
cardLocked = false;
cardInserted = false;
w32(getStatus());
NOTICE_LOG(MAPLE, "RFID card %d unlocked", player_num);
os_notify("Card ejected", 2000);
return (MapleDeviceRV)0xfe;
case 0xB1: // write to card
{
w32(getStatus());
u32 offset = r8() * 4;
size_t size = r8() * 4;
skip(2);
DEBUG_LOG(MAPLE, "RFID card write: offset 0x%x len %d", offset, (int)size);
rptr(cardData + offset, std::min(size, sizeof(cardData) - offset));
saveCard();
return (MapleDeviceRV)0xfe;
}
case 0xD1: // decrement counter
{
int counter = r8();
switch (counter) {
case 0x03:
counter = 0;
break;
case 0x0c:
counter = 1;
break;
case 0x30:
counter = 2;
break;
case 0xc0:
counter = 3;
break;
default:
WARN_LOG(MAPLE, "Unknown counter selector %x", counter);
counter = 0;
break;
}
DEBUG_LOG(MAPLE, "RFID decrement %d", counter);
cardData[19 - counter]--;
saveCard();
w32(getStatus());
return (MapleDeviceRV)0xfe;
}
default:
INFO_LOG(MAPLE, "RFIDReaderWriter: unknown MAPLE COMMAND %d", cmd);
return MDRE_UnknownCmd;
}
}
MapleDeviceType get_device_type() override {
return MDT_RFIDReaderWriter;
}
void OnSetup() override
{
memset(cardData, 0, sizeof(cardData));
transientData = false;
}
std::string getCardPath() const
{
int playerNum;
if (config::GGPOEnable && !config::ActAsServer)
// Always load P1 card with GGPO to be consistent with P1 inputs being used
playerNum = 1;
else
playerNum = player_num + 1;
return hostfs::getArcadeFlashPath() + "-p" + std::to_string(playerNum) + ".card";
}
void loadCard()
{
if (transientData)
return;
std::string path = getCardPath();
FILE *fp = nowide::fopen(path.c_str(), "rb");
if (fp == nullptr)
{
if (settings.content.gameId.substr(0, 8) == "MKG TKOB")
{
constexpr u8 MUSHIKING_CHIP_DATA[128] = {
0x12, 0x34, 0x56, 0x78, // Serial No.0
0x31, 0x00, 0x86, 0x07, // Serial No.1
0x00, 0x00, 0x00, 0x00, // Key
0x04, 0xf6, 0x00, 0xAA, // Extend Extend Access Mode
0x23, 0xFF, 0xFF, 0xFF, // Counter4 Counter3 Counter2 Counter1
0x00, 0x00, 0x00, 0x00, // User Data (first set date: day bits 0-4, month bits 5-8, year bits 9-... + 2000)
0x00, 0x00, 0x00, 0x00, // User Data
0x00, 0x00, 0x00, 0x00, // User Data
0x00, 0x00, 0x00, 0x00, // User Data
0x00, 0x00, 0x00, 0x00, // User Data
0x00, 0x00, 0x00, 0x00, // User Data
0x23, 0xFF, 0xFF, 0xFF, // User Data (max counters)
};
memcpy(cardData, MUSHIKING_CHIP_DATA, sizeof(MUSHIKING_CHIP_DATA));
for (int i = 0; i < 8; i++)
cardData[i] = rand() & 0xff;
u32 mask = 0;
if (settings.content.gameId == "MKG TKOB 2 JPN VER2.001-" // mushik2e
|| settings.content.gameId == "MKG TKOB 4 JPN VER2.000-") // mushik4e
mask = 0x40;
cardData[4] &= ~0xc0;
cardData[4] |= mask;
u32 serial1 = (cardData[4] << 24) | (cardData[5] << 16) | (cardData[6] << 8) | cardData[7];
u32 key = ~serial1;
key = ((key >> 4) & 0x0f0f0f0f)
| ((key << 4) & 0xf0f0f0f0);
cardData[8] = key >> 24;
cardData[9] = key >> 16;
cardData[10] = key >> 8;
cardData[11] = key;
}
else
{
constexpr u8 VF4_CARD_DATA[128] = {
0x10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,0x6c, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0xff
};
memcpy(cardData, VF4_CARD_DATA, sizeof(VF4_CARD_DATA));
// Generate random bytes used by vf4 vanilla to make the card id
srand(time(0));
cardData[2] = rand() & 0xff;
cardData[4] = rand() & 0xff;
cardData[5] = rand() & 0xff;
cardData[6] = rand() & 0xff;
cardData[7] = rand() & 0xff;
}
INFO_LOG(NAOMI, "Card P%d initialized", player_num + 1);
}
else
{
INFO_LOG(NAOMI, "Loading card file from %s", path.c_str());
if (fread(cardData, 1, sizeof(cardData), fp) != sizeof(cardData))
WARN_LOG(NAOMI, "Truncated or empty card file: %s" ,path.c_str());
fclose(fp);
}
}
void saveCard() const
{
if (transientData)
return;
std::string path = getCardPath();
FILE *fp = nowide::fopen(path.c_str(), "wb");
if (fp == nullptr)
{
WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno);
return;
}
INFO_LOG(NAOMI, "Saving card file to %s", path.c_str());
if (fwrite(cardData, 1, sizeof(cardData), fp) != sizeof(cardData))
WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str());
fclose(fp);
}
void serialize(Serializer& ser) const override
{
maple_device::serialize(ser);
ser << cardData;
ser << d4Seen;
ser << cardInserted;
ser << cardLocked;
}
void deserialize(Deserializer& deser) override
{
maple_device::deserialize(deser);
deser >> cardData;
deser >> d4Seen;
deser >> cardInserted;
deser >> cardLocked;
}
void insertCard()
{
if (!cardInserted) {
cardInserted = true;
loadCard();
}
else if (!cardLocked)
{
cardInserted = false;
if (!transientData)
memset(cardData, 0, sizeof(cardData));
}
}
const u8 *getCardData() {
loadCard();
return cardData;
}
void setCardData(u8 *data) {
memcpy(cardData, data, sizeof(cardData));
transientData = true;
}
u8 cardData[128];
bool d4Seen = false;
bool cardInserted = false;
bool cardLocked = false;
bool transientData = false;
};
void insertRfidCard(int playerNum)
{
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->insertCard();
}
void setRfidCardData(int playerNum, u8 *data)
{
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->setCardData(data);
}
const u8 *getRfidCardData(int playerNum)
{
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
return std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->getCardData();
else
return nullptr;
}
std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
{
switch(type)
{
case MDT_SegaController:
if (!settings.platform.isAtomiswave())
return std::make_shared<maple_sega_controller>();
else
return std::make_shared<maple_atomiswave_controller>();
case MDT_Microphone: return std::make_shared<maple_microphone>();
case MDT_SegaVMU: return std::make_shared<maple_sega_vmu>();
case MDT_PurupuruPack: return std::make_shared<maple_sega_purupuru>();
case MDT_Keyboard: return std::make_shared<maple_keyboard>();
case MDT_Mouse: return std::make_shared<maple_mouse>();
case MDT_LightGun:
if (!settings.platform.isAtomiswave())
return std::make_shared<maple_lightgun>();
else
return std::make_shared<atomiswave_lightgun>();
case MDT_NaomiJamma: return std::make_shared<maple_naomi_jamma>();
case MDT_TwinStick: return std::make_shared<maple_sega_twinstick>();
case MDT_AsciiStick: return std::make_shared<maple_ascii_stick>();
case MDT_MaracasController: return std::make_shared<maple_maracas_controller>();
case MDT_FishingController: return std::make_shared<maple_fishing_controller>();
case MDT_PopnMusicController: return std::make_shared<maple_popnmusic_controller>();
case MDT_RacingController: return std::make_shared<maple_racing_controller>();
case MDT_DenshaDeGoController: return std::make_shared<maple_densha_controller>();
case MDT_SegaControllerXL: return std::make_shared<FullController>();
case MDT_RFIDReaderWriter: return std::make_shared<RFIDReaderWriter>();
default:
ERROR_LOG(MAPLE, "Invalid device type %d", type);
die("Invalid maple device type");
break;
}
return nullptr;
}
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP) && defined(USE_SDL) && !defined(LIBRETRO)
#include "sdl/dreamlink.h"
#include <list>
#include <memory>
struct DreamLinkVmu : 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();})
{
// 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
{
// 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;
const MapleMsg* msg = reinterpret_cast<const MapleMsg*>(dma_buffer_in - 4);
if (functionId == MFID_1_Storage)
{
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);
}
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("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;
}
}
}
}
};
u64 DreamLinkVmu::lastNotifyTime = 0;
u64 DreamLinkVmu::lastErrorNotifyTime = 0;
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);
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;
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];
if ((dreamlink->getFunctionCode(i + 1) & MFID_1_Storage) || (dev != nullptr && dev->get_device_type() == MDT_SegaVMU))
{
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;
}
else if (stateLoaded)
{
if (vmu->useRealVmuMemory)
{
// Disconnect from real VMU memory when a state is loaded
vmu->useRealVmuMemory = false;
os_notify("WARNING: Disconnected from physical VMU memory due to load state", 6000);
}
}
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);
}
}
}
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 || stateLoaded || !rumbleFound)
{
if (!rumble)
{
rumble = std::make_shared<DreamLinkPurupuru>(dreamlink);
}
rumble->Setup(bus, i);
if (!rumbleFound) dreamLinkPurupurus.push_back(rumble);
}
}
}
}
void tearDownDreamLinkDevices(std::shared_ptr<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.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;
}
}
}
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.get())
{
std::shared_ptr<maple_device> dev = maple_Create(MDT_PurupuruPack);
dev->Setup(bus, 1);
iter = dreamLinkPurupurus.erase(iter);
break;
}
else
{
++iter;
}
}
}
#endif