mirror of
https://github.com/holub/mame
synced 2025-05-20 12:48:53 +03:00
905 lines
28 KiB
C
905 lines
28 KiB
C
/*****************************************************************************
|
|
|
|
nes.c
|
|
|
|
Nintendo Entertainment System (Famicom)
|
|
|
|
****************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "crsshair.h"
|
|
#include "cpu/m6502/m6502.h"
|
|
#include "video/ppu2c0x.h"
|
|
#include "includes/nes.h"
|
|
//#include "includes/nes_mmc.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
|
|
***************************************************************************/
|
|
|
|
void nes_state::init_nes_core()
|
|
{
|
|
address_space &space = machine().device("maincpu")->memory().space(AS_PROGRAM);
|
|
static const char *const bank_names[] = { "bank1", "bank2", "bank3", "bank4" };
|
|
int i;
|
|
m_prg_chunks = 0;
|
|
m_chr_chunks = 0;
|
|
m_vram_chunks = 0;
|
|
m_pcb_id = NO_BOARD;
|
|
|
|
m_rom = machine().root_device().memregion("maincpu")->base();
|
|
m_ciram = machine().root_device().memregion("ciram")->base();
|
|
// other pointers got set in the loading routine
|
|
|
|
/* Brutal hack put in as a consequence of the new memory system; we really need to fix the NES code */
|
|
space.install_readwrite_bank(0x0000, 0x07ff, 0, 0x1800, "bank10");
|
|
|
|
machine().device("ppu")->memory().space(AS_PROGRAM).install_readwrite_handler(0, 0x1fff, read8_delegate(FUNC(nes_state::nes_chr_r),this), write8_delegate(FUNC(nes_state::nes_chr_w),this));
|
|
machine().device("ppu")->memory().space(AS_PROGRAM).install_readwrite_handler(0x2000, 0x3eff, read8_delegate(FUNC(nes_state::nes_nt_r),this), write8_delegate(FUNC(nes_state::nes_nt_w),this));
|
|
|
|
membank("bank10")->set_base(m_rom);
|
|
|
|
// get cart info from cartslot
|
|
if (m_cartslot && m_cartslot->m_cart)
|
|
{
|
|
m_prg_chunks = m_cartslot->m_cart->get_prg_size() / 0x4000;
|
|
m_chr_chunks = m_cartslot->m_cart->get_vrom_size() / 0x2000;
|
|
m_vram_chunks = m_cartslot->m_cart->get_vram_size() / 0x2000;
|
|
// printf("%d - %d - %d\n", m_prg_chunks, m_chr_chunks, m_vram_chunks);
|
|
m_prg = m_cartslot->m_cart->get_prg_base();
|
|
if (m_cartslot->m_cart->get_vrom_size())
|
|
m_vrom = m_cartslot->m_cart->get_vrom_base();
|
|
if (m_cartslot->m_cart->get_vram_size())
|
|
m_vram = auto_alloc_array(machine(), UINT8, m_cartslot->m_cart->get_vram_size());
|
|
|
|
m_pcb_id = m_cartslot->get_pcb_id();
|
|
m_hard_mirroring = m_cartslot->m_cart->get_mirroring();
|
|
m_four_screen_vram = m_cartslot->m_cart->get_four_screen_vram();
|
|
|
|
// setup memory pointers and related variables
|
|
m_prg_ram = m_cartslot->m_cart->get_battery_size() ? 1 : 0;
|
|
m_wram_size = m_cartslot->m_cart->get_prgram_size();
|
|
m_mapper_ram_size = m_cartslot->m_cart->get_mapper_ram_size();
|
|
m_mapper_bram_size = m_cartslot->m_cart->get_mapper_bram_size();
|
|
m_battery = m_cartslot->m_cart->get_battery_size() ? 1 : 0;
|
|
m_battery_size = m_cartslot->m_cart->get_battery_size();
|
|
|
|
if (m_prg_ram)
|
|
m_wram = m_cartslot->m_cart->get_prgram_base();
|
|
if (m_mapper_ram_size)
|
|
m_mapper_ram = m_cartslot->m_cart->get_mapper_ram_base();
|
|
if (m_battery)
|
|
m_battery_ram = m_cartslot->m_cart->get_battery_base();
|
|
if (m_mapper_bram_size)
|
|
m_mapper_bram = m_cartslot->m_cart->get_mapper_bram_base();
|
|
|
|
// setup the rest of the variables involved
|
|
m_chr_open_bus = m_cartslot->get_chr_open_bus();
|
|
m_ce_mask = m_cartslot->get_ce_mask();
|
|
m_ce_state = m_cartslot->get_ce_state();
|
|
m_vrc_ls_prg_a = m_cartslot->get_vrc_ls_prg_a();
|
|
m_vrc_ls_prg_b = m_cartslot->get_vrc_ls_prg_b();
|
|
m_vrc_ls_chr = m_cartslot->get_vrc_ls_chr();
|
|
m_crc_hack = m_cartslot->get_crc_hack();
|
|
}
|
|
|
|
int prg_banks = (m_prg_chunks == 1) ? (2 * 2) : (m_prg_chunks * 2);
|
|
|
|
/* If there is Disk Expansion and no cart has been loaded, setup memory accordingly */
|
|
if (m_disk_expansion && m_pcb_id == NO_BOARD)
|
|
{
|
|
/* If we are loading a disk we have already filled m_fds_data and we don't want to overwrite it,
|
|
if we are loading a cart image identified as mapper 20 (probably wrong mapper...) we need to alloc
|
|
memory for use in nes_fds_r/nes_fds_w. Same goes for allocation of fds_ram (used for bank2) */
|
|
if (m_fds_data == NULL)
|
|
{
|
|
UINT32 size = (m_prg_chunks == 1) ? 2 * 0x4000 : m_prg_chunks * 0x4000;
|
|
m_fds_data = auto_alloc_array_clear(machine(), UINT8, size);
|
|
memcpy(m_fds_data, m_prg, size); // copy in fds_data the cart PRG
|
|
}
|
|
if (m_fds_ram == NULL)
|
|
m_fds_ram = auto_alloc_array(machine(), UINT8, 0x8000);
|
|
|
|
space.install_read_handler(0x4030, 0x403f, read8_delegate(FUNC(nes_state::nes_fds_r),this));
|
|
space.install_read_bank(0x6000, 0xdfff, "bank2");
|
|
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, "bank2");
|
|
|
|
membank("bank1")->set_base(&m_rom[0xe000]);
|
|
membank("bank2")->set_base(m_fds_ram);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Set up the mapper callbacks */
|
|
pcb_handlers_setup();
|
|
|
|
/* Set up the memory handlers for the mapper */
|
|
space.install_read_bank(0x8000, 0x9fff, "bank1");
|
|
space.install_read_bank(0xa000, 0xbfff, "bank2");
|
|
space.install_read_bank(0xc000, 0xdfff, "bank3");
|
|
space.install_read_bank(0xe000, 0xffff, "bank4");
|
|
space.install_readwrite_bank(0x6000, 0x7fff, "bank5");
|
|
|
|
/* configure banks 1-4 */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
membank(bank_names[i])->configure_entries(0, prg_banks, m_prg, 0x2000);
|
|
// some mappers (e.g. MMC5) can map PRG RAM in 0x8000-0xffff as well
|
|
if (m_prg_ram)
|
|
membank(bank_names[i])->configure_entries(prg_banks, m_wram_size / 0x2000, m_wram, 0x2000);
|
|
// however, at start we point to PRG ROM
|
|
membank(bank_names[i])->set_entry(i);
|
|
m_prg_bank[i] = i;
|
|
}
|
|
|
|
/* bank 5 configuration is more delicate, since it can have PRG RAM, PRG ROM or SRAM mapped to it */
|
|
/* we first map PRG ROM banks, then the battery bank (if a battery is present), and finally PRG RAM (m_wram) */
|
|
membank("bank5")->configure_entries(0, prg_banks, m_prg, 0x2000);
|
|
m_battery_bank5_start = prg_banks;
|
|
m_prgram_bank5_start = prg_banks;
|
|
m_empty_bank5_start = prg_banks;
|
|
|
|
/* add battery ram, but only if there's no trainer since they share overlapping memory. */
|
|
if (m_battery && !m_trainer)
|
|
{
|
|
UINT32 bank_size = (m_battery_size > 0x2000) ? 0x2000 : m_battery_size;
|
|
int bank_num = (m_battery_size > 0x2000) ? m_battery_size / 0x2000 : 1;
|
|
membank("bank5")->configure_entries(prg_banks, bank_num, m_battery_ram, bank_size);
|
|
m_prgram_bank5_start += bank_num;
|
|
m_empty_bank5_start += bank_num;
|
|
}
|
|
/* add prg ram. */
|
|
if (m_prg_ram)
|
|
{
|
|
membank("bank5")->configure_entries(m_prgram_bank5_start, m_wram_size / 0x2000, m_wram, 0x2000);
|
|
m_empty_bank5_start += m_wram_size / 0x2000;
|
|
}
|
|
|
|
membank("bank5")->configure_entry(m_empty_bank5_start, m_rom + 0x6000);
|
|
|
|
/* if we have any additional PRG RAM, point bank5 to its first bank */
|
|
if (m_battery || m_prg_ram)
|
|
m_prg_bank[4] = m_battery_bank5_start;
|
|
else
|
|
m_prg_bank[4] = m_empty_bank5_start; // or shall we point to "maincpu" region at 0x6000? point is that we should never access this region if no sram or wram is present!
|
|
|
|
membank("bank5")->set_entry(m_prg_bank[4]);
|
|
|
|
if (m_four_screen_vram)
|
|
{
|
|
m_extended_ntram = auto_alloc_array_clear(machine(), UINT8, 0x2000);
|
|
save_pointer(NAME(m_extended_ntram), 0x2000);
|
|
}
|
|
|
|
if (m_four_screen_vram)
|
|
set_nt_mirroring(PPU_MIRROR_4SCREEN);
|
|
else
|
|
{
|
|
switch (m_hard_mirroring)
|
|
{
|
|
case PPU_MIRROR_HORZ:
|
|
case PPU_MIRROR_VERT:
|
|
case PPU_MIRROR_HIGH:
|
|
case PPU_MIRROR_LOW:
|
|
set_nt_mirroring(m_hard_mirroring);
|
|
break;
|
|
default:
|
|
set_nt_mirroring(PPU_MIRROR_NONE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// there are still some quirk about writes to bank5... I hope to fix them soon. (mappers 34,45,52,246 have both mid_w and WRAM-->check)
|
|
if (!m_mmc_write_low.isnull())
|
|
space.install_write_handler(0x4100, 0x5fff, m_mmc_write_low);
|
|
if (!m_mmc_write_mid.isnull())
|
|
space.install_write_handler(0x6000, 0x7fff, m_mmc_write_mid);
|
|
if (!m_mmc_write.isnull())
|
|
space.install_write_handler(0x8000, 0xffff, m_mmc_write);
|
|
|
|
// In fact, we also allow single pcbs to overwrite the bank read handlers defined above,
|
|
// because some pcbs (mainly pirate ones) require protection values to be read instead of
|
|
// the expected ROM banks: these handlers, though, must take care of the ROM access as well
|
|
if (!m_mmc_read_low.isnull())
|
|
space.install_read_handler(0x4100, 0x5fff, m_mmc_read_low);
|
|
if (!m_mmc_read_mid.isnull())
|
|
space.install_read_handler(0x6000, 0x7fff, m_mmc_read_mid);
|
|
if (!m_mmc_read.isnull())
|
|
space.install_read_handler(0x8000, 0xffff, m_mmc_read);
|
|
|
|
// install additional handlers
|
|
if (m_pcb_id == BTL_SMB2B || m_mapper == 50)
|
|
{
|
|
space.install_write_handler(0x4020, 0x403f, write8_delegate(FUNC(nes_state::smb2jb_extra_w),this));
|
|
space.install_write_handler(0x40a0, 0x40bf, write8_delegate(FUNC(nes_state::smb2jb_extra_w),this));
|
|
}
|
|
|
|
if (m_pcb_id == KAISER_KS7017)
|
|
{
|
|
space.install_read_handler(0x4030, 0x4030, read8_delegate(FUNC(nes_state::ks7017_extra_r),this));
|
|
space.install_write_handler(0x4020, 0x40ff, write8_delegate(FUNC(nes_state::ks7017_extra_w),this));
|
|
}
|
|
|
|
if (m_pcb_id == UNL_603_5052)
|
|
{
|
|
space.install_read_handler(0x4020, 0x40ff, read8_delegate(FUNC(nes_state::unl_6035052_extra_r),this));
|
|
space.install_write_handler(0x4020, 0x40ff, write8_delegate(FUNC(nes_state::unl_6035052_extra_w),this));
|
|
}
|
|
|
|
if (m_pcb_id == WAIXING_SH2)
|
|
machine().device("ppu")->memory().space(AS_PROGRAM).install_read_handler(0, 0x1fff, read8_delegate(FUNC(nes_state::waixing_sh2_chr_r),this));
|
|
}
|
|
|
|
// 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_pcb_id == NO_BOARD)
|
|
m_ppu->set_hblank_callback(ppu2c0x_hblank_delegate(FUNC(nes_state::fds_irq),this));
|
|
else
|
|
nes_pcb_reset();
|
|
|
|
/* Reset the serial input ports */
|
|
m_in_0.shift = 0;
|
|
m_in_1.shift = 0;
|
|
|
|
m_maincpu->reset();
|
|
}
|
|
|
|
TIMER_CALLBACK_MEMBER(nes_state::nes_irq_callback)
|
|
{
|
|
m_maincpu->set_input_line(M6502_IRQ_LINE, HOLD_LINE);
|
|
m_irq_timer->adjust(attotime::never);
|
|
}
|
|
|
|
void nes_state::nes_banks_restore()
|
|
{
|
|
membank("bank1")->set_entry(m_prg_bank[0]);
|
|
membank("bank2")->set_entry(m_prg_bank[1]);
|
|
membank("bank3")->set_entry(m_prg_bank[2]);
|
|
membank("bank4")->set_entry(m_prg_bank[3]);
|
|
membank("bank5")->set_entry(m_prg_bank[4]);
|
|
}
|
|
|
|
static void nes_state_register( running_machine &machine )
|
|
{
|
|
nes_state *state = machine.driver_data<nes_state>();
|
|
|
|
state->save_item(NAME(state->m_prg_bank));
|
|
|
|
state->save_item(NAME(state->m_MMC5_floodtile));
|
|
state->save_item(NAME(state->m_MMC5_floodattr));
|
|
state->save_item(NAME(state->m_mmc5_vram_control));
|
|
|
|
state->save_item(NAME(state->m_nes_vram_sprite));
|
|
state->save_item(NAME(state->m_last_frame_flip));
|
|
|
|
// shared mapper variables
|
|
state->save_item(NAME(state->m_IRQ_enable));
|
|
state->save_item(NAME(state->m_IRQ_enable_latch));
|
|
state->save_item(NAME(state->m_IRQ_count));
|
|
state->save_item(NAME(state->m_IRQ_count_latch));
|
|
state->save_item(NAME(state->m_IRQ_toggle));
|
|
state->save_item(NAME(state->m_IRQ_reset));
|
|
state->save_item(NAME(state->m_IRQ_status));
|
|
state->save_item(NAME(state->m_IRQ_mode));
|
|
state->save_item(NAME(state->m_mult1));
|
|
state->save_item(NAME(state->m_mult2));
|
|
state->save_item(NAME(state->m_mmc_chr_source));
|
|
state->save_item(NAME(state->m_mmc_cmd1));
|
|
state->save_item(NAME(state->m_mmc_cmd2));
|
|
state->save_item(NAME(state->m_mmc_count));
|
|
state->save_item(NAME(state->m_mmc_prg_base));
|
|
state->save_item(NAME(state->m_mmc_prg_mask));
|
|
state->save_item(NAME(state->m_mmc_chr_base));
|
|
state->save_item(NAME(state->m_mmc_chr_mask));
|
|
state->save_item(NAME(state->m_mmc_prg_bank));
|
|
state->save_item(NAME(state->m_mmc_vrom_bank));
|
|
state->save_item(NAME(state->m_mmc_extra_bank));
|
|
|
|
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_pointer(NAME(state->m_wram), state->m_wram_size);
|
|
if (state->m_battery)
|
|
state->save_pointer(NAME(state->m_battery_ram), state->m_battery_size);
|
|
|
|
machine.save().register_postload(save_prepost_delegate(FUNC(nes_state::nes_banks_restore), state));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// machine_start
|
|
//-------------------------------------------------
|
|
|
|
void nes_state::machine_start()
|
|
{
|
|
m_ppu = machine().device<ppu2c0x_device>("ppu");
|
|
|
|
init_nes_core();
|
|
|
|
m_maincpu = machine().device<cpu_device>("maincpu");
|
|
m_sound = machine().device("nessound");
|
|
m_io_ctrlsel = ioport("CTRLSEL");
|
|
m_io_fckey[0] = ioport("FCKEY0");
|
|
m_io_fckey[1] = ioport("FCKEY1");
|
|
m_io_fckey[2] = ioport("FCKEY2");
|
|
m_io_fckey[3] = ioport("FCKEY3");
|
|
m_io_fckey[4] = ioport("FCKEY4");
|
|
m_io_fckey[5] = ioport("FCKEY5");
|
|
m_io_fckey[6] = ioport("FCKEY6");
|
|
m_io_fckey[7] = ioport("FCKEY7");
|
|
m_io_fckey[8] = ioport("FCKEY8");
|
|
m_io_subkey[0 ] = ioport("SUBKEY0");
|
|
m_io_subkey[1 ] = ioport("SUBKEY1");
|
|
m_io_subkey[2 ] = ioport("SUBKEY2");
|
|
m_io_subkey[3 ] = ioport("SUBKEY3");
|
|
m_io_subkey[4 ] = ioport("SUBKEY4");
|
|
m_io_subkey[5 ] = ioport("SUBKEY5");
|
|
m_io_subkey[6 ] = ioport("SUBKEY6");
|
|
m_io_subkey[7 ] = ioport("SUBKEY7");
|
|
m_io_subkey[8 ] = ioport("SUBKEY8");
|
|
m_io_subkey[9 ] = ioport("SUBKEY9");
|
|
m_io_subkey[10] = ioport("SUBKEY10");
|
|
m_io_subkey[11] = ioport("SUBKEY11");
|
|
m_io_subkey[12] = ioport("SUBKEY12");
|
|
m_io_pad[0] = ioport("PAD1");
|
|
m_io_pad[1] = ioport("PAD2");
|
|
m_io_pad[2] = ioport("PAD3");
|
|
m_io_pad[3] = ioport("PAD4");
|
|
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");
|
|
m_io_paddle = ioport("PADDLE");
|
|
m_io_mahjong[0] = ioport("MAH0");
|
|
m_io_mahjong[1] = ioport("MAH1");
|
|
m_io_mahjong[2] = ioport("MAH2");
|
|
m_io_mahjong[3] = ioport("MAH3");
|
|
m_prg_bank_mem[0] = membank("bank1");
|
|
m_prg_bank_mem[1] = membank("bank2");
|
|
m_prg_bank_mem[2] = membank("bank3");
|
|
m_prg_bank_mem[3] = membank("bank4");
|
|
m_prg_bank_mem[4] = membank("bank5");
|
|
|
|
// If we're starting famicom with no disk inserted, we still haven't initialized the VRAM needed for
|
|
// video emulation, so we need to take care of it now
|
|
if (!m_vram)
|
|
{
|
|
m_vram = auto_alloc_array(machine(), UINT8, 0x4000);
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
m_chr_map[i].source = CHRRAM;
|
|
m_chr_map[i].origin = i * 0x400; // for save state uses!
|
|
m_chr_map[i].access = &m_vram[m_chr_map[i].origin];
|
|
}
|
|
}
|
|
|
|
m_irq_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(nes_state::nes_irq_callback),this));
|
|
nes_state_register(machine());
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// update_prg_banks
|
|
//-------------------------------------------------
|
|
|
|
void nes_state::update_prg_banks(int prg_bank_start, int prg_bank_end)
|
|
{
|
|
for (int prg_bank = prg_bank_start; prg_bank <= prg_bank_end; prg_bank++)
|
|
{
|
|
assert(prg_bank >= 0);
|
|
assert(prg_bank < ARRAY_LENGTH(m_prg_bank));
|
|
assert(prg_bank < ARRAY_LENGTH(m_prg_bank_mem));
|
|
|
|
m_prg_bank_mem[prg_bank]->set_entry(m_prg_bank[prg_bank]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
READ8_MEMBER(nes_state::nes_IN0_r)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
int ret;
|
|
|
|
if ((cfg & 0x000f) >= 0x08) // for now we treat the FC keyboard separately from other inputs!
|
|
{
|
|
// here we should have the tape input
|
|
ret = 0;
|
|
}
|
|
else
|
|
{
|
|
/* 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. */
|
|
ret = 0x40;
|
|
|
|
ret |= ((m_in_0.i0 >> m_in_0.shift) & 0x01);
|
|
|
|
/* zapper */
|
|
if ((cfg & 0x000f) == 0x0002)
|
|
{
|
|
int x = m_in_0.i1; /* read Zapper x-position */
|
|
int y = m_in_0.i2; /* read Zapper 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();
|
|
|
|
/* look at the screen and see 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 */
|
|
|
|
/* If button 1 is pressed, indicate the light gun trigger is pressed */
|
|
ret |= ((m_in_0.i0 & 0x01) << 4);
|
|
}
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 0 read, val: %02x, pc: %04x, bits read: %d, chan0: %08x\n", ret, space.device().safe_pc(), m_in_0.shift, m_in_0.i0);
|
|
|
|
m_in_0.shift++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
READ8_MEMBER(nes_state::nes_IN1_r)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
int ret;
|
|
|
|
// row of the keyboard matrix are read 4-bits at time, and gets returned as bit1->bit4
|
|
if ((cfg & 0x000f) == 0x08) // for now we treat the FC keyboard separately from other inputs!
|
|
{
|
|
if (m_fck_scan < 9)
|
|
ret = ~(((m_io_fckey[m_fck_scan]->read() >> (m_fck_mode * 4)) & 0x0f) << 1) & 0x1e;
|
|
else
|
|
ret = 0x1e;
|
|
}
|
|
else if ((cfg & 0x000f) == 0x09) // for now we treat the Subor keyboard separately from other inputs!
|
|
{
|
|
if (m_fck_scan < 12)
|
|
ret = ~(((m_io_subkey[m_fck_scan]->read() >> (m_fck_mode * 4)) & 0x0f) << 1) & 0x1e;
|
|
else
|
|
ret = 0x1e;
|
|
}
|
|
else
|
|
{
|
|
/* 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. */
|
|
ret = 0x40;
|
|
|
|
/* Handle data line 0's serial output */
|
|
if((cfg & 0x00f0) == 0x0070)
|
|
ret |= (((m_in_1.i0 >> m_in_1.shift) & 0x01) << 1);
|
|
else
|
|
ret |= ((m_in_1.i0 >> m_in_1.shift) & 0x01);
|
|
|
|
/* zapper */
|
|
if ((cfg & 0x00f0) == 0x0030)
|
|
{
|
|
int x = m_in_1.i1; /* read Zapper x-position */
|
|
int y = m_in_1.i2; /* read Zapper 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();
|
|
|
|
/* look at the screen and see 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 */
|
|
|
|
/* If button 1 is pressed, indicate the light gun trigger is pressed */
|
|
ret |= ((m_in_1.i0 & 0x01) << 4);
|
|
}
|
|
|
|
/* arkanoid dial */
|
|
else if ((cfg & 0x00f0) == 0x0040)
|
|
{
|
|
/* Handle data line 2's serial output */
|
|
ret |= ((m_in_1.i2 >> m_in_1.shift) & 0x01) << 3;
|
|
|
|
/* Handle data line 3's serial output - bits are reversed */
|
|
/* NPW 27-Nov-2007 - there is no third subscript! commenting out */
|
|
/* ret |= ((m_in_1[3] >> m_in_1.shift) & 0x01) << 4; */
|
|
/* ret |= ((m_in_1[3] << m_in_1.shift) & 0x80) >> 3; */
|
|
}
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 1 read, val: %02x, pc: %04x, bits read: %d, chan0: %08x\n", ret, space.device().safe_pc(), m_in_1.shift, m_in_1.i0);
|
|
|
|
m_in_1.shift++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// FIXME: this is getting messier and messier (no pun intended). inputs reading should be simplified and port_categories cleaned up
|
|
// to also emulate the fact that nothing should be in Port 2 if there is a Crazy Climber pad, etc.
|
|
static void nes_read_input_device( running_machine &machine, int cfg, nes_input *vals, int pad_port, int supports_zapper, int mux_data )
|
|
{
|
|
nes_state *state = machine.driver_data<nes_state>();
|
|
|
|
vals->i0 = 0;
|
|
vals->i1 = 0;
|
|
vals->i2 = 0;
|
|
|
|
switch (cfg & 0x0f)
|
|
{
|
|
case 0x01: /* gamepad */
|
|
if (pad_port >= 0)
|
|
vals->i0 = state->m_io_pad[pad_port]->read();
|
|
break;
|
|
|
|
case 0x02: /* zapper 1 */
|
|
if (supports_zapper)
|
|
{
|
|
vals->i0 = state->m_io_zapper1_t->read();
|
|
vals->i1 = state->m_io_zapper1_x->read();
|
|
vals->i2 = state->m_io_zapper1_y->read();
|
|
}
|
|
break;
|
|
|
|
case 0x03: /* zapper 2 */
|
|
if (supports_zapper)
|
|
{
|
|
vals->i0 = state->m_io_zapper2_t->read();
|
|
vals->i1 = state->m_io_zapper2_x->read();
|
|
vals->i2 = state->m_io_zapper2_y->read();
|
|
}
|
|
break;
|
|
|
|
case 0x04: /* arkanoid paddle */
|
|
if (pad_port == 1)
|
|
vals->i0 = (UINT8) ((UINT8) state->m_io_paddle->read() + (UINT8)0x52) ^ 0xff;
|
|
break;
|
|
|
|
case 0x06: /* crazy climber controller */
|
|
if (pad_port == 0)
|
|
{
|
|
state->m_in_0.i0 = state->m_io_cc_left->read();
|
|
state->m_in_1.i0 = state->m_io_cc_right->read();
|
|
}
|
|
break;
|
|
|
|
case 0x07: /* Mahjong Panel */
|
|
if(mux_data & 0xf8)
|
|
logerror("Error: Mahjong panel read with mux data %02x\n",mux_data);
|
|
else
|
|
vals->i0 = state->m_io_mahjong[mux_data >> 1]->read();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
TIMER_CALLBACK_MEMBER(nes_state::lightgun_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) == 0x0030)
|
|
{
|
|
/* enable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 1, CROSSHAIR_SCREEN_ALL);
|
|
}
|
|
else
|
|
{
|
|
/* disable lightpen crosshair */
|
|
crosshair_set_screen(machine(), 1, CROSSHAIR_SCREEN_NONE);
|
|
}
|
|
}
|
|
|
|
WRITE8_MEMBER(nes_state::nes_IN0_w)
|
|
{
|
|
int cfg = m_io_ctrlsel->read();
|
|
|
|
/* Check if lightgun has been chosen as input: if so, enable crosshair */
|
|
machine().scheduler().timer_set(attotime::zero, timer_expired_delegate(FUNC(nes_state::lightgun_tick),this));
|
|
|
|
if ((cfg & 0x000f) >= 0x08) // for now we treat the FC keyboard separately from other inputs!
|
|
{
|
|
// here we should also have the tape output
|
|
|
|
if (BIT(data, 2)) // keyboard active
|
|
{
|
|
int lines = ((cfg & 0x000f) == 0x04) ? 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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (data & 0x01)
|
|
return;
|
|
|
|
if (LOG_JOY)
|
|
logerror("joy 0 bits read: %d\n", m_in_0.shift);
|
|
|
|
/* Toggling bit 0 high then low resets both controllers */
|
|
m_in_0.shift = 0;
|
|
m_in_1.shift = 0;
|
|
|
|
/* Read the input devices */
|
|
if ((cfg & 0x000f) != 0x06)
|
|
{
|
|
nes_read_input_device(machine(), cfg >> 0, &m_in_0, 0, TRUE,data & 0xfe);
|
|
nes_read_input_device(machine(), cfg >> 4, &m_in_1, 1, TRUE,data & 0xfe);
|
|
nes_read_input_device(machine(), cfg >> 8, &m_in_2, 2, FALSE,data & 0xfe);
|
|
nes_read_input_device(machine(), cfg >> 12, &m_in_3, 3, FALSE,data & 0xfe);
|
|
}
|
|
else // crazy climber pad
|
|
{
|
|
nes_read_input_device(machine(), 0, &m_in_1, 1, TRUE,data & 0xfe);
|
|
nes_read_input_device(machine(), 0, &m_in_2, 2, FALSE,data & 0xfe);
|
|
nes_read_input_device(machine(), 0, &m_in_3, 3, FALSE,data & 0xfe);
|
|
nes_read_input_device(machine(), cfg >> 0, &m_in_0, 0, TRUE,data & 0xfe);
|
|
}
|
|
|
|
if (cfg & 0x0f00)
|
|
m_in_0.i0 |= (m_in_2.i0 << 8) | (0x08 << 16);
|
|
|
|
if (cfg & 0xf000)
|
|
m_in_1.i0 |= (m_in_3.i0 << 8) | (0x04 << 16);
|
|
}
|
|
}
|
|
|
|
|
|
WRITE8_MEMBER(nes_state::nes_IN1_w)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
void nes_partialhash(hash_collection &dest, const unsigned char *data,
|
|
unsigned long length, const char *functions)
|
|
{
|
|
if (length <= 16)
|
|
return;
|
|
dest.compute(&data[16], length - 16, functions);
|
|
}
|
|
|
|
/**************************
|
|
|
|
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 == NULL)
|
|
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 == NULL)
|
|
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);
|
|
set_nt_mirroring(BIT(data, 3) ? PPU_MIRROR_HORZ : 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;
|
|
}
|
|
}
|
|
|
|
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 == NULL)
|
|
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)
|
|
{
|
|
/* clear some of the variables we don't use */
|
|
m_trainer = 0;
|
|
m_battery = 0;
|
|
m_prg_ram = 0;
|
|
m_four_screen_vram = 0;
|
|
m_hard_mirroring = 0;
|
|
m_prg_chunks = m_chr_chunks = 0;
|
|
|
|
/* initialize the disk system */
|
|
m_disk_expansion = 1;
|
|
m_pcb_id = NO_BOARD;
|
|
|
|
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_data = auto_alloc_array_clear(machine(), UINT8, 65500 * 2);
|
|
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);
|
|
}
|