#include "types.h" #include #include #include "maple_if.h" #include "maple_helper.h" #include "maple_devs.h" #include "maple_cfg.h" #include "hw/pvr/spg.h" #include "hw/naomi/naomi_cart.h" #include "hw/naomi/naomi_periph.h" #include "hw/naomi/printer.h" #include "hw/naomi/midiffb.h" #include "savewriter.h" #include "vmu_beep.h" #include #include #include #include #include #define LOGJVS(...) DEBUG_LOG(JVS, __VA_ARGS__) const char* maple_sega_controller_name = "Dreamcast Controller"; const char* maple_sega_twinstick_name = "Twin Stick"; const char* maple_sega_vmu_name = "Visual Memory"; const char* maple_ascii_stick_name = "ASCII STICK"; 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 Devic"; const char* maple_sega_dreameye_name_2 = "Dreamcast Camera Flash LDevic"; 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_brand = "Produced By or Under License From SEGA ENTERPRISES,LTD."; bool enable_naomi_15khz_dipswitch = false; bool naomi_bios_multiregion = false; enum MapleFunctionID { MFID_0_Input = 0x01000000, //DC Controller, Lightgun buttons, arcade stick .. stuff like that MFID_1_Storage = 0x02000000, //VMU , VMS MFID_2_LCD = 0x04000000, //VMU MFID_3_Clock = 0x08000000, //VMU MFID_4_Mic = 0x10000000, //DC Mic (, dreameye too ?) MFID_5_ARGun = 0x20000000, //Artificial Retina gun ? seems like this one was never developed or smth -- I only remember of lightguns MFID_6_Keyboard = 0x40000000, //DC Keyboard MFID_7_LightGun = 0x80000000, //DC Lightgun MFID_8_Vibration = 0x00010000, //Puru Puru Puur~~~ MFID_9_Mouse = 0x00020000, //DC Mouse MFID_10_StorageExt = 0x00040000, //Storage ? probably never used MFID_11_Camera = 0x00080000, //DreamEye }; enum MapleDeviceCommand { MDC_DeviceRequest = 0x01, //7 words.Note : Initialises device MDC_AllStatusReq = 0x02, //7 words + device dependent ( seems to be 8 words) MDC_DeviceReset = 0x03, //0 words MDC_DeviceKill = 0x04, //0 words MDC_DeviceStatus = 0x05, //Same as MDC_DeviceRequest ? MDC_DeviceAllStatus = 0x06, //Same as MDC_AllStatusReq ? //Various Functions MDCF_GetCondition = 0x09, //FT MDCF_GetMediaInfo = 0x0A, //FT,PT,3 pad MDCF_BlockRead = 0x0B, //FT,PT,Phase,Block # MDCF_BlockWrite = 0x0C, //FT,PT,Phase,Block #,data ... MDCF_GetLastError = 0x0D, //FT,PT,Phase,Block # MDCF_SetCondition = 0x0E, //FT,data ... MDCF_MICControl = 0x0F, //FT,MIC data ... MDCF_ARGunControl = 0x10, //FT,AR-Gun data ... MDC_JVSUploadFirmware = 0x80, // JVS bridge firmware MDC_JVSGetId = 0x82, MDC_JVSSelfTest = 0x84, // JVS Self Test MDC_JVSCommand = 0x86, // JVS I/O }; enum MapleDeviceRV { MDRS_JVSNone = 0x00, // No reply, used for multiple JVS I/O boards MDRS_DeviceStatus = 0x05, //28 words MDRS_DeviceStatusAll = 0x06, //28 words + device dependent data MDRS_DeviceReply = 0x07, //0 words MDRS_DataTransfer = 0x08, //FT,depends on the command MDRE_UnknownFunction = 0xFE, //0 words MDRE_UnknownCmd = 0xFD, //0 words MDRE_TransmitAgain = 0xFC, //0 words MDRE_FileError = 0xFB, //1 word, bitfield MDRE_LCDError = 0xFA, //1 word, bitfield MDRE_ARGunError = 0xF9, //1 word, bitfield MDRS_JVSSelfTestReply= 0x85, // JVS I/O SelfTest MDRS_JVSReply = 0x87, // JVS I/O }; //fill in the info void maple_device::Setup(u32 prt) { maple_port = prt; bus_port = maple_GetPort(prt); bus_id = maple_GetBusId(prt); logical_port[0] = 'A' + bus_id; logical_port[1] = bus_port == 5 ? 'x' : '1' + bus_port; logical_port[2] = 0; } maple_device::~maple_device() { delete config; } /* Base class with dma helpers and stuff */ struct maple_base: maple_device { u8* dma_buffer_out; u32* dma_count_out; u8* dma_buffer_in; u32 dma_count_in; void w8(u8 data) { *((u8*)dma_buffer_out)=data;dma_buffer_out+=1;dma_count_out[0]+=1; } void w16(u16 data) { *((u16*)dma_buffer_out)=data;dma_buffer_out+=2;dma_count_out[0]+=2; } void w32(u32 data) { *(u32*)dma_buffer_out=data;dma_buffer_out+=4;dma_count_out[0]+=4; } void wptr(const void* src,u32 len) { u8* src8=(u8*)src; while(len--) w8(*src8++); } void wstr(const char* str,u32 len) { size_t ln=strlen(str); verify(len>=ln); len-=ln; while(ln--) w8(*str++); while(len--) w8(0x20); } u8 r8() { u8 rv=*((u8*)dma_buffer_in);dma_buffer_in+=1;dma_count_in-=1; return rv; } u16 r16() { u16 rv=*((u16*)dma_buffer_in);dma_buffer_in+=2;dma_count_in-=2; return rv; } // (a frame can be shorter than its command needs: the words it has not got read as 0, and the count does not go below 0) u32 r32() { if (dma_count_in<4) return 0; u32 rv=*(u32*)dma_buffer_in;dma_buffer_in+=4;dma_count_in-=4; return rv; } void rptr(void* dst, u32 len) { u8* dst8=(u8*)dst; while(len--) *dst8++=r8(); } u32 r_count() { return dma_count_in; } u32 Dma(u32 Command,u32* buffer_in,u32 buffer_in_len,u32* buffer_out,u32& buffer_out_len) { dma_buffer_out=(u8*)buffer_out; dma_count_out=&buffer_out_len; dma_buffer_in=(u8*)buffer_in; dma_count_in=buffer_in_len; return dma(Command); } virtual u32 dma(u32 cmd)=0; virtual u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) { 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 ) | (send << 8) | (reci << 16) | ((outlen / 4) << 24); return outlen + 4; } }; /* 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 u32 transform_kcode(u32 kcode) { return kcode | 0xF901; // mask off DPad2, C, D and Z; } virtual u32 get_analog_axis(int index, const PlainJoystickState &pjs) { switch (index) { case 2: return pjs.joy[PJAI_X1]; case 3: return pjs.joy[PJAI_Y1]; case 0: return pjs.trigger[PJTI_R]; // Right trigger case 1: return pjs.trigger[PJTI_L]; // Left trigger default: return 0x80; // unused } } virtual MapleDeviceType get_device_type() { 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 dma(u32 cmd) { //printf("maple_sega_controller::dma Called 0x%X;Command %d\n",device_instance->port,Command); switch (cmd) { case MDC_DeviceRequest: case MDC_AllStatusReq: //caps //4 w32(MFID_0_Input); //struct data //3*4 w32(get_capabilities()); w32( 0); w32( 0); //1 area code w8(0xFF); //1 direction w8(0); //30 wstr(get_device_name(), 30); //60 wstr(get_device_brand(), 60); //2 w16(0x01AE); // 43 mA //2 w16(0x01F4); // 50 mA if (cmd == MDC_AllStatusReq) { // what a controller adds when asked for all of its status: its maker's notes, 80 bytes 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; } return MDRS_DeviceStatus; //controller condition case MDCF_GetCondition: { PlainJoystickState pjs; if (!SYSTEM_IS_NAOMI()) config->GetInput(&pjs); //caps //4 w32(MFID_0_Input); //state data //2 key code w16(transform_kcode(pjs.kcode)); //triggers //1 R w8(get_analog_axis(0, pjs)); //1 L w8(get_analog_axis(1, pjs)); //joyx //1 w8(get_analog_axis(2, pjs)); //joyy //1 w8(get_analog_axis(3, pjs)); //not used on dreamcast //1 w8(get_analog_axis(4, pjs)); //1 w8(get_analog_axis(5, 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 { virtual 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 } virtual u32 transform_kcode(u32 kcode) override { return kcode | AWAVE_TRIGGER_KEY; } virtual u32 get_analog_axis(int index, const PlainJoystickState &pjs) override { if (index < 2 || index > 5) return 0x80; index -= 2; u8 val; if (naomi_game_inputs != NULL && naomi_game_inputs->axes[index].name != NULL) { switch (naomi_game_inputs->axes[index].axis) { case 0: val = pjs.joy[0] - 0x80; break; case 1: val = pjs.joy[1] - 0x80; break; case 2: val = pjs.joy[2] - 0x80; break; case 3: val = pjs.joy[3] - 0x80; break; case 4: val = pjs.trigger[1]; break; case 5: val = pjs.trigger[0]; break; } if (naomi_game_inputs->axes[index].inverted) val = 0xff - val; } else val = pjs.joy[index] - 0x80; return val; } }; /* Sega Twin Stick Controller */ struct maple_sega_twinstick : maple_sega_controller { virtual 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 } virtual u32 transform_kcode(u32 kcode) override { return kcode | 0x0101; } virtual MapleDeviceType get_device_type() override { return MDT_TwinStick; } virtual u32 get_analog_axis(int index, const PlainJoystickState &pjs) override { return 0x80; } virtual const char *get_device_name() override { return maple_sega_twinstick_name; } }; /* Ascii Stick (Arcade Stick) */ struct maple_ascii_stick : maple_sega_controller { virtual 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 } virtual u32 transform_kcode(u32 kcode) override { return kcode | 0xF800; } virtual MapleDeviceType get_device_type() override { return MDT_AsciiStick; } virtual u32 get_analog_axis(int index, const PlainJoystickState &pjs) override { return 0x80; } virtual const char *get_device_name() override { return maple_ascii_stick_name; } }; /* Sega Dreamcast Visual Memory Unit This is pretty much done (?) */ u8 vmu_default[] = { 0x78, 0x9c, 0xed, 0xdd, 0x5b, 0x88, 0x1c, 0x55, 0x1e, 0x07, 0xe0, 0xd3, 0x31, 0x6a, 0xf0, 0x3a, 0x0d, 0x0a, 0x5e, 0x18, 0x6a, 0x16, 0x02, 0x2e, 0xfb, 0x20, 0x68, 0x22, 0x2c, 0x28, 0x24, 0x21, 0x3d, 0xc2, 0x2e, 0xc6, 0x40, 0x26, 0xc3, 0x40, 0x2e, 0x90, 0x96, 0xec, 0xc8, 0x9a, 0xc4, 0x81, 0x6c, 0xa2, 0x20, 0x82, 0x11, 0xc1, 0x37, 0x41, 0x9f, 0x14, 0xf1, 0x61, 0x37, 0x64, 0xbc, 0x34, 0xb3, 0x17, 0xf0, 0xc1, 0x87, 0x0e, 0x9b, 0xb7, 0x55, 0x02, 0x3a, 0x19, 0x30, 0x8d, 0x0c, 0x1c, 0x82, 0xac, 0x17, 0x76, 0x59, 0x30, 0x77, 0xb5, 0x92, 0xce, 0xb1, 0xaa, 0x27, 0xc6, 0x98, 0x98, 0x68, 0xb2, 0x31, 0x15, 0x93, 0xef, 0x2b, 0x8a, 0x9a, 0xff, 0xef, 0xd4, 0xa9, 0x73, 0xe8, 0x3a, 0xf5, 0xd4, 0xc5, 0x74, 0x08, 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, 0xd5, 0xfa, 0xf3, 0xdc, 0x2f, 0x47, 0x52, 0xfa, 0xae, 0xee, 0x7b, 0xfe, 0xf6, 0xaf, 0xe6, 0x0e, 0x84, 0xd0, 0x78, 0x20, 0x84, 0x4d, 0xa3, 0x95, 0x4d, 0x0b, 0x00, 0x7e, 0x31, 0xfe, 0xf8, 0xfb, 0x87, 0x6f, 0xa8, 0x7a, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x95, 0xe1, 0x77, 0x9f, 0xfc, 0x69, 0xb2, 0x3c, 0xde, 0x7a, 0xed, 0x9c, 0xf0, 0xef, 0xa1, 0xe9, 0x63, 0x55, 0xcf, 0x07, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x07, 0x0b, 0xcf, 0x92, 0x5d, 0x5f, 0x7b, 0x69, 0xf0, 0xc7, 0xce, 0xfd, 0x29, 0xd7, 0x2b, 0xc5, 0x3b, 0x47, 0x36, 0xbd, 0x58, 0xfb, 0xe7, 0x55, 0xc7, 0x8a, 0x6d, 0x7b, 0x2d, 0x84, 0x05, 0x0b, 0x6e, 0xfe, 0xed, 0x6d, 0x3f, 0x75, 0x92, 0x5c, 0x14, 0x0b, 0x65, 0x57, 0x5c, 0x56, 0xe5, 0xd8, 0xb2, 0x6a, 0xb3, 0x4b, 0x6d, 0x3e, 0xb2, 0x6a, 0xb2, 0x2a, 0xc7, 0x96, 0xb9, 0xef, 0xb2, 0xea, 0xb3, 0x2a, 0xc7, 0x96, 0xb9, 0xef, 0xb2, 0x8b, 0x9f, 0x5d, 0x6a, 0xf3, 0x91, 0x55, 0x93, 0x55, 0x39, 0xb6, 0xcc, 0x7d, 0x97, 0x55, 0x9f, 0x55, 0x39, 0xb6, 0xac, 0xda, 0xfb, 0xfe, 0x7f, 0x6a, 0x8c, 0x8d, 0x6d, 0x78, 0x64, 0x7d, 0x73, 0xed, 0x1f, 0x4e, 0x04, 0x1b, 0x9b, 0x8f, 0xac, 0xdf, 0xbc, 0x69, 0x6c, 0xe3, 0xb7, 0xc1, 0xd0, 0xba, 0xb1, 0x8d, 0x63, 0x8f, 0x37, 0x37, 0x9d, 0xe8, 0xb2, 0xbc, 0xa8, 0xd7, 0x7d, 0xef, 0x22, 0xbf, 0x2e, 0xf6, 0xeb, 0x8a, 0xbd, 0x16, 0x66, 0xfe, 0xd1, 0xd7, 0x96, 0x0d, 0xff, 0x69, 0xbc, 0xfc, 0xfc, 0xdb, 0xc5, 0x94, 0x9f, 0x5a, 0xfc, 0xdf, 0xa2, 0xe3, 0xb3, 0xb3, 0x26, 0xff, 0x75, 0xf2, 0xf9, 0x0b, 0x7f, 0x60, 0x22, 0xb2, 0xca, 0xd7, 0x02, 0xf0, 0x0b, 0xf3, 0x41, 0xd5, 0x13, 0xa0, 0x22, 0x7d, 0x67, 0x6a, 0x98, 0xf3, 0xa3, 0x5d, 0x6b, 0x17, 0x74, 0x22, 0xe7, 0xed, 0xb3, 0xbb, 0xa7, 0xef, 0x5f, 0x74, 0x7a, 0x9c, 0xae, 0x3f, 0x2d, 0xfa, 0xcd, 0x45, 0x98, 0x0d, 0x17, 0xdb, 0x19, 0x57, 0xf0, 0xec, 0x62, 0x9f, 0x75, 0xa2, 0x38, 0x96, 0xce, 0x55, 0xaf, 0xdf, 0xb9, 0x77, 0x3b, 0xa3, 0x2c, 0xec, 0x3d, 0xb7, 0x0e, 0x0b, 0xce, 0x7f, 0xf0, 0x67, 0x8a, 0xa9, 0x6f, 0x39, 0xfb, 0xe7, 0x76, 0xd3, 0x85, 0xf8, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x24, 0xa4, 0x6d, 0x31, 0xc6, 0x3d, 0xdd, 0x5d, 0x77, 0x64, 0xbb, 0xfa, 0xa7, 0xba, 0xf9, 0x93, 0x69, 0xe7, 0xea, 0x18, 0xf3, 0x38, 0x55, 0xef, 0xee, 0xea, 0xcf, 0x52, 0xeb, 0xe9, 0x94, 0x2f, 0x89, 0x71, 0xd5, 0x5f, 0x9b, 0x59, 0x77, 0xaa, 0x9e, 0x26, 0x27, 0x26, 0x77, 0x1c, 0xdd, 0xb6, 0x3a, 0x4e, 0x77, 0x9b, 0x59, 0x7c, 0x21, 0x4b, 0xf9, 0xe4, 0x91, 0x1d, 0x69, 0xe7, 0xb6, 0x46, 0xa3, 0xf1, 0xab, 0xb4, 0x7c, 0x38, 0x5b, 0x31, 0x91, 0x8e, 0xb4, 0x53, 0xda, 0xd9, 0x6a, 0xb5, 0x26, 0xf3, 0xd6, 0x44, 0x9a, 0x48, 0xa9, 0xdd, 0xde, 0x71, 0xf2, 0x97, 0xd2, 0x47, 0xda, 0xed, 0xf6, 0xf7, 0xcb, 0xd3, 0xdf, 0xdb, 0x98, 0xd3, 0x7b, 0x0f, 0xb4, 0x7c, 0xb5, 0x24, 0xbf, 0x2d, 0x84, 0xc7, 0x76, 0x7f, 0xfa, 0x79, 0x79, 0xac, 0xcd, 0x0a, 0x61, 0xd9, 0xa2, 0xc7, 0x9f, 0x68, 0x6e, 0x3c, 0xf9, 0xdc, 0x5b, 0x06, 0xeb, 0xb3, 0x9f, 0xdb, 0xfa, 0xca, 0xab, 0xff, 0xb8, 0x7a, 0xd1, 0xa3, 0xd7, 0x5c, 0x88, 0x8f, 0x63, 0xf6, 0x59, 0xda, 0x2e, 0xcb, 0xdf, 0xcc, 0x7b, 0x68, 0x69, 0x63, 0xe9, 0xd0, 0xd0, 0xc0, 0xc8, 0xa2, 0x65, 0x03, 0x3d, 0xcb, 0x06, 0x17, 0x2f, 0x5d, 0xd6, 0x18, 0x58, 0xfa, 0xe0, 0xc0, 0xa9, 0x2d, 0x33, 0x4e, 0xed, 0x3f, 0xf3, 0x0a, 0xd0, 0x6b, 0xb5, 0xb5, 0x4f, 0xfc, 0xf0, 0xf5, 0xbf, 0x4e, 0x07, 0xd3, 0x9b, 0xf9, 0xe8, 0xe1, 0xb5, 0x87, 0x87, 0x0e, 0xdd, 0x7b, 0xf0, 0xde, 0x03, 0xf7, 0x1c, 0x2c, 0xb6, 0x03, 0xd9, 0xde, 0x81, 0xfd, 0x7d, 0xfb, 0xc2, 0xbe, 0xb0, 0xb7, 0xdd, 0x7e, 0x67, 0xb8, 0xb0, 0xbc, 0xb1, 0x78, 0xde, 0xbc, 0xf9, 0x2b, 0xdb, 0xc7, 0xcb, 0xe1, 0xe1, 0xc1, 0x32, 0x29, 0x83, 0xcd, 0x2b, 0x8a, 0xe5, 0x55, 0x2e, 0xa9, 0x56, 0x6b, 0x7c, 0xde, 0x60, 0x7b, 0xfb, 0xf0, 0xbb, 0x1f, 0xd7, 0xeb, 0x33, 0xcb, 0x27, 0x1f, 0x9f, 0xbf, 0x72, 0xfb, 0xaa, 0xb4, 0xa1, 0x96, 0xba, 0x31, 0xee, 0x2e, 0x96, 0x71, 0x3e, 0x74, 0xff, 0xd1, 0x9d, 0xdd, 0x58, 0x2b, 0xca, 0x38, 0x51, 0xae, 0xeb, 0xf1, 0xfb, 0x52, 0x2b, 0x76, 0x7a, 0xf5, 0x92, 0x5e, 0x3d, 0x2f, 0xfd, 0x3d, 0x76, 0xea, 0x65, 0x3d, 0x5d, 0xec, 0x31, 0x9f, 0x9f, 0x26, 0xe2, 0x68, 0x56, 0xfe, 0xb9, 0xa7, 0x57, 0x37, 0xd2, 0x1b, 0x71, 0x4d, 0x51, 0x77, 0x46, 0xdf, 0x5f, 0x31, 0x53, 0x8f, 0xc7, 0x62, 0xad, 0x97, 0xe7, 0x7c, 0x78, 0xbc, 0xee, 0x34, 0xbb, 0xb1, 0xd3, 0x4c, 0x31, 0xae, 0xd8, 0x13, 0xf3, 0xc1, 0x34, 0x5e, 0x34, 0x75, 0x46, 0x6b, 0x33, 0x33, 0x28, 0xea, 0xad, 0xcd, 0x7a, 0xbd, 0x2f, 0xf4, 0x86, 0x98, 0x58, 0x9d, 0x8f, 0xa4, 0xad, 0x59, 0xfa, 0xa2, 0x5e, 0xcf, 0xca, 0xba, 0x68, 0x1f, 0x49, 0xaf, 0x17, 0x3d, 0x53, 0x3c, 0xae, 0xec, 0x5f, 0x1e, 0xa7, 0x5b, 0x6f, 0x95, 0x87, 0xd1, 0xff, 0x0d, 0x17, 0xf5, 0x9e, 0x38, 0x5a, 0x5c, 0xa3, 0x3b, 0x5a, 0x8c, 0x92, 0x95, 0xf5, 0xee, 0xce, 0x9a, 0x6c, 0x5f, 0x16, 0x9b, 0xb1, 0xd3, 0x97, 0xf5, 0xce, 0x7f, 0xbd, 0xd6, 0x9d, 0x2a, 0xea, 0x2f, 0x3a, 0xbb, 0x7a, 0xed, 0xf1, 0x70, 0xad, 0x78, 0x58, 0x3f, 0xaa, 0x67, 0x71, 0x2a, 0xf5, 0xea, 0xbf, 0xd5, 0xe3, 0x96, 0xfe, 0x4e, 0xbd, 0xbf, 0xd3, 0xcc, 0x7b, 0xf5, 0x87, 0xf5, 0xb8, 0xa6, 0xff, 0x2f, 0x7b, 0xfb, 0x3b, 0x59, 0x51, 0x17, 0xcf, 0x7b, 0x27, 0xef, 0x2f, 0x1e, 0xee, 0xe2, 0x79, 0x4f, 0xf9, 0xc8, 0xcf, 0xb9, 0xb6, 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, 0xe0, 0x72, 0x73, 0x34, 0xd9, 0x2e, 0xef, 0x2d, 0x4f, 0xfb, 0xc2, 0xfe, 0x70, 0x20, 0x1c, 0x0c, 0x87, 0xc2, 0xe1, 0xf0, 0x65, 0xf8, 0x2a, 0x7c, 0x1d, 0xf2, 0x70, 0x24, 0x1c, 0x0d, 0x79, 0xd1, 0x36, 0x7c, 0x8a, 0x5a, 0xf9, 0xc3, 0x7a, 0x67, 0x59, 0x2f, 0xb7, 0xbf, 0x52, 0xbf, 0x2b, 0x84, 0x81, 0x13, 0xbf, 0x93, 0x98, 0x7a, 0xfb, 0xb1, 0x50, 0x0b, 0xdd, 0x70, 0x63, 0xd8, 0x1c, 0xde, 0x0b, 0x59, 0x91, 0x6c, 0xf9, 0xb9, 0x97, 0x2d, 0x17, 0xc8, 0x37, 0x7a, 0x6b, 0xe6, 0x6e }; /* The default VMU image is kept as a zlib stream; inflate it into @dst, * which must hold exactly the uncompressed size. */ static bool vmu_inflate_default(u8 *dst, size_t dst_len) { void *inf; size_t wr = 0; int rv; if (!(inf = rinflate_new(15))) return false; rinflate_set_in(inf, vmu_default, sizeof(vmu_default)); rinflate_set_out(inf, dst, dst_len); rv = rinflate_process(inf, NULL, &wr); rinflate_free(inf); return rv == RDEFLATE_PROCESS_END && wr == dst_len; } /* How the writes a device hands to the save writer are getting on. */ struct MapleSaveState { retro_atomic_int_t failed; // set by the writer: a write did not go through u32 wait; // frames until the next try, after one that failed u32 again; // how long the last such wait was MapleSaveState() : wait(0), again(0) { retro_atomic_int_init(&failed, 0); } /* Whether a write has failed since this was last asked: 0 if not, 1 * the first time, 2 every time after. There is a while before the * next try - a second, then twice as long each time, up to a minute: * a file that cannot be written at all (no such directory, a disk * that is full) is not hammered at, and whoever logs the failure does * so the first time only. */ int Failed() { if (!retro_atomic_load_acquire_int(&failed) || !retro_atomic_exchange_int(&failed, 0)) return 0; again = again == 0 ? 60 : again >= 1800 ? 3600 : again * 2; wait = again; return again == 60 ? 1 : 2; } // Whether it is still too soon after a failure to try again bool Waiting() { if (wait == 0) return false; wait--; return true; } }; #include "hw/aica/aica_if.h" /* A date as a memory card gives it, from the console's clock (seconds since * 1950-01-01, local time): year (two bytes, low first), month, day, hour, * minute, second, and a byte that is 0. */ static void vmu_clock_date(u32 rtc, u8 *buf) { const u32 secs = rtc % 86400; // days since 0000-03-01 of the Gregorian calendar, in 400-year cycles const u32 days = rtc / 86400 + 712163; const u32 era = days / 146097; const u32 doe = days - era * 146097; const u32 yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; const u32 doy = doe - (365 * yoe + yoe / 4 - yoe / 100); const u32 mp = (5 * doy + 2) / 153; const u32 day = doy - (153 * mp + 2) / 5 + 1; const u32 month = mp < 10 ? mp + 3 : mp - 9; const u32 year = yoe + era * 400 + (month <= 2); buf[0] = year % 256; buf[1] = year / 256; buf[2] = month; buf[3] = day; buf[4] = secs / 3600; buf[5] = secs / 60 % 60; buf[6] = secs % 60; buf[7] = 0; } struct maple_sega_vmu: maple_base { RFILE* file; /* Bytes of flash_data changed since the last flush: [dirty_lo, dirty_hi) */ u32 dirty_lo; u32 dirty_hi; /* Bytes where the card is not what its file has because a state was * loaded: [loaded_lo, loaded_hi). See maple_unserialize(). */ u32 loaded_lo = 0; u32 loaded_hi = 0; MapleSaveState save_state; // of the writes handed over: see FlushSave() u8 flash_data[128*1024]; u8 lcd_data[192]; u8 lcd_data_decoded[VMU_SCREEN_WIDTH*VMU_SCREEN_HEIGHT]; std::string path; // the card's file virtual MapleDeviceType get_device_type() { return MDT_SegaVMU; } virtual bool maple_serialize(void **data, unsigned int *total_size) { LIBRETRO_SA(flash_data,128*1024); LIBRETRO_SA(lcd_data,192); LIBRETRO_SA(lcd_data_decoded,48*32); return true ; } virtual bool maple_unserialize(void **data, unsigned int *total_size) { /* What is on the card is about to be what the state says, and the * file is still what it was. Loading a state does not write to the * file: where the two now differ is noted, and goes to the file * with the next thing the game writes to the card - or the file * would be the card in part and what it was before in part, a * broken file system. Whatever was waiting to be written belongs to * the card as it was: out it goes first. * * (The whole card used to be written then, and every load counted * as one that changed the card. A frontend's run-ahead loads a * state every frame, and it is the card as it is but for what the * game wrote in the last frame: usually nothing, and then there is * nothing to do; while a game saves, a block or two, where it was * 128K a frame.) */ u32 lo = 0, hi = sizeof(flash_data); if (ra_unserialize_has(*data, sizeof(flash_data))) { const u8 * const from = (const u8 *)*data; // (by the block: that is what a game writes) while (lo < hi && memcmp(&flash_data[lo], &from[lo], 512) == 0) lo += 512; while (hi > lo && memcmp(&flash_data[hi - 512], &from[hi - 512], 512) == 0) hi -= 512; } if (lo < hi) { save_state.wait = 0; FlushSave(); if (dirty_lo < dirty_hi) { // (not taken: it waits with the rest) lo = std::min(lo, dirty_lo); hi = std::max(hi, dirty_hi); dirty_lo = 0; dirty_hi = 0; } if (loaded_lo < loaded_hi) { lo = std::min(lo, loaded_lo); hi = std::max(hi, loaded_hi); } loaded_lo = lo; loaded_hi = hi; } LIBRETRO_USA(flash_data,128*1024); LIBRETRO_USA(lcd_data,192); LIBRETRO_USA(lcd_data_decoded,48*32); /* The two cards of a controller show on one screen, which has the * picture the game last drew on either. The second card's, after * the first's, only if it has ever been drawn on: or a card the * game does not draw on puts its blank screen in place of the * other's picture at every load. */ bool drawn = bus_port == 0; for (u32 i = 0; !drawn && i < sizeof(lcd_data); i++) drawn = lcd_data[i] != 0; if (drawn) vmu_lcd_publish(bus_id, lcd_data_decoded); return true ; } virtual void OnSetup() { memset(flash_data,0,sizeof(flash_data)); memset(lcd_data,0,sizeof(lcd_data)); dirty_lo = 0; dirty_hi = 0; std::string apath = get_writable_vmu_path(logical_port); path = apath; vmu_screen_params[bus_id].vmu_lcd_screen = lcd_data_decoded ; // What the renderer shows until the game draws: the screen as it is now vmu_lcd_publish(bus_id, lcd_data_decoded); INFO_LOG(MAPLE, "Initializing VMU data..."); verify(vmu_inflate_default(flash_data, sizeof(flash_data))); file=filestream_open(apath.c_str(), RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (!file) { INFO_LOG(MAPLE, "Unable to open VMU save file \"%s\", creating new file",apath.c_str()); file=filestream_open(apath.c_str(), RETRO_VFS_FILE_ACCESS_READ_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (file) { filestream_write(file, flash_data, sizeof(flash_data)); filestream_seek(file,0,RETRO_VFS_SEEK_POSITION_START); } else { INFO_LOG(MAPLE, "Unable to create VMU!"); } } if (!file) { WARN_LOG(MAPLE, "Failed to create VMU save file \"%s\"", apath.c_str()); } else { filestream_read(file,flash_data,sizeof(flash_data)); NOTICE_LOG(MAPLE, "Loaded VMU from file \"%s\"", apath.c_str()); } } /* What the game wrote to the card since the last time goes to the * card's file: handed to the writer's thread (savewriter.h), which * does the waiting for the disk. The file is the writer's from the * first hand-over on. * * A write that fails is found out later: the whole card is then * written again, since which part of the file is not what it should * be is no longer known, a second or so later and for as long as it * goes on failing. It used to be forgotten - what was to be written * counted as written whatever the write said. */ virtual void FlushSave() { if (const int failed = save_state.Failed()) { if (failed == 1) WARN_LOG(MAPLE, "VMU %s: a write to its file failed. The card will be written again", logical_port); dirty_lo = 0; dirty_hi = sizeof(flash_data); } if (dirty_lo >= dirty_hi || save_state.Waiting()) return; if (file) { // (not taken: too much is waiting to be written. Next frame.) if (!save_writer_put(file, dirty_lo, &flash_data[dirty_lo], dirty_hi - dirty_lo, &save_state.failed)) return; } else INFO_LOG(MAPLE, "Failed to save VMU %s data", logical_port); dirty_lo = 0; dirty_hi = 0; } virtual ~maple_sega_vmu() { save_state.wait = 0; FlushSave(); if (save_state.wait != 0) { // (that found a write had failed, and put the next one off: now, all the same) save_state.wait = 0; FlushSave(); } // the writer has the file, and the flag that is this card's save_writer_drain(); if (file && save_state.Failed()) WARN_LOG(MAPLE, "VMU %s: the last write to its file failed", logical_port); if (file) filestream_close(file); } virtual u32 dma(u32 cmd) { //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) { // and a memory card's 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; } 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 { 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 ? // (the screen's own bytes: it was the first 192 of the card's memory) wptr(lcd_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); /* The date and time the console's clock says. (It * was the host's: a game reading it got another * answer when a frame was run again from a save * state, which run-ahead and netplay do.) */ u8* timebuf=dma_buffer_out; u8 date[8]; vmu_clock_date(RealTimeClock, date); wptr(date, sizeof(date)); 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: { switch(r32()) { 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); return MDRE_TransmitAgain; //invalid params } rptr(&flash_data[write_adr],write_len); /* Written out by FlushSave() between frames */ if (dirty_lo >= dirty_hi) { dirty_lo = write_adr; dirty_hi = write_adr + write_len; } else { if (write_adr < dirty_lo) dirty_lo = write_adr; if (write_adr + write_len > dirty_hi) dirty_hi = write_adr + write_len; } if (loaded_lo < loaded_hi) { // the first write after a state was loaded that changed the card: see maple_unserialize() dirty_lo = std::min(dirty_lo, loaded_lo); dirty_hi = std::max(dirty_hi, loaded_hi); loaded_lo = 0; loaded_hi = 0; } return MDRS_DeviceReply;//just ko } case MFID_2_LCD: { DEBUG_LOG(MAPLE, "VMU %s LCD write", logical_port); r32(); if (r_count() < 192) { INFO_LOG(MAPLE, "VMU %s LCD write invalid params: rcount %d", logical_port, r_count()); return MDRE_TransmitAgain; //invalid params } 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); push_vmu_screen(lcd_data_decoded); return MDRS_DeviceReply;//just ko } 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 -> Bad function used, returning MDRE_UnknownFunction"); return MDRE_UnknownFunction;//bad function } } break; case MDCF_GetLastError: return MDRS_DeviceReply;//just ko case MDCF_SetCondition: { switch(r32()) { case MFID_3_Clock: { /* The alarm: its period, then its on time, in * the first two bytes (the other two are a * second alarm, not emulated). */ u32 bp=r32(); if (bp) { INFO_LOG(MAPLE, "BEEP : %08X", bp); } vmu_beep_set(bp & 0xFF, (bp >> 8) & 0xFF); return MDRS_DeviceReply;//just ko } break; default: { INFO_LOG(MAPLE, "VMU: command MDCF_SetCondition -> Bad function used, returning MDRE_UnknownFunction"); return MDRE_UnknownFunction;//bad function } break; } } case MDC_DeviceReset: vmu_beep_set(0, 0); return MDRS_DeviceReply; case MDC_DeviceKill: vmu_beep_set(0, 0); return MDRS_DeviceReply; default: DEBUG_LOG(MAPLE, "Unknown MAPLE COMMAND %d", cmd); return MDRE_UnknownCmd; } } }; /* Whether a memory card would now be read from another file than the one it * was read from: the settings say which, and they may have changed. */ bool maple_vmu_file_changed(maple_device *dev) { if (dev->get_device_type() != MDT_SegaVMU) return false; maple_sega_vmu *vmu = (maple_sega_vmu *)dev; return vmu->path != get_writable_vmu_path(vmu->logical_port); } struct maple_microphone: maple_base { u8 micdata[SIZE_OF_MIC_DATA]; virtual MapleDeviceType get_device_type() { return MDT_Microphone; } virtual bool maple_serialize(void **data, unsigned int *total_size) { LIBRETRO_SA(micdata,SIZE_OF_MIC_DATA); return true ; } virtual bool maple_unserialize(void **data, unsigned int *total_size) { LIBRETRO_USA(micdata,SIZE_OF_MIC_DATA); return true ; } virtual void OnSetup() { memset(micdata,0,sizeof(micdata)); } virtual u32 dma(u32 cmd) { //printf("maple_microphone::dma Called 0x%X;Command %d\n",this->maple_port,cmd); //LOGD("maple_microphone::dma Called 0x%X;Command %d\n",this->maple_port,cmd); switch (cmd) { case MDC_DeviceRequest: case MDC_AllStatusReq: DEBUG_LOG(MAPLE, "maple_microphone::dma MDC_DeviceRequest"); //this was copied from the controller case with just the id and name replaced! //caps //4 w32(MFID_4_Mic); //struct data //3*4 w32( 0xfe060f00); 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(0x01AE); //2 w16(0x01F4); return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll; /* (A microphone has no condition to ask for: the request is one it * does not know. The answer made here was the controller's with all * but two bytes taken out, six bytes where an answer is whole words.) */ case MDC_DeviceReset: //uhhh do nothing? DEBUG_LOG(MAPLE, "maple_microphone::dma MDC_DeviceReset"); return MDRS_DeviceReply; case MDCF_MICControl: { //LOGD("maple_microphone::dma handling MDCF_MICControl %d\n",cmd); //MONEY u32 function=r32(); //LOGD("maple_microphone::dma MDCF_MICControl function (1st word) %#010x\n", function); //LOGD("maple_microphone::dma MDCF_MICControl words: %d\n", dma_count_in); switch(function) { case MFID_4_Mic: { //MAGIC HERE //http://dcemulation.org/phpBB/viewtopic.php?f=34&t=69600 // <3 <3 BlueCrab <3 <3 /* 2nd word What it does: 0x0000??03 Sets the amplifier gain, ?? can be from 00 to 1F 0x0f = default 0x00008002 Enables recording 0x00000001 Returns sampled data while recording is enabled While not enabled, returns status of the mic. 0x00000002 Disables recording * */ u32 secondword=r32(); //LOGD("maple_microphone::dma MDCF_MICControl subcommand (2nd word) %#010x\n", subcommand); u32 subcommand = secondword & 0xFF; //just get last byte for now, deal with params later //LOGD("maple_microphone::dma MDCF_MICControl (3rd word) %#010x\n", r32()); //LOGD("maple_microphone::dma MDCF_MICControl (4th word) %#010x\n", r32()); switch(subcommand) { case 0x01: { //printf("maple_microphone::dma MDCF_MICControl someone wants some data! (2nd word) %#010x\n", secondword); w32(MFID_4_Mic); //from what i can tell this is up to spec but results in transmit again //w32(secondword); //32 bit header w8(0x04);//status (just the bit for recording) w8(0x0f);//gain (default) w8(0);//exp ? if(get_mic_data(micdata)){ w8(240);//ct (240 samples) wptr(micdata, SIZE_OF_MIC_DATA); }else { w8(0); } return MDRS_DataTransfer; } case 0x02: DEBUG_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl toggle recording %#010x", secondword); return MDRS_DeviceReply; case 0x03: DEBUG_LOG(MAPLE, "maple_microphone::dma MDCF_MICControl set gain %#010x", secondword); 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 secondword %#010x", secondword); return MDRE_UnknownFunction; } } default: INFO_LOG(MAPLE, "maple_microphone::dma UNHANDLED function %#010x", function); return MDRE_UnknownFunction; } } 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; virtual MapleDeviceType get_device_type() { return MDT_PurupuruPack; } virtual bool maple_serialize(void **data, unsigned int *total_size) { LIBRETRO_S(AST); LIBRETRO_S(AST_ms); LIBRETRO_S(VIBSET); return true ; } virtual bool maple_unserialize(void **data, unsigned int *total_size) { LIBRETRO_US(AST); LIBRETRO_US(AST_ms); LIBRETRO_US(VIBSET); return true ; } virtual u32 dma(u32 cmd) { 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); //2 w16(0x0640); return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll; //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 // (if the frame is long enough to have one) if (dma_count_in >= 12) { AST = dma_buffer_in[10]; AST_ms = AST * 250 + 250; } return MDRS_DeviceReply; case MDCF_SetCondition: // (a frame too short to say how is not acted on) if (dma_count_in >= 8) { VIBSET = *(u32*)&dma_buffer_in[4]; //Do the rumble thing! config->SetVibration(VIBSET, AST_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; } } }; char EEPROM[0x100]; bool EEPROM_loaded = false; bool naomi_cart_GetBootHeader(u8 *header, u32 size); // naomi_cart.cpp /* The games with two I/O boards whose players 1 and 2 are on the second * board (node 2) when the cabinet is set for more than two. */ static bool jvs_dual_reversed_game(void) { return settings.mapping.JammaSetup == JVS_WorldKicksPCB || (settings.mapping.JammaSetup == JVS_DualIOBoards4P && (!strcmp(naomi_game_id, "VIRTUA ATHLETE") || !strcmp(naomi_game_id, "ROYAL RUMBLE") || !strcmp(naomi_game_id, "BEACH SPIKERS JAPAN") || !strcmp(naomi_game_id, "MJ JAPAN"))); } /* The EEPROM's first record, the BIOS's settings: bytes 2 to 17, their * CRC in bytes 0 and 1, and all of it again in bytes 18 to 35. Byte 8 is * the cabinet: bit 0 the coin chutes (individual), bits 4-5 the players * less one. */ static u16 naomi_eeprom_crc(const u8 *buf, int size) { u32 n = 0xdebdeb00; for (int i = 0; i <= size; i++) { // (and a zero byte after the last) n = (n & 0xffffff00) + (i < size ? buf[i] : 0); for (int c = 0; c < 8; c++) n = (n & 0x80000000) ? (n << 1) + 0x10210000 : n << 1; } return n >> 16; } static void naomi_eeprom_bios_set(u32 offset, u8 value) { u8 *eeprom = (u8 *)EEPROM; eeprom[offset] = value; eeprom[offset + 18] = value; const u16 crc = naomi_eeprom_crc(eeprom + 2, 16); eeprom[0] = eeprom[18] = (u8)crc; eeprom[1] = eeprom[19] = (u8)(crc >> 8); } /* A cabinet of these games is set for four players when the EEPROM has * not been set up for the game (its file is not there, or is another * game's): the BIOS's settings are then what the BIOS would make from the * game's header, with the four-player cabinet. One that has been is left * as it is, unless its number of players is one the header says the game * cannot have. */ static void naomi_eeprom_dual_cabinet(void) { u8 header[0x500]; const u8 *eeprom = (const u8 *)EEPROM; if (!jvs_dual_reversed_game() || !naomi_cart_GetBootHeader(header, sizeof(header))) return; const u8 *game_id = &header[0x134]; const u8 *coin = &header[0x1e0]; // the first region's coin settings const u8 cabinet = header[0x429]; // the numbers of players the game can have, a bit each const u8 vertical = header[0x42b]; if (memcmp(game_id, eeprom + 3, 4)) { for (int i = 0; i < 4; i++) naomi_eeprom_bios_set(3 + i, game_id[i]); naomi_eeprom_bios_set(7, 9); naomi_eeprom_bios_set(8, cabinet == 0 || (cabinet & 8) ? 0x30 : (cabinet & 4) ? 0x20 : (cabinet & 2) ? 0x10 : 0); if (coin[0] == 1) { // the game's own naomi_eeprom_bios_set(2, (coin[1] & 1) | 0x10); if (coin[2] == 1) naomi_eeprom_bios_set(8, eeprom[8] | 1); naomi_eeprom_bios_set(9, coin[3] - 1); naomi_eeprom_bios_set(10, std::max(coin[6], (u8)1)); naomi_eeprom_bios_set(11, std::max(coin[4], (u8)1)); naomi_eeprom_bios_set(12, std::max(coin[5], (u8)1)); naomi_eeprom_bios_set(13, coin[7]); for (int i = 0; i < 4; i++) naomi_eeprom_bios_set(14 + i, coin[8 + i * 2] | (coin[9 + i * 2] << 4)); } else { // the BIOS's naomi_eeprom_bios_set(2, (vertical & 2) ? 0x11 : 0x10); naomi_eeprom_bios_set(9, 0); naomi_eeprom_bios_set(10, 1); naomi_eeprom_bios_set(11, 1); naomi_eeprom_bios_set(12, 1); naomi_eeprom_bios_set(13, 0); for (int i = 0; i < 4; i++) naomi_eeprom_bios_set(14 + i, 0x11); } NOTICE_LOG(MAPLE, "EEPROM: set up for game %s, %d-player cabinet", naomi_game_id, (eeprom[8] >> 4) + 1); } if (cabinet != 0 && cabinet < 0x10 && !(cabinet & (1 << (eeprom[8] >> 4)))) { const u8 players = (cabinet & 8) ? 0x30 : (cabinet & 4) ? 0x20 : (cabinet & 2) ? 0x10 : 0; NOTICE_LOG(MAPLE, "EEPROM: %d-player cabinet", (players >> 4) + 1); naomi_eeprom_bios_set(8, players | (eeprom[8] & 1)); } } u8 kb_shift; // shift keys pressed (bitmask) u8 kb_led; // leds currently lit u8 kb_key[6]={0}; // normal keys pressed struct maple_keyboard : maple_base { virtual MapleDeviceType get_device_type() { return MDT_Keyboard; } virtual u32 dma(u32 cmd) { switch (cmd) { case MDC_DeviceRequest: case MDC_AllStatusReq: //caps //4 w32(MFID_6_Keyboard); //struct data //3*4 w32(0x80000502); // US, 104 keys 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); // Maximum current consumption (2) w16(0x01F5); return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll; case MDCF_GetCondition: w32(MFID_6_Keyboard); //struct data //int8 shift ; shift keys pressed (bitmask) //1 w8(kb_shift); //int8 led ; leds currently lit //1 w8(kb_led); //int8 key[6] ; normal keys pressed //6 for (int i = 0; i < 6; i++) { w8(kb_key[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; } } }; // Mouse buttons // bit 0: Button C // bit 1: Right button (B) // bit 2: Left button (A) // bit 3: Wheel button u32 mo_buttons[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; // Relative mouse coordinates [-512:511] f32 mo_x_delta[4]; f32 mo_y_delta[4]; f32 mo_wheel_delta[4]; // Absolute mouse coordinates // Range [0:639] [0:479] // but may be outside this range if the pointer is offscreen or outside the 4:3 window. f32 mo_x_abs[4]; f32 mo_y_abs[4]; struct maple_mouse : maple_base { virtual MapleDeviceType get_device_type() { return MDT_Mouse; } static u16 mo_cvt(f32 delta) { delta += 0x200; if (delta<=0) delta=0; else if (delta>0x3FF) delta=0x3FF; return (u16)lroundf(delta); } virtual u32 dma(u32 cmd) { 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(0x0069); // Maximum current consumption (2) w16(0x0120); return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll; case MDCF_GetCondition: { u32 buttons; f32 delta_x, delta_y, delta_wheel; config->GetMouse(&buttons, &delta_x, &delta_y, &delta_wheel); w32(MFID_9_Mouse); //struct data /* int8 buttons, pressed is 0: bit 2 left, bit 1 right, bit 3 * the middle one (the wheel's). The three bytes after it are * options, the axes that overflowed and one kept for later, * and are 0. (All four were sent as one word of buttons with * every bit above the three set: every axis always * overflowing. Upstream 9d4711a95: Silent Scope, Dreamkey.) */ w8((u8)buttons); w8(0); w8(0); w8(0); //int16 axis1 ; horizontal movement (0-$3FF) (little endian) w16(mo_cvt(delta_x)); //int16 axis2 ; vertical movement (0-$3FF) (little endian) w16(mo_cvt(delta_y)); //int16 axis3 ; mouse wheel movement (0-$3FF) (little endian) w16(mo_cvt(delta_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) { return kcode | 0xFF01; } virtual MapleDeviceType get_device_type() { return MDT_LightGun; } virtual u32 dma(u32 cmd) { 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(0x01); // 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)); // the two trigger axes, which a gun has not: at rest. (They read as // fully pulled, and Death Crimson 2 took that for "skip the story".) //2 w16(0); //not used //4 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; } } virtual bool get_lightgun_pos() { f32 x, y; config->GetAbsolutePosition(&x, &y); read_lightgun_position(x + 0.5f, y + 0.5f); return true; } }; struct atomiswave_lightgun : maple_lightgun { virtual u32 transform_kcode(u32 kcode) override { return (kcode & AWAVE_TRIGGER_KEY) == 0 ? ~AWAVE_BTN0_KEY : ~0; } }; #include "libretro/pad_state.h" extern char eeprom_file[PATH_MAX]; /* Sega JVS I/O board */ struct maple_naomi_jamma; class jvs_io_board { public: jvs_io_board(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) { this->node_id = node_id; this->parent = parent; memset(coin_count, 0, sizeof(coin_count)); memset(coin_chute, 0, sizeof(coin_chute)); this->first_player = first_player; } virtual ~jvs_io_board() = default; u32 handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_out); bool maple_serialize(void **data, unsigned int *total_size); bool maple_unserialize(void **data, unsigned int *total_size); // What a board has had in a save state since V21: see maple_jvs_serialize_v21() void maple_serialize_v21(void **data, unsigned int *total_size); void maple_state_v21(const u32 *coins, const u8 *flags); // What a board has had in a save state since V25: see maple_jvs_serialize_v25() virtual u8 get_state_v25() { return 0; } virtual void set_state_v25(u8 v) { } // and since V27: see maple_jvs_serialize_v27() virtual void get_state_v27(u8 *v) { v[0] = output0; } virtual void set_state_v27(const u8 *v) { output0 = v[0]; } bool lightgun_as_analog = false; // Whose inputs the board has: the players' from this one on void set_first_player(u8 player) { first_player = player; } protected: virtual const char *get_id() = 0; virtual u32 remap_buttons(u32 keycode) { return keycode; } virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted); virtual u32 read_digital_in(int player_num) { u32 keycode = player_num > 3 ? 0 : ~kcode[player_num]; return remap_buttons(keycode); } virtual void write_digital_out(int count, u8 *data) { } // The cabinet's test switch, as the board reports it virtual bool read_test_switch() { return !(kcode[0] & NAOMI_TEST_KEY); } // Where a player's gun or finger is, as the board reports it /* The 640x480 screen position as it is. (It was scaled to the range * Ninja Assault reads, and a gun pointed off the screen, 0,0, then read * as on it: the game's guns would not reload.) */ virtual void read_screen_position(int player_num, s16& x, s16& y) { x = (s16)mo_x_abs[player_num]; y = (s16)mo_y_abs[player_num]; } // What a drive board last answered, on a board that is wired to one virtual u8 get_drive_board() { return 0; } virtual void set_drive_board(u8 value) { } /* A player's switches as the game sees them, after the game's own * buttons have been put where it has them: some are wired to a drive * board, a shifter. */ virtual u32 wired_inputs(int player_num, u32 keycode) { return keycode; } /* Before the rotary encoders are read, @chans of them: the mice's motion * (mo_x_delta, mo_y_delta) where the board's encoders take it. */ virtual void turn_encoders(int chans) { } // The first byte of the outputs the game last set on board @node (1 or 2) u8 board_output0(u32 node); u32 player_count = 0; u32 digital_in_count = 0; u32 coin_input_count = 0; u32 analog_count = 0; u32 encoder_count = 0; u32 light_gun_count = 0; u32 output_count = 0; bool init_in_progress = false; // The first byte of the outputs the game last set (lamps, motors) u8 output0 = 0; private: u16 read_rotary_encoder(f32 &encoder_value, f32 delta_value); u8 node_id = 0; maple_naomi_jamma *parent; // a chute for each player a board can have (there were two: a third and fourth player's coins were never counted) u32 coin_count[4]; bool coin_chute[4]; u8 first_player; }; // Most common JVS board class jvs_837_13551 : public jvs_io_board { public: jvs_837_13551(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 2; digital_in_count = 13; coin_input_count = 2; analog_count = 8; output_count = 6; } protected: virtual const char *get_id() override { return "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"; } }; class jvs_837_13551_noanalog : public jvs_837_13551 { public: jvs_837_13551_noanalog(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13551(node_id, parent, first_player) { analog_count = 0; } }; // Same in 4-player mode class jvs_837_13551_4P : public jvs_837_13551 { public: jvs_837_13551_4P(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13551(node_id, parent, first_player) { player_count = 4; digital_in_count = 12; coin_input_count = 4; analog_count = 0; } }; /* Mushiking: the card dispenser's switches are on the players' inputs. It * says it has cards (P1 START) unless the test switch is down, never that * it is empty (P1 RIGHT) or jammed (P2 START). The test switch is seen a * read later than it goes down, or the BIOS menu cannot be used with it. */ class jvs_837_13551_mushiking : public jvs_837_13551 { public: jvs_837_13551_mushiking(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13551(node_id, parent, first_player) { } protected: virtual bool read_test_switch() override { if (kcode[0] & NAOMI_TEST_KEY) { test_down = 0; return false; } if (!test_down) { test_down = 1; return false; } return true; } virtual u32 read_digital_in(int player_num) override { u32 rv = jvs_837_13551::read_digital_in(player_num); if (player_num == 0) { if (kcode[0] & NAOMI_TEST_KEY) rv |= NAOMI_START_KEY; rv &= ~NAOMI_RIGHT_KEY; } else if (player_num == 1) rv &= ~NAOMI_START_KEY; return rv; } public: virtual u8 get_state_v25() override { return test_down; } virtual void set_state_v25(u8 v) override { test_down = v & 1; } private: u8 test_down = 0; }; // Rotary encoders 2nd board // Virtua Golf, Outtrigger, Shootout Pool class jvs_837_13938 : public jvs_io_board { public: jvs_837_13938(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 1; digital_in_count = 9; encoder_count = 4; output_count = 8; } protected: virtual const char *get_id() override { return "SEGA ENTERPRISES,LTD.;837-13938 ENCORDER BD ;Ver0.01;99/08"; } }; /* Shootout Pool: the cue's tip is aimed with the trackball (the first two * encoders) and the cue pushed with a roller, the third. The trackball is * the roller while BUTTON 4 is held: its motion up and down turns the third * encoder then, and the aim stays where it is. */ class jvs_837_13938_shootout : public jvs_837_13938 { public: jvs_837_13938_shootout(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13938(node_id, parent, first_player) { } protected: virtual void turn_encoders(int chans) override { if (chans >= 3 && (~kcode[0] & NAOMI_BTN3_KEY)) { mo_x_delta[1] -= mo_y_delta[0]; mo_x_delta[0] = 0; mo_y_delta[0] = 0; } } }; // Kick '4' Cash: the encoders are at 45 degrees to the trackball's axes class jvs_837_13938_kick4cash : public jvs_837_13938 { public: jvs_837_13938_kick4cash(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13938(node_id, parent, first_player) { } protected: virtual void turn_encoders(int chans) override { // (both are read, or the motion left for the other is turned again) if (chans < 2) return; const f32 x = mo_x_delta[0]; const f32 y = -mo_y_delta[0]; mo_x_delta[0] = (x - y) * 0.7071f; mo_y_delta[0] = -(x + y) * 0.7071f; } }; /* Crackin'DJ: the turntables are the first and third encoders. A motor * turns them, 10 a read, while the game has the second board's output 5 on * (the records spinning) and the player is not turning them. */ class jvs_837_13938_crackindj : public jvs_837_13938 { public: jvs_837_13938_crackindj(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13938(node_id, parent, first_player) { } protected: virtual void turn_encoders(int chans) override { if (!(board_output0(2) & 0x10)) return; // (an encoder is a third of the mouse's motion) if (chans >= 1 && mo_x_delta[0] == 0) mo_x_delta[0] = -30.f; if (chans >= 3 && mo_x_delta[1] == 0) mo_x_delta[1] = -30.f; } }; // Sega Marine Fishing class jvs_837_13844 : public jvs_io_board { public: jvs_837_13844(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 2; coin_input_count = 2; digital_in_count = 12; analog_count = 8; output_count = 22; } protected: virtual const char *get_id() override { return "SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;Ver1.00;99/07"; } }; class jvs_837_13844_encoders : public jvs_837_13844 { public: jvs_837_13844_encoders(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13844(node_id, parent, first_player) { digital_in_count = 8; encoder_count = 4; } }; class jvs_837_13844_touch : public jvs_837_13844 { public: jvs_837_13844_touch(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13844(node_id, parent, first_player) { light_gun_count = 1; } protected: /* The touchscreen gives where it is touched on the 640x480 screen, with * the top bits set while it is touched (any value from 0x1000 does, * once the screen is calibrated: Touch de Uno 1 and 2). */ virtual void read_screen_position(int player_num, s16& x, s16& y) override { x = (s16)std::min(std::max(mo_x_abs[player_num], 0.f), 639.f); y = (s16)std::min(std::max(mo_y_abs[player_num], 0.f), 479.f); if (~kcode[player_num] & NAOMI_BTN0_KEY) { x |= 0xf000; y |= 0xf000; } } }; // Wave Runner GP: fake the drive board class jvs_837_13844_wrungp : public jvs_837_13844 { public: jvs_837_13844_wrungp(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13844(node_id, parent, first_player) { } protected: virtual u32 read_digital_in(int player_num) override { u32 rv = jvs_837_13844::read_digital_in(player_num); // The drive board RX0-7 is connected to the following player inputs if (player_num == 0) { rv |= NAOMI_BTN2_KEY | NAOMI_BTN3_KEY | NAOMI_BTN4_KEY | NAOMI_BTN5_KEY; if (drive_board & 16) rv &= ~NAOMI_BTN5_KEY; if (drive_board & 32) rv &= ~NAOMI_BTN4_KEY; if (drive_board & 64) rv &= ~NAOMI_BTN3_KEY; if (drive_board & 128) rv &= ~NAOMI_BTN2_KEY; } else if (player_num == 1) { rv |= NAOMI_BTN2_KEY | NAOMI_BTN3_KEY | NAOMI_BTN4_KEY | NAOMI_BTN5_KEY; if (drive_board & 1) rv &= ~NAOMI_BTN5_KEY; if (drive_board & 2) rv &= ~NAOMI_BTN4_KEY; if (drive_board & 4) rv &= ~NAOMI_BTN3_KEY; if (drive_board & 8) rv &= ~NAOMI_BTN2_KEY; } return rv; } virtual void write_digital_out(int count, u8 *data) override { if (count != 3) return; // The drive board TX0-7 is connected to outputs 15-22 // shifting right by 2 to get the last 8 bits of the output u16 out = (data[1] << 6) | (data[2] >> 2); // reverse out = (out & 0xF0) >> 4 | (out & 0x0F) << 4; out = (out & 0xCC) >> 2 | (out & 0x33) << 2; out = (out & 0xAA) >> 1 | (out & 0x55) << 1; if (out == 0xff) drive_board = 0xff; else if ((out & 0xf) == 0xf) { out >>= 4; if (out > 7) drive_board = 0xff & ~(1 << (14 - out)); else drive_board = 0xff & ~(1 << out); } else if ((out & 0xf0) == 0xf0) { out &= 0xf; if (out > 7) drive_board = 0xff & ~(1 << (out - 7)); else drive_board = 0xff & ~(1 << (7 - out)); } } virtual u8 get_drive_board() override { return drive_board; } virtual void set_drive_board(u8 value) override { drive_board = value; } private: u8 drive_board = 0; }; /* The 837-13844 wired to a drive board with force feedback: 838-13843 of * F355 Challenge (ROM EPR-21867), 838-13992 of 18 Wheeler (EPR-23000), * with the 838-12912-01 servo board of the Model 3. The game sends the * drive board a byte on outputs 15 to 22, and reads its answer on the * players' BUTTON 3 to 6. What the motor does is given to the frontend as * rumble, where a rumble motor can show it. * https://github.com/njz3/vJoyIOFeederWithFFB/blob/master/DRIVEBOARD.md */ class jvs_837_13844_racing : public jvs_837_13844 { public: jvs_837_13844_racing(u8 node_id, maple_naomi_jamma *parent, int first_player = 0, bool tokyo_bus = false) : jvs_837_13844(node_id, parent, first_player), tokyo_bus(tokyo_bus) { } /* In a save state from V27 (after the output byte): * [1] bit 0 test mode, bit 1 active * [2] the damper's high bits * [3] the motor's power, in percent * [4] the force of the test menu's roll, -7 to 7 */ virtual void get_state_v27(u8 *v) override { jvs_837_13844::get_state_v27(v); v[1] = (test_mode ? 1 : 0) | (active ? 2 : 0); v[2] = damper_high; v[3] = power; v[4] = (u8)torque; } virtual void set_state_v27(const u8 *v) override { jvs_837_13844::set_state_v27(v); test_mode = (v[1] & 1) != 0; active = (v[1] & 2) != 0; damper_high = v[2] & 0xf; power = v[3] <= 100 ? v[3] : 100; torque = (s8)v[4] >= -7 && (s8)v[4] <= 7 ? (s8)v[4] : 0; if (active && torque != 0) naomi_ffb_rumble(NAOMI_FFB_FORCE, abs(torque) / 7.f, 0); else naomi_ffb_rumble(NAOMI_FFB_STOP, 0.f, 0); } protected: virtual u32 wired_inputs(int player_num, u32 keycode) override { // The drive board's answer: bits 4-7 on the first player's switches, 0-3 on the second's u32 bits; if (player_num == 0) bits = drive_board >> 4; else if (player_num == 1) bits = drive_board & 0xf; else return keycode; keycode |= NAOMI_BTN2_KEY | NAOMI_BTN3_KEY | NAOMI_BTN4_KEY | NAOMI_BTN5_KEY; if (bits & 1) keycode &= ~NAOMI_BTN5_KEY; if (bits & 2) keycode &= ~NAOMI_BTN4_KEY; if (bits & 4) keycode &= ~NAOMI_BTN3_KEY; if (bits & 8) keycode &= ~NAOMI_BTN2_KEY; return keycode; } virtual void write_digital_out(int count, u8 *data) override { if (count != 3) return; // Outputs 15 to 22, the last 8 bits, the other way round u8 in = (u8)((data[1] << 6) | (data[2] >> 2)); in = (in & 0xF0) >> 4 | (in & 0x0F) << 4; in = (in & 0xCC) >> 2 | (in & 0x33) << 2; in = (in & 0xAA) >> 1 | (in & 0x55) << 1; drive_board = command(in); } virtual u8 get_drive_board() override { return drive_board; } virtual void set_drive_board(u8 value) override { drive_board = value; } private: u8 command(u8 in) { in = ~in; u8 out = 0; switch (in) { case 0: case 1: case 2: // 18 Wheeler: light (60%), normal (80%), heavy (100%) if (!active) power = 60 + in * 20; break; case 0xf0: active = false; test_mode = true; break; case 0xfe: active = true; break; case 0xff: test_mode = false; active = false; naomi_ffb_rumble(NAOMI_FFB_STOP, 0.f, 0); break; case 0xf1: out = 0x10; // F355 and Tokyo Bus Guide wait for it break; // F355: 100%, 90%, 80%, 60% case 0xfa: power = 100; break; case 0xfb: power = 90; break; case 0xfc: power = 80; break; case 0xfd: power = 60; break; default: if (!active) break; if (in >= 0x40 && in <= 0x7f) { /* The spring that brings the wheel back to the centre (bits * 0-3 its strength). Tokyo Bus Guide sets the power with * it: 0x44 light, 0x47 normal, 0x4a heavy. */ if (tokyo_bus && in <= 0x4a) power = (in & 0xf) * 10; } else if (in >= 0x80 && in <= 0xbf) { // Shaking, at 25 Hz (softened by half) naomi_ffb_rumble(NAOMI_FFB_SHAKE, (in & 0x3f) / 63.f * power / 100.f / 2.f, 650); } else if (in >= 0xe0 && in <= 0xef) { // The test menu's roll to the left and right torque = in < 0xe8 ? 0xe0 - in : in - 0xe8; naomi_ffb_rumble(NAOMI_FFB_FORCE, abs(torque) / 7.f, 0); } else if ((in & 0xf0) == 0xd0) { // The high bits of the damper (friction), which the next 0xcN sets damper_high = in & 0xf; } break; } if (test_mode) out = in; out = (out & 0xF0) >> 4 | (out & 0x0F) << 4; out = (out & 0xCC) >> 2 | (out & 0x33) << 2; out = (out & 0xAA) >> 1 | (out & 0x55) << 1; return out; } const bool tokyo_bus; u8 drive_board = 0; // what the drive board answers bool test_mode = false; bool active = false; u8 damper_high = 8; u8 power = 100; s8 torque = 0; }; /* 18 Wheeler: the shifter, the wheel's travel. SHIFT L/H changes from low * to high gear and back, SHIFT R is reverse; the gear is on the second * player's UP, DOWN and LEFT. */ class jvs_837_13844_18wheeler : public jvs_837_13844_racing { public: jvs_837_13844_18wheeler(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13844_racing(node_id, parent, first_player) { } // In a save state from V27: [1] bit 2 SHIFT L/H held, [5] the gear (0 low, 1 high, 0xff reverse) virtual void get_state_v27(u8 *v) override { jvs_837_13844_racing::get_state_v27(v); v[1] |= shift_held ? 4 : 0; v[5] = gear; } virtual void set_state_v27(const u8 *v) override { jvs_837_13844_racing::set_state_v27(v); shift_held = (v[1] & 4) != 0; gear = v[5] == 1 || v[5] == 0xff ? v[5] : 0; } protected: virtual u32 wired_inputs(int player_num, u32 keycode) override { keycode = jvs_837_13844_racing::wired_inputs(player_num, keycode); if (player_num == 0) { const u32 buttons = ~kcode[0]; if (buttons & NAOMI_BTN2_KEY) gear = 0xff; else if (buttons & NAOMI_BTN1_KEY) { if (!shift_held) { gear = gear == 0 ? 1 : 0; shift_held = true; } } else shift_held = false; } else if (player_num == 1) { switch (gear) { case 0xff: keycode |= NAOMI_LEFT_KEY | NAOMI_DOWN_KEY; keycode &= ~NAOMI_UP_KEY; break; case 1: keycode &= ~(NAOMI_LEFT_KEY | NAOMI_DOWN_KEY); keycode |= NAOMI_UP_KEY; break; default: keycode |= NAOMI_DOWN_KEY; keycode &= ~(NAOMI_UP_KEY | NAOMI_LEFT_KEY); break; } } return keycode; } // The wheel does not turn all the way virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted) override { u16 v = jvs_837_13844_racing::read_analog_axis(player_num, player_axis, inverted); if (player_axis == 0) v = std::min(0xefff, std::max(0x1000, v)); return v; } private: u8 gear = 0; bool shift_held = false; }; /* Samba de Amigo: the maracas' sensors, as the game takes them: rounded, * no more than 0xf000, the first X lower and the second higher. */ class jvs_837_13844_samba : public jvs_837_13844 { public: jvs_837_13844_samba(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_837_13844(node_id, parent, first_player) { } protected: virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted) override { int v = jvs_837_13844::read_analog_axis(player_num, player_axis, inverted); v = std::min(v & ~0xff, 0xf000); if (player_axis == 0) v = std::max(v - 0x2000, 0); else if (player_axis == 2) v = std::min(v + 0x2000, 0xf000); return (u16)v; } }; // Ninja assault class jvs_namco_jyu : public jvs_io_board { public: jvs_namco_jyu(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 2; coin_input_count = 2; digital_in_count = 12; output_count = 16; light_gun_count = 2; } protected: virtual const char *get_id() override { return "namco ltd.;JYU-PCB;Ver1.00;JPN,2Coins 2Guns"; } virtual u32 remap_buttons(u32 keycode) { // Swap button 1 and 4 u16 mapped = keycode | NAOMI_BTN1_KEY | NAOMI_BTN4_KEY; if (!(keycode & NAOMI_BTN1_KEY)) mapped &= ~NAOMI_BTN4_KEY; if (!(keycode & NAOMI_BTN4_KEY)) mapped &= ~NAOMI_BTN1_KEY; return mapped; } }; // Mazan class jvs_namco_fcb : public jvs_io_board { public: jvs_namco_fcb(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 1; coin_input_count = 2; digital_in_count = 16; output_count = 6; light_gun_count = 1; analog_count = 7; encoder_count = 2; } protected: virtual const char *get_id() override { return "namco ltd.;FCB;Ver1.0;JPN,Touch Panel & Multipurpose"; } virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted) override { if (init_in_progress) return 0; if (mo_x_abs[player_num] < 0 || mo_x_abs[player_num] > 639 || mo_y_abs[player_num] < 0 || mo_y_abs[player_num] > 479) return 0; else return 0x8000; } }; // Gun Survivor class jvs_namco_fca : public jvs_io_board { public: jvs_namco_fca(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 1; coin_input_count = 1; // 2 makes bios crash digital_in_count = 16; output_count = 6; analog_count = 7; encoder_count = 2; } protected: virtual const char *get_id() override { return "namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder"; } }; // World Kicks class jvs_namco_v226 : public jvs_io_board { public: jvs_namco_v226(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 1; digital_in_count = 16; coin_input_count = 1; analog_count = 12; output_count = 6; } protected: virtual const char *get_id() override { return "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"; } virtual u32 read_digital_in(int player_num) override { u32 keycode1 = ~kcode[0]; u32 keycode2 = ~kcode[1]; u32 keycode3 = ~kcode[2]; u32 keycode4 = ~kcode[3]; // main button return ((keycode1 & NAOMI_BTN0_KEY) << 6) // start | ((keycode2 & NAOMI_BTN0_KEY) << 2) // left | ((keycode3 & NAOMI_BTN0_KEY) << 1) // right | ((keycode4 & NAOMI_BTN0_KEY) >> 1) // btn1 // soft kick // | ((~keycode1 & NAOMI_BTN1_KEY) >> 1) // btn2 // | ((~keycode2 & NAOMI_BTN1_KEY) >> 3) // btn4 // | ((~keycode3 & NAOMI_BTN1_KEY) >> 5) // btn6 // | ((~keycode4 & NAOMI_BTN1_KEY) >> 7) // test? // // hard kick // | ((~keycode1 & NAOMI_BTN2_KEY) >> 1) // btn3 // | ((~keycode2 & NAOMI_BTN2_KEY) >> 3) // btn5 // | ((~keycode3 & NAOMI_BTN2_KEY) >> 5) // btn7 // | ((~keycode4 & NAOMI_BTN2_KEY) >> 7) // ? // enter | ((keycode1 & NAOMI_BTN3_KEY) << 3) // btn0 // service menu | (keycode1 & (NAOMI_SERVICE_KEY | NAOMI_UP_KEY | NAOMI_DOWN_KEY)); } u16 read_joystick_x(int joy_num) { s8 axis_x = joyx[joy_num]; axis_y = joyy[joy_num]; limit_joystick_magnitude<64>(axis_x, axis_y); return (0xff - axis_x) << 8; } u16 read_joystick_y(int joy_num) { return (0xff - axis_y) << 8; } virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted) override { switch (player_num) { case 0: switch (player_axis) { case 0: return read_joystick_x(0); case 1: return read_joystick_y(0); case 2: return read_joystick_x(1); case 3: return read_joystick_y(1); } break; case 1: switch (player_axis) { case 0: return read_joystick_x(2); case 1: return read_joystick_y(2); case 2: return read_joystick_x(3); case 3: return read_joystick_y(3); } break; case 2: switch (player_axis) { case 0: return rt[0] << 8; case 1: return rt[1] << 8; case 2: return rt[2] << 8; case 3: return rt[3] << 8; } break; } return 0x8000; } private: s8 axis_y = 0; }; // World Kicks PCB class jvs_namco_v226_pcb : public jvs_io_board { public: jvs_namco_v226_pcb(u8 node_id, maple_naomi_jamma *parent, int first_player = 0) : jvs_io_board(node_id, parent, first_player) { player_count = 2; digital_in_count = 16; coin_input_count = 1; analog_count = 12; output_count = 6; } protected: virtual const char *get_id() override { return "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"; } virtual u32 read_digital_in(int player_num) override { // (how many players a request asks for is the game's to say) if (player_num > 3) return 0; u32 keycode = ~kcode[player_num]; u8 trigger = rt[player_num] >> 2; // Ball button return ((trigger & 0x20) << 3) | ((trigger & 0x10) << 5) | ((trigger & 0x08) << 7) | ((trigger & 0x04) << 9) | ((trigger & 0x02) << 11) | ((trigger & 0x01) << 13) // other buttons | (keycode & (NAOMI_SERVICE_KEY | NAOMI_TEST_KEY | NAOMI_START_KEY)) | ((keycode & NAOMI_BTN0_KEY) >> 4); // remap button4 to button0 (change button) } u16 read_joystick_x(int joy_num) { s8 axis_x = joyx[joy_num]; axis_y = joyy[joy_num]; limit_joystick_magnitude<48>(axis_x, axis_y); return axis_x << 8; } u16 read_joystick_y(int joy_num) { return (0xff - axis_y) << 8; } // (each player's stick: the second board has players 3 and 4) virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted) override { if (player_num >= 0 && player_num < 4) { switch (player_axis) { case 0: return read_joystick_x(player_num); case 1: return read_joystick_y(player_num); } } return 0x8000; } private: s8 axis_y = 0; }; /* The program a game sends to the I/O board's processor, a piece at a time * over many frames: which program it is decides how the board's requests * are read (crazy_mode). All zeroes when none is being sent. In a save * state from V23: a state loaded while one is on its way goes on with what * had been sent by then. */ static u8 jvs_firmware[0x10000]; /* What is on the I/O board bridge's RS-422 port: Derby Owners Club's card * reader, through a converter board. Not the bridge's own: the bridge is * made anew by a state load. */ static SerialPort::Pipe *mie_pipe; void naomi_mie_set_pipe(SerialPort::Pipe *pipe) { mie_pipe = pipe; } struct maple_naomi_jamma : maple_sega_controller { const u8 ALL_NODES = 0xff; std::vector io_boards; bool crazy_mode = false; // The two boards' players follow the cabinet setting (order_boards()): set from the game's ID bool cabinet_order = false; // The firmware is The House of the Dead 2 prototype's. A state does not // have this: on a load it is worked out from the game's name. bool hotd2p = false; u8 jvs_repeat_request[32][256]; u8 jvs_receive_buffer[32][258]; u32 jvs_receive_length[32] = { 0 }; bool eeprom_dirty = false; MapleSaveState save_state; maple_naomi_jamma() { /* The boards are there from the start, as they are on a cabinet: * made only when the game first spoke to them, a state saved * before that was smaller than one saved after, and a frontend * asks the size once. */ create_io_boards(); } virtual ~maple_naomi_jamma() { save_state.wait = 0; FlushSave(); if (save_state.wait != 0) { save_state.wait = 0; FlushSave(); } // (the flag is this device's: the writer is done with it first) save_writer_drain(); if (save_state.Failed()) WARN_LOG(MAPLE, "Cannot save EEPROM to file %s", eeprom_file); EEPROM_loaded = false; } /* The EEPROM is read from its file once, when the machine starts * (maple_naomi_eeprom_load()), and is in memory from then on. (It was * read again each time the game asked. What the game writes goes to the * file later, from the writer's thread: asked for before that, the file * was still the old one, and the old one was then what got written.) * The game asking for it reads it here only after a state saved before * it had been read. */ void load_eeprom() { if (EEPROM_loaded) return; EEPROM_loaded = true; memset(EEPROM, 0, sizeof(EEPROM)); RFILE* f = filestream_open(eeprom_file, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (f) { filestream_read(f, EEPROM, 0x80); filestream_close(f); DEBUG_LOG(MAPLE, "Loaded EEPROM from %s", eeprom_file); } else if (naomi_default_eeprom != NULL) memcpy(EEPROM, naomi_default_eeprom, 0x80); naomi_eeprom_dual_cabinet(); } /* The EEPROM, when the game has written to it, to its file: made * anew each time, by the writer's thread (savewriter.h). Tried again * later if that fails, as a memory card's is. */ virtual void FlushSave() { if (const int failed = save_state.Failed()) { if (failed == 1) WARN_LOG(MAPLE, "Cannot save EEPROM to file %s. It will be tried again", eeprom_file); eeprom_dirty = true; } if (!eeprom_dirty || save_state.Waiting()) return; if (save_writer_put_file(eeprom_file, EEPROM, 0x80, &save_state.failed)) eeprom_dirty = false; } void create_io_boards() { if (!io_boards.empty()) return; switch (settings.mapping.JammaSetup) { case JVS_Default: default: if (!strncmp(naomi_game_id, "MKG TKOB", 8) || !strncmp(naomi_game_id, "MUSHIKING", 9) || !strcmp(naomi_game_id, "MUSHIUSA '04 1ST VER0.900-")) io_boards.emplace_back(new jvs_837_13551_mushiking(1, this)); else io_boards.emplace_back(new jvs_837_13551(1, this)); break; case JVS_FourPlayers: io_boards.emplace_back(new jvs_837_13551_4P(1, this)); break; case JVS_RotaryEncoders: if (!strncmp(naomi_game_id, "SHOOTOUT POOL", 13)) io_boards.emplace_back(new jvs_837_13938_shootout(1, this)); else if (!strcmp(naomi_game_id, "KICK '4' CASH")) io_boards.emplace_back(new jvs_837_13938_kick4cash(1, this)); else if (!strncmp(naomi_game_id, "CRACKIN'DJ", 10)) io_boards.emplace_back(new jvs_837_13938_crackindj(1, this)); else io_boards.emplace_back(new jvs_837_13938(1, this)); io_boards.emplace_back(new jvs_837_13551(2, this)); break; case JVS_OutTrigger: io_boards.emplace_back(new jvs_837_13938(1, this)); io_boards.emplace_back(new jvs_837_13551_noanalog(2, this)); break; case JVS_SegaMarineFishing: io_boards.emplace_back(new jvs_837_13844(1, this)); break; case JVS_DualIOBoards4P: io_boards.emplace_back(new jvs_837_13551(1, this)); io_boards.emplace_back(new jvs_837_13551(2, this, 2)); cabinet_order = jvs_dual_reversed_game(); break; case JVS_LightGun: io_boards.emplace_back(new jvs_namco_jyu(1, this)); break; case JVS_LightGunAsAnalog: // Regular board sending lightgun coords as axis 0/1 io_boards.emplace_back(new jvs_837_13551(1, this)); io_boards.back()->lightgun_as_analog = true; break; case JVS_Mazan: io_boards.emplace_back(new jvs_namco_fcb(1, this)); io_boards.emplace_back(new jvs_namco_fcb(2, this)); break; case JVS_GunSurvivor: io_boards.emplace_back(new jvs_namco_fca(1, this)); break; case JVS_DogWalking: io_boards.emplace_back(new jvs_837_13844_encoders(1, this)); break; case JVS_TouchDeUno: io_boards.emplace_back(new jvs_837_13844_touch(1, this)); break; case JVS_WorldKicks: io_boards.emplace_back(new jvs_namco_v226(1, this)); break; case JVS_WorldKicksPCB: io_boards.emplace_back(new jvs_namco_v226_pcb(1, this)); io_boards.emplace_back(new jvs_namco_v226_pcb(2, this, 2)); cabinet_order = true; break; case JVS_WaveRunnerGP: io_boards.emplace_back(new jvs_837_13844_wrungp(1, this)); break; case JVS_F355: io_boards.emplace_back(new jvs_837_13844_racing(1, this)); break; case JVS_TokyoBus: io_boards.emplace_back(new jvs_837_13844_racing(1, this, 0, true)); break; case JVS_EighteenWheeler: io_boards.emplace_back(new jvs_837_13844_18wheeler(1, this)); break; case JVS_SambaDeAmigo: io_boards.emplace_back(new jvs_837_13844_samba(1, this)); break; } } virtual MapleDeviceType get_device_type() { return MDT_NaomiJamma; } u8 sense_line(u32 node_id) { bool last_node = node_id == io_boards.size(); return last_node ? 0x8E : 0x8F; } /* The players on each of the two boards, from the cabinet setting in * the EEPROM: set for three or four players, the game has players 1 * and 2 on the second board (node 2) and 3 and 4 on the first; for one * or two, it reads the first, and that has players 1 and 2. So * the first two ports are players 1 and 2 either way, and the board * order follows what the game is set for as soon as it is set. */ void order_boards() { const bool reversed = (EEPROM[8] & 0x30) >= 0x20; io_boards[0]->set_first_player(reversed ? 2 : 0); io_boards[1]->set_first_player(reversed ? 0 : 2); } void send_jvs_message(u32 node_id, u32 channel, u32 length, u8 *data) { if (node_id - 1 < io_boards.size()) { if (cabinet_order) order_boards(); u8 temp_buffer[256]; u32 out_len = io_boards[node_id - 1]->handle_jvs_message(data, length, temp_buffer); if (out_len > 0) { u8 *pbuf = &jvs_receive_buffer[channel][jvs_receive_length[channel]]; if (jvs_receive_length[channel] + out_len + 3 <= sizeof(jvs_receive_buffer[0])) { if (crazy_mode) { pbuf[0] = 0x00; // ? 0: ok, 2: timeout, 3: dest node !=0, 4: checksum failed pbuf[1] = out_len; memcpy(&pbuf[2], temp_buffer, out_len); jvs_receive_length[channel] += out_len + 2; } else { pbuf[0] = node_id; pbuf[1] = 0x00; // 0: ok, 2: timeout, 3: dest node !=0, 4: checksum failed pbuf[2] = out_len; memcpy(&pbuf[3], temp_buffer, out_len); jvs_receive_length[channel] += out_len + 3; } } } } } void send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, u8 *data, bool repeat_first) { /* A request is 255 bytes at the most. The four more are zeroes put * after it: a request that stops short of what its code needs is read * by a board that far past its end, and no further. */ u8 temp_buffer[256 + 4]; if (data) { memcpy(temp_buffer, data, length); } if (node_id == ALL_NODES) { memset(temp_buffer + length, 0, 4); for (int i = 0; i < io_boards.size(); i++) send_jvs_message(i + 1, channel, length, temp_buffer); } else if (node_id >= 1 && node_id <= 32) { u32 repeat_len = jvs_repeat_request[node_id - 1][0]; // (a stored request is left out if it and this one together are longer than a request can be) if (use_repeat && repeat_len > 0 && length + repeat_len <= 256) { if (repeat_first) { memmove(temp_buffer + repeat_len, temp_buffer, length); memcpy(temp_buffer, &jvs_repeat_request[node_id - 1][1], repeat_len); } else { memcpy(temp_buffer + length, &jvs_repeat_request[node_id - 1][1], repeat_len); } length += repeat_len; } memset(temp_buffer + length, 0, 4); send_jvs_message(node_id, channel, length, temp_buffer); } } void receive_jvs_messages(u32 channel) { if (dma_count_in == 0) { w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x00); return; } u32 dword_length = (jvs_receive_length[channel] + 0x10 + 3 - 1) / 4 + 1; w8(MDRS_JVSReply); w8(0x00); w8(0x20); if (jvs_receive_length[channel] == 0) { w8(0x05); w8(0x32); } else { w8(dword_length); w8(0x16); } w8(0xff); w8(0xff); w8(0xff); w32(0xffffff00); w32(0); w32(0); if (jvs_receive_length[channel] == 0) { w32(0); return; } w8(0); w8(channel); if (crazy_mode) w8(0x8E); else w8(sense_line(jvs_receive_buffer[channel][0])); // bit 0 is sense line level. If set during F1 , more I/O boards need addressing memcpy(dma_buffer_out, jvs_receive_buffer[channel], jvs_receive_length[channel]); /* The reply is made up to whole words, and what is past the message * is zeroes: it was whatever the buffer had there (upstream * 58f05f74c, an SH4 crash while a NAOMI started). */ memset(dma_buffer_out + jvs_receive_length[channel], 0, dword_length * 4 - 0x10 - 3 - jvs_receive_length[channel]); dma_buffer_out += dword_length * 4 - 0x10 - 3; *dma_count_out += dword_length * 4 - 0x10 - 3; jvs_receive_length[channel] = 0; } void handle_86_subcommand() { if (dma_count_in == 0) { w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x00); return; } u32 subcode = dma_buffer_in[0]; // CT fw uses 13 as a 17, and 17 as 13 and also uses 19 if (crazy_mode) { switch (subcode) { case 0x13: subcode = 0x17; break; case 0x17: subcode = 0x13; break; } } u8 node_id = 0; u8 *cmd = NULL; u32 len = 0; u8 channel = 0; if (dma_count_in >= 3) { // every request but "store" has its channel, node and length further on, if it is long enough if (subcode != 0x13 && dma_count_in >= 8) { node_id = dma_buffer_in[6]; len = dma_buffer_in[7]; cmd = &dma_buffer_in[8]; channel = dma_buffer_in[5] & 0x1f; } else { node_id = dma_buffer_in[1]; len = dma_buffer_in[2]; cmd = &dma_buffer_in[3]; } // (the length is the game's: no more than the frame has after it) if (len > (u32)(dma_buffer_in + dma_count_in - cmd)) len = dma_buffer_in + dma_count_in - cmd; } switch (subcode) { case 0x13: // Store repeated request if (len > 0 && node_id > 0 && node_id <= 0x1f) { INFO_LOG(MAPLE, "JVS node %d: Storing %d cmd bytes", node_id, len); jvs_repeat_request[node_id - 1][0] = len; memcpy(&jvs_repeat_request[node_id - 1][1], cmd, len); } w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(dma_buffer_in[0] + 1); // subcommand + 1 w8(0); w8(len + 1); w8(0); break; case 0x15: // Receive JVS data // (there are 32 channels) receive_jvs_messages(dma_buffer_in[1] & 0x1f); if (hotd2p) { // this firmware sends its next request along with it send_jvs_messages(node_id, channel, true, len, cmd, false); w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(0x18); // always w8(channel); w8(sense_line(node_id)); w8(0); } break; case 0x17: // Transmit without repeat jvs_receive_length[channel] = 0; send_jvs_messages(node_id, channel, false, len, cmd, false); w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(0x18); // always w8(channel); w8(0x8E); //sense_line(node_id)); w8(0); break; case 0x19: // Transmit with repeat jvs_receive_length[channel] = 0; send_jvs_messages(node_id, channel, true, len, cmd, true); w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(0x18); // always w8(channel); w8(sense_line(node_id)); w8(0); break; case 0x21: // Transmit with repeat jvs_receive_length[channel] = 0; send_jvs_messages(node_id, channel, true, len, cmd, false); w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(0x18); // always w8(channel); w8(sense_line(node_id)); w8(0); break; case 0x35: // Receive then transmit with repeat (15 then 27) receive_jvs_messages(channel); // FALLTHROUGH case 0x27: // Transmit with repeat { jvs_receive_length[channel] = 0; /* How many requests, and how long each is, are the game's * to say: no more than the frame has is read. */ u32 cmd_count = dma_count_in >= 8 ? dma_buffer_in[6] : 0; u32 idx = 7; for (int i = 0; i < cmd_count && idx + 2 <= dma_count_in; i++) { node_id = dma_buffer_in[idx]; len = dma_buffer_in[idx + 1]; cmd = &dma_buffer_in[idx + 2]; if (len > dma_count_in - idx - 2) len = dma_count_in - idx - 2; idx += len + 2; send_jvs_messages(node_id, channel, true, len, cmd, false); } w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(0x26); w8(channel); w8(sense_line(node_id)); w8(0); } break; case 0x33: // Receive then transmit with repeat (15 then 21) receive_jvs_messages(channel); send_jvs_messages(node_id, channel, true, len, cmd, false); w8(MDRS_JVSReply); w8(0); w8(0x20); w8(0x01); w8(0x18); // always w8(channel); w8(sense_line(node_id)); w8(0); break; case 0x0B: //EEPROM write { int address = dma_buffer_in[1]; int size = dma_buffer_in[2]; DEBUG_LOG(MAPLE, "EEprom write %08X %08X\n", address, size); //printState(Command,buffer_in,buffer_in_len); load_eeprom(); // (the chip has 0x80 bytes; the game's address and size went unchecked) address &= 0x7f; if (size > 0x80 - address) size = 0x80 - address; // (nor more than the frame has) if (size > (int)dma_count_in - 4) size = (int)dma_count_in - 4; memcpy(EEPROM + address, dma_buffer_in + 4, size); eeprom_dirty = true; w8(MDRS_JVSReply); w8(0x00); w8(0x20); w8(0x01); memcpy(dma_buffer_out, EEPROM, 4); dma_buffer_out += 4; *dma_count_out += 4; } break; case 0x3: //EEPROM read { load_eeprom(); //printf("EEprom READ\n"); // (0x80 bytes from an address below 0x80 are inside the array, which has 0x100) int address = dma_buffer_in[1] & 0x7f; //printState(Command,buffer_in,buffer_in_len); w8(MDRS_JVSReply); w8(0x00); w8(0x20); w8(0x20); memcpy(dma_buffer_out, EEPROM + address, 0x80); dma_buffer_out += 0x80; *dma_count_out += 0x80; } break; case 0x31: // DIP switches { w8(MDRS_JVSReply); w8(0x00); w8(0x20); w8(0x05); w8(0x32); w8(0xff); // in(0) w8(0xff); // in(1) w8(0xff); // in(2) w8(0x00); w8(0xff); // in(4) { // in(5) bit0: 1=VGA, 0=NTSCi u8 dip = enable_naomi_15khz_dipswitch ? 0x00 : 0xff; /* Switches 2 to 4 say the region to the multi-region * BIOS: Korea is 2 and 3 on, 4 off (other BIOSes have their own). */ if (naomi_bios_multiregion) dip = (dip & ~0x0e) | 0x08; w8(dip); } w8(0xff); // in(6) w32(0x00); w32(0x00); w32(0x00); } break; //case 0x3: // break; case 0x1: w8(MDRS_JVSReply); w8(0x00); w8(0x20); w8(0x01); w8(0x2); w8(0x0); w8(0x0); w8(0x0); break; /* The RS-422 port, with a device on it (it is not answered * at all then). Without one the bridge says it knows none of * these, as it always has here. */ case 0x41: // reset? if (mie_pipe == NULL) goto unknown; DEBUG_LOG(MAPLE, "JVS: RS422 reset"); while (mie_pipe->available() > 0) mie_pipe->read(); break; case 0x47: // send data if (mie_pipe == NULL) goto unknown; DEBUG_LOG(MAPLE, "JVS: RS422 send %02x", dma_buffer_in[4]); if (dma_count_in >= 5) mie_pipe->write(dma_buffer_in[4]); break; case 0x4d: // receive data { if (mie_pipe == NULL) goto unknown; const int avail = std::min(mie_pipe->available(), 0xfe); DEBUG_LOG(MAPLE, "JVS: RS422 receive %d bytes", avail); w8(MDRS_JVSReply); w8(0x00); w8(0x20); w8(1 + (avail + 3) / 4); w8(0); w8(0); w8(0); w8(avail == 0 ? 0xff : avail); // 0xff => no data, else byte count for (int i = 0; i < ((avail + 3) / 4) * 4; i++) w8(i >= avail ? 0 : mie_pipe->read()); break; } case 0x49: // I? case 0x4b: // K? case 0x4f: // O? if (mie_pipe == NULL) goto unknown; break; default: unknown: INFO_LOG(MAPLE, "JVS: Unknown 0x86 sub-command %x", subcode); w8(MDRE_UnknownCmd); w8(0x00); w8(0x20); w8(0x00); break; } } virtual u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) { #ifdef DUMP_JVS printf("JVS IN: "); u8 *p = (u8*)buffer_in; for (int i = 0; i < buffer_in_len; i++) printf("%02x ", *p++); printf("\n"); #endif create_io_boards(); // the card reader and hopper buttons, read as the board's inputs are naomi_periph_poll(); u32 out_len = 0; dma_buffer_out = (u8 *)buffer_out; dma_count_out = &out_len; dma_buffer_in = (u8 *)buffer_in + 4; dma_count_in = buffer_in_len - 4; u32 cmd = *(u8*)buffer_in; switch (cmd) { case MDC_JVSSelfTest: w8(MDRS_JVSSelfTestReply); w8(0x00); w8(0x20); w8(0x01); w8(0x00); break; case MDC_JVSCommand: handle_86_subcommand(); break; case MDC_JVSUploadFirmware: { u8 *ram = jvs_firmware; if (dma_count_in < 4) { // (not long enough to say where its bytes go) w8(MDRE_UnknownCmd); w8(0x00); w8(0x00); w8(0x00); break; } if (dma_buffer_in[1] == 0xff) { u32 hash = xxh32(ram, 0x10000, 0); LOGJVS("JVS Firmware hash %08x\n", hash); hotd2p = hash == 0xa6784e26; if (hash == 0xa7c50459 // CT || hash == 0xae841e36 // HOTD2 || hotd2p) crazy_mode = true; else crazy_mode = false; #ifdef DUMP_JVS_FW RFILE *fw_dump; char filename[128]; for (int i = 0; ; i++) { sprintf(filename, "z80_fw_%d.bin", i); fw_dump = filestream_open(filename, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (fw_dump == NULL) { fw_dump = filestream_open(filename, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); INFO_LOG(MAPLE, "Saving JVS firmware to %s", filename); break; } } if (fw_dump) { filestream_write(fw_dump, ram, 0x10000); filestream_close(fw_dump); } #endif // (empty for the next one) memset(ram, 0, 0x10000); for (int i = 0; i < 32; i++) jvs_repeat_request[i][0] = 0; // (the reply was never written: its code was returned as the length of one) w8(MDRS_DeviceReply); w8(0x00); w8(0x20); w8(0x00); break; } int xfer_bytes; if (dma_buffer_in[0] == 0xff) xfer_bytes = 0x1C; else xfer_bytes = 0x18; u16 addr = (dma_buffer_in[2] << 8) + dma_buffer_in[3]; // (where the bytes go is the game's to say: what is past the end of the 64 KB is left out) if (xfer_bytes > 0x10000 - addr) xfer_bytes = 0x10000 - addr; // (nor what is past the end of the frame, which may be the end of RAM) if (xfer_bytes > (int)dma_count_in - 4) xfer_bytes = (int)dma_count_in - 4; memcpy(ram + addr, &dma_buffer_in[4], xfer_bytes); u8 sum = 0; for (u32 i = 0; i < 0x1C && i < dma_count_in; i++) sum += dma_buffer_in[i]; w8(0x80); // or 0x81 on bootrom? w8(0); w8(0x20); w8(0x01); w8(sum); w8(0); w8(0); w8(0); w8(MDRS_DeviceReply); w8(0x00); w8(0x20); w8(0x00); } break; case MDC_JVSGetId: { const char ID1[] = "315-6149 COPYRIGHT SEGA E"; const char ID2[] = "NTERPRISES CO,LTD. 1998 "; w8(0x83); w8(0x00); w8(0x20); w8(0x07); wstr(ID1, 28); w8(0x83); w8(0x00); w8(0x20); w8(0x05); wstr(ID2, 28); } break; case MDC_DeviceRequest: w8(MDRS_DeviceStatus); w8(0x00); w8(0x20); w8(0x00); break; case MDC_AllStatusReq: w8(MDRS_DeviceStatusAll); w8(0x00); w8(0x20); w8(0x00); // (it went on into the next answer, and both were sent) break; case MDC_DeviceReset: case MDC_DeviceKill: w8(MDRS_DeviceReply); w8(0x00); w8(0x20); w8(0x00); break; case MDCF_GetCondition: w8(MDRE_UnknownCmd); w8(0x00); w8(0x00); w8(0x00); break; default: INFO_LOG(MAPLE, "Unknown Maple command %x", cmd); w8(MDRE_UnknownCmd); w8(0x00); w8(0x00); w8(0x00); break; } #ifdef DUMP_JVS printf("JVS OUT: "); p = (u8 *)buffer_out; for (int i = 0; i < out_len; i++) printf("%02x ", p[i]); printf("\n"); #endif return out_len; } virtual bool maple_serialize(void **data, unsigned int *total_size) { LIBRETRO_S(crazy_mode); LIBRETRO_S(jvs_repeat_request); LIBRETRO_S(jvs_receive_length); LIBRETRO_S(jvs_receive_buffer); size_t board_count = io_boards.size(); LIBRETRO_S(board_count); for (int i = 0; i < io_boards.size(); i++) io_boards[i]->maple_serialize(data, total_size); return true ; } virtual bool maple_unserialize(void **data, unsigned int *total_size) { LIBRETRO_US(crazy_mode); LIBRETRO_US(jvs_repeat_request); LIBRETRO_US(jvs_receive_length); LIBRETRO_US(jvs_receive_buffer); // (a state cannot have more waiting on a channel than a channel holds) for (int i = 0; i < 32; i++) if (jvs_receive_length[i] > sizeof(jvs_receive_buffer[0])) jvs_receive_length[i] = 0; // (the prototype's own firmware is one of those that set crazy_mode; the BIOS's, before it, is not) hotd2p = crazy_mode && !strcmp(naomi_game_id, "hotd2p"); create_io_boards(); /* How many boards there are is written as the build's own size_t: * eight bytes by a 64-bit build, four by a 32-bit one. The four * more are zeroes. Where a 32-bit build's state goes straight on, * the next byte is the first board's address on the bus or, with no * board, the device on the next port: never 0. So a state of either * build is read by both. (Each took the other's for its own: the * boards and every device after them were read four bytes out.) */ u32 board_count = 0; u32 count_high = 0; LIBRETRO_US(board_count); if (LIBRETRO_US(count_high) && count_high != 0) { *data = (u8 *)*data - sizeof(count_high); *total_size -= sizeof(count_high); } /* The boards are this cabinet's, how many the state has is the * state's to say: one it has and the cabinet has not is read past * (what a board keeps: jvs_io_board::maple_serialize()), and there * are 31 addresses on the bus. */ for (u32 i = 0; i < board_count && i < 31; i++) { if (i < io_boards.size()) io_boards[i]->maple_unserialize(data, total_size); else { u8 skipped[sizeof(u8) + sizeof(bool)]; LIBRETRO_US(skipped); } } return true; } }; /* The I/O board's program on its way, for naomi_serialize_v23(). */ void naomi_jvs_serialize_v23(void **data, unsigned int *total_size) { LIBRETRO_SA(jvs_firmware, sizeof(jvs_firmware)); } bool naomi_jvs_unserialize_v23(void **data, unsigned int *total_size) { return LIBRETRO_USA(jvs_firmware, sizeof(jvs_firmware)); } /* A reset: a program that was on its way to the board is sent again from * its start, and what had come of it is not to be taken for part of it. */ void maple_jvs_firmware_reset(void) { memset(jvs_firmware, 0, sizeof(jvs_firmware)); } u8 jvs_io_board::board_output0(u32 node) { return node >= 1 && node <= parent->io_boards.size() ? parent->io_boards[node - 1]->output0 : 0; } u16 jvs_io_board::read_analog_axis(int player_num, int player_axis, bool inverted) { u16 v; switch (player_axis) { case 0: v = joyx[player_num]; break; case 1: v = joyy[player_num]; break; case 2: v = joyrx[player_num]; break; case 3: v = joyry[player_num]; break; case 4: v = rt[player_num]; break; case 5: v = lt[player_num]; break; default: return 0x8000; } return (inverted ? 0xff - v : v) << 8; } /* Where the rotary encoders are: one for each of the two ways each of the * four mice moves. A game goes by how far one has turned since it last * looked, so they are in the save state, from V21. */ static f32 rotx[4]; static f32 roty[4]; u16 jvs_io_board::read_rotary_encoder(f32 &encoder_value, f32 delta_value) { encoder_value = fmod(encoder_value + delta_value, 65536.f); while (encoder_value < 0) encoder_value += 65536.f; return (u16)(encoder_value + 0.5); } #define JVS_OUT(b) buffer_out[length++] = b #define JVS_STATUS1() JVS_OUT(1) /* A board's answer is 255 bytes at the most, its sum, the last of them, * included. How many inputs a request asks for is the game's to say: * JVS_FIT() is how many of @n, at two bytes each, there is still room for. */ #define JVS_OUT_MAX 254 #define JVS_FIT(n) ((u32)(n) * 2 > JVS_OUT_MAX - length ? (JVS_OUT_MAX - length) / 2 : (u32)(n)) u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_out) { u8 jvs_cmd = buffer_in[0]; if (jvs_cmd == 0xF0) // JVS reset // Nothing to do return 0; if (jvs_cmd == 0xF1 && (length_in < 2 || buffer_in[1] != node_id)) // Not for us return 0; u32 length = 0; u8 calc_crc = 0; JVS_OUT(0xE0); // sync JVS_OUT(0); // master node id u8& jvs_length = buffer_out[length++]; switch (jvs_cmd) { case 0xF1: // set board address JVS_STATUS1(); // status JVS_STATUS1(); // report JVS_OUT(5); // ? LOGJVS("JVS Node %d address assigned\n", node_id); break; case 0x10: // Read ID data JVS_STATUS1(); // status JVS_STATUS1(); // report for (const char *p = get_id(); *p != 0; ) JVS_OUT(*p++); JVS_OUT(0); break; case 0x11: // Get command format version JVS_STATUS1(); JVS_STATUS1(); JVS_OUT(0x11); // 1.1 break; case 0x12: // Get JAMMA VIDEO version JVS_STATUS1(); JVS_STATUS1(); JVS_OUT(0x20); // 2.0 break; case 0x13: // Get communication version JVS_STATUS1(); JVS_STATUS1(); JVS_OUT(0x10); // 1.0 break; case 0x14: // Get slave features JVS_STATUS1(); JVS_STATUS1(); JVS_OUT(1); // Digital inputs JVS_OUT(player_count); JVS_OUT(digital_in_count); JVS_OUT(0); if (coin_input_count > 0) { JVS_OUT(2); // Coin inputs JVS_OUT(coin_input_count); JVS_OUT(0); JVS_OUT(0); } if (analog_count > 0) { JVS_OUT(3); // Analog inputs JVS_OUT(analog_count); // channel count JVS_OUT(0x10); // 16 bits per channel, 0: unknown JVS_OUT(0); } if (encoder_count > 0) { JVS_OUT(4); // Rotary encoders JVS_OUT(encoder_count); JVS_OUT(0); JVS_OUT(0); } if (light_gun_count > 0) { JVS_OUT(6); // Light gun JVS_OUT(16); // X bits JVS_OUT(16); // Y bits JVS_OUT(light_gun_count); } JVS_OUT(0x12); // General output driver JVS_OUT(output_count); JVS_OUT(0); JVS_OUT(0); JVS_OUT(0); // End of list break; case 0x15: // Master board ID JVS_STATUS1(); JVS_STATUS1(); break; case 0x70: LOGJVS("JVS 0x70: %02x %02x %02x %02x", buffer_in[1], buffer_in[2], buffer_in[3], buffer_in[4]); init_in_progress = true; JVS_STATUS1(); JVS_STATUS1(); if (buffer_in[2] == 3) { JVS_OUT(0x10); for (int i = 0; i < 16; i++) JVS_OUT(0x7f); if (buffer_in[4] == 0x10 || buffer_in[4] == 0x11) init_in_progress = false; } else { JVS_OUT(2); JVS_OUT(3); JVS_OUT(1); } break; default: if ((jvs_cmd >= 0x20 && jvs_cmd <= 0x38) || (jvs_cmd == 0x74 && printer_present())) // Read inputs and more { LOGJVS("JVS Node %d: ", node_id); PlainJoystickState pjs; parent->config->GetInput(&pjs); JVS_STATUS1(); // status for (int cmdi = 0; cmdi < length_in; ) { // (what there is no room left to answer is not answered: five bytes is the most a request takes before its inputs) if (length > JVS_OUT_MAX - 5) break; switch (buffer_in[cmdi]) { case 0x20: // Read digital input { JVS_STATUS1(); // report byte LOGJVS("btns "); u8 global_btns = 0; if (read_test_switch()) // Global test button global_btns |= 0x80; LOGJVS("glob %02x ", global_btns); JVS_OUT(global_btns); // test, tilt1, tilt2, tilt3, unused, unused, unused, unused u32 next_keycode = 0; const int players = JVS_FIT(buffer_in[cmdi + 1]); for (int player = 0; player < players; player++) { u32 keycode = read_digital_in(first_player + player); if (naomi_game_inputs != NULL) { u32 new_keycode = 0; u32 own_keycode = 0; // switches read as others of the player's own for (int i = 0; naomi_game_inputs->buttons[i].mask != 0; i++) { const ButtonDescriptor& button = naomi_game_inputs->buttons[i]; if (button.target == 0xffffffff) { // a switch that is always on if (button.p2_mask == 0) keycode |= button.mask; else if (first_player + player == 0) new_keycode |= button.p2_mask; } else if ((button.mask & keycode) != 0 && button.p2_mask != 0) { new_keycode |= button.p2_mask; keycode &= ~button.mask; } else if ((button.mask & keycode) != 0 && button.target != 0) { own_keycode |= button.target; keycode &= ~button.mask; } } keycode |= own_keycode; keycode |= next_keycode; next_keycode = new_keycode; } keycode = wired_inputs(first_player + player, keycode); LOGJVS("P%d %02x ", first_player + player + 1, (keycode >> 8) & 0xFF); JVS_OUT(keycode >> 8); if (buffer_in[cmdi + 2] > 1) { LOGJVS("%02x ", keycode & 0xFF); JVS_OUT(keycode); } } cmdi += 3; } break; case 0x21: // Read coins { JVS_STATUS1(); // report byte LOGJVS("coins "); const int slots = JVS_FIT(buffer_in[cmdi + 1]); for (int slot = 0; slot < slots; slot++) { if (slot < ARRAY_SIZE(coin_count) && first_player + slot < 4) { if (!(kcode[first_player + slot] & NAOMI_COIN_KEY)) { if (!coin_chute[slot]) { coin_chute[slot] = true; coin_count[slot]++; } } else coin_chute[slot] = false; LOGJVS("0:%d ", coin_count[slot]); JVS_OUT((coin_count[slot] >> 8) & 0x3F); // status (2 highest bits, 0: normal), coin count MSB JVS_OUT(coin_count[slot]); // coin count LSB } else { LOGJVS("%d:0 ", slot); JVS_OUT(0); JVS_OUT(0); } } cmdi += 2; } break; case 0x22: // Read analog inputs { JVS_STATUS1(); // report byte int axis = 0; const int axes = JVS_FIT(buffer_in[cmdi + 1]); LOGJVS("ana "); if (lightgun_as_analog) { // Death Crimson / Confidential Mission while (axis < axes && first_player * 2 + axis < 8) { int player_num = first_player + axis / 2; u16 x = mo_x_abs[player_num] * 0xFFFF / 639 + 0.5f; u16 y = mo_y_abs[player_num] * 0xFFFF / 479 + 0.5f; if (mo_x_abs[player_num] < 0 || mo_x_abs[player_num] > 639 || mo_y_abs[player_num] < 0 || mo_y_abs[player_num] > 479) { x = 0; y = 0; } LOGJVS("P%d x,y:%4x,%4x ", player_num + 1, x, y); JVS_OUT(x >> 8); // X, MSB JVS_OUT(x); // X, LSB axis++; if (axis < axes) { JVS_OUT(y >> 8); // Y, MSB JVS_OUT(y); // Y, LSB axis++; } } } int axes_per_player = 4; if (naomi_game_inputs != NULL) { int i = 0; while (naomi_game_inputs->axes[i].name != NULL) i++; if (i == 0) axes_per_player = 2; // Default else axes_per_player = i; } if (settings.mapping.JammaSetup == JVS_DualIOBoards4P && !parent->cabinet_order) // Hack for Ring Out 4x4 axes_per_player = 2; for (; axis < axes; axis++) { int player_num = first_player + axis / axes_per_player; int player_axis = axis % axes_per_player; u16 axis_value = 0x8000; if (player_num < 4) { bool inverted = false; bool half = false; bool unmapped_half = false; if (naomi_game_inputs != NULL) { const AxisDescriptor& desc = naomi_game_inputs->axes[player_axis]; inverted = desc.inverted; half = desc.name != NULL && desc.type == Half; // a pedal or lever no stick or trigger is for is let go, not half way unmapped_half = half && desc.axis > 5; player_axis = desc.axis; } axis_value = unmapped_half ? 0 : read_analog_axis(player_num, player_axis, inverted); // King of Route 66 takes a pedal at exactly half way for the race won if (half && axis_value == 0x8000) axis_value = 0x8100; } LOGJVS("P%d.%d:%4x ", player_num + 1, player_axis + 1, axis_value); JVS_OUT(axis_value >> 8); JVS_OUT(axis_value); } cmdi += 2; } break; case 0x23: // Read rotary encoders { JVS_STATUS1(); // report byte LOGJVS("rotenc "); // (there are four mice, an encoder for each of the two ways one moves) int chans = JVS_FIT(buffer_in[cmdi + 1]); if (chans > 8) chans = 8; turn_encoders(chans); for (int chan = 0; chan < chans; chan++) { u16 v; if (chan & 1) { v = read_rotary_encoder(roty[chan / 2], -mo_y_delta[chan / 2] / 3.f); mo_y_delta[chan / 2] = 0; } else { v = read_rotary_encoder(rotx[chan / 2], mo_x_delta[chan / 2] / 3.f); mo_x_delta[chan / 2] = 0; } LOGJVS("%d:%4x ", chan, v); JVS_OUT(v >> 8); // MSB JVS_OUT(v); // LSB } cmdi += 2; } break; case 0x25: // Read screen pos inputs { JVS_STATUS1(); // report byte u32 channel = buffer_in[cmdi + 1] - 1; int player_num = first_player + channel; // (guns are numbered from 1: number 0 is no player either) if ((u32)player_num >= 4) { LOGJVS("P%d lightgun inv ", player_num + 1); JVS_OUT(0xFF); JVS_OUT(0xFF); JVS_OUT(0xFF); JVS_OUT(0xFF); } else { //u16 x = mo_x_abs[player_num] * 0xFFFF / 639 + 0.5f; //u16 y = (479 - mo_y_abs[player_num]) * 0xFFFF / 479 + 0.5f; s16 x, y; read_screen_position(player_num, x, y); LOGJVS("P%d lightgun %4x,%4x ", player_num + 1, x, y); JVS_OUT(x >> 8); // X, MSB JVS_OUT(x); // X, LSB JVS_OUT(y >> 8); // Y, MSB JVS_OUT(y); // Y, LSB } cmdi += 2; } break; case 0x32: // switched outputs LOGJVS("output(%d) %x", buffer_in[cmdi + 1], buffer_in[cmdi + 2]); if (buffer_in[cmdi + 1] > 0) output0 = buffer_in[cmdi + 2]; write_digital_out(buffer_in[cmdi + 1], &buffer_in[cmdi + 2]); JVS_STATUS1(); // report byte cmdi += buffer_in[cmdi + 1] + 2; break; case 0x33: // analog outputs: not switches, and nothing here has any LOGJVS("analog output(%d) %x", buffer_in[cmdi + 1], buffer_in[cmdi + 2]); JVS_STATUS1(); // report byte cmdi += buffer_in[cmdi + 1] + 2; break; case 0x30: // substract coin // (the chute's number is the game's: one there is, or nothing is done) if (buffer_in[cmdi + 1] >= 1 && buffer_in[cmdi + 1] <= ARRAY_SIZE(coin_count)) coin_count[buffer_in[cmdi + 1] - 1] -= (buffer_in[cmdi + 2] << 8) + buffer_in[cmdi + 3]; JVS_STATUS1(); // report byte cmdi += 4; break; case 0x74: // the board's serial port, which a printer is on (Touch de Uno) if (!printer_present()) goto unknown_input; { // (no more than the request has) const u32 len = std::min(buffer_in[cmdi + 1], length_in - cmdi - 1); for (u32 i = 0; i < len; i++) printer_print(buffer_in[cmdi + 2 + i]); cmdi += len + 2; } JVS_STATUS1(); // report // 00 hardware error, 01 head up error, 02 Vp Volt error, 03 auto cutter error, // 04 head temp error, 3* paper end error JVS_OUT(0xf); // printer ok break; default: unknown_input: DEBUG_LOG(MAPLE, "JVS: Unknown input type %x", buffer_in[cmdi]); JVS_OUT(2); // report byte: command error cmdi = length_in; // Ignore subsequent commands break; } } } else if (length_in == 0) { // A request with nothing in it has nothing wrong with it: the // board says so, and has nothing to report JVS_STATUS1(); } else { INFO_LOG(MAPLE, "JVS: Unknown JVS command %x", jvs_cmd); JVS_OUT(2); // Unknown command } break; } jvs_length = length - 2; for (int i = 1; i < length; i++) calc_crc = ((calc_crc + buffer_out[i]) & 0xFF); JVS_OUT(calc_crc); LOGJVS("CRC %x", calc_crc); return length; } bool jvs_io_board::maple_serialize(void **data, unsigned int *total_size) { LIBRETRO_S(node_id); LIBRETRO_S(lightgun_as_analog); return true; } bool jvs_io_board::maple_unserialize(void **data, unsigned int *total_size) { LIBRETRO_US(node_id); LIBRETRO_US(lightgun_as_analog); return true ; } /* What the I/O boards have had in a save state since V21, after everything * older. It is the same size whatever the cabinet has, and whether or not * its boards have been made yet (they are when the game first talks to * them): room for two boards, which is the most a cabinet has, and the * eight rotary encoders. A board is * u32 coin_count[4] the coins counted in each chute * u8 coin_chute[4] whether each coin switch was closed when last read * u8 drive_board what Wave Runner GP's drive board last answered * u8 init_in_progress * None of it was in a state. A load made the boards anew, with nothing * counted, and the game, which goes by how far a count has moved since it * last looked, saw coins come or go: on every frame with run-ahead, which * loads a state each frame. The switches are kept with the counts: a coin * that is in the chute across a load is otherwise counted twice. */ #define JVS_STATE_BOARDS 2 #define JVS_STATE_BOARD_FLAGS 6 #define JVS_STATE_BOARD_SIZE (4 * sizeof(u32) + JVS_STATE_BOARD_FLAGS) void jvs_io_board::maple_serialize_v21(void **data, unsigned int *total_size) { u8 flags[JVS_STATE_BOARD_FLAGS]; for (int i = 0; i < 4; i++) flags[i] = coin_chute[i]; flags[4] = get_drive_board(); flags[5] = init_in_progress; LIBRETRO_SA(coin_count, 4); LIBRETRO_SA(flags, JVS_STATE_BOARD_FLAGS); } // (a counter and the drive board's byte can be anything; a flag is 0 or 1 whatever byte the state has for it) void jvs_io_board::maple_state_v21(const u32 *coins, const u8 *flags) { for (int i = 0; i < 4; i++) { coin_count[i] = coins[i]; coin_chute[i] = flags[i] != 0; } set_drive_board(flags[4]); init_in_progress = flags[5] != 0; } // The NAOMI's I/O board device, if the machine has one: it is the first port's, and its boards are the ones in the state static maple_naomi_jamma *jvs_state_device() { maple_device *dev = MapleDevices[0][5]; if (dev != NULL && dev->get_device_type() == MDT_NaomiJamma) return (maple_naomi_jamma *)dev; return NULL; } /* The EEPROM read from its file as the machine starts, before a state can * be saved: a state has it then, and not word that it is yet to be read. * Read only when the game first asked, a state saved before that was loaded * into a machine whose file the game had written to since - which run-ahead * does every frame - and the game found what it had not yet written. */ void maple_naomi_eeprom_load(void) { maple_naomi_jamma *jamma = jvs_state_device(); if (jamma != NULL) jamma->load_eeprom(); } void maple_jvs_serialize_v21(void **data, unsigned int *total_size) { static const u8 no_board[JVS_STATE_BOARD_SIZE] = { 0 }; maple_naomi_jamma *jamma = jvs_state_device(); for (u32 i = 0; i < JVS_STATE_BOARDS; i++) { if (jamma != NULL && i < jamma->io_boards.size()) jamma->io_boards[i]->maple_serialize_v21(data, total_size); else LIBRETRO_SA(no_board, JVS_STATE_BOARD_SIZE); } LIBRETRO_SA(rotx, 4); LIBRETRO_SA(roty, 4); } void maple_jvs_unserialize_v21(void **data, unsigned int *total_size) { maple_naomi_jamma *jamma = jvs_state_device(); // (the boards have been made by now: the older part of the state does that) for (u32 i = 0; i < JVS_STATE_BOARDS; i++) { u32 coins[4] = { 0 }; u8 flags[JVS_STATE_BOARD_FLAGS] = { 0 }; LIBRETRO_USA(coins, 4); LIBRETRO_USA(flags, JVS_STATE_BOARD_FLAGS); if (jamma != NULL && i < jamma->io_boards.size()) jamma->io_boards[i]->maple_state_v21(coins, flags); } f32 rot[8] = { 0 }; LIBRETRO_USA(rot, 8); for (int i = 0; i < 8; i++) { // An encoder counts from 0 to 65535 and round again: anything else (not a number, too) is not where one can be if (!(rot[i] >= 0.f && rot[i] < 65536.f)) rot[i] = 0.f; } memcpy(rotx, &rot[0], sizeof(rotx)); memcpy(roty, &rot[4], sizeof(roty)); } /* A state from before V21 has none of this. Its boards are new ones, with * nothing counted, as they have been on every load so far. The drive * board's byte is the one it answers with when it has nothing to say, * which is what the game most likely last saw, and not the 0 of a board * that has not been spoken to yet. The encoders stay where they are. */ void maple_jvs_state_before_v21(void) { static const u32 coins[4] = { 0 }; static const u8 flags[JVS_STATE_BOARD_FLAGS] = { 0, 0, 0, 0, 0xff, 0 }; maple_naomi_jamma *jamma = jvs_state_device(); if (jamma == NULL) return; for (u32 i = 0; i < jamma->io_boards.size(); i++) jamma->io_boards[i]->maple_state_v21(coins, flags); } /* What the I/O boards have had in a save state since V25, after everything * older: a byte for each of the two boards a cabinet can have, which only * Mushiking's uses (whether its test switch was down at the last read), 0 * for any other or no board. 2 bytes. */ void maple_jvs_serialize_v25(void **data, unsigned int *total_size) { maple_naomi_jamma *jamma = jvs_state_device(); u8 boards[JVS_STATE_BOARDS] = { 0 }; for (u32 i = 0; i < JVS_STATE_BOARDS; i++) if (jamma != NULL && i < jamma->io_boards.size()) boards[i] = jamma->io_boards[i]->get_state_v25(); LIBRETRO_SA(boards, JVS_STATE_BOARDS); } bool maple_jvs_unserialize_v25(void **data, unsigned int *total_size) { maple_naomi_jamma *jamma = jvs_state_device(); u8 boards[JVS_STATE_BOARDS] = { 0 }; LIBRETRO_USA(boards, JVS_STATE_BOARDS); for (u32 i = 0; i < JVS_STATE_BOARDS; i++) if (jamma != NULL && i < jamma->io_boards.size()) jamma->io_boards[i]->set_state_v25(boards[i]); return true; } /* A state from before V25: the boards are new ones, as they were on every * load until now (the test switch not seen down). */ void maple_jvs_state_before_v25(void) { maple_naomi_jamma *jamma = jvs_state_device(); if (jamma == NULL) return; for (u32 i = 0; i < jamma->io_boards.size(); i++) jamma->io_boards[i]->set_state_v25(0); } /* What the I/O boards have had in a save state since V27, after everything * older: 6 bytes for each of the two boards a cabinet can have (zeros for * none), 12 bytes. Every board has the first byte of its outputs, which * Crackin'DJ's turntables go by; the boards wired to a drive board (F355 * Challenge, Tokyo Bus Guide, 18 Wheeler) what the drive board is doing, * and 18 Wheeler its shifter. */ #define JVS_STATE_V27_SIZE 6 void maple_jvs_serialize_v27(void **data, unsigned int *total_size) { maple_naomi_jamma *jamma = jvs_state_device(); u8 boards[JVS_STATE_BOARDS][JVS_STATE_V27_SIZE]; memset(boards, 0, sizeof(boards)); for (u32 i = 0; i < JVS_STATE_BOARDS; i++) if (jamma != NULL && i < jamma->io_boards.size()) jamma->io_boards[i]->get_state_v27(boards[i]); LIBRETRO_SA(&boards[0][0], JVS_STATE_BOARDS * JVS_STATE_V27_SIZE); } bool maple_jvs_unserialize_v27(void **data, unsigned int *total_size) { maple_naomi_jamma *jamma = jvs_state_device(); u8 boards[JVS_STATE_BOARDS][JVS_STATE_V27_SIZE]; memset(boards, 0, sizeof(boards)); LIBRETRO_USA(&boards[0][0], JVS_STATE_BOARDS * JVS_STATE_V27_SIZE); // (each board checks its own) for (u32 i = 0; i < JVS_STATE_BOARDS; i++) if (jamma != NULL && i < jamma->io_boards.size()) jamma->io_boards[i]->set_state_v27(boards[i]); return true; } /* A state from before V27: no outputs set, a drive board not yet told to * work, at full power, damper at its middle, in low gear. */ void maple_jvs_state_before_v27(void) { maple_naomi_jamma *jamma = jvs_state_device(); const u8 board[JVS_STATE_V27_SIZE] = { 0, 0, 8, 100, 0, 0 }; if (jamma == NULL) return; for (u32 i = 0; i < jamma->io_boards.size(); i++) jamma->io_boards[i]->set_state_v27(board); } /* The IC card reader/writers of Virtua Fighter 4 and Mushiking: a 838-14245-92 maple to RS232 converter wired to a 838-14243 RFID reader/writer (apparently a Saxa HW210), one on the second and one on the third bus, for the first and second player. The cards are the players': what is on them, whether one is in and whether the game has locked it in are kept here and not in the devices, which a state load makes anew. Each player's card is in a file in the save directory, read when the game is started. */ struct RfidCard { u8 data[128]; u8 d4Seen; u8 inserted; u8 locked; }; static RfidCard rfid_card[2]; static u8 rfid_file[2][128]; static bool rfid_game; static retro_atomic_int_t rfid_save_failed; static const char *rfid_file_suffix(int player) { return player == 0 ? "-p1.card" : "-p2.card"; } static u8 rfid_random(u32& seed) { seed = seed * 1103515245u + 12345u; return seed >> 16; } /* A player's card from its file, or a new one: a Mushiking card with its * serial number and the key made from it, or a blank Virtua Fighter 4 * card with the random bytes the game makes its card number of, from * @seed (not rand(), which anything else in the process can move on). */ static void rfid_load_card(int player, u32& seed) { u8 *card = rfid_file[player]; if (periph_load_file(rfid_file_suffix(player), card, sizeof(rfid_file[0])) != 0) return; if (!strncmp(naomi_game_id, "MKG TKOB", 8)) { static const 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(card, MUSHIKING_CHIP_DATA, sizeof(MUSHIKING_CHIP_DATA)); for (int i = 0; i < 8; i++) card[i] = rfid_random(seed); u32 mask = 0; if (!strcmp(naomi_game_id, "MKG TKOB 2 JPN VER2.001-") // mushik2e || !strcmp(naomi_game_id, "MKG TKOB 4 JPN VER2.000-")) // mushik4e mask = 0x40; card[4] &= ~0xc0; card[4] |= mask; u32 serial1 = (card[4] << 24) | (card[5] << 16) | (card[6] << 8) | card[7]; u32 key = ~serial1; key = ((key >> 4) & 0x0f0f0f0f) | ((key << 4) & 0xf0f0f0f0); card[8] = key >> 24; card[9] = key >> 16; card[10] = key >> 8; card[11] = key; } else { memset(card, 0, sizeof(rfid_file[0])); card[0] = 0x10; card[12] = 4; card[13] = 0x6c; card[127] = 0xff; card[2] = rfid_random(seed); card[4] = rfid_random(seed); card[5] = rfid_random(seed); card[6] = rfid_random(seed); card[7] = rfid_random(seed); } INFO_LOG(NAOMI, "Card P%d initialized", player + 1); } static void rfid_save_card(int player) { memcpy(rfid_file[player], rfid_card[player].data, sizeof(rfid_file[0])); periph_save_file(rfid_file_suffix(player), rfid_file[player], sizeof(rfid_file[0]), &rfid_save_failed); } /* The game is one with the readers (or is not, with @game false): the * cards read from their files, and put in at once with @insert. */ void rfid_init(bool game, bool insert) { rfid_game = game; memset(rfid_card, 0, sizeof(rfid_card)); if (!game) return; u32 seed = (u32)time(0) * 2654435761u; for (int i = 0; i < 2; i++) { rfid_load_card(i, seed); if (insert) insertRfidCard(i); } } // The card is put in, or taken out if it is in and not locked in void insertRfidCard(int playerNum) { if (!rfid_game || playerNum < 0 || playerNum > 1) return; RfidCard& card = rfid_card[playerNum]; if (!card.inserted) { card.inserted = 1; memcpy(card.data, rfid_file[playerNum], sizeof(card.data)); } else if (!card.locked) { card.inserted = 0; memset(card.data, 0, sizeof(card.data)); } } const u8 *getRfidCardData(int playerNum) { if (!rfid_game || playerNum < 0 || playerNum > 1) return NULL; return rfid_card[playerNum].data; } /* The cards in a save state (V24): what is on each, whether it is in and * locked, and whether the game asked for its data. 262 bytes. */ void rfid_serialize_v24(void **data, unsigned int *total_size) { if (!rfid_game) return; for (int i = 0; i < 2; i++) { u8 flags[3] = { rfid_card[i].d4Seen, rfid_card[i].inserted, rfid_card[i].locked }; LIBRETRO_SA(rfid_card[i].data, sizeof(rfid_card[i].data)); LIBRETRO_SA(flags, 3); } } bool rfid_unserialize_v24(void **data, unsigned int *total_size) { if (!rfid_game) return true; for (int i = 0; i < 2; i++) { u8 flags[3] = { 0, 0, 0 }; LIBRETRO_USA(rfid_card[i].data, sizeof(rfid_card[i].data)); if (!LIBRETRO_USA(flags, 3)) return false; rfid_card[i].d4Seen = flags[0] != 0; rfid_card[i].inserted = flags[1] != 0; rfid_card[i].locked = flags[2] != 0; } return true; } // An older state: no card was in, and no reader was there for one void rfid_state_before_v24(void) { memset(rfid_card, 0, sizeof(rfid_card)); } /* What each card out of its reader is - its file, or what the game last * wrote on it - in a state from V30 on (naomi_cards_serialize_v30()), * 256 bytes: a card put in after a state load is the one the state was * saved with. Read past when the game has no readers. */ void rfid_files_serialize_v30(void **data, unsigned int *total_size) { LIBRETRO_SA(&rfid_file[0][0], sizeof(rfid_file)); } bool rfid_files_unserialize_v30(void **data, unsigned int *total_size) { static u8 files[2][128]; if (!LIBRETRO_USA(&files[0][0], sizeof(files))) return false; if (rfid_game) memcpy(rfid_file, files, sizeof(rfid_file)); return true; } struct maple_rfid_reader : maple_base { virtual MapleDeviceType get_device_type() { return MDT_RFIDReaderWriter; } RfidCard& card() { return rfid_card[bus_id == 2 ? 1 : 0]; } u32 status() { // 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 (card().inserted) status &= ~1; if (card().locked) status |= 0x40; return status; } virtual u32 dma(u32 cmd) { RfidCard& c = card(); 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_JVSSelfTest: w32(0); return MDRS_JVSSelfTestReply; case MDC_DeviceReset: case MDC_DeviceKill: return MDRS_DeviceReply; // 90 get status // d0, 91, a0, 91, a1 (read md5 in data, or the data itself after d4), d4, 91: read test // d9 lock // da unlock // d0, 91, then b1 (offset, length in words, two bytes, data) and 91 for each part, c1, 91: write test case 0xD0: c.d4Seen = 0; // FALLTHROUGH case 0x90: case 0x91: case 0xA0: case 0xD4: case 0xC1: w32(status()); if (cmd == 0xd4) c.d4Seen = 1; return 0xfe; case 0xA1: // read card data DEBUG_LOG(MAPLE, "RFID card read (data? %d)", c.d4Seen); w32(status()); // serial0 and serial1 only, unless after D4 wptr(c.data, c.d4Seen ? sizeof(c.data) : 8); return 0xfe; case 0xD9: // lock card c.locked = 1; w32(status()); INFO_LOG(MAPLE, "RFID card %d locked", bus_id); return 0xfe; case 0xDA: // unlock card c.locked = 0; c.inserted = 0; w32(status()); NOTICE_LOG(MAPLE, "RFID card %d unlocked", bus_id); return 0xfe; case 0xB1: // write to card w32(status()); if (r_count() >= 4) { const u32 offset = r8() * 4; u32 size = r8() * 4; r8(); r8(); // (where and how much are the game's: no more than the card has, nor the frame) size = std::min(size, r_count()); if (offset < sizeof(c.data)) { size = std::min(size, sizeof(c.data) - offset); DEBUG_LOG(MAPLE, "RFID card write: offset 0x%x len %d", offset, size); rptr(c.data + offset, size); rfid_save_card(bus_id == 2 ? 1 : 0); } } return 0xfe; case 0xD1: // decrement counter { int counter = r_count() >= 1 ? r8() : 0x03; 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); c.data[19 - counter]--; rfid_save_card(bus_id == 2 ? 1 : 0); w32(status()); return 0xfe; } default: INFO_LOG(MAPLE, "RFID: Unknown Maple command %x", cmd); return MDRE_UnknownCmd; } } virtual u32 RawDma(u32* buffer_in, u32 buffer_in_len, u32* buffer_out) { const u32 cmd = buffer_in[0] & 0xff; if (cmd == MDC_JVSGetId || cmd == MDC_JVSUploadFirmware || cmd == MDC_JVSCommand) { // what the bridge answers with frames of its own u32 out_len = 0; dma_buffer_out = (u8 *)buffer_out; dma_count_out = &out_len; dma_buffer_in = (u8 *)buffer_in + 4; dma_count_in = buffer_in_len - 4; if (cmd == MDC_JVSGetId) { w8(0x83); w8(0x00); w8(0x20); w8(0x07); wstr("315-6149 COPYRIGHT SEGA E", 28); w8(0x83); w8(0x00); w8(0x20); w8(0x05); wstr("NTERPRISES CO,LTD. 1998 ", 28); } else if (cmd == MDC_JVSUploadFirmware && dma_count_in >= 4 && dma_buffer_in[1] != 0xff) { // (the program is for the I/O board's processor, which this has not got) u8 sum = 0; for (u32 i = 0; i < 0x1C && i < dma_count_in; i++) sum += dma_buffer_in[i]; w8(0x80); w8(0); w8(0x20); w8(0x01); w8(sum); w8(0); w8(0); w8(0); w8(MDRS_DeviceReply); w8(0x00); w8(0x20); w8(0x00); } else if (cmd == MDC_JVSUploadFirmware) { w8(MDRS_DeviceReply); w8(0x00); w8(0x20); w8(0x00); } else { w8(MDRE_UnknownCmd); w8(0x00); w8(0x20); w8(0x00); } return out_len; } const u32 len = maple_base::RawDma(buffer_in, buffer_in_len, buffer_out); // The recipient and the sender are not swapped in these replies (VF4 Final Tuned and Mushiking look) if ((buffer_out[0] & 0xff) == 0xfe) { u8 *p = (u8 *)buffer_out; std::swap(p[1], p[2]); } return len; } }; /* The device of @type: made at @at - the memory of one of the same type, * gone, so that a state loaded makes nothing - or, with none, new */ #define MAPLE_MAKE(T) (at != NULL ? (maple_device *)new (at) T() : (maple_device *)new T()) maple_device* maple_CreateAt(MapleDeviceType type, void *at) { maple_device* rv=0; switch(type) { case MDT_SegaController: if (settings.System == DC_PLATFORM_ATOMISWAVE) rv = MAPLE_MAKE(maple_atomiswave_controller); else rv = MAPLE_MAKE(maple_sega_controller); break; case MDT_TwinStick: rv = MAPLE_MAKE(maple_sega_twinstick); break; case MDT_AsciiStick: rv = MAPLE_MAKE(maple_ascii_stick); break; case MDT_Microphone: rv = MAPLE_MAKE(maple_microphone); break; case MDT_SegaVMU: rv = MAPLE_MAKE(maple_sega_vmu); break; case MDT_PurupuruPack: rv = MAPLE_MAKE(maple_sega_purupuru); break; case MDT_Keyboard: rv = MAPLE_MAKE(maple_keyboard); break; case MDT_Mouse: rv = MAPLE_MAKE(maple_mouse); break; case MDT_LightGun: if (settings.System == DC_PLATFORM_ATOMISWAVE) rv = MAPLE_MAKE(atomiswave_lightgun); else rv = MAPLE_MAKE(maple_lightgun); break; case MDT_NaomiJamma: rv = MAPLE_MAKE(maple_naomi_jamma); break; case MDT_RFIDReaderWriter: rv = MAPLE_MAKE(maple_rfid_reader); break; default: ERROR_LOG(MAPLE, "Invalid device type %d", type); die("Invalid maple device type"); break; } return rv; } u32 maple_dev_dma(maple_device *dev, u32 *in, u32 in_len, u32 *out) { return dev->RawDma(in, in_len, out); } bool maple_dev_has_slots(maple_device *dev) { return maple_HasSlots(dev->get_device_type()); } bool maple_dev_lightgun_pos(maple_device *dev) { return dev->get_lightgun_pos(); } void maple_dev_flush_save(maple_device *dev) { dev->FlushSave(); } maple_device* maple_Create(MapleDeviceType type) { return maple_CreateAt(type, NULL); }