mirror of
https://github.com/holub/mame
synced 2025-10-06 00:54:22 +03:00

* simplified input reading * moved most Famicom controllers to the expansion port (still configurable in the Driver Config menu) because it is more accurate and because it allows to use both the FC keyboard and the controllers in BASIC games * fixed NES paddle emulation which broke some years ago * added FC paddle emulation, see Arkanoid and Chase HQ * added Hori Twin Adapter with correct P3 & P4 Famicom protocol, see e.g. four players games by Technos Japan just a cleanup and the whatsnew entry for previous changes...
901 lines
25 KiB
C
901 lines
25 KiB
C
/*****************************************************************************
|
|
|
|
nes.c
|
|
|
|
Nintendo Entertainment System (Famicom)
|
|
|
|
****************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "crsshair.h"
|
|
#include "cpu/m6502/m6502.h"
|
|
#include "includes/nes.h"
|
|
#include "imagedev/cartslot.h"
|
|
#include "imagedev/flopdrv.h"
|
|
#include "hashfile.h"
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
/* Set to dump info about the inputs to the errorlog */
|
|
#define LOG_JOY 0
|
|
|
|
/* Set to generate prg & chr files when the cart is loaded */
|
|
#define SPLIT_PRG 0
|
|
#define SPLIT_CHR 0
|
|
|
|
/***************************************************************************
|
|
FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
// to be probably removed (it does nothing since a long time)
|
|
int nes_state::nes_ppu_vidaccess( int address, int data )
|
|
{
|
|
return data;
|
|
}
|
|
|
|
void nes_state::machine_reset()
|
|
{
|
|
/* Reset the mapper variables. Will also mark the char-gen ram as dirty */
|
|
if (m_disk_expansion && m_cartslot && !m_cartslot->m_cart)
|
|
m_ppu->set_hblank_callback(ppu2c0x_hblank_delegate(FUNC(nes_state::fds_irq),this));
|
|
else if (m_cartslot)
|
|
m_cartslot->pcb_reset();
|
|
|
|
m_maincpu->reset();
|
|
|
|
memset(m_pad_latch, 0, sizeof(m_pad_latch));
|
|
memset(m_zapper_latch, 0, sizeof(m_zapper_latch));
|
|
m_paddle_latch = 0;
|
|
m_paddle_btn_latch = 0;
|
|
}
|
|
|
|
static void nes_state_register( running_machine &machine )
|
|
{
|
|
nes_state *state = machine.driver_data<nes_state>();
|
|
|
|
state->save_item(NAME(state->m_last_frame_flip));
|
|
|
|
state->save_item(NAME(state->m_fds_motor_on));
|
|
state->save_item(NAME(state->m_fds_door_closed));
|
|
state->save_item(NAME(state->m_fds_current_side));
|
|
state->save_item(NAME(state->m_fds_head_position));
|
|
state->save_item(NAME(state->m_fds_status0));
|
|
state->save_item(NAME(state->m_fds_read_mode));
|
|
state->save_item(NAME(state->m_fds_write_reg));
|
|
state->save_item(NAME(state->m_fds_last_side));
|
|
state->save_item(NAME(state->m_fds_count));
|
|
state->save_item(NAME(state->m_fds_mirroring));
|
|
|
|
state->save_pointer(NAME(state->m_ciram), 0x800);
|
|
|
|
if (state->m_disk_expansion)
|
|
state->save_pointer(NAME(state->m_vram), 0x800);
|
|
|
|
state->save_item(NAME(state->m_pad_latch));
|
|
state->save_item(NAME(state->m_zapper_latch));
|
|
state->save_item(NAME(state->m_paddle_latch));
|
|
state->save_item(NAME(state->m_paddle_btn_latch));
|
|
state->save_item(NAME(state->m_mjpanel_latch));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// machine_start
|
|
//-------------------------------------------------
|
|
|
|
void nes_state::machine_start()
|
|
{
|
|
for (int i = 0; i < 9; i++)
|
|
{
|
|
char str[7];
|
|
sprintf(str, "FCKEY%i", i);
|
|
m_io_fckey[i] = ioport(str);
|
|
}
|
|
for (int i = 0; i < 13; i++)
|
|
{
|
|
char str[9];
|
|
sprintf(str, "SUBKEY%i", i);
|
|
m_io_subkey[i] = ioport(str);
|
|
}
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
char str[5];
|
|
sprintf(str, "PAD%i", i + 1);
|
|
m_io_pad[i] = ioport(str);
|
|
sprintf(str, "MAH%i", i);
|
|
m_io_mahjong[i] = ioport(str);
|
|
}
|
|
|
|
m_io_ctrlsel = ioport("CTRLSEL");
|
|
m_io_exp = ioport("EXP");
|
|
m_io_paddle = ioport("PADDLE");
|
|
m_io_paddle_btn = ioport("PADDLE_BUTTON");
|
|
m_io_cc_left = ioport("CC_LEFT");
|
|
m_io_cc_right = ioport("CC_RIGHT");
|
|
m_io_zapper1_t = ioport("ZAPPER1_T");
|
|
m_io_zapper1_x = ioport("ZAPPER1_X");
|
|
m_io_zapper1_y = ioport("ZAPPER1_Y");
|
|
m_io_zapper2_t = ioport("ZAPPER2_T");
|
|
m_io_zapper2_x = ioport("ZAPPER2_X");
|
|
m_io_zapper2_y = ioport("ZAPPER2_Y");
|
|
|
|
address_space &space = m_maincpu->space(AS_PROGRAM);
|
|
|
|
// CIRAM (Character Internal RAM)
|
|
// NES has 2KB of internal RAM which can be used to fill the 4x1KB banks of PPU RAM at $2000-$2fff
|
|
// Line A10 is exposed to the carts, so that games can change CIRAM mapping in PPU (we emulate this with the set_nt_mirroring
|
|
// function). CIRAM can also be disabled by the game (if e.g. VROM or cart RAM has to be used in PPU...
|
|
m_ciram = auto_alloc_array(machine(), UINT8, 0x800);
|
|
// other pointers got set in the loading routine, because they 'belong' to the cart itself
|
|
|
|
if (m_cartslot && m_cartslot->m_cart)
|
|
{
|
|
// Set up memory handlers
|
|
space.install_read_handler(0x4100, 0x5fff, read8_delegate(FUNC(nes_cart_slot_device::read_l), (nes_cart_slot_device *)m_cartslot));
|
|
space.install_write_handler(0x4100, 0x5fff, write8_delegate(FUNC(nes_cart_slot_device::write_l), (nes_cart_slot_device *)m_cartslot));
|
|
space.install_read_handler(0x6000, 0x7fff, read8_delegate(FUNC(nes_cart_slot_device::read_m), (nes_cart_slot_device *)m_cartslot));
|
|
space.install_write_handler(0x6000, 0x7fff, write8_delegate(FUNC(nes_cart_slot_device::write_m), (nes_cart_slot_device *)m_cartslot));
|
|
space.install_read_bank(0x8000, 0x9fff, "prg0");
|
|
space.install_read_bank(0xa000, 0xbfff, "prg1");
|
|
space.install_read_bank(0xc000, 0xdfff, "prg2");
|
|
space.install_read_bank(0xe000, 0xffff, "prg3");
|
|
space.install_write_handler(0x8000, 0xffff, write8_delegate(FUNC(nes_cart_slot_device::write_h), (nes_cart_slot_device *)m_cartslot));
|
|
|
|
m_cartslot->pcb_start(m_ciram);
|
|
m_cartslot->m_cart->pcb_reg_postload(machine());
|
|
m_ppu->space(AS_PROGRAM).install_readwrite_handler(0, 0x1fff, read8_delegate(FUNC(device_nes_cart_interface::chr_r),m_cartslot->m_cart), write8_delegate(FUNC(device_nes_cart_interface::chr_w),m_cartslot->m_cart));
|
|
m_ppu->space(AS_PROGRAM).install_readwrite_handler(0x2000, 0x3eff, read8_delegate(FUNC(device_nes_cart_interface::nt_r),m_cartslot->m_cart), write8_delegate(FUNC(device_nes_cart_interface::nt_w),m_cartslot->m_cart));
|
|
m_ppu->set_scanline_callback(ppu2c0x_scanline_delegate(FUNC(device_nes_cart_interface::scanline_irq),m_cartslot->m_cart));
|
|
m_ppu->set_hblank_callback(ppu2c0x_hblank_delegate(FUNC(device_nes_cart_interface::hblank_irq),m_cartslot->m_cart));
|
|
m_ppu->set_latch(ppu2c0x_latch_delegate(FUNC(device_nes_cart_interface::ppu_latch),m_cartslot->m_cart));
|
|
|
|
// install additional handlers (read_h, read_ex, write_ex)
|
|
if (m_cartslot->get_pcb_id() == GG_NROM || m_cartslot->get_pcb_id() == SUNSOFT_DCS
|
|
|| m_cartslot->get_pcb_id() == AVE_MAXI15 || m_cartslot->get_pcb_id() == KAISER_KS7022 || m_cartslot->get_pcb_id() == KAISER_KS7031 || m_cartslot->get_pcb_id() == BMC_VT5201
|
|
|| m_cartslot->get_pcb_id() == UNL_LH32 || m_cartslot->get_pcb_id() == UNL_LH10 || m_cartslot->get_pcb_id() == UNL_2708
|
|
|| m_cartslot->get_pcb_id() == UNL_43272 || m_cartslot->get_pcb_id() == BMC_G63IN1 || m_cartslot->get_pcb_id() == BMC_8157
|
|
|| m_cartslot->get_pcb_id() == BMC_GOLD150 || m_cartslot->get_pcb_id() == BMC_CH001
|
|
|| m_cartslot->get_pcb_id() == BMC_70IN1 || m_cartslot->get_pcb_id() == BMC_800IN1)
|
|
{
|
|
logerror("read_h installed!\n");
|
|
space.install_read_handler(0x8000, 0xffff, read8_delegate(FUNC(nes_cart_slot_device::read_h), (nes_cart_slot_device *)m_cartslot));
|
|
}
|
|
|
|
if (m_cartslot->get_pcb_id() == BTL_SMB2JB || m_cartslot->get_pcb_id() == UNL_AC08 || m_cartslot->get_pcb_id() == UNL_SMB2J || m_cartslot->get_pcb_id() == BTL_09034A)
|
|
{
|
|
logerror("write_ex installed!\n");
|
|
space.install_write_handler(0x4020, 0x40ff, write8_delegate(FUNC(nes_cart_slot_device::write_ex), (nes_cart_slot_device *)m_cartslot));
|
|
}
|
|
|
|
if (m_cartslot->get_pcb_id() == KAISER_KS7017 || m_cartslot->get_pcb_id() == UNL_603_5052)
|
|
{
|
|
logerror("write_ex & read_ex installed!\n");
|
|
space.install_read_handler(0x4020, 0x40ff, read8_delegate(FUNC(nes_cart_slot_device::read_ex), (nes_cart_slot_device *)m_cartslot));
|
|
space.install_write_handler(0x4020, 0x40ff, write8_delegate(FUNC(nes_cart_slot_device::write_ex), (nes_cart_slot_device *)m_cartslot));
|
|
}
|
|
}
|
|
else if (m_disk_expansion) // if there is Disk Expansion and no cart has been loaded, setup memory accordingly
|
|
{
|
|
m_ppu->space(AS_PROGRAM).install_readwrite_handler(0, 0x1fff, read8_delegate(FUNC(nes_state::fds_chr_r),this), write8_delegate(FUNC(nes_state::fds_chr_w),this));
|
|
m_ppu->space(AS_PROGRAM).install_readwrite_handler(0x2000, 0x3eff, read8_delegate(FUNC(nes_state::fds_nt_r),this), write8_delegate(FUNC(nes_state::fds_nt_w),this));
|
|
|
|
if (!m_fds_ram)
|
|
m_fds_ram = auto_alloc_array(machine(), UINT8, 0x8000);
|
|
if (!m_vram)
|
|
m_vram = auto_alloc_array(machine(), UINT8, 0x2000);
|
|
|
|
space.install_read_handler(0x4030, 0x403f, read8_delegate(FUNC(nes_state::nes_fds_r),this));
|
|
space.install_read_bank(0x6000, 0xdfff, "fdsram");
|
|
space.install_read_bank(0xe000, 0xffff, "bank1");
|
|
|
|
space.install_write_handler(0x4020, 0x402f, write8_delegate(FUNC(nes_state::nes_fds_w),this));
|
|
space.install_write_bank(0x6000, 0xdfff, "fdsram");
|
|
|
|
membank("bank1")->set_base(machine().root_device().memregion("maincpu")->base() + 0xe000);
|
|
membank("fdsram")->set_base(m_fds_ram);
|
|
}
|
|
|
|
nes_state_register(machine());
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// INPUTS
|
|
//-------------------------------------------------
|
|
|
|
READ8_MEMBER(nes_state::nes_in0_r)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
|
|
// Some games expect bit 6 to be set because the last entry on the data bus shows up
|
|
// in the unused upper 3 bits, so typically a read from $4016 leaves 0x40 there.
|
|
UINT8 ret = 0x40;
|
|
ret |= (m_pad_latch[0] & 0x01);
|
|
|
|
// shift
|
|
m_pad_latch[0] >>= 1;
|
|
|
|
// zapper
|
|
if ((cfg & 0x000f) == 0x0002)
|
|
{
|
|
int x = m_zapper_latch[0][1]; // x-position
|
|
int y = m_zapper_latch[0][2]; // y-position
|
|
UINT32 pix, color_base;
|
|
|
|
// get the pixel at the gun position
|
|
pix = m_ppu->get_pixel(x, y);
|
|
|
|
// get the color base from the ppu
|
|
color_base = m_ppu->get_colorbase();
|
|
|
|
// check if the cursor is over a bright pixel
|
|
if ((pix == color_base + 0x20) || (pix == color_base + 0x30) ||
|
|
(pix == color_base + 0x33) || (pix == color_base + 0x34))
|
|
ret &= ~0x08; // sprite hit
|
|
else
|
|
ret |= 0x08; // no sprite hit
|
|
|
|
// light gun trigger
|
|
ret |= (m_zapper_latch[0][0] << 4);
|
|
}
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 0 read, val: %02x, pc: %04x\n", ret, space.device().safe_pc());
|
|
|
|
return ret;
|
|
}
|
|
|
|
READ8_MEMBER(nes_state::nes_in1_r)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
|
|
// Some games expect bit 6 to be set because the last entry on the data bus shows up
|
|
// in the unused upper 3 bits, so typically a read from $4017 leaves 0x40 there.
|
|
UINT8 ret = 0x40;
|
|
ret |= (m_pad_latch[1] & 0x01);
|
|
|
|
// shift
|
|
m_pad_latch[1] >>= 1;
|
|
|
|
// zapper
|
|
if ((cfg & 0x00f0) == 0x0020)
|
|
{
|
|
int x = m_zapper_latch[1][1]; // x-position
|
|
int y = m_zapper_latch[1][2]; // y-position
|
|
UINT32 pix, color_base;
|
|
|
|
// get the pixel at the gun position
|
|
pix = m_ppu->get_pixel(x, y);
|
|
|
|
// get the color base from the ppu
|
|
color_base = m_ppu->get_colorbase();
|
|
|
|
// check if the cursor is over a bright pixel
|
|
if ((pix == color_base + 0x20) || (pix == color_base + 0x30) ||
|
|
(pix == color_base + 0x33) || (pix == color_base + 0x34))
|
|
ret &= ~0x08; // sprite hit
|
|
else
|
|
ret |= 0x08; // no sprite hit
|
|
|
|
// light gun trigger
|
|
ret |= (m_zapper_latch[1][0] << 4);
|
|
}
|
|
|
|
// arkanoid paddle
|
|
if ((cfg & 0x00f0) == 0x0040)
|
|
{
|
|
ret |= (m_paddle_btn_latch << 3); // button
|
|
ret |= ((m_paddle_latch & 0x80) >> 3); // paddle data
|
|
m_paddle_latch <<= 1;
|
|
m_paddle_latch &= 0xff;
|
|
}
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 1 read, val: %02x, pc: %04x\n", ret, space.device().safe_pc());
|
|
|
|
return ret;
|
|
}
|
|
|
|
WRITE8_MEMBER(nes_state::nes_in0_w)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy write, val: %02x, pc: %04x\n", data, space.device().safe_pc());
|
|
|
|
// Check if lightgun has been chosen as input: if so, enable crosshair
|
|
timer_set(attotime::zero, TIMER_ZAPPER_TICK);
|
|
|
|
if (data & 0x01)
|
|
return;
|
|
|
|
// Toggling bit 0 high then low resets controllers
|
|
m_pad_latch[0] = 0;
|
|
m_pad_latch[1] = 0;
|
|
m_zapper_latch[0][0] = 0;
|
|
m_zapper_latch[0][1] = 0;
|
|
m_zapper_latch[0][2] = 0;
|
|
m_zapper_latch[1][0] = 0;
|
|
m_zapper_latch[1][1] = 0;
|
|
m_zapper_latch[1][2] = 0;
|
|
m_paddle_btn_latch = 0;
|
|
m_paddle_latch = 0;
|
|
|
|
// P1 inputs
|
|
switch (cfg & 0x000f)
|
|
{
|
|
case 0x01: // pad 1
|
|
m_pad_latch[0] = m_io_pad[0]->read();
|
|
break;
|
|
|
|
case 0x02: // zapper (secondary)
|
|
m_zapper_latch[0][0] = m_io_zapper1_t->read();
|
|
m_zapper_latch[0][1] = m_io_zapper1_x->read();
|
|
m_zapper_latch[0][2] = m_io_zapper1_y->read();
|
|
break;
|
|
}
|
|
|
|
// P2 inputs
|
|
switch ((cfg & 0x00f0) >> 4)
|
|
{
|
|
case 0x01: // pad 2
|
|
m_pad_latch[1] = m_io_pad[1]->read();
|
|
break;
|
|
|
|
case 0x02: // zapper (primary) - most games expect pad in port1 & zapper in port2
|
|
m_zapper_latch[1][0] = m_io_zapper2_t->read();
|
|
m_zapper_latch[1][1] = m_io_zapper2_x->read();
|
|
m_zapper_latch[1][2] = m_io_zapper2_y->read();
|
|
break;
|
|
|
|
case 0x04: // arkanoid paddle
|
|
m_paddle_btn_latch = m_io_paddle_btn->read();
|
|
m_paddle_latch = (UINT8) (m_io_paddle->read() ^ 0xff);
|
|
break;
|
|
}
|
|
|
|
// P3 & P4 inputs in NES Four Score are read serially with P1 & P2
|
|
// P3 inputs
|
|
if ((cfg & 0x0f00))
|
|
m_pad_latch[0] |= ((m_io_pad[2]->read() << 8) | (0x08 << 16)); // pad 3 + signature
|
|
|
|
// P4 inputs
|
|
if ((cfg & 0xf000))
|
|
m_pad_latch[1] |= ((m_io_pad[3]->read() << 8) | (0x04 << 16)); // pad 4 + signature
|
|
}
|
|
|
|
|
|
WRITE8_MEMBER(nes_state::nes_in1_w)
|
|
{
|
|
}
|
|
|
|
|
|
READ8_MEMBER(nes_state::fc_in0_r)
|
|
{
|
|
int exp = m_io_exp->read();
|
|
|
|
// Some games expect bit 6 to be set because the last entry on the data bus shows up
|
|
// in the unused upper 3 bits, so typically a read from $4016 leaves 0x40 there.
|
|
UINT8 ret = 0x40;
|
|
ret |= (m_pad_latch[0] & 0x01);
|
|
|
|
// shift
|
|
m_pad_latch[0] >>= 1;
|
|
|
|
// EXP input
|
|
switch (exp & 0x0f)
|
|
{
|
|
case 0x02: // FC Keyboard: tape input
|
|
if ((m_cassette->get_state() & CASSETTE_MASK_UISTATE) == CASSETTE_PLAY)
|
|
{
|
|
double level = m_cassette->input();
|
|
if (level < 0)
|
|
ret |= 0x00;
|
|
else
|
|
ret |= 0x02;
|
|
}
|
|
break;
|
|
|
|
case 0x04: // Arkanoid paddle
|
|
ret |= (m_paddle_btn_latch << 1); // button
|
|
break;
|
|
|
|
case 0x06: // Mahjong Panel
|
|
ret |= ((m_mjpanel_latch & 0x01) << 1);
|
|
m_mjpanel_latch >>= 1;
|
|
break;
|
|
|
|
case 0x07: // 'multitap' p3
|
|
ret |= ((m_pad_latch[2] & 0x01) << 1);
|
|
m_pad_latch[2] >>= 1;
|
|
break;
|
|
}
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 0 read, val: %02x, pc: %04x\n", ret, space.device().safe_pc());
|
|
|
|
return ret;
|
|
}
|
|
|
|
READ8_MEMBER(nes_state::fc_in1_r)
|
|
{
|
|
int exp = m_io_exp->read();
|
|
|
|
// Some games expect bit 6 to be set because the last entry on the data bus shows up
|
|
// in the unused upper 3 bits, so typically a read from $4017 leaves 0x40 there.
|
|
UINT8 ret = 0x40;
|
|
ret |= (m_pad_latch[1] & 0x01);
|
|
|
|
// shift
|
|
m_pad_latch[1] >>= 1;
|
|
|
|
|
|
// EXP input
|
|
switch (exp & 0x0f)
|
|
{
|
|
case 0x01: // Lightgun
|
|
{
|
|
int x = m_zapper_latch[0][1]; // x-position
|
|
int y = m_zapper_latch[0][2]; // y-position
|
|
UINT32 pix, color_base;
|
|
|
|
// get the pixel at the gun position
|
|
pix = m_ppu->get_pixel(x, y);
|
|
|
|
// get the color base from the ppu
|
|
color_base = m_ppu->get_colorbase();
|
|
|
|
// check if the cursor is over a bright pixel
|
|
if ((pix == color_base + 0x20) || (pix == color_base + 0x30) ||
|
|
(pix == color_base + 0x33) || (pix == color_base + 0x34))
|
|
ret &= ~0x08; // sprite hit
|
|
else
|
|
ret |= 0x08; // no sprite hit
|
|
|
|
// light gun trigger
|
|
ret |= (m_zapper_latch[0][0] << 4);
|
|
}
|
|
break;
|
|
|
|
case 0x02: // FC Keyboard: rows of the keyboard matrix are read 4-bits at time and returned as bit1->bit4
|
|
if (m_fck_scan < 9)
|
|
ret |= ~(((m_io_fckey[m_fck_scan]->read() >> (m_fck_mode * 4)) & 0x0f) << 1) & 0x1e;
|
|
else
|
|
ret |= 0x1e;
|
|
break;
|
|
|
|
case 0x03: // Subor Keyboard: rows of the keyboard matrix are read 4-bits at time and returned as bit1->bit4
|
|
if (m_fck_scan < 12)
|
|
ret |= ~(((m_io_subkey[m_fck_scan]->read() >> (m_fck_mode * 4)) & 0x0f) << 1) & 0x1e;
|
|
else
|
|
ret |= 0x1e;
|
|
break;
|
|
|
|
case 0x04: // Arkanoid paddle
|
|
ret |= ((m_paddle_latch & 0x80) >> 6); // paddle data
|
|
m_paddle_latch <<= 1;
|
|
m_paddle_latch &= 0xff;
|
|
break;
|
|
|
|
case 0x06: // Mahjong Panel
|
|
ret |= ((m_mjpanel_latch & 0x01) << 1);
|
|
m_mjpanel_latch >>= 1;
|
|
break;
|
|
|
|
case 0x07: // 'multitap' p4
|
|
ret |= ((m_pad_latch[3] & 0x01) << 1);
|
|
m_pad_latch[3] >>= 1;
|
|
break;
|
|
}
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 1 read, val: %02x, pc: %04x\n", ret, space.device().safe_pc());
|
|
|
|
return ret;
|
|
}
|
|
|
|
WRITE8_MEMBER(nes_state::fc_in0_w)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
int exp = m_io_exp->read();
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy write, val: %02x, pc: %04x\n", data, space.device().safe_pc());
|
|
|
|
// Check if lightgun has been chosen as input: if so, enable crosshair
|
|
timer_set(attotime::zero, TIMER_LIGHTGUN_TICK);
|
|
|
|
if ((exp & 0x0f) == 0x02 || (exp & 0x0f) == 0x03)
|
|
{
|
|
// tape output (not fully tested)
|
|
if ((m_cassette->get_state() & CASSETTE_MASK_UISTATE) == CASSETTE_RECORD)
|
|
m_cassette->output(((data & 0x07) == 0x07) ? +1.0 : -1.0);
|
|
|
|
if (BIT(data, 2)) // keyboard active
|
|
{
|
|
int lines = ((exp & 0x0f) == 0x02) ? 9 : 12;
|
|
UINT8 out = BIT(data, 1); // scan
|
|
|
|
if (m_fck_mode && !out && ++m_fck_scan > lines)
|
|
m_fck_scan = 0;
|
|
|
|
m_fck_mode = out; // access lower or upper 4 bits
|
|
|
|
if (BIT(data, 0)) // reset
|
|
m_fck_scan = 0;
|
|
}
|
|
}
|
|
|
|
if (data & 0x01)
|
|
return;
|
|
|
|
// Toggling bit 0 high then low resets controllers
|
|
m_pad_latch[0] = 0;
|
|
m_pad_latch[1] = 0;
|
|
m_pad_latch[2] = 0;
|
|
m_pad_latch[3] = 0;
|
|
m_zapper_latch[0][0] = 0;
|
|
m_zapper_latch[0][1] = 0;
|
|
m_zapper_latch[0][2] = 0;
|
|
m_paddle_btn_latch = 0;
|
|
m_paddle_latch = 0;
|
|
m_mjpanel_latch = 0;
|
|
|
|
// P1 inputs
|
|
switch (cfg & 0x000f)
|
|
{
|
|
case 0x01: // pad 1
|
|
m_pad_latch[0] = m_io_pad[0]->read();
|
|
break;
|
|
|
|
case 0x02: // crazy climber (left stick)
|
|
m_pad_latch[0] = m_io_cc_left->read();
|
|
break;
|
|
}
|
|
|
|
// P2 inputs
|
|
switch ((cfg & 0x00f0) >> 4)
|
|
{
|
|
case 0x01: // pad 2
|
|
m_pad_latch[1] = m_io_pad[1]->read();
|
|
break;
|
|
|
|
case 0x02: // crazy climber (right stick)
|
|
m_pad_latch[1] = m_io_cc_right->read();
|
|
break;
|
|
|
|
}
|
|
|
|
// P3 & P4 inputs in Famicom (e.g. through Hori Twin Adapter or Hori 4 Players Adapter)
|
|
// are read in parallel with P1 & P2 (just using diff bits)
|
|
// P3 inputs
|
|
if ((exp & 0x0f) == 7 && (cfg & 0x0f00) == 0x0100)
|
|
m_pad_latch[2] = m_io_pad[2]->read(); // pad 3
|
|
|
|
// P4 inputs
|
|
if ((exp & 0x0f) == 7 && (cfg & 0xf000) == 0x1000)
|
|
m_pad_latch[3] = m_io_pad[3]->read(); // pad 4
|
|
|
|
|
|
// EXP input
|
|
switch (exp & 0x0f)
|
|
{
|
|
case 0x01: // Lightgun
|
|
m_zapper_latch[0][0] = m_io_zapper2_t->read();
|
|
m_zapper_latch[0][1] = m_io_zapper2_x->read();
|
|
m_zapper_latch[0][2] = m_io_zapper2_y->read();
|
|
break;
|
|
|
|
case 0x02: // FC Keyboard
|
|
case 0x03: // Subor Keyboard
|
|
// these are scanned differently than other devices:
|
|
// writes to $4016 with bit2 set always update the
|
|
// line counter and writing bit0 set resets the counter
|
|
break;
|
|
|
|
case 0x04: // Arkanoid paddle
|
|
m_paddle_btn_latch = m_io_paddle_btn->read();
|
|
m_paddle_latch = (UINT8) (m_io_paddle->read() ^ 0xff);
|
|
break;
|
|
|
|
case 0x06: // Mahjong Panel
|
|
if (data & 0xf8)
|
|
logerror("Error: Mahjong panel read with mux data %02x\n", (data & 0xfe));
|
|
else
|
|
m_mjpanel_latch = m_io_mahjong[(data & 0xfe) >> 1]->read();
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void nes_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
|
{
|
|
switch (id)
|
|
{
|
|
case TIMER_ZAPPER_TICK:
|
|
if ((m_io_ctrlsel->read() & 0x000f) == 0x0002)
|
|
{
|
|
/* enable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 0, CROSSHAIR_SCREEN_ALL);
|
|
}
|
|
else
|
|
{
|
|
/* disable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 0, CROSSHAIR_SCREEN_NONE);
|
|
}
|
|
|
|
if ((m_io_ctrlsel->read() & 0x00f0) == 0x0020)
|
|
{
|
|
/* enable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 1, CROSSHAIR_SCREEN_ALL);
|
|
}
|
|
else
|
|
{
|
|
/* disable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 1, CROSSHAIR_SCREEN_NONE);
|
|
}
|
|
break;
|
|
case TIMER_LIGHTGUN_TICK:
|
|
if ((m_io_exp->read() & 0x0f) == 0x01)
|
|
{
|
|
/* enable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 0, CROSSHAIR_SCREEN_ALL);
|
|
}
|
|
else
|
|
{
|
|
/* disable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 0, CROSSHAIR_SCREEN_NONE);
|
|
}
|
|
break;
|
|
default:
|
|
assert_always(FALSE, "Unknown id in nes_state::device_timer");
|
|
}
|
|
}
|
|
|
|
|
|
/**************************
|
|
|
|
Disk System emulation
|
|
|
|
**************************/
|
|
|
|
void nes_state::fds_irq( int scanline, int vblank, int blanked )
|
|
{
|
|
if (m_IRQ_enable_latch)
|
|
m_maincpu->set_input_line(M6502_IRQ_LINE, HOLD_LINE);
|
|
|
|
if (m_IRQ_enable)
|
|
{
|
|
if (m_IRQ_count <= 114)
|
|
{
|
|
m_maincpu->set_input_line(M6502_IRQ_LINE, HOLD_LINE);
|
|
m_IRQ_enable = 0;
|
|
m_fds_status0 |= 0x01;
|
|
}
|
|
else
|
|
m_IRQ_count -= 114;
|
|
}
|
|
}
|
|
|
|
READ8_MEMBER(nes_state::nes_fds_r)
|
|
{
|
|
UINT8 ret = 0x00;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* $4030 - disk status 0 */
|
|
ret = m_fds_status0;
|
|
/* clear the disk IRQ detect flag */
|
|
m_fds_status0 &= ~0x01;
|
|
break;
|
|
case 0x01: /* $4031 - data latch */
|
|
/* don't read data if disk is unloaded */
|
|
if (!m_fds_data)
|
|
ret = 0;
|
|
else if (m_fds_current_side)
|
|
{
|
|
// a bunch of games (e.g. bshashsc and fairypin) keep reading beyond the last track
|
|
// what is the expected behavior?
|
|
if (m_fds_head_position > 65500)
|
|
m_fds_head_position = 0;
|
|
ret = m_fds_data[(m_fds_current_side - 1) * 65500 + m_fds_head_position++];
|
|
}
|
|
else
|
|
ret = 0;
|
|
break;
|
|
case 0x02: /* $4032 - disk status 1 */
|
|
/* return "no disk" status if disk is unloaded */
|
|
if (!m_fds_data)
|
|
ret = 1;
|
|
else if (m_fds_last_side != m_fds_current_side)
|
|
{
|
|
/* If we've switched disks, report "no disk" for a few reads */
|
|
ret = 1;
|
|
m_fds_count++;
|
|
if (m_fds_count == 50)
|
|
{
|
|
m_fds_last_side = m_fds_current_side;
|
|
m_fds_count = 0;
|
|
}
|
|
}
|
|
else
|
|
ret = (m_fds_current_side == 0); /* 0 if a disk is inserted */
|
|
break;
|
|
case 0x03: /* $4033 */
|
|
ret = 0x80;
|
|
break;
|
|
default:
|
|
ret = 0x00;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
WRITE8_MEMBER(nes_state::nes_fds_w)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00:
|
|
m_IRQ_count_latch = (m_IRQ_count_latch & 0xff00) | data;
|
|
break;
|
|
case 0x01:
|
|
m_IRQ_count_latch = (m_IRQ_count_latch & 0x00ff) | (data << 8);
|
|
break;
|
|
case 0x02:
|
|
m_IRQ_count = m_IRQ_count_latch;
|
|
m_IRQ_enable = data;
|
|
break;
|
|
case 0x03:
|
|
// d0 = sound io (1 = enable)
|
|
// d1 = disk io (1 = enable)
|
|
break;
|
|
case 0x04:
|
|
/* write data out to disk */
|
|
break;
|
|
case 0x05:
|
|
m_fds_motor_on = BIT(data, 0);
|
|
|
|
if (BIT(data, 1))
|
|
m_fds_head_position = 0;
|
|
|
|
m_fds_read_mode = BIT(data, 2);
|
|
if (BIT(data, 3))
|
|
m_fds_mirroring = PPU_MIRROR_HORZ;
|
|
else
|
|
m_fds_mirroring = PPU_MIRROR_VERT;
|
|
|
|
if ((!(data & 0x40)) && (m_fds_write_reg & 0x40))
|
|
m_fds_head_position -= 2; // ???
|
|
|
|
m_IRQ_enable_latch = BIT(data, 7);
|
|
m_fds_write_reg = data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
WRITE8_MEMBER(nes_state::fds_chr_w)
|
|
{
|
|
m_vram[offset] = data;
|
|
}
|
|
|
|
READ8_MEMBER(nes_state::fds_chr_r)
|
|
{
|
|
return m_vram[offset];
|
|
}
|
|
|
|
WRITE8_MEMBER(nes_state::fds_nt_w)
|
|
{
|
|
int page = ((offset & 0xc00) >> 10);
|
|
int bank;
|
|
|
|
switch (page)
|
|
{
|
|
case 0:
|
|
bank = 0;
|
|
break;
|
|
case 1:
|
|
bank = (m_fds_mirroring == PPU_MIRROR_VERT) ? 1 : 0;
|
|
break;
|
|
case 2:
|
|
bank = (m_fds_mirroring == PPU_MIRROR_VERT) ? 0 : 1;
|
|
break;
|
|
case 3:
|
|
default:
|
|
bank = 1;
|
|
break;
|
|
}
|
|
|
|
m_ciram[(bank * 0x400) + (offset & 0x3ff)] = data;
|
|
}
|
|
|
|
READ8_MEMBER(nes_state::fds_nt_r)
|
|
{
|
|
int page = ((offset & 0xc00) >> 10);
|
|
int bank;
|
|
|
|
switch (page)
|
|
{
|
|
case 0:
|
|
bank = 0;
|
|
break;
|
|
case 1:
|
|
bank = (m_fds_mirroring == PPU_MIRROR_VERT) ? 1 : 0;
|
|
break;
|
|
case 2:
|
|
bank = (m_fds_mirroring == PPU_MIRROR_VERT) ? 0 : 1;
|
|
break;
|
|
case 3:
|
|
default:
|
|
bank = 1;
|
|
break;
|
|
}
|
|
|
|
return m_ciram[(bank * 0x400) + (offset & 0x3ff)];
|
|
}
|
|
|
|
|
|
// Disk interface
|
|
|
|
static void nes_load_proc( device_image_interface &image )
|
|
{
|
|
nes_state *state = image.device().machine().driver_data<nes_state>();
|
|
int header = 0;
|
|
state->m_fds_sides = 0;
|
|
|
|
if (image.length() % 65500)
|
|
header = 0x10;
|
|
|
|
state->m_fds_sides = (image.length() - header) / 65500;
|
|
|
|
if (!state->m_fds_data)
|
|
state->m_fds_data = auto_alloc_array(image.device().machine(),UINT8,state->m_fds_sides * 65500); // I don't think we can arrive here ever, probably it can be removed...
|
|
|
|
/* if there is an header, skip it */
|
|
image.fseek(header, SEEK_SET);
|
|
|
|
image.fread(state->m_fds_data, 65500 * state->m_fds_sides);
|
|
return;
|
|
}
|
|
|
|
static void nes_unload_proc( device_image_interface &image )
|
|
{
|
|
nes_state *state = image.device().machine().driver_data<nes_state>();
|
|
|
|
/* TODO: should write out changes here as well */
|
|
state->m_fds_sides = 0;
|
|
}
|
|
|
|
DRIVER_INIT_MEMBER(nes_state,famicom)
|
|
{
|
|
/* initialize the disk system */
|
|
m_disk_expansion = 1;
|
|
|
|
m_fds_sides = 0;
|
|
m_fds_last_side = 0;
|
|
m_fds_count = 0;
|
|
m_fds_motor_on = 0;
|
|
m_fds_door_closed = 0;
|
|
m_fds_current_side = 1;
|
|
m_fds_head_position = 0;
|
|
m_fds_status0 = 0;
|
|
m_fds_read_mode = m_fds_write_reg = 0;
|
|
|
|
m_fds_mirroring = PPU_MIRROR_VERT; // correct?
|
|
|
|
m_fds_ram = auto_alloc_array_clear(machine(), UINT8, 0x8000);
|
|
save_pointer(NAME(m_fds_ram), 0x8000);
|
|
|
|
floppy_install_load_proc(floppy_get_device(machine(), 0), nes_load_proc);
|
|
floppy_install_unload_proc(floppy_get_device(machine(), 0), nes_unload_proc);
|
|
|
|
// setup alt input handlers for additional FC input devices
|
|
address_space &space = machine().device<cpu_device>("maincpu")->space(AS_PROGRAM);
|
|
space.install_read_handler(0x4016, 0x4016, read8_delegate(FUNC(nes_state::fc_in0_r), this));
|
|
space.install_write_handler(0x4016, 0x4016, write8_delegate(FUNC(nes_state::fc_in0_w), this));
|
|
space.install_read_handler(0x4017, 0x4017, read8_delegate(FUNC(nes_state::fc_in1_r), this));
|
|
}
|