// license:BSD-3-Clause // copyright-holders:Ryan Holtz, David Haywood #include "emu.h" #include "includes/spg2xx.h" #include "pvmil.lh" class pvmil_state : public spg2xx_game_state { public: pvmil_state(const machine_config &mconfig, device_type type, const char *tag) : spg2xx_game_state(mconfig, type, tag), m_portcdata(0x0000), m_latchcount(0), m_latchbit(0), m_outdat(0), m_p4inputs(*this, "EXTRA"), m_leds(*this, "led%u", 0U) { } void pvmil(machine_config &config); DECLARE_READ_LINE_MEMBER(pvmil_p4buttons_r); protected: virtual void machine_start() override; virtual void porta_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; virtual void portb_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; virtual void portc_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; private: uint16_t m_portcdata; int m_latchcount; int m_latchbit; uint16_t m_outdat; optional_ioport m_p4inputs; output_finder<4> m_leds; }; void pvmil_state::machine_start() { spg2xx_game_state::machine_start(); m_leds.resolve(); save_item(NAME(m_portcdata)); save_item(NAME(m_latchcount)); save_item(NAME(m_latchbit)); save_item(NAME(m_outdat)); } void pvmil_state::porta_w(offs_t offset, uint16_t data, uint16_t mem_mask) { logerror("%s: pvmil_porta_w %04x\n", machine().describe_context(), data); } void pvmil_state::portb_w(offs_t offset, uint16_t data, uint16_t mem_mask) { logerror("%s: pvmil_portb_w %04x\n", machine().describe_context(), data); } READ_LINE_MEMBER(pvmil_state::pvmil_p4buttons_r) { return m_latchbit; } void pvmil_state::portc_w(offs_t offset, uint16_t data, uint16_t mem_mask) { // ---- -432 1--- r-?c // 4,3,2,1 = player controller LEDs // r = reset input multiplexer // ? = unknown // m = input multiplexer clock // p4 input reading // the code to read them is interesting tho, it even includes loops that poll port a 16 times before/after, why? logerror("%s: pvmil_portc_w %04x\n", machine().describe_context(), data); uint16_t bit; // for logging bits changed on the port if (0) { for (int i = 0; i < 16; i++) { bit = 1 << i; if ((m_portcdata & bit) != (data & bit)) { if (data & bit) { logerror("port c %04x low to high\n", bit); } else { logerror("port c %04x high to low\n", bit); } } if ((m_portcdata & 0x0400) != (data & 0x0400)) { logerror("-------------------------------------------------\n"); } } } // happens on startup, before it starts reading inputs for the first time, assume 'reset counter' bit = 0x0008; if ((m_portcdata & bit) != (data & bit)) { if (data & bit) { logerror("reset read counter\n"); m_latchcount = 0; } } bit = 0x0001; if ((m_portcdata & bit) != (data & bit)) { if (!(data & bit)) { //logerror("latch with count of %d (outbit is %d)\n", m_latchcount, (m_portcdata & 0x0002)>>1 ); // what is bit 0x0002? it gets flipped in the same code as the inputs are read. // it doesn't follow any obvious pattern m_outdat &= ~(1 << m_latchcount); m_outdat |= ((data & 0x0002) >> 1) << m_latchcount; if (0) popmessage("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", (m_outdat & 0x8000) ? 1 : 0, (m_outdat & 0x4000) ? 1 : 0, (m_outdat & 0x2000) ? 1 : 0, (m_outdat & 0x1000) ? 1 : 0, (m_outdat & 0x0800) ? 1 : 0, (m_outdat & 0x0400) ? 1 : 0, (m_outdat & 0x0200) ? 1 : 0, (m_outdat & 0x0100) ? 1 : 0, (m_outdat & 0x0080) ? 1 : 0, (m_outdat & 0x0040) ? 1 : 0, (m_outdat & 0x0020) ? 1 : 0, (m_outdat & 0x0010) ? 1 : 0, (m_outdat & 0x0008) ? 1 : 0, (m_outdat & 0x0004) ? 1 : 0, (m_outdat & 0x0002) ? 1 : 0, (m_outdat & 0x0001) ? 1 : 0); m_latchbit = (((m_p4inputs->read()) << m_latchcount) & 0x8000) ? 1 : 0; m_latchcount++; if (m_latchcount == 16) m_latchcount = 0; } } m_portcdata = data; if (0) popmessage("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", (m_portcdata & 0x8000) ? 1 : 0, (m_portcdata & 0x4000) ? 1 : 0, (m_portcdata & 0x2000) ? 1 : 0, (m_portcdata & 0x1000) ? 1 : 0, (m_portcdata & 0x0800) ? 1 : 0, (m_portcdata & 0x0400) ? 1 : 0, (m_portcdata & 0x0200) ? 1 : 0, (m_portcdata & 0x0100) ? 1 : 0, (m_portcdata & 0x0080) ? 1 : 0, (m_portcdata & 0x0040) ? 1 : 0, (m_portcdata & 0x0020) ? 1 : 0, (m_portcdata & 0x0010) ? 1 : 0, (m_portcdata & 0x0008) ? 1 : 0, (m_portcdata & 0x0004) ? 1 : 0, (m_portcdata & 0x0002) ? 1 : 0, (m_portcdata & 0x0001) ? 1 : 0); m_leds[0] = (m_portcdata & 0x0080) ? 0 : 1; m_leds[1] = (m_portcdata & 0x0100) ? 0 : 1; m_leds[2] = (m_portcdata & 0x0200) ? 0 : 1; m_leds[3] = (m_portcdata & 0x0400) ? 0 : 1; } static INPUT_PORTS_START( pvmil ) // hold "console start" + "console select" on boot for test mode PORT_START("P1") PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(1) PORT_NAME("Player 1 A") PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(1) PORT_NAME("Player 1 B") PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(1) PORT_NAME("Player 1 C") PORT_BIT( 0x0008, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_PLAYER(1) PORT_NAME("Player 1 D") PORT_BIT( 0x0010, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(2) PORT_NAME("Player 2 A") PORT_BIT( 0x0020, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(2) PORT_NAME("Player 2 B") PORT_BIT( 0x0040, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(2) PORT_NAME("Player 2 C") PORT_BIT( 0x0080, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_PLAYER(2) PORT_NAME("Player 2 D") PORT_BIT( 0x0100, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(3) PORT_NAME("Player 3 A") PORT_BIT( 0x0200, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(3) PORT_NAME("Player 3 B") PORT_BIT( 0x0400, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(3) PORT_NAME("Player 3 C") PORT_BIT( 0x0800, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_PLAYER(3) PORT_NAME("Player 3 D") PORT_BIT( 0x1000, IP_ACTIVE_HIGH, IPT_BUTTON5 ) PORT_PLAYER(1) PORT_NAME("Player 1 Lifeline") PORT_BIT( 0x2000, IP_ACTIVE_HIGH, IPT_START ) PORT_CODE(KEYCODE_1) PORT_NAME("Console Start") PORT_BIT( 0x4000, IP_ACTIVE_LOW, IPT_UNUSED ) PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_SELECT ) PORT_CODE(KEYCODE_5) PORT_NAME("Console Select") PORT_START("P2") PORT_BIT( 0xffff, IP_ACTIVE_HIGH, IPT_UNUSED ) PORT_START("P3") PORT_BIT( 0x0003, IP_ACTIVE_LOW, IPT_UNUSED ) PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_READ_LINE_MEMBER(pvmil_state, pvmil_p4buttons_r) // Player 4 buttons read through here PORT_BIT( 0x0008, IP_ACTIVE_LOW, IPT_UNUSED ) PORT_BIT( 0x0010, IP_ACTIVE_HIGH, IPT_BUTTON5 ) PORT_PLAYER(2) PORT_NAME("Player 2 Lifeline") PORT_BIT( 0x0020, IP_ACTIVE_HIGH, IPT_BUTTON5 ) PORT_PLAYER(3) PORT_NAME("Player 3 Lifeline") PORT_BIT( 0x0040, IP_ACTIVE_HIGH, IPT_BUTTON5 ) PORT_PLAYER(4) PORT_NAME("Player 4 Lifeline") PORT_BIT( 0xff80, IP_ACTIVE_LOW, IPT_UNUSED ) PORT_START("EXTRA") PORT_BIT( 0x0fff, IP_ACTIVE_LOW, IPT_UNUSED ) PORT_BIT( 0x1000, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(4) PORT_NAME("Player 4 A") PORT_BIT( 0x2000, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(4) PORT_NAME("Player 4 B") PORT_BIT( 0x4000, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(4) PORT_NAME("Player 4 C") PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_PLAYER(4) PORT_NAME("Player 4 D") INPUT_PORTS_END void pvmil_state::pvmil(machine_config &config) { SPG24X(config, m_maincpu, XTAL(27'000'000), m_screen); m_maincpu->set_addrmap(AS_PROGRAM, &pvmil_state::mem_map_4m); m_maincpu->set_pal(true); spg2xx_base(config); m_screen->set_refresh_hz(50); //m_screen->set_size(320, 312); m_maincpu->porta_in().set_ioport("P1"); m_maincpu->portb_in().set_ioport("P2"); m_maincpu->portc_in().set_ioport("P3"); m_maincpu->porta_out().set(FUNC(pvmil_state::porta_w)); m_maincpu->portb_out().set(FUNC(pvmil_state::portb_w)); m_maincpu->portc_out().set(FUNC(pvmil_state::portc_w)); config.set_default_layout(layout_pvmil); } ROM_START( pvmil ) ROM_REGION( 0x800000, "maincpu", ROMREGION_ERASE00 ) // Fujistu 29Z0002TN, read as ST M29W320FB ROM_LOAD16_WORD_SWAP( "millionare4.bin", 0x000000, 0x400000, CRC(9c43d0f2) SHA1(fb4ba0115000b10b7c0e0d44b9fa3234c900e694) ) ROM_END CONS( 2006, pvmil, 0, 0, pvmil, pvmil, pvmil_state, empty_init, "Play Vision", "Who Wants to Be a Millionaire? (Play Vision, Plug and Play, UK)", MACHINE_IMPERFECT_SOUND | MACHINE_IMPERFECT_GRAPHICS )