flycast/core/hw/maple/maple_jvs.cpp

1693 lines
39 KiB
C++

/*
Copyright 2020 flyinghead
This file is part of flycast.
flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include <array>
#include "maple_devs.h"
#include "stdclass.h"
#include "cfg/cfg.h"
#include "hw/naomi/naomi_cart.h"
#include "input/gamepad_device.h"
#include <xxhash.h>
#define LOGJVS(...) DEBUG_LOG(JVS, __VA_ARGS__)
u8 EEPROM[0x100];
bool EEPROM_loaded = false;
void load_naomi_eeprom()
{
if (!EEPROM_loaded)
{
EEPROM_loaded = true;
std::string nvmemSuffix = cfgLoadStr("net", "nvmem", "");
std::string eeprom_file = get_game_save_prefix() + nvmemSuffix + ".eeprom";
FILE* f = nowide::fopen(eeprom_file.c_str(), "rb");
if (f)
{
std::fread(EEPROM, 1, 0x80, f);
std::fclose(f);
DEBUG_LOG(MAPLE, "Loaded EEPROM from %s", eeprom_file.c_str());
}
else if (naomi_default_eeprom != NULL)
{
DEBUG_LOG(MAPLE, "Using default EEPROM file");
memcpy(EEPROM, naomi_default_eeprom, 0x80);
}
else
DEBUG_LOG(MAPLE, "EEPROM file not found at %s and no default found", eeprom_file.c_str());
}
}
static u32 naomi_button_mapping[32] = {
NAOMI_SERVICE_KEY, // DC_BTN_C
NAOMI_BTN1_KEY, // DC_BTN_B
NAOMI_BTN0_KEY, // DC_BTN_A
NAOMI_START_KEY, // DC_BTN_START
NAOMI_UP_KEY, // DC_DPAD_UP
NAOMI_DOWN_KEY, // DC_DPAD_DOWN
NAOMI_LEFT_KEY, // DC_DPAD_LEFT
NAOMI_RIGHT_KEY, // DC_DPAD_RIGHT
NAOMI_TEST_KEY, // DC_BTN_Z
NAOMI_BTN3_KEY, // DC_BTN_Y
NAOMI_BTN2_KEY, // DC_BTN_X
NAOMI_COIN_KEY, // DC_BTN_D
NAOMI_BTN4_KEY, // DC_DPAD2_UP
NAOMI_BTN5_KEY, // DC_DPAD2_DOWN
NAOMI_BTN6_KEY, // DC_DPAD2_LEFT
NAOMI_BTN7_KEY, // DC_DPAD2_RIGHT
0, // DC_BTN_RELOAD
NAOMI_BTN8_KEY,
};
extern u32 awave_button_mapping[32];
extern u32 awavelg_button_mapping[32];
const char *GetCurrentGameButtonName(DreamcastKey key)
{
if (NaomiGameInputs == nullptr || key == EMU_BTN_NONE)
return nullptr;
u32 pos = 0;
u32 val = (u32)key;
while ((val & 1) == 0)
{
pos++;
val >>= 1;
}
u32 arcade_key;
if (settings.platform.system == DC_PLATFORM_NAOMI)
{
if (pos >= ARRAY_SIZE(naomi_button_mapping))
return nullptr;
arcade_key = naomi_button_mapping[pos];
}
else
{
if (pos >= ARRAY_SIZE(awave_button_mapping))
return nullptr;
const u32* mapping = settings.input.JammaSetup == JVS::LightGun ? awavelg_button_mapping : awave_button_mapping;
arcade_key = mapping[pos];
}
for (int i = 0; NaomiGameInputs->buttons[i].source != 0; i++)
if (NaomiGameInputs->buttons[i].source == arcade_key)
return NaomiGameInputs->buttons[i].name;
return nullptr;
}
const char *GetCurrentGameAxisName(DreamcastKey axis)
{
if (NaomiGameInputs == nullptr || axis == EMU_BTN_NONE)
return nullptr;
for (int i = 0; NaomiGameInputs->axes[i].name != nullptr; i++)
{
DreamcastKey cur_axis;
switch (NaomiGameInputs->axes[i].axis)
{
case 0:
cur_axis = DC_AXIS_X;
break;
case 1:
cur_axis = DC_AXIS_Y;
break;
case 2:
cur_axis = DC_AXIS_X2;
break;
case 3:
cur_axis = DC_AXIS_Y2;
break;
case 4:
cur_axis = DC_AXIS_RT;
break;
case 5:
cur_axis = DC_AXIS_LT;
break;
default:
cur_axis = EMU_BTN_NONE;
break;
}
if (cur_axis == axis)
return NaomiGameInputs->axes[i].name;
}
return nullptr;
}
/*
* Sega JVS I/O board
*/
static bool old_coin_chute[4];
static int coin_count[4];
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;
this->first_player = first_player;
init_mappings();
}
virtual ~jvs_io_board() = default;
u32 handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_out);
bool serialize(void **data, unsigned int *total_size);
bool unserialize(void **data, unsigned int *total_size, serialize_version_enum version);
bool lightgun_as_analog = false;
protected:
virtual const char *get_id() = 0;
virtual u16 read_analog_axis(int player_num, int player_axis, bool inverted);
virtual void read_digital_in(u16 *v)
{
memset(v, 0, sizeof(u16) * 4);
for (u32 player = first_player; player < ARRAY_SIZE(kcode); player++)
{
u32 keycode = ~kcode[player];
if (keycode == 0)
continue;
if (keycode & DC_BTN_RELOAD)
keycode |= DC_BTN_A;
// P1 mapping (only for P2)
if (player == 1)
{
for (u32 i = 0; i < p1_mapping.size(); i++)
if ((keycode & (1 << i)) != 0)
v[0] |= p1_mapping[i];
}
// normal mapping
for (u32 i = 0; i < cur_mapping.size(); i++)
if ((keycode & (1 << i)) != 0)
v[player - first_player] |= cur_mapping[i];
// P2 mapping (only for P1)
if (player == 0)
{
bool found = false;
for (u32 i = 0; i < p2_mapping.size(); i++)
{
if ((keycode & (1 << i)) != 0)
v[1] |= p2_mapping[i];
if (p2_mapping[i] != 0)
found = true;
}
if (found)
// if there are P2 mappings for P1 then there's only 1 player
break;
}
}
}
virtual void write_digital_out(int count, u8 *data) { }
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;
private:
void init_mappings()
{
p1_mapping.fill(0);
p2_mapping.fill(0);
memcpy(&cur_mapping[0], naomi_button_mapping, sizeof(naomi_button_mapping));
if (NaomiGameInputs == nullptr)
// Use default mapping
return;
for (int i = 0; NaomiGameInputs->buttons[i].source != 0; i++)
{
u32 source = NaomiGameInputs->buttons[i].source;
for (u32 j = 0; j < ARRAY_SIZE(naomi_button_mapping); j++)
{
if (naomi_button_mapping[j] == source)
{
p1_mapping[j] = NaomiGameInputs->buttons[i].p1_target;
p2_mapping[j] = NaomiGameInputs->buttons[i].p2_target;
u32 target = NaomiGameInputs->buttons[i].target;
if (target == 0 && p1_mapping[j] == 0 && p2_mapping[j] == 0)
target = source;
cur_mapping[j] = target;
break;
}
}
}
}
u8 node_id;
maple_naomi_jamma *parent;
u8 first_player;
std::array<u32, 32> cur_mapping;
std::array<u32, 32> p1_mapping;
std::array<u32, 32> p2_mapping;
};
// 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:
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;
}
};
// 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:
const char *get_id() override { return "SEGA ENTERPRISES,LTD.;837-13938 ENCORDER BD ;Ver0.01;99/08"; }
};
// Sega Marine Fishing, 18 Wheeler (TODO)
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:
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;
}
};
// 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:
void read_digital_in(u16 *v) override
{
jvs_837_13844::read_digital_in(v);
// The drive board RX0-7 is connected to the following player inputs
v[0] |= NAOMI_BTN2_KEY | NAOMI_BTN3_KEY | NAOMI_BTN4_KEY | NAOMI_BTN5_KEY;
if (drive_board & 16)
v[0] &= ~NAOMI_BTN5_KEY;
if (drive_board & 32)
v[0] &= ~NAOMI_BTN4_KEY;
if (drive_board & 64)
v[0] &= ~NAOMI_BTN3_KEY;
if (drive_board & 128)
v[0] &= ~NAOMI_BTN2_KEY;
v[1] |= NAOMI_BTN2_KEY | NAOMI_BTN3_KEY | NAOMI_BTN4_KEY | NAOMI_BTN5_KEY;
if (drive_board & 1)
v[1] &= ~NAOMI_BTN5_KEY;
if (drive_board & 2)
v[1] &= ~NAOMI_BTN4_KEY;
if (drive_board & 4)
v[1] &= ~NAOMI_BTN3_KEY;
if (drive_board & 8)
v[1] &= ~NAOMI_BTN2_KEY;
}
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));
}
}
private:
u8 drive_board = 0;
};
// 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:
const char *get_id() override { return "namco ltd.;JYU-PCB;Ver1.00;JPN,2Coins 2Guns"; }
};
// 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:
const char *get_id() override { return "namco ltd.;FCB;Ver1.0;JPN,Touch Panel & Multipurpose"; }
u16 read_analog_axis(int player_num, int player_axis, bool inverted) override {
if (init_in_progress)
return 0;
player_num = std::min(player_num, (int)ARRAY_SIZE(mo_x_abs));
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:
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:
const char *get_id() override { return "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"; }
void read_digital_in(u16 *v) override
{
jvs_io_board::read_digital_in(v);
// main button
v[0] = ((v[0] & NAOMI_BTN0_KEY) << 6) // start
| ((v[1] & NAOMI_BTN0_KEY) << 2) // left
| ((v[2] & NAOMI_BTN0_KEY) << 1) // right
| ((v[3] & NAOMI_BTN0_KEY) >> 1) // btn1
// enter
| ((v[0] & NAOMI_BTN3_KEY) << 3) // btn0
// service menu
| (v[0] & (NAOMI_TEST_KEY | 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 std::min(0xff, 0x80 - axis_x) << 8;
}
u16 read_joystick_y(int joy_num)
{
return std::min(0xff, 0x80 - axis_y) << 8;
}
u16 read_analog_axis(int player_num, int player_axis, bool inverted) override {
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);
case 4:
return read_joystick_x(2);
case 5:
return read_joystick_y(2);
case 6:
return read_joystick_x(3);
case 7:
return read_joystick_y(3);
case 8:
return rt[0] << 8;
case 9:
return rt[1] << 8;
case 10:
return rt[2] << 8;
case 11:
return rt[3] << 8;
default:
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:
const char *get_id() override { return "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551 ;Ver1.00;98/10"; }
void read_digital_in(u16 *v) override
{
jvs_io_board::read_digital_in(v);
for (u32 player = 0; player < player_count; player++)
{
u8 trigger = rt[player] >> 2;
// Ball button
v[player] = ((trigger & 0x20) << 3) | ((trigger & 0x10) << 5) | ((trigger & 0x08) << 7)
| ((trigger & 0x04) << 9) | ((trigger & 0x02) << 11) | ((trigger & 0x01) << 13)
// other buttons
| (v[player] & (NAOMI_SERVICE_KEY | NAOMI_TEST_KEY | NAOMI_START_KEY))
| ((v[player] & 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 + 128) << 8;
}
u16 read_joystick_y(int joy_num)
{
return std::min(0xff, 0x80 - axis_y) << 8;
}
u16 read_analog_axis(int player_num, int player_axis, bool inverted) override {
switch (player_axis)
{
case 0:
return read_joystick_x(0);
case 1:
return read_joystick_y(0);
case 4:
return read_joystick_x(1);
case 5:
return read_joystick_y(1);
default:
return 0x8000;
}
}
private:
s8 axis_y = 0;
};
maple_naomi_jamma::maple_naomi_jamma()
{
switch (settings.input.JammaSetup)
{
case JVS::Default:
default:
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:
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));
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));
break;
case JVS::WaveRunnerGP:
io_boards.emplace_back(new jvs_837_13844_wrungp(1, this));
break;
}
}
maple_naomi_jamma::~maple_naomi_jamma()
{
EEPROM_loaded = false;
}
void maple_naomi_jamma::send_jvs_message(u32 node_id, u32 channel, u32 length, u8 *data)
{
if (node_id - 1 < io_boards.size())
{
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 maple_naomi_jamma::send_jvs_messages(u32 node_id, u32 channel, bool use_repeat, u32 length, u8 *data, bool repeat_first)
{
u8 temp_buffer[256];
if (data)
{
memcpy(temp_buffer, data, length);
}
if (node_id == ALL_NODES)
{
for (u32 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];
if (use_repeat && repeat_len > 0)
{
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;
}
send_jvs_message(node_id, channel, length, temp_buffer);
}
}
bool maple_naomi_jamma::receive_jvs_messages(u32 channel)
{
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 false;
}
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]);
dma_buffer_out += dword_length * 4 - 0x10 - 3;
*dma_count_out += dword_length * 4 - 0x10 - 3;
jvs_receive_length[channel] = 0;
return true;
}
void maple_naomi_jamma::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)
{
if (dma_buffer_in[1] > 31 && dma_buffer_in[1] != 0xff && dma_count_in >= 8) // TODO what is this?
{
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];
}
}
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
receive_jvs_messages(dma_buffer_in[1]);
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;
u32 cmd_count = dma_buffer_in[6];
u32 idx = 7;
for (u32 i = 0; i < cmd_count; i++)
{
node_id = dma_buffer_in[idx];
len = dma_buffer_in[idx + 1];
cmd = &dma_buffer_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
{
load_naomi_eeprom();
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);
memcpy(EEPROM + address, dma_buffer_in + 4, size);
std::string nvmemSuffix = cfgLoadStr("net", "nvmem", "");
std::string eeprom_file = get_game_save_prefix() + nvmemSuffix + ".eeprom";
FILE* f = nowide::fopen(eeprom_file.c_str(), "wb");
if (f)
{
std::fwrite(EEPROM, 1, 0x80, f);
std::fclose(f);
INFO_LOG(MAPLE, "Saved EEPROM to %s", eeprom_file.c_str());
}
else
WARN_LOG(MAPLE, "EEPROM SAVE FAILED to %s", eeprom_file.c_str());
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_naomi_eeprom();
//printf("EEprom READ\n");
int address = dma_buffer_in[1];
//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)
w8(0xff); // in(5) bit0: 1=VGA, 0=NTSCi
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;
default:
INFO_LOG(MAPLE, "JVS: Unknown 0x86 sub-command %x", subcode);
w8(MDRE_UnknownCmd);
w8(0x00);
w8(0x20);
w8(0x00);
break;
}
}
u32 maple_naomi_jamma::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
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:
{
static u8 *ram;
if (ram == NULL)
ram = (u8 *)calloc(0x10000, 1);
if (dma_buffer_in[1] == 0xff)
{
u32 hash = XXH32(ram, 0x10000, 0);
LOGJVS("JVS Firmware hash %08x\n", hash);
if (hash == 0xa7c50459 // CT
|| hash == 0xae841e36) // HOTD2
crazy_mode = true;
else
crazy_mode = false;
#ifdef DUMP_JVS_FW
FILE *fw_dump;
char filename[128];
for (int i = 0; ; i++)
{
sprintf(filename, "z80_fw_%d.bin", i);
fw_dump = fopen(filename, "r");
if (fw_dump == NULL)
{
fw_dump = fopen(filename, "w");
INFO_LOG(MAPLE, "Saving JVS firmware to %s", filename);
break;
}
}
if (fw_dump)
{
fwrite(ram, 1, 0x10000, fw_dump);
fclose(fw_dump);
}
#endif
free(ram);
ram = NULL;
for (int i = 0; i < 32; i++)
jvs_repeat_request[i][0] = 0;
return MDRS_DeviceReply;
}
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];
memcpy(ram + addr, &dma_buffer_in[4], xfer_bytes);
u8 sum = 0;
for (int i = 0; i < 0x1C; 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);
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;
}
bool maple_naomi_jamma::serialize(void **data, unsigned int *total_size)
{
maple_base::serialize(data, total_size);
REICAST_S(crazy_mode);
REICAST_S(jvs_repeat_request);
REICAST_S(jvs_receive_length);
REICAST_S(jvs_receive_buffer);
size_t board_count = io_boards.size();
REICAST_S(board_count);
for (u32 i = 0; i < io_boards.size(); i++)
io_boards[i]->serialize(data, total_size);
return true ;
}
bool maple_naomi_jamma::unserialize(void **data, unsigned int *total_size, serialize_version_enum version)
{
maple_base::unserialize(data, total_size, version);
REICAST_US(crazy_mode);
REICAST_US(jvs_repeat_request);
REICAST_US(jvs_receive_length);
REICAST_US(jvs_receive_buffer);
size_t board_count;
REICAST_US(board_count);
for (u32 i = 0; i < board_count; i++)
io_boards[i]->unserialize(data, total_size, version);
return true ;
}
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] + 128) << 8;
break;
case 1:
v = (joyy[player_num] + 128) << 8;
break;
case 2:
v = (joyrx[player_num] + 128) << 8;
break;
case 3:
v = (joyry[player_num] + 128) << 8;
break;
default:
return 0x8000;
}
return inverted ? 0xff00 - v : v;
}
#define JVS_OUT(b) buffer_out[length++] = b
#define JVS_STATUS1() JVS_OUT(1)
u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_out)
{
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;
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) // Read inputs and more
{
LOGJVS("JVS Node %d: ", node_id);
JVS_STATUS1(); // status
for (u32 cmdi = 0; cmdi < length_in; )
{
switch (buffer_in[cmdi])
{
case 0x20: // Read digital input
{
JVS_STATUS1(); // report byte
u16 btns[4];
read_digital_in(btns);
JVS_OUT((btns[0] & NAOMI_TEST_KEY) ? 0x80 : 0x00); // test, tilt1, tilt2, tilt3, unused, unused, unused, unused
LOGJVS("btns ");
for (int player = 0; player < buffer_in[cmdi + 1]; player++)
{
LOGJVS("P%d %02x ", player + 1 + first_player, btns[player] >> 8);
JVS_OUT(btns[player] >> 8);
if (buffer_in[cmdi + 2] == 2)
{
LOGJVS("%02x ", btns[player] & 0xFF);
JVS_OUT(btns[player]);
}
}
cmdi += 3;
}
break;
case 0x21: // Read coins
{
JVS_STATUS1(); // report byte
LOGJVS("coins ");
u32 mask = 0;
for (u32 i = 0; i < ARRAY_SIZE(naomi_button_mapping); i++)
{
if (naomi_button_mapping[i] == NAOMI_COIN_KEY)
{
mask = 1 << i;
break;
}
}
for (int slot = 0; slot < buffer_in[cmdi + 1]; slot++)
{
u32 keycode = ~kcode[first_player + slot];
bool coin_chute = false;
if (keycode & mask)
{
coin_chute = true;
if (!old_coin_chute[first_player + slot])
coin_count[first_player + slot] += 1;
}
old_coin_chute[first_player + slot] = coin_chute;
LOGJVS("%d:%d ", slot + 1 + first_player, coin_count[first_player + slot]);
// status (2 highest bits, 0: normal), coin count MSB
JVS_OUT((coin_count[first_player + slot] >> 8) & 0x3F);
// coin count LSB
JVS_OUT(coin_count[first_player + slot]);
}
cmdi += 2;
}
break;
case 0x22: // Read analog inputs
{
JVS_STATUS1(); // report byte
u32 axis = 0;
LOGJVS("ana ");
if (lightgun_as_analog)
{
for (; axis / 2 < player_count && axis < buffer_in[cmdi + 1]; axis += 2)
{
int playerNum = first_player + axis / 2;
u16 x;
u16 y;
if (mo_x_abs[playerNum] < 0 || mo_x_abs[playerNum] > 639
|| mo_y_abs[playerNum] < 0 || mo_y_abs[playerNum] > 479
|| (kcode[playerNum] & DC_BTN_RELOAD) == 0)
{
x = 0;
y = 0;
}
else
{
x = mo_x_abs[playerNum] * 0xFFFF / 639;
y = mo_y_abs[playerNum] * 0xFFFF / 479;
}
LOGJVS("x,y:%4x,%4x ", x, y);
JVS_OUT(x >> 8); // X, MSB
JVS_OUT(x); // X, LSB
JVS_OUT(y >> 8); // Y, MSB
JVS_OUT(y); // Y, LSB
}
}
u32 player_num = first_player;
u32 player_axis = axis;
for (; axis < buffer_in[cmdi + 1]; axis++, player_axis++)
{
u16 axis_value;
if (NaomiGameInputs != NULL)
{
if (player_axis >= ARRAY_SIZE(NaomiGameInputs->axes)
|| NaomiGameInputs->axes[player_axis].name == NULL)
{
// Next player
player_num++;
player_axis = 0;
}
if (player_num < 4)
{
const AxisDescriptor& axisDesc = NaomiGameInputs->axes[player_axis];
if (axisDesc.type == Half)
{
if (axisDesc.axis == 4)
axis_value = rt[player_num] << 8;
else if (axisDesc.axis == 5)
axis_value = lt[player_num] << 8;
else
axis_value = 0;
if (axisDesc.inverted)
axis_value = 0xff00u - axis_value;
}
else
{
axis_value = read_analog_axis(player_num, axisDesc.axis, axisDesc.inverted);
}
}
else
{
axis_value = 0x8000;
}
}
else
{
axis_value = read_analog_axis(player_num, player_axis, false);
}
LOGJVS("%d:%4x ", axis, axis_value);
JVS_OUT(axis_value >> 8);
JVS_OUT(axis_value);
}
cmdi += 2;
}
break;
case 0x23: // Read rotary encoders
{
JVS_STATUS1(); // report byte
static s16 rotx = 0;
static s16 roty = 0;
// TODO Add more players.
// I can't think of any naomi multiplayer game that uses rotary encoders
rotx += mo_x_delta[first_player] * 5;
roty -= mo_y_delta[first_player] * 5;
mo_x_delta[first_player] = 0;
mo_y_delta[first_player] = 0;
LOGJVS("rotenc ");
for (int chan = 0; chan < buffer_in[cmdi + 1]; chan++)
{
if (chan == 0)
{
LOGJVS("%d:%4x ", chan, rotx & 0xFFFF);
JVS_OUT(rotx >> 8); // MSB
JVS_OUT(rotx); // LSB
}
else if (chan == 1)
{
LOGJVS("%d:%4x ", chan, roty & 0xFFFF);
JVS_OUT(roty >> 8); // MSB
JVS_OUT(roty); // LSB
}
else
{
LOGJVS("%d:%4x ", chan, 0);
JVS_OUT(0x00); // MSB
JVS_OUT(0x00); // LSB
}
}
cmdi += 2;
}
break;
case 0x25: // Read screen pos inputs
{
JVS_STATUS1(); // report byte
// Channel number (1-based) is in jvs_request[cmdi + 1]
int playerNum = first_player + buffer_in[cmdi + 1] - 1;
s16 x;
s16 y;
if ((kcode[playerNum] & DC_BTN_RELOAD) == 0)
{
x = 0;
y = 0;
}
else
{
// specs:
//u16 x = mo_x_abs * 0xFFFF / 639;
//u16 y = (479 - mo_y_abs) * 0xFFFF / 479;
// Ninja Assault:
u32 xr = 0x19d - 0x37;
u32 yr = 0x1fe - 0x40;
x = mo_x_abs[playerNum] * xr / 639 + 0x37;
y = mo_y_abs[playerNum] * yr / 479 + 0x40;
}
LOGJVS("lightgun %4x,%4x ", 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
case 0x33:
LOGJVS("output(%d) %x", buffer_in[cmdi + 1], 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 0x30: // substract coin
if (buffer_in[cmdi + 1] > 0 && first_player + buffer_in[cmdi + 1] - 1 < (int)ARRAY_SIZE(coin_count))
coin_count[first_player + buffer_in[cmdi + 1] - 1] -= (buffer_in[cmdi + 2] << 8) + buffer_in[cmdi + 3];
JVS_STATUS1(); // report byte
cmdi += 4;
break;
default:
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;
}
}
LOGJVS("\n");
}
else
{
INFO_LOG(MAPLE, "JVS: Unknown JVS command %x", jvs_cmd);
JVS_OUT(2); // Unknown command
}
break;
}
jvs_length = length - 2;
u8 calc_crc = 0;
for (u32 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::serialize(void **data, unsigned int *total_size)
{
REICAST_S(node_id);
REICAST_S(lightgun_as_analog);
return true ;
}
bool jvs_io_board::unserialize(void **data, unsigned int *total_size, serialize_version_enum version)
{
REICAST_US(node_id);
REICAST_US(lightgun_as_analog);
return true ;
}