// license:LGPL-2.1+ // copyright-holders:Angelo Salese /*************************************************************************** Nichibutsu 1412M2 device emulation Written by Angelo Salese Fancy data decrypter + timer + ? Most creative usage is hooked up in Mighty Guy sound CPU, other games just uses it as a much simpler protection chip providing code snippets. It is unknown at current stage if inside the chip there's a MCU (with internal ROM). TODO: - main sound timer is "jumpy" (like BGM tempo gets screwy then fixes itself somehow), fiddling with port 0x90 seems to improve things, why? - DAC timer is guessworked; Legacy notes from drivers: - m_mAmazonProtReg[4] bit 0 used on hiscore data (clear on code), 0x29f vs 0x29e (not an offset?) - static const uint16_t mAmazonProtData[] = { 0x0000,0x5000,0x5341,0x4b45,0x5349,0x4755,0x5245, <- default high scores (0x40db4) - wrong data ? 0x0000,0x4000,0x0e4b,0x4154,0x5544,0x4f4e,0x0e0e, 0x0000,0x3000,0x414e,0x4b41,0x4b45,0x5544,0x4f4e, 0x0000,0x2000,0x0e0e,0x4b49,0x5455,0x4e45,0x0e0e, 0x0000,0x1000,0x0e4b,0x414b,0x4553,0x4f42,0x410e, 0x4ef9,0x0000,0x62fa,0x0000,0x4ef9,0x0000,0x805E,0x0000, <- code (0x40d92) 0xc800 <- checksum }; - static const uint16_t mAmatelasProtData[] = { 0x0000,0x5000,0x5341,0x4b45,0x5349,0x4755,0x5245, <- default high scores (0x40db4) 0x0000,0x4000,0x0e4b,0x4154,0x5544,0x4f4e,0x0e0e, 0x0000,0x3000,0x414e,0x4b41,0x4b45,0x5544,0x4f4e, 0x0000,0x2000,0x0e0e,0x4b49,0x5455,0x4e45,0x0e0e, 0x0000,0x1000,0x0e4b,0x414b,0x4553,0x4f42,0x410e, 0x4ef9,0x0000,0x632e,0x0000,0x4ef9,0x0000,0x80C2,0x0000, <- code (0x40d92) 0x6100 <- checksum }; - static const uint16_t mHoreKidProtData[] = { 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, <- N/A 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x4e75,0x4e75,0x4e75,0x4e75,0x4e75,0x4e75,0x4e75,0x4e75, <- code (0x40dba) It actually never jumps there? 0x1800 <- checksum }; - mightguy: * sound "protection" uses address/data to ports 2/3 (R/W) * Register map: * 0x32: always 5, rom read trigger? * 0x33: - address 1 (source?) * 0x34: / * 0x35: - address 2 (adjust value in rom?) * 0x36: / * 0x37: R reused for ym3526 register set, read protection rom (same as amatelas) * * 0x40: counter set, always 1? * 0x41: R bit 0 pulse timer? W oneshot timer? * 0x42: counter set, always 3? * 0x43: counter set, always 3? * * 0x92: data in/out (for dac?) * 0x94: test register? (W 0xaa, checks if R 0xaa) ***************************************************************************/ #include "emu.h" #include "machine/nb1412m2.h" //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(NB1412M2, nb1412m2_device, "nb1412m2", "NB1412M2 Mahjong Custom") //************************************************************************** // LIVE DEVICE //************************************************************************** void nb1412m2_device::nb1412m2_map(address_map &map) { // data decrypter map(0x32, 0x32).w(this, FUNC(nb1412m2_device::rom_op_w)); map(0x33, 0x34).w(this, FUNC(nb1412m2_device::rom_address_w)); map(0x35, 0x36).w(this, FUNC(nb1412m2_device::rom_adjust_w)); map(0x37, 0x37).r(this, FUNC(nb1412m2_device::rom_decrypt_r)); // timer map(0x40, 0x40).w(this, FUNC(nb1412m2_device::timer_w)); map(0x41, 0x41).rw(this, FUNC(nb1412m2_device::timer_r), FUNC(nb1412m2_device::timer_ack_w)); map(0x42, 0x43).nopw(); // always 0x03 // DAC control map(0x11, 0x11).nopw(); // - unknown (volume/channel control?) map(0x18, 0x18).w(this, FUNC(nb1412m2_device::dac_timer_w)); // timer frequency map(0x19, 0x19).nopw(); // 2 written at POST map(0x51, 0x52).w(this, FUNC(nb1412m2_device::dac_address_w)); // start address // latches? map(0x90, 0x90).rw(this, FUNC(nb1412m2_device::const90_r),FUNC(nb1412m2_device::const90_w)); //ram(); map(0x92, 0x92).ram(); map(0x94, 0x94).ram(); //rw(this,FUNC(nb1412m2_device::xor_r),FUNC(nb1412m2_device::xor_w)); // 16-bit registers (1=upper address), more latches? map(0xa0, 0xa1).ram(); map(0xa2, 0xa3).ram(); } //------------------------------------------------- // nb1412m2_device - constructor //------------------------------------------------- nb1412m2_device::nb1412m2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, NB1412M2, tag, owner, clock) , device_memory_interface(mconfig, *this) , m_space_config("regs", ENDIANNESS_LITTLE, 8, 8, 0, address_map_constructor(), address_map_constructor(FUNC(nb1412m2_device::nb1412m2_map), this)) , m_data(*this, DEVICE_SELF) , m_dac_cb(*this) { } device_memory_interface::space_config_vector nb1412m2_device::memory_space_config() const { return space_config_vector { std::make_pair(0, &m_space_config) }; } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void nb1412m2_device::device_start() { save_item(NAME(m_command)); save_item(NAME(m_rom_address)); save_item(NAME(m_dac_start_address)); save_item(NAME(m_dac_current_address)); save_item(NAME(m_adj_address)); save_item(NAME(m_rom_op)); save_item(NAME(m_timer_reg)); save_item(NAME(m_dac_playback)); save_item(NAME(m_dac_frequency)); save_item(NAME(m_const90)); m_timer = timer_alloc(TIMER_MAIN); m_timer->adjust(attotime::never); m_dac_timer = timer_alloc(TIMER_DAC); m_dac_cb.resolve_safe(); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void nb1412m2_device::device_reset() { m_command = 0xff; m_rom_address = 0; m_adj_address = 0; m_rom_op = 0; m_const90 = 0x18; // fixes coin sample if inserted at first title screen m_dac_current_address = m_dac_start_address = 0; m_dac_frequency = 4000; m_timer_reg = false; m_dac_playback = false; m_dac_timer->adjust(attotime::never); } void nb1412m2_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_MAIN: m_timer_reg = true; m_timer->adjust(attotime::never); break; case TIMER_DAC: if(m_dac_playback == true) { uint8_t dac_value; dac_value = m_data[m_dac_current_address]; m_dac_cb(dac_value); m_dac_current_address++; m_dac_current_address&= m_data.mask(); if(dac_value == 0x80) m_dac_playback = false; } break; default: assert_always(false, "Unknown id in nb1412m2_device::device_timer"); } } //************************************************************************** // READ/WRITE HANDLERS //************************************************************************** WRITE8_MEMBER( nb1412m2_device::rom_op_w ) { // data == 2 starts DAC playback if(data == 2) { m_dac_current_address = m_dac_start_address; m_dac_timer->adjust(attotime::never); m_dac_timer->adjust(attotime::from_hz(m_dac_frequency), 0, attotime::from_hz(m_dac_frequency)); m_dac_playback = true; } // TODO: data == 5 probably loads the result into 0x37 // if(data != 5) // printf("%02x rom_op\n",data); m_rom_op = data; } WRITE8_MEMBER( nb1412m2_device::rom_address_w ) { if(offset == 0) // rom hi address { m_rom_address &= 0x00ff; m_rom_address |= data << 8; } else // rom lo address { m_rom_address &= 0xff00; m_rom_address |= data; } } WRITE8_MEMBER( nb1412m2_device::rom_adjust_w ) { if(offset == 0) // rom hi address { m_adj_address &= 0x00ff; m_adj_address |= data << 8; } else // rom lo address { m_adj_address &= 0xff00; m_adj_address |= data; } } // readback from ROM data READ8_MEMBER( nb1412m2_device::rom_decrypt_r ) { uint8_t prot_adj; // all games but Mighty Guy uses this form as protection: // 0xff: -> 0x44 // Mighty Guy variants // 0x86: SFXs -> 0xbd // 0x94: BGM -> 0xaf // 0x00: DAC -> 0x43 // summing adjust entry and actual value needed always make 0x143 therefore: prot_adj = (0x43 - m_data[m_adj_address]) & 0xff; // printf("%02x %04x %04x %02x\n",m_data[m_adj_address],m_rom_address,m_adj_address,m_rom_op); return m_data[m_rom_address & 0x1fff] - prot_adj; } // Mighty Guy specifics READ8_MEMBER( nb1412m2_device::timer_r ) { return m_timer_reg == true; } WRITE8_MEMBER( nb1412m2_device::timer_w ) { if(data != 1) logerror("nb1412m2: timer_w with data == %02x\n",data); // TODO: timing of this, related to m_const90? m_timer->adjust(attotime::from_hz(960)); } WRITE8_MEMBER( nb1412m2_device::timer_ack_w ) { m_timer_reg = false; m_timer->adjust(attotime::never); } WRITE8_MEMBER( nb1412m2_device::dac_address_w ) { if(offset == 0) // dac hi address { m_dac_start_address &= 0x00ff; m_dac_start_address |= data << 8; } else // dac lo address { m_dac_start_address &= 0xff00; m_dac_start_address |= data; } } // seems directly correlated to the DAC timer frequency // 0xd0 - 0xe0 - 0xf0 are the settings used WRITE8_MEMBER( nb1412m2_device::dac_timer_w ) { // popmessage("%02x",data); // TODO: unknown algo, 0xe0*18 gives 4032 Hz which seems close enough for sample 36 m_dac_frequency = data*18; } // controls music tempo READ8_MEMBER( nb1412m2_device::const90_r ) { // TODO: likely wrong return m_const90; } WRITE8_MEMBER( nb1412m2_device::const90_w ) { m_const90 = data; } // public accessors READ8_MEMBER( nb1412m2_device::data_r ) { return this->space().read_byte(m_command); } WRITE8_MEMBER( nb1412m2_device::data_w ) { this->space().write_byte(m_command, data); } WRITE8_MEMBER( nb1412m2_device::command_w ) { m_command = data; }