Files
libretroadmin 50b60907e7 C89: the cache controller and the AICA interface
ccn and aica_if become C. CCN_QACR_write() takes its queue as an
argument; the G2 DMA channels' templates are one table of their
registers and interrupts, looked up by the register written. CCR's
cache invalidations go through sh4_icache_invalidate() and
sh4_ocache_invalidate().

mmu.h and blockmanager.h get C parts, with their templates, reference
parameters and the block classes behind __cplusplus; sh4_if.h declares
sh4_cpu for C. The AICA interface's state is declared in aica_if.h, where
serialize.cpp and maple_devs.cpp declared it themselves.

tools/aica/g2dma_run.sh checks that each expansion DMA channel moves its
own transfer, leaves its own registers as a finished transfer does and
raises its own interrupt, touching no other; the C++ channels pass it
too. It runs in the Headless workflow, with the sanitizers as well.
2026-11-12 19:20:06 +01:00

4905 lines
135 KiB
C++

#include "types.h"
#include <new>
#include <streams/file_stream.h>
#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 <math.h>
#include <time.h>
#include <algorithm>
#include <encodings/deflate.h>
#include <encodings/xxh32.h>
#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<u16>(0xefff, std::max<u16>(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<jvs_io_board *> 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 <n>, 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<u32>(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<u32>(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;
}
maple_device* maple_Create(MapleDeviceType type)
{
return maple_CreateAt(type, NULL);
}