diff options
Diffstat (limited to 'src/mess/machine/nes.c')
-rw-r--r-- | src/mess/machine/nes.c | 1711 |
1 files changed, 1711 insertions, 0 deletions
diff --git a/src/mess/machine/nes.c b/src/mess/machine/nes.c new file mode 100644 index 00000000000..90aaf95b693 --- /dev/null +++ b/src/mess/machine/nes.c @@ -0,0 +1,1711 @@ +#include "emu.h" +#include "crsshair.h" +#include "cpu/m6502/m6502.h" +#include "video/ppu2c0x.h" +#include "includes/nes.h" +#include "machine/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 + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static void init_nes_core(running_machine &machine); +static void nes_machine_stop(running_machine &machine); + + +static void fds_irq(device_t *device, int scanline, int vblank, int blanked); + +/*************************************************************************** + FUNCTIONS +***************************************************************************/ + +static void init_nes_core( running_machine &machine ) +{ + nes_state *state = machine.driver_data<nes_state>(); + address_space *space = machine.device("maincpu")->memory().space(AS_PROGRAM); + static const char *const bank_names[] = { "bank1", "bank2", "bank3", "bank4" }; + int prg_banks = (state->m_prg_chunks == 1) ? (2 * 2) : (state->m_prg_chunks * 2); + int i; + + state->m_rom = machine.root_device().memregion("maincpu")->base(); + state->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_legacy_readwrite_handler(0, 0x1fff, FUNC(nes_chr_r), FUNC(nes_chr_w)); + machine.device("ppu")->memory().space(AS_PROGRAM)->install_legacy_readwrite_handler(0x2000, 0x3eff, FUNC(nes_nt_r), FUNC(nes_nt_w)); + + state->membank("bank10")->set_base(state->m_rom); + + /* If there is Disk Expansion and no cart has been loaded, setup memory accordingly */ + if (state->m_disk_expansion && state->m_pcb_id == NO_BOARD) + { + /* If we are loading a disk we have already filled state->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 (state->m_fds_data == NULL) + { + UINT32 size = (state->m_prg_chunks == 1) ? 2 * 0x4000 : state->m_prg_chunks * 0x4000; + state->m_fds_data = auto_alloc_array_clear(machine, UINT8, size); + memcpy(state->m_fds_data, state->m_prg, size); // copy in fds_data the cart PRG + } + if (state->m_fds_ram == NULL) + state->m_fds_ram = auto_alloc_array(machine, UINT8, 0x8000); + + space->install_read_handler(0x4030, 0x403f, read8_delegate(FUNC(nes_state::nes_fds_r),state)); + 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),state)); + space->install_write_bank(0x6000, 0xdfff, "bank2"); + + state->membank("bank1")->set_base(&state->m_rom[0xe000]); + state->membank("bank2")->set_base(state->m_fds_ram); + return; + } + + /* Set up the mapper callbacks */ + pcb_handlers_setup(machine); + + /* 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++) + { + state->membank(bank_names[i])->configure_entries(0, prg_banks, state->m_prg, 0x2000); + // some mappers (e.g. MMC5) can map PRG RAM in 0x8000-0xffff as well + if (state->m_prg_ram) + state->membank(bank_names[i])->configure_entries(prg_banks, state->m_wram_size / 0x2000, state->m_wram, 0x2000); + // however, at start we point to PRG ROM + state->membank(bank_names[i])->set_entry(i); + state->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 (state->m_wram) */ + state->membank("bank5")->configure_entries(0, prg_banks, state->m_prg, 0x2000); + state->m_battery_bank5_start = prg_banks; + state->m_prgram_bank5_start = prg_banks; + state->m_empty_bank5_start = prg_banks; + + /* add battery ram, but only if there's no trainer since they share overlapping memory. */ + if (state->m_battery && !state->m_trainer) + { + UINT32 bank_size = (state->m_battery_size > 0x2000) ? 0x2000 : state->m_battery_size; + int bank_num = (state->m_battery_size > 0x2000) ? state->m_battery_size / 0x2000 : 1; + state->membank("bank5")->configure_entries(prg_banks, bank_num, state->m_battery_ram, bank_size); + state->m_prgram_bank5_start += bank_num; + state->m_empty_bank5_start += bank_num; + } + /* add prg ram. */ + if (state->m_prg_ram) + { + state->membank("bank5")->configure_entries(state->m_prgram_bank5_start, state->m_wram_size / 0x2000, state->m_wram, 0x2000); + state->m_empty_bank5_start += state->m_wram_size / 0x2000; + } + + state->membank("bank5")->configure_entry(state->m_empty_bank5_start, state->m_rom + 0x6000); + + /* if we have any additional PRG RAM, point bank5 to its first bank */ + if (state->m_battery || state->m_prg_ram) + state->m_prg_bank[4] = state->m_battery_bank5_start; + else + state->m_prg_bank[4] = state->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! + + state->membank("bank5")->set_entry(state->m_prg_bank[4]); + + if (state->m_four_screen_vram) + { + state->m_extended_ntram = auto_alloc_array_clear(machine, UINT8, 0x2000); + state->save_pointer(NAME(state->m_extended_ntram), 0x2000); + } + + if (state->m_four_screen_vram) + set_nt_mirroring(machine, PPU_MIRROR_4SCREEN); + else + { + switch (state->m_hard_mirroring) + { + case PPU_MIRROR_HORZ: + case PPU_MIRROR_VERT: + case PPU_MIRROR_HIGH: + case PPU_MIRROR_LOW: + set_nt_mirroring(machine, state->m_hard_mirroring); + break; + default: + set_nt_mirroring(machine, 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 (state->m_mmc_write_mid) + space->install_legacy_write_handler(0x6000, 0x7fff, state->m_mmc_write_mid,state->m_mmc_write_mid_name); + if (state->m_mmc_write) + space->install_legacy_write_handler(0x8000, 0xffff, state->m_mmc_write, state->m_mmc_write_name); + + // 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 (state->m_mmc_read_mid) + space->install_legacy_read_handler(0x6000, 0x7fff, state->m_mmc_read_mid,state->m_mmc_read_mid_name); + if (state->m_mmc_read) + space->install_legacy_read_handler(0x8000, 0xffff, state->m_mmc_read,state->m_mmc_read_name); + + // install additional handlers + if (state->m_pcb_id == BTL_SMB2B || state->m_mapper == 50) + { + space->install_legacy_write_handler(0x4020, 0x403f, FUNC(smb2jb_extra_w)); + space->install_legacy_write_handler(0x40a0, 0x40bf, FUNC(smb2jb_extra_w)); + } + + if (state->m_pcb_id == KAISER_KS7017) + { + space->install_legacy_read_handler(0x4030, 0x4030, FUNC(ks7017_extra_r)); + space->install_legacy_write_handler(0x4020, 0x40ff, FUNC(ks7017_extra_w)); + } + + if (state->m_pcb_id == UNL_603_5052) + { + space->install_legacy_read_handler(0x4020, 0x40ff, FUNC(unl_6035052_extra_r)); + space->install_legacy_write_handler(0x4020, 0x40ff, FUNC(unl_6035052_extra_w)); + } + + if (state->m_pcb_id == WAIXING_SH2) + machine.device("ppu")->memory().space(AS_PROGRAM)->install_legacy_read_handler(0, 0x1fff, FUNC(waixing_sh2_chr_r)); +} + +// to be probably removed (it does nothing since a long time) +int nes_ppu_vidaccess( device_t *device, int address, int data ) +{ + return data; +} + +MACHINE_RESET( nes ) +{ + nes_state *state = machine.driver_data<nes_state>(); + + /* Reset the mapper variables. Will also mark the char-gen ram as dirty */ + if (state->m_disk_expansion && state->m_pcb_id == NO_BOARD) + state->m_ppu->set_hblank_callback(fds_irq); + else + nes_pcb_reset(machine); + + /* Reset the serial input ports */ + state->m_in_0.shift = 0; + state->m_in_1.shift = 0; + + machine.device("maincpu")->reset(); +} + +static TIMER_CALLBACK( nes_irq_callback ) +{ + nes_state *state = machine.driver_data<nes_state>(); + device_set_input_line(state->m_maincpu, M6502_IRQ_LINE, HOLD_LINE); + state->m_irq_timer->adjust(attotime::never); +} + +static void nes_banks_restore(nes_state *state) +{ + state->membank("bank1")->set_entry(state->m_prg_bank[0]); + state->membank("bank2")->set_entry(state->m_prg_bank[1]); + state->membank("bank3")->set_entry(state->m_prg_bank[2]); + state->membank("bank4")->set_entry(state->m_prg_bank[3]); + state->membank("bank5")->set_entry(state->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_banks_restore), state)); +} + +MACHINE_START( nes ) +{ + nes_state *state = machine.driver_data<nes_state>(); + + state->m_ppu = machine.device<ppu2c0x_device>("ppu"); + init_nes_core(machine); + machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(nes_machine_stop),&machine)); + + state->m_maincpu = machine.device("maincpu"); + state->m_sound = machine.device("nessound"); + state->m_cart = machine.device("cart"); + + // 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 (!state->m_vram) + { + state->m_vram = auto_alloc_array(machine, UINT8, 0x4000); + for (int i = 0; i < 8; i++) + { + state->m_chr_map[i].source = CHRRAM; + state->m_chr_map[i].origin = i * 0x400; // for save state uses! + state->m_chr_map[i].access = &state->m_vram[state->m_chr_map[i].origin]; + } + } + + state->m_irq_timer = machine.scheduler().timer_alloc(FUNC(nes_irq_callback)); + nes_state_register(machine); +} + +static void nes_machine_stop( running_machine &machine ) +{ + nes_state *state = machine.driver_data<nes_state>(); + device_image_interface *image = dynamic_cast<device_image_interface *>(state->m_cart); + /* Write out the battery file if necessary */ + if (state->m_battery) + image->battery_save(state->m_battery_ram, state->m_battery_size); + + if (state->m_mapper_bram_size) + image->battery_save(state->m_mapper_bram, state->m_mapper_bram_size); +} + + + +READ8_MEMBER(nes_state::nes_IN0_r) +{ + int cfg = ioport("CTRLSEL")->read(); + int ret; + + if ((cfg & 0x000f) >= 0x07) // 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, cpu_get_pc(&space.device()), m_in_0.shift, m_in_0.i0); + + m_in_0.shift++; + } + + return ret; +} + +// row of the keyboard matrix are read 4-bits at time, and gets returned as bit1->bit4 +static UINT8 nes_read_fc_keyboard_line( running_machine &machine, UINT8 scan, UINT8 mode ) +{ + static const char *const fc_keyport_names[] = { "FCKEY0", "FCKEY1", "FCKEY2", "FCKEY3", "FCKEY4", "FCKEY5", "FCKEY6", "FCKEY7", "FCKEY8" }; + return ((machine.root_device().ioport(fc_keyport_names[scan])->read() >> (mode * 4)) & 0x0f) << 1; +} + +static UINT8 nes_read_subor_keyboard_line( running_machine &machine, UINT8 scan, UINT8 mode ) +{ + static const char *const sub_keyport_names[] = { "SUBKEY0", "SUBKEY1", "SUBKEY2", "SUBKEY3", "SUBKEY4", + "SUBKEY5", "SUBKEY6", "SUBKEY7", "SUBKEY8", "SUBKEY9", "SUBKEY10", "SUBKEY11", "SUBKEY12" }; + return ((machine.root_device().ioport(sub_keyport_names[scan])->read() >> (mode * 4)) & 0x0f) << 1; +} + +READ8_MEMBER(nes_state::nes_IN1_r) +{ + int cfg = ioport("CTRLSEL")->read(); + int ret; + + if ((cfg & 0x000f) == 0x07) // for now we treat the FC keyboard separately from other inputs! + { + if (m_fck_scan < 9) + ret = ~nes_read_fc_keyboard_line(machine(), m_fck_scan, m_fck_mode) & 0x1e; + else + ret = 0x1e; + } + else if ((cfg & 0x000f) == 0x08) // for now we treat the Subor keyboard separately from other inputs! + { + if (m_fck_scan < 12) + ret = ~nes_read_subor_keyboard_line(machine(), m_fck_scan, m_fck_mode) & 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 */ + 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, cpu_get_pc(&space.device()), 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 ) +{ + nes_state *state = machine.driver_data<nes_state>(); + static const char *const padnames[] = { "PAD1", "PAD2", "PAD3", "PAD4", "CC_LEFT", "CC_RIGHT" }; + + vals->i0 = 0; + vals->i1 = 0; + vals->i2 = 0; + + switch (cfg & 0x0f) + { + case 0x01: /* gamepad */ + if (pad_port >= 0) + vals->i0 = machine.root_device().ioport(padnames[pad_port])->read(); + break; + + case 0x02: /* zapper 1 */ + if (supports_zapper) + { + vals->i0 = machine.root_device().ioport("ZAPPER1_T")->read(); + vals->i1 = machine.root_device().ioport("ZAPPER1_X")->read(); + vals->i2 = machine.root_device().ioport("ZAPPER1_Y")->read(); + } + break; + + case 0x03: /* zapper 2 */ + if (supports_zapper) + { + vals->i0 = machine.root_device().ioport("ZAPPER2_T")->read(); + vals->i1 = machine.root_device().ioport("ZAPPER2_X")->read(); + vals->i2 = machine.root_device().ioport("ZAPPER2_Y")->read(); + } + break; + + case 0x04: /* arkanoid paddle */ + if (pad_port == 1) + vals->i0 = (UINT8) ((UINT8) machine.root_device().ioport("PADDLE")->read() + (UINT8)0x52) ^ 0xff; + break; + + case 0x06: /* crazy climber controller */ + if (pad_port == 0) + { + state->m_in_0.i0 = machine.root_device().ioport(padnames[4])->read(); + state->m_in_1.i0 = machine.root_device().ioport(padnames[5])->read(); + } + break; + } +} + + +static TIMER_CALLBACK( lightgun_tick ) +{ + if ((machine.root_device().ioport("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 ((machine.root_device().ioport("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 = ioport("CTRLSEL")->read(); + + /* Check if lightgun has been chosen as input: if so, enable crosshair */ + machine().scheduler().timer_set(attotime::zero, FUNC(lightgun_tick)); + + if ((cfg & 0x000f) >= 0x07) // 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); + nes_read_input_device(machine(), cfg >> 4, &m_in_1, 1, TRUE); + nes_read_input_device(machine(), cfg >> 8, &m_in_2, 2, FALSE); + nes_read_input_device(machine(), cfg >> 12, &m_in_3, 3, FALSE); + } + else // crazy climber pad + { + nes_read_input_device(machine(), 0, &m_in_1, 1, TRUE); + nes_read_input_device(machine(), 0, &m_in_2, 2, FALSE); + nes_read_input_device(machine(), 0, &m_in_3, 3, FALSE); + nes_read_input_device(machine(), cfg >> 0, &m_in_0, 0, TRUE); + } + + 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) +{ +} + +struct nes_cart_lines +{ + const char *tag; + int line; +}; + +static const struct nes_cart_lines nes_cart_lines_table[] = +{ + { "PRG A0", 0 }, + { "PRG A1", 1 }, + { "PRG A2", 2 }, + { "PRG A3", 3 }, + { "PRG A4", 4 }, + { "PRG A5", 5 }, + { "PRG A6", 6 }, + { "PRG A7", 7 }, + { "CHR A10", 10 }, + { "CHR A11", 11 }, + { "CHR A12", 12 }, + { "CHR A13", 13 }, + { "CHR A14", 14 }, + { "CHR A15", 15 }, + { "CHR A16", 16 }, + { "CHR A17", 17 }, + { "NC", 127 }, + { 0 } +}; + +static int nes_cart_get_line( const char *feature ) +{ + const struct nes_cart_lines *nes_line = &nes_cart_lines_table[0]; + + if (feature == NULL) + return 128; + + while (nes_line->tag) + { + if (strcmp(nes_line->tag, feature) == 0) + break; + + nes_line++; + } + + return nes_line->line; +} + +DEVICE_IMAGE_LOAD( nes_cart ) +{ + nes_state *state = image.device().machine().driver_data<nes_state>(); + state->m_pcb_id = NO_BOARD; // initialization + + if (image.software_entry() == NULL) + { + const char *mapinfo = NULL; + int mapint1 = 0, mapint2 = 0, mapint3 = 0, mapint4 = 0; //, goodcrcinfo = 0; + char magic[4], extend[5]; + int local_options = 0; + char m; + + /* Check first 4 bytes of the image to decide if it is UNIF or iNES */ + /* Unfortunately, many .unf files have been released as .nes, so we cannot rely on extensions only */ + memset(magic, '\0', sizeof(magic)); + image.fread(magic, 4); + + if ((magic[0] == 'N') && (magic[1] == 'E') && (magic[2] == 'S')) /* If header starts with 'NES' it is iNES */ + { + UINT32 prg_size; + state->m_ines20 = 0; + state->m_battery_size = NES_BATTERY_SIZE; // with iNES we can only support 8K WRAM battery (iNES 2.0 might fix this) + state->m_prg_ram = 1; // always map state->m_wram in bank5 (eventually, this should be enabled only for some mappers) + + // check if the image is recognized by nes.hsi + mapinfo = hashfile_extrainfo(image); + + // image_extrainfo() resets the file position back to start. + // Let's skip past the magic header once again. + image.fseek(4, SEEK_SET); + + image.fread(&state->m_prg_chunks, 1); + image.fread(&state->m_chr_chunks, 1); + /* Read the first ROM option byte (offset 6) */ + image.fread(&m, 1); + + /* Interpret the iNES header flags */ + state->m_mapper = (m & 0xf0) >> 4; + local_options = m & 0x0f; + + /* Read the second ROM option byte (offset 7) */ + image.fread(&m, 1); + + switch (m & 0xc) + { + case 0x4: + case 0xc: + // probably the header got corrupted: don't trust upper bits for mapper + break; + + case 0x8: // it's iNES 2.0 format + state->m_ines20 = 1; + case 0x0: + default: + state->m_mapper = state->m_mapper | (m & 0xf0); + break; + } + + if (mapinfo /* && !state->m_ines20 */) + { + if (4 == sscanf(mapinfo,"%d %d %d %d", &mapint1, &mapint2, &mapint3, &mapint4)) + { + /* image is present in nes.hsi: overwrite the header settings with these */ + state->m_mapper = mapint1; + local_options = mapint2 & 0x0f; + state->m_crc_hack = (mapint2 & 0xf0) >> 4; // this is used to differentiate among variants of the same Mapper (see below) + state->m_prg_chunks = mapint3; + state->m_chr_chunks = mapint4; + logerror("NES.HSI info: %d %d %d %d\n", mapint1, mapint2, mapint3, mapint4); +// mame_printf_error("NES.HSI info: %d %d %d %d\n", mapint1, mapint2, mapint3, mapint4); +// goodcrcinfo = 1; + state->m_ines20 = 0; + } + else + { + logerror("NES: [%s], Invalid mapinfo found\n", mapinfo); + } + } + else + { + logerror("NES: No extrainfo found\n"); + } + + state->m_hard_mirroring = (local_options & 0x01) ? PPU_MIRROR_VERT : PPU_MIRROR_HORZ; +// mame_printf_error("%s\n", state->m_hard_mirroring & 0x01 ? "Vertical" : "Horizontal"); + state->m_battery = local_options & 0x02; + state->m_trainer = local_options & 0x04; + state->m_four_screen_vram = local_options & 0x08; + + if (state->m_battery) + logerror("-- Battery found\n"); + + if (state->m_trainer) + logerror("-- Trainer found\n"); + + if (state->m_four_screen_vram) + logerror("-- 4-screen VRAM\n"); + + if (state->m_ines20) + { + logerror("Extended iNES format:\n"); + image.fread(&extend, 5); + state->m_mapper |= (extend[0] & 0x0f) << 8; + logerror("-- mapper: %d\n", state->m_mapper); + logerror("-- submapper: %d\n", (extend[0] & 0xf0) >> 4); + state->m_prg_chunks |= ((extend[1] & 0x0f) << 8); + state->m_chr_chunks |= ((extend[1] & 0xf0) << 4); + logerror("-- PRG chunks: %d\n", state->m_prg_chunks); + logerror("-- CHR chunks: %d\n", state->m_chr_chunks); + logerror("-- PRG NVWRAM: %d\n", extend[2] & 0x0f); + logerror("-- PRG WRAM: %d\n", (extend[2] & 0xf0) >> 4); + logerror("-- CHR NVWRAM: %d\n", extend[3] & 0x0f); + logerror("-- CHR WRAM: %d\n", (extend[3] & 0xf0) >> 4); + logerror("-- TV System: %d\n", extend[4] & 3); + } + + // Allocate class pointers for PRG/VROM/VRAM/WRAM + prg_size = (state->m_prg_chunks == 1) ? 2 * 0x4000 : state->m_prg_chunks * 0x4000; + state->m_prg = auto_alloc_array(image.device().machine(), UINT8, prg_size); + if (state->m_chr_chunks) + state->m_vrom = auto_alloc_array(image.device().machine(), UINT8, state->m_chr_chunks * 0x2000); + + state->m_vram_chunks = 2; + state->m_vram = auto_alloc_array(image.device().machine(), UINT8, 0x4000); + + // FIXME: this should only be allocated if there is actual wram in the cart (i.e. if state->m_prg_ram = 1)! + // or if there is a trainer, I think + state->m_wram_size = 0x10000; + state->m_wram = auto_alloc_array(image.device().machine(), UINT8, state->m_wram_size); + + /* Setup PCB type (needed to add proper handlers later) */ + state->m_pcb_id = nes_get_mmc_id(image.device().machine(), state->m_mapper); + + // a few mappers correspond to multiple PCBs, so we need a few additional checks + switch (state->m_pcb_id) + { + case STD_CNROM: + if (state->m_mapper == 185) + { + switch (state->m_crc_hack) + { + case 0x0: // pin26: CE, pin27: CE (B-Wings, Bird Week) + state->m_ce_mask = 0x03; + state->m_ce_state = 0x03; + break; + case 0x4: // pin26: CE, pin27: /CE (Mighty Bomb Jack, Spy Vs. Spy) + state->m_ce_mask = 0x03; + state->m_ce_state = 0x01; + break; + case 0x8: // pin26: /CE, pin27: CE (Sansu 1, 2, 3 Nen) + state->m_ce_mask = 0x03; + state->m_ce_state = 0x02; + break; + case 0xc: // pin26: /CE, pin27: /CE (Seicross v2.0) + state->m_ce_mask = 0x03; + state->m_ce_state = 0x00; + break; + } + } + break; + case KONAMI_VRC2: + if (state->m_mapper == 22) + { + state->m_vrc_ls_prg_a = 0; + state->m_vrc_ls_prg_b = 1; + state->m_vrc_ls_chr = 1; + } + if (state->m_mapper == 23 && !state->m_crc_hack) + { + state->m_vrc_ls_prg_a = 1; + state->m_vrc_ls_prg_b = 0; + state->m_vrc_ls_chr = 0; + } + if (state->m_mapper == 23 && state->m_crc_hack) + { + // here there are also Akumajou Special, Crisis Force, Parodius da!, Tiny Toons which are VRC-4 + state->m_vrc_ls_prg_a = 3; + state->m_vrc_ls_prg_b = 2; + state->m_pcb_id = KONAMI_VRC4; // this allows for konami_irq to be installed at reset + } + break; + case KONAMI_VRC4: + if (state->m_mapper == 21) + { + // Wai Wai World 2 & Ganbare Goemon Gaiden 2 (the latter with crc_hack) + state->m_vrc_ls_prg_a = state->m_crc_hack ? 7 : 2; + state->m_vrc_ls_prg_b = state->m_crc_hack ? 6 : 1; + } + if (state->m_mapper == 25) // here there is also Ganbare Goemon Gaiden which is VRC-2 + { + state->m_vrc_ls_prg_a = state->m_crc_hack ? 2 : 0; + state->m_vrc_ls_prg_b = state->m_crc_hack ? 3 : 1; + } + break; + case KONAMI_VRC6: + if (state->m_mapper == 24) + { + state->m_vrc_ls_prg_a = 1; + state->m_vrc_ls_prg_b = 0; + } + if (state->m_mapper == 26) + { + state->m_vrc_ls_prg_a = 0; + state->m_vrc_ls_prg_b = 1; + } + break; + case IREM_G101: + if (state->m_crc_hack) + state->m_hard_mirroring = PPU_MIRROR_HIGH; // Major League has hardwired mirroring + break; + case DIS_74X161X161X32: + if (state->m_mapper == 70) + state->m_hard_mirroring = PPU_MIRROR_VERT; // only hardwired mirroring makes different mappers 70 & 152 + break; + case SUNSOFT_2: + if (state->m_mapper == 93) + state->m_hard_mirroring = PPU_MIRROR_VERT; // only hardwired mirroring makes different mappers 89 & 93 + break; + case STD_BXROM: + if (state->m_crc_hack) + state->m_pcb_id = AVE_NINA01; // Mapper 34 is used for 2 diff boards + break; + case BANDAI_LZ93: + if (state->m_crc_hack) + state->m_pcb_id = BANDAI_JUMP2; // Mapper 153 is used for 2 diff boards + break; + case IREM_HOLYDIV: + if (state->m_crc_hack) + state->m_pcb_id = JALECO_JF16; // Mapper 78 is used for 2 diff boards + break; + case CAMERICA_BF9093: + if (state->m_crc_hack) + state->m_pcb_id = CAMERICA_BF9097; // Mapper 71 is used for 2 diff boards + break; + case HES_BOARD: + if (state->m_crc_hack) + state->m_pcb_id = HES6IN1_BOARD; // Mapper 113 is used for 2 diff boards + break; + case WAIXING_ZS: + if (state->m_crc_hack) + state->m_pcb_id = WAIXING_DQ8; // Mapper 242 is used for 2 diff boards + break; + case BMC_GOLD_7IN1: + if (state->m_crc_hack) + state->m_pcb_id = BMC_MARIOPARTY_7IN1; // Mapper 52 is used for 2 diff boards + break; + //FIXME: we also have to fix Action 52 PRG loading somewhere... + } + + /* Allocate internal Mapper RAM for boards which require it */ + if (state->m_pcb_id == STD_EXROM) + state->m_mapper_ram = auto_alloc_array(image.device().machine(), UINT8, 0x400); + + if (state->m_pcb_id == TAITO_X1_005 || state->m_pcb_id == TAITO_X1_005_A) + state->m_mapper_bram = auto_alloc_array(image.device().machine(), UINT8, 0x80); + + if (state->m_pcb_id == TAITO_X1_017) + state->m_mapper_bram = auto_alloc_array(image.device().machine(), UINT8, 0x1400); + + if (state->m_pcb_id == NAMCOT_163) + state->m_mapper_ram = auto_alloc_array(image.device().machine(), UINT8, 0x2000); + + if (state->m_pcb_id == FUKUTAKE_BOARD) + state->m_mapper_ram = auto_alloc_array(image.device().machine(), UINT8, 2816); + + /* Position past the header */ + image.fseek(16, SEEK_SET); + + /* Load the 0x200 byte trainer at 0x7000 if it exists */ + if (state->m_trainer) + image.fread(&state->m_wram[0x1000], 0x200); + + /* Read in the program chunks */ + image.fread(&state->m_prg[0], 0x4000 * state->m_prg_chunks); + if (state->m_prg_chunks == 1) + memcpy(&state->m_prg[0x4000], &state->m_prg[0], 0x4000); + +#if SPLIT_PRG + { + FILE *prgout; + char outname[255]; + + sprintf(outname, "%s.prg", image.filename()); + prgout = fopen(outname, "wb"); + if (prgout) + { + fwrite(&state->m_prg[0], 1, 0x4000 * state->m_prg_chunks, prgout); + mame_printf_error("Created PRG chunk\n"); + } + + fclose(prgout); + } +#endif + + logerror("**\n"); + logerror("Mapper: %d\n", state->m_mapper); + logerror("PRG chunks: %02x, size: %06x\n", state->m_prg_chunks, 0x4000 * state->m_prg_chunks); + // mame_printf_error("Mapper: %d\n", state->m_mapper); + // mame_printf_error("PRG chunks: %02x, size: %06x\n", state->m_prg_chunks, 0x4000 * state->m_prg_chunks); + + /* Read in any chr chunks */ + if (state->m_chr_chunks > 0) + { + image.fread(state->m_vrom, state->m_chr_chunks * 0x2000); + if (state->m_mapper == 2) + logerror("Warning: VROM has been found in VRAM-based mapper. Either the mapper is set wrong or the ROM image is incorrect.\n"); + } + +#if SPLIT_CHR + if (state->m_chr_chunks > 0) + { + FILE *chrout; + char outname[255]; + + sprintf(outname, "%s.chr", image.filename()); + chrout= fopen(outname, "wb"); + if (chrout) + { + fwrite(state->m_vrom, 1, 0x2000 * state->m_chr_chunks, chrout); + mame_printf_error("Created CHR chunk\n"); + } + fclose(chrout); + } +#endif + + logerror("CHR chunks: %02x, size: %06x\n", state->m_chr_chunks, 0x2000 * state->m_chr_chunks); + logerror("**\n"); + // mame_printf_error("CHR chunks: %02x, size: %06x\n", state->m_chr_chunks, 0x2000 * state->m_chr_chunks); + // mame_printf_error("**\n"); + } + else if ((magic[0] == 'U') && (magic[1] == 'N') && (magic[2] == 'I') && (magic[3] == 'F')) /* If header starts with 'UNIF' it is UNIF */ + { + UINT32 unif_ver = 0; + char magic2[4]; + UINT8 buffer[4]; + UINT32 chunk_length = 0, read_length = 0x20; + UINT32 prg_start = 0, chr_start = 0, prg_size; + char unif_mapr[32]; // here we should store MAPR chunks + UINT32 size = image.length(); + int mapr_chunk_found = 0; + // allocate space to temporarily store PRG & CHR banks + UINT8 *temp_prg = auto_alloc_array(image.device().machine(), UINT8, 256 * 0x4000); + UINT8 *temp_chr = auto_alloc_array(image.device().machine(), UINT8, 256 * 0x2000); + UINT8 temp_byte = 0; + + /* init prg/chr chunks to 0: the exact number of chunks will be determined while reading the file */ + state->m_prg_chunks = 0; + state->m_chr_chunks = 0; + + image.fread(&buffer, 4); + unif_ver = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + logerror("UNIF file found, version %d\n", unif_ver); + + if (size <= 0x20) + { + logerror("%s only contains the UNIF header and no data.\n", image.filename()); + return IMAGE_INIT_FAIL; + } + + do + { + image.fseek(read_length, SEEK_SET); + + memset(magic2, '\0', sizeof(magic2)); + image.fread(&magic2, 4); + + /* We first run through the whole image to find a [MAPR] chunk. This is needed + because, unfortunately, the MAPR chunk is not always the first chunk (see + Super 24-in-1). When such a chunk is found, we set mapr_chunk_found=1 and + we go back to load other chunks! */ + if (!mapr_chunk_found) + { + if ((magic2[0] == 'M') && (magic2[1] == 'A') && (magic2[2] == 'P') && (magic2[3] == 'R')) + { + mapr_chunk_found = 1; + logerror("[MAPR] chunk found: "); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + if (chunk_length <= 0x20) + image.fread(&unif_mapr, chunk_length); + + // find out prg/chr size, battery, wram, etc. + unif_mapr_setup(image.device().machine(), unif_mapr); + + /* now that we found the MAPR chunk, we can go back to load other chunks */ + image.fseek(0x20, SEEK_SET); + read_length = 0x20; + } + else + { + logerror("Skip this chunk. We need a [MAPR] chunk before anything else.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + } + else + { + /* What kind of chunk do we have here? */ + if ((magic2[0] == 'M') && (magic2[1] == 'A') && (magic2[2] == 'P') && (magic2[3] == 'R')) + { + /* The [MAPR] chunk has already been read, so we skip it */ + /* TO DO: it would be nice to check if more than one MAPR chunk is present */ + logerror("[MAPR] chunk found (in the 2nd run). Already loaded.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'R') && (magic2[1] == 'E') && (magic2[2] == 'A') && (magic2[3] == 'D')) + { + logerror("[READ] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'N') && (magic2[1] == 'A') && (magic2[2] == 'M') && (magic2[3] == 'E')) + { + logerror("[NAME] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'W') && (magic2[1] == 'R') && (magic2[2] == 'T') && (magic2[3] == 'R')) + { + logerror("[WRTR] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'T') && (magic2[1] == 'V') && (magic2[2] == 'C') && (magic2[3] == 'I')) + { + logerror("[TVCI] chunk found.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + image.fread(&temp_byte, 1); + logerror("Television Standard : %s\n", (temp_byte == 0) ? "NTSC" : (temp_byte == 1) ? "PAL" : "Does not matter"); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'T') && (magic2[1] == 'V') && (magic2[2] == 'S') && (magic2[3] == 'C')) // is this the same as TVCI?? + { + logerror("[TVSC] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'D') && (magic2[1] == 'I') && (magic2[2] == 'N') && (magic2[3] == 'F')) + { + logerror("[DINF] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'C') && (magic2[1] == 'T') && (magic2[2] == 'R') && (magic2[3] == 'L')) + { + logerror("[CTRL] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'B') && (magic2[1] == 'A') && (magic2[2] == 'T') && (magic2[3] == 'R')) + { + logerror("[BATR] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'V') && (magic2[1] == 'R') && (magic2[2] == 'O') && (magic2[3] == 'R')) + { + logerror("[VROR] chunk found. No support yet.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'M') && (magic2[1] == 'I') && (magic2[2] == 'R') && (magic2[3] == 'R')) + { + logerror("[MIRR] chunk found.\n"); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + image.fread(&temp_byte, 1); + switch (temp_byte) + { + case 0: // Horizontal Mirroring (Hard Wired) + state->m_hard_mirroring = PPU_MIRROR_HORZ; + break; + case 1: // Vertical Mirroring (Hard Wired) + state->m_hard_mirroring = PPU_MIRROR_VERT; + break; + case 2: // Mirror All Pages From $2000 (Hard Wired) + state->m_hard_mirroring = PPU_MIRROR_LOW; + break; + case 3: // Mirror All Pages From $2400 (Hard Wired) + state->m_hard_mirroring = PPU_MIRROR_HIGH; + break; + case 4: // Four Screens of VRAM (Hard Wired) + state->m_four_screen_vram = 1; + break; + case 5: // Mirroring Controlled By Mapper Hardware + logerror("Mirroring handled by the board hardware.\n"); + // default to horizontal at start + state->m_hard_mirroring = PPU_MIRROR_HORZ; + break; + default: + logerror("Undocumented mirroring value.\n"); + // default to horizontal + state->m_hard_mirroring = PPU_MIRROR_HORZ; + break; + } + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'P') && (magic2[1] == 'C') && (magic2[2] == 'K')) + { + logerror("[PCK%c] chunk found. No support yet.\n", magic2[3]); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'C') && (magic2[1] == 'C') && (magic2[2] == 'K')) + { + logerror("[CCK%c] chunk found. No support yet.\n", magic2[3]); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'P') && (magic2[1] == 'R') && (magic2[2] == 'G')) + { + logerror("[PRG%c] chunk found. ", magic2[3]); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + // FIXME: we currently don't support PRG chunks smaller than 16K! + state->m_prg_chunks += (chunk_length / 0x4000); + + if (chunk_length / 0x4000) + logerror("It consists of %d 16K-blocks.\n", chunk_length / 0x4000); + else + logerror("This chunk is smaller than 16K: the emulation might have issues. Please report this file to the MESS forums.\n"); + + /* Read in the program chunks */ + image.fread(&temp_prg[prg_start], chunk_length); + + prg_start += chunk_length; + read_length += (chunk_length + 8); + } + else if ((magic2[0] == 'C') && (magic2[1] == 'H') && (magic2[2] == 'R')) + { + logerror("[CHR%c] chunk found. ", magic2[3]); + image.fread(&buffer, 4); + chunk_length = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + + state->m_chr_chunks += (chunk_length / 0x2000); + + logerror("It consists of %d 8K-blocks.\n", chunk_length / 0x2000); + + /* Read in the vrom chunks */ + image.fread(&temp_chr[chr_start], chunk_length); + + chr_start += chunk_length; + read_length += (chunk_length + 8); + } + else + { + logerror("Unsupported UNIF chunk or corrupted header. Please report the problem at MESS Board.\n"); + read_length = size; + } + } + } while (size > read_length); + + if (!mapr_chunk_found) + { + auto_free(image.device().machine(), temp_prg); + auto_free(image.device().machine(), temp_chr); + fatalerror("UNIF should have a [MAPR] chunk to work. Check if your image has been corrupted"); + } + + if (!prg_start) + { + auto_free(image.device().machine(), temp_prg); + auto_free(image.device().machine(), temp_chr); + fatalerror("Unsupported UNIF chunk or corrupted header. Please report the problem at MESS Board.\n"); + } + + // Allocate class pointers for PRG/VROM/VRAM/WRAM and copy data there from the temp copies + + /* Take care of PRG */ + prg_size = (state->m_prg_chunks == 1) ? 2 * 0x4000 : state->m_prg_chunks * 0x4000; + state->m_prg = auto_alloc_array(image.device().machine(), UINT8, prg_size); + + memcpy(&state->m_prg[0], &temp_prg[0], state->m_prg_chunks * 0x4000); + /* If only a single 16K PRG chunk is present, mirror it! */ + if (state->m_prg_chunks == 1) + memcpy(&state->m_prg[0x4000], &state->m_prg[0], 0x4000); + + /* Take care of CHR ROM */ + if (state->m_chr_chunks) + { + state->m_vrom = auto_alloc_array(image.device().machine(), UINT8, state->m_chr_chunks * 0x2000); + memcpy(&state->m_vrom[0x00000], &temp_chr[0x00000], state->m_chr_chunks * 0x2000); + } + + /* Take care of CHR RAM */ + if (state->m_vram_chunks) + state->m_vram = auto_alloc_array(image.device().machine(), UINT8, state->m_vram_chunks * 0x2000); + + // FIXME: this should only be allocated if there is actual wram in the cart (i.e. if state->m_prg_ram = 1)! + state->m_wram_size = 0x10000; + state->m_wram = auto_alloc_array(image.device().machine(), UINT8, state->m_wram_size); + +#if SPLIT_PRG + { + FILE *prgout; + char outname[255]; + + sprintf(outname, "%s.prg", image.filename()); + prgout = fopen(outname, "wb"); + if (prgout) + { + fwrite(&state->m_prg[0], 1, 0x4000 * state->m_prg_chunks, prgout); + mame_printf_error("Created PRG chunk\n"); + } + + fclose(prgout); + } +#endif + +#if SPLIT_CHR + if (state->m_chr_chunks > 0) + { + FILE *chrout; + char outname[255]; + + sprintf(outname, "%s.chr", image.filename()); + chrout= fopen(outname, "wb"); + if (chrout) + { + fwrite(state->m_vrom, 1, 0x2000 * state->m_chr_chunks, chrout); + mame_printf_error("Created CHR chunk\n"); + } + fclose(chrout); + } +#endif + // free the temporary copy of PRG/CHR + auto_free(image.device().machine(), temp_prg); + auto_free(image.device().machine(), temp_chr); + logerror("UNIF support is only very preliminary.\n"); + } + else + { + logerror("%s is NOT a file in either iNES or UNIF format.\n", image.filename()); + return IMAGE_INIT_FAIL; + } + } + else + { + UINT32 prg_size = image.get_software_region_length("prg"); + UINT32 chr_size = image.get_software_region_length("chr"); + UINT32 vram_size = image.get_software_region_length("vram"); + vram_size += image.get_software_region_length("vram2"); + + // validate the xml fields + if (!prg_size) + fatalerror("No PRG entry for this software! Please check if the xml list got corrupted"); + if (prg_size < 0x8000) + fatalerror("PRG entry is too small! Please check if the xml list got corrupted"); + + // Allocate class pointers for PRG/VROM/VRAM/WRAM and copy data there from the temp copies + state->m_prg = auto_alloc_array(image.device().machine(), UINT8, prg_size); + + if (chr_size) + state->m_vrom = auto_alloc_array(image.device().machine(), UINT8, chr_size); + + if (vram_size) + state->m_vram = auto_alloc_array(image.device().machine(), UINT8, vram_size); + + memcpy(state->m_prg, image.get_software_region("prg"), prg_size); + + if (chr_size) + memcpy(state->m_vrom, image.get_software_region("chr"), chr_size); + + state->m_prg_chunks = prg_size / 0x4000; + state->m_chr_chunks = chr_size / 0x2000; + state->m_vram_chunks = vram_size / 0x2000; + + state->m_pcb_id = nes_get_pcb_id(image.device().machine(), image.get_feature("pcb")); + + if (state->m_pcb_id == STD_TVROM || state->m_pcb_id == STD_DRROM || state->m_pcb_id == IREM_LROG017) + state->m_four_screen_vram = 1; + else + state->m_four_screen_vram = 0; + + state->m_battery = (image.get_software_region("bwram") != NULL) ? 1 : 0; + state->m_battery_size = image.get_software_region_length("bwram"); + + if (state->m_pcb_id == BANDAI_LZ93EX) + { + // allocate the 24C01 or 24C02 EEPROM + state->m_battery = 1; + state->m_battery_size += 0x2000; + } + + if (state->m_pcb_id == BANDAI_DATACH) + { + // allocate the 24C01 and 24C02 EEPROM + state->m_battery = 1; + state->m_battery_size += 0x4000; + } + + state->m_prg_ram = (image.get_software_region("wram") != NULL) ? 1 : 0; + state->m_wram_size = image.get_software_region_length("wram"); + state->m_mapper_ram_size = image.get_software_region_length("mapper_ram"); + state->m_mapper_bram_size = image.get_software_region_length("mapper_bram"); + + if (state->m_prg_ram) + state->m_wram = auto_alloc_array(image.device().machine(), UINT8, state->m_wram_size); + if (state->m_mapper_ram_size) + state->m_mapper_ram = auto_alloc_array(image.device().machine(), UINT8, state->m_mapper_ram_size); + if (state->m_mapper_bram_size) + state->m_mapper_bram = auto_alloc_array(image.device().machine(), UINT8, state->m_mapper_bram_size); + + /* Check for mirroring */ + if (image.get_feature("mirroring") != NULL) + { + const char *mirroring = image.get_feature("mirroring"); + if (!strcmp(mirroring, "horizontal")) + state->m_hard_mirroring = PPU_MIRROR_HORZ; + if (!strcmp(mirroring, "vertical")) + state->m_hard_mirroring = PPU_MIRROR_VERT; + if (!strcmp(mirroring, "high")) + state->m_hard_mirroring = PPU_MIRROR_HIGH; + if (!strcmp(mirroring, "low")) + state->m_hard_mirroring = PPU_MIRROR_LOW; + } + + state->m_chr_open_bus = 0; + state->m_ce_mask = 0; + state->m_ce_state = 0; + state->m_vrc_ls_prg_a = 0; + state->m_vrc_ls_prg_b = 0; + state->m_vrc_ls_chr = 0; + + /* Check for pins in specific boards which require them */ + if (state->m_pcb_id == STD_CNROM) + { + if (image.get_feature("chr-pin26") != NULL) + { + state->m_ce_mask |= 0x01; + state->m_ce_state |= !strcmp(image.get_feature("chr-pin26"), "CE") ? 0x01 : 0; + } + if (image.get_feature("chr-pin27") != NULL) + { + state->m_ce_mask |= 0x02; + state->m_ce_state |= !strcmp(image.get_feature("chr-pin27"), "CE") ? 0x02 : 0; + } + } + + if (state->m_pcb_id == TAITO_X1_005 && image.get_feature("x1-pin17") != NULL && image.get_feature("x1-pin31") != NULL) + { + if (!strcmp(image.get_feature("x1-pin17"), "CIRAM A10") && !strcmp(image.get_feature("x1-pin31"), "NC")) + state->m_pcb_id = TAITO_X1_005_A; + } + + if (state->m_pcb_id == KONAMI_VRC2) + { + state->m_vrc_ls_prg_a = nes_cart_get_line(image.get_feature("vrc2-pin3")); + state->m_vrc_ls_prg_b = nes_cart_get_line(image.get_feature("vrc2-pin4")); + state->m_vrc_ls_chr = (nes_cart_get_line(image.get_feature("vrc2-pin21")) != 10) ? 1 : 0; +// mame_printf_error("VRC-2, pin3: A%d, pin4: A%d, pin21: %s\n", state->m_vrc_ls_prg_a, state->m_vrc_ls_prg_b, state->m_vrc_ls_chr ? "NC" : "A10"); + } + + if (state->m_pcb_id == KONAMI_VRC4) + { + state->m_vrc_ls_prg_a = nes_cart_get_line(image.get_feature("vrc4-pin3")); + state->m_vrc_ls_prg_b = nes_cart_get_line(image.get_feature("vrc4-pin4")); +// mame_printf_error("VRC-4, pin3: A%d, pin4: A%d\n", state->m_vrc_ls_prg_a, state->m_vrc_ls_prg_b); + } + + if (state->m_pcb_id == KONAMI_VRC6) + { + state->m_vrc_ls_prg_a = nes_cart_get_line(image.get_feature("vrc6-pin9")); + state->m_vrc_ls_prg_b = nes_cart_get_line(image.get_feature("vrc6-pin10")); +// mame_printf_error("VRC-6, pin9: A%d, pin10: A%d\n", state->m_vrc_ls_prg_a, state->m_vrc_ls_prg_b); + } + + /* Check for other misc board variants */ + if (state->m_pcb_id == STD_SOROM) + { + if (image.get_feature("mmc1_type") != NULL && !strcmp(image.get_feature("mmc1_type"), "MMC1A")) + state->m_pcb_id = STD_SOROM_A; // in MMC1-A PRG RAM is always enabled + } + + if (state->m_pcb_id == STD_SXROM) + { + if (image.get_feature("mmc1_type") != NULL && !strcmp(image.get_feature("mmc1_type"), "MMC1A")) + state->m_pcb_id = STD_SXROM_A; // in MMC1-A PRG RAM is always enabled + } + + if (state->m_pcb_id == STD_NXROM || state->m_pcb_id == SUNSOFT_DCS) + { + if (image.get_software_region("minicart") != NULL) // check for dual minicart + { + state->m_pcb_id = SUNSOFT_DCS; + // we shall load somewhere the minicart, but we still do not support this + } + } + +#if 0 + if (state->m_pcb_id == UNSUPPORTED_BOARD) + mame_printf_error("This board (%s) is currently not supported by MESS\n", image.get_feature("pcb")); + mame_printf_error("PCB Feature: %s\n", image.get_feature("pcb")); + mame_printf_error("PRG chunks: %d\n", state->m_prg_chunks); + mame_printf_error("CHR chunks: %d\n", state->m_chr_chunks); + mame_printf_error("VRAM: Present %s, size: %d\n", state->m_vram_chunks ? "Yes" : "No", vram_size); + mame_printf_error("NVWRAM: Present %s, size: %d\n", state->m_battery ? "Yes" : "No", state->m_battery_size); + mame_printf_error("WRAM: Present %s, size: %d\n", state->m_prg_ram ? "Yes" : "No", state->m_wram_size); +#endif + } + + // Attempt to load a battery file for this ROM + // A few boards have internal RAM with a battery (MMC6, Taito X1-005 & X1-017, etc.) + if (state->m_battery || state->m_mapper_bram_size) + { + UINT8 *temp_nvram = auto_alloc_array(image.device().machine(), UINT8, state->m_battery_size + state->m_mapper_bram_size); + image.battery_load(temp_nvram, state->m_battery_size + state->m_mapper_bram_size, 0x00); + if (state->m_battery) + { + state->m_battery_ram = auto_alloc_array(image.device().machine(), UINT8, state->m_battery_size); + memcpy(state->m_battery_ram, temp_nvram, state->m_battery_size); + } + if (state->m_mapper_bram_size) + memcpy(state->m_mapper_bram, temp_nvram + state->m_battery_size, state->m_mapper_bram_size); + + auto_free(image.device().machine(), temp_nvram); + } + + return IMAGE_INIT_PASS; +} + + + +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 + +**************************/ + +static void fds_irq( device_t *device, int scanline, int vblank, int blanked ) +{ + nes_state *state = device->machine().driver_data<nes_state>(); + + if (state->m_IRQ_enable_latch) + device_set_input_line(state->m_maincpu, M6502_IRQ_LINE, HOLD_LINE); + + if (state->m_IRQ_enable) + { + if (state->m_IRQ_count <= 114) + { + device_set_input_line(state->m_maincpu, M6502_IRQ_LINE, HOLD_LINE); + state->m_IRQ_enable = 0; + state->m_fds_status0 |= 0x01; + } + else + state->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(machine(), 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); +} |