// license:BSD-3-Clause // copyright-holders:Richard Davies /*************************************************************************** phoenix.cpp Functions to emulate the video hardware of the machine. ***************************************************************************/ #include "emu.h" #include "includes/phoenix.h" #include "video/resnet.h" /*************************************************************************** Convert the color PROMs into a more useable format. Phoenix has two 256x4 palette PROMs, one containing the high bits and the other the low bits (2x2x2 color space). The palette PROMs are connected to the RGB output this way: bit 3 -- -- 270 ohm resistor -- GREEN -- 270 ohm resistor -- BLUE bit 0 -- 270 ohm resistor -- RED bit 3 -- -- GREEN -- BLUE bit 0 -- RED plus 270 ohm pulldown and 100 ohm pullup resistors on all lines ***************************************************************************/ static const res_net_decode_info phoenix_decode_info = { 2, // there may be two proms needed to construct color 0, // start at 0 255, // end at 255 // R, G, B, R, G, B { 0, 0, 0, 256, 256, 256}, // offsets { 0, 2, 1, -1, 1, 0}, // shifts {0x01,0x01,0x01,0x02,0x02,0x02} // masks }; static const res_net_info phoenix_net_info = { RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_OPEN_COL, { { RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } }, { RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } }, { RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } } } }; static const res_net_info pleiades_net_info = { RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_OPEN_COL, { { RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } }, { RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } }, { RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } } } }; static const res_net_info survival_net_info = { RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_OPEN_COL, { { RES_NET_AMP_NONE, 100, 270, 2, { 180, 1 } }, { RES_NET_AMP_NONE, 100, 270, 2, { 180, 1 } }, { RES_NET_AMP_NONE, 100, 270, 2, { 180, 1 } } } }; void phoenix_state::phoenix_palette(palette_device &palette) const { uint8_t const *const color_prom = memregion("proms")->base(); std::vector rgb; compute_res_net_all(rgb, color_prom, phoenix_decode_info, phoenix_net_info); // native order for (int i = 0; i < 256; i++) { int const col = bitswap<7>(i, 6, 5, 1, 0, 4, 3, 2); palette.set_pen_color(i, rgb[col]); } palette.palette()->normalize_range(0, 255); } void phoenix_state::survival_palette(palette_device &palette) const { uint8_t const *const color_prom = memregion("proms")->base(); std::vector rgb; compute_res_net_all(rgb, color_prom, phoenix_decode_info, survival_net_info); // native order for (int i = 0; i < 256; i++) { int const col = bitswap<7>(i, 6, 5, 1, 0, 4, 3, 2); palette.set_pen_color(i, rgb[col]); } palette.palette()->normalize_range(0, 255); } void phoenix_state::pleiads_palette(palette_device &palette) const { uint8_t const *const color_prom = memregion("proms")->base(); std::vector rgb; compute_res_net_all(rgb, color_prom, phoenix_decode_info, pleiades_net_info); // native order for (int i = 0; i < 256; i++) { int const col = bitswap<8>(i, 7, 6, 5, 1, 0, 4, 3, 2); palette.set_pen_color(i, rgb[col]); } palette.palette()->normalize_range(0, 255); } /*************************************************************************** Callbacks for the TileMap code ***************************************************************************/ TILE_GET_INFO_MEMBER(phoenix_state::get_fg_tile_info) { int code, col; code = m_videoram_pg[m_videoram_pg_index][tile_index]; col = (code >> 5); col = col | 0x08 | (m_palette_bank << 4); SET_TILE_INFO_MEMBER(1, code, col, 0); } TILE_GET_INFO_MEMBER(phoenix_state::get_bg_tile_info) { int code, col; code = m_videoram_pg[m_videoram_pg_index][tile_index + 0x800]; col = (code >> 5); col = col | 0x00 | (m_palette_bank << 4); SET_TILE_INFO_MEMBER(0, code, col, 0); } /*************************************************************************** Start the video hardware emulation. ***************************************************************************/ VIDEO_START_MEMBER(phoenix_state,phoenix) { m_videoram_pg[0] = std::make_unique(0x1000); memset(m_videoram_pg[0].get(), 0x00, 0x1000 * sizeof(uint8_t)); m_videoram_pg[1] = std::make_unique(0x1000); memset(m_videoram_pg[1].get(), 0x00, 0x1000 * sizeof(uint8_t)); membank("bank1")->configure_entry(0, m_videoram_pg[0].get()); membank("bank1")->configure_entry(1, m_videoram_pg[1].get()); membank("bank1")->set_entry(0); m_videoram_pg_index = 0; m_palette_bank = 0; m_cocktail_mode = 0; m_fg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(phoenix_state::get_fg_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 32, 32); m_bg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(phoenix_state::get_bg_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 32, 32); m_fg_tilemap->set_transparent_pen(0); save_pointer(NAME(m_videoram_pg[0]), 0x1000); save_pointer(NAME(m_videoram_pg[1]), 0x1000); save_item(NAME(m_videoram_pg_index)); save_item(NAME(m_palette_bank)); save_item(NAME(m_cocktail_mode)); /* some more candidates */ m_pleiads_protection_question = 0; m_survival_protection_value = 0; m_survival_sid_value = 0; m_survival_input_readc = 0; m_survival_input_latches[0] = 0; m_survival_input_latches[1] = 0; save_item(NAME(m_pleiads_protection_question)); save_item(NAME(m_survival_protection_value)); save_item(NAME(m_survival_sid_value)); save_item(NAME(m_survival_input_readc)); save_item(NAME(m_survival_input_latches)); } /*************************************************************************** Memory handlers ***************************************************************************/ WRITE8_MEMBER(phoenix_state::phoenix_videoram_w) { uint8_t *rom = memregion("maincpu")->base(); m_videoram_pg[m_videoram_pg_index][offset] = data; if ((offset & 0x7ff) < 0x340) { if (offset & 0x800) m_bg_tilemap->mark_tile_dirty(offset & 0x3ff); else m_fg_tilemap->mark_tile_dirty(offset & 0x3ff); } /* as part of the protection, Survival executes code from $43a4 */ rom[offset + 0x4000] = data; } WRITE8_MEMBER(phoenix_state::phoenix_videoreg_w) { if (m_videoram_pg_index != (data & 1)) { /* set memory bank */ m_videoram_pg_index = data & 1; membank("bank1")->set_entry(m_videoram_pg_index); m_cocktail_mode = m_videoram_pg_index && (ioport("CAB")->read() & 0x01); machine().tilemap().set_flip_all(m_cocktail_mode ? (TILEMAP_FLIPX | TILEMAP_FLIPY) : 0); machine().tilemap().mark_all_dirty(); } /* Phoenix has only one palette select effecting both layers */ if (m_palette_bank != ((data >> 1) & 1)) { m_palette_bank = (data >> 1) & 1; machine().tilemap().mark_all_dirty(); } } WRITE8_MEMBER(phoenix_state::pleiads_videoreg_w) { if (m_videoram_pg_index != (data & 1)) { /* set memory bank */ m_videoram_pg_index = data & 1; membank("bank1")->set_entry(m_videoram_pg_index); m_cocktail_mode = m_videoram_pg_index && (ioport("CAB")->read() & 0x01); machine().tilemap().set_flip_all(m_cocktail_mode ? (TILEMAP_FLIPX | TILEMAP_FLIPY) : 0); machine().tilemap().mark_all_dirty(); } /* the palette table is at $0420-$042f and is set by $06bc. Four palette changes by level. The palette selection is wrong, but the same palette is used for both layers. */ if (m_palette_bank != ((data >> 1) & 3)) { m_palette_bank = ((data >> 1) & 3); machine().tilemap().mark_all_dirty(); logerror("Palette: %02X\n", (data & 0x06) >> 1); } m_pleiads_protection_question = data & 0xfc; /* send two bits to sound control C (not sure if they are there) */ m_pleiads_custom->control_c_w(space, offset, data); } WRITE8_MEMBER(phoenix_state::phoenix_scroll_w) { m_bg_tilemap->set_scrollx(0,data); } CUSTOM_INPUT_MEMBER(phoenix_state::player_input_r) { if (m_cocktail_mode) return (ioport("CTRL")->read() & 0xf0) >> 4; else return (ioport("CTRL")->read() & 0x0f) >> 0; } READ_LINE_MEMBER(phoenix_state::pleiads_protection_r) { /* handle Pleiads protection */ switch (m_pleiads_protection_question) { case 0x00: case 0x20: /* Bit 3 is 0 */ return 0; case 0x0c: case 0x30: /* Bit 3 is 1 */ return 1; default: logerror("%s:Unknown protection question %02X\n", machine().describe_context(), m_pleiads_protection_question); return 0; } } /* Protection. There is a 14 pin part connected to the 8910 Port B D0 labeled DL57S22 Inputs are demangled at 0x1ae6-0x1b04 using the table at 0x1b26 and bit 0 of the data from the AY8910 port B. The equation is: input = map[input] + ay_data + b@437c (b@437c is set and cleared elsewhere in the code, but is always 0 during the demangling.) A routine at 0x2f31 checks for incorrect AY8910 port B data. Incorrect values increment an error counter at 0x4396 which causes bad sprites and will kill the game after a specified number of errors. For input & 0xf0 == 0 or 2 or 4, AY8910 port B must have bit 0 cleared. For all other joystick bits, it must be set. Another routine at 0x02bc checks for bad SID data, and increments the same error counter and cancels certain joystick input. The hiscore data entry routine at 0x2fd8 requires unmangled inputs at 0x3094. This could explain the significance of the loop where the joystick inputs are read for gameplay at 0x2006-0x202a. The code waits here for two consecutive identical reads from the AY8910. This probably means there's a third read of raw data with some or all of the otherwise unused bits 1-7 on the AY8910 port B set to distinguish it from a gameplay read. */ #define REMAP_JS(js) ((ret & 0xf) | ( (js & 0xf) << 4)) READ8_MEMBER(phoenix_state::survival_input_port_0_r) { uint8_t ret; if (m_cocktail_mode) ret = ~ioport("IN1")->read(); else ret = ~ioport("IN0")->read(); if( m_survival_input_readc++ == 2 ) { m_survival_input_readc = 0; m_survival_protection_value = 0; return ~ret; } // Any value that remaps the joystick input to 0,2,4 must clear bit 0 // on the AY8910 port B. All other remaps must set bit 0. m_survival_protection_value = 0xff; m_survival_sid_value = 0; switch( ( ret >> 4) & 0xf ) { case 0: // js_nop = 7 + 1 ret = REMAP_JS( 7 ); break; case 1: // js_n = 1 + 1 ret = REMAP_JS( 8 ); break; case 2: // js_e = 0 + 0 m_survival_sid_value = 0x80; m_survival_protection_value = 0xfe; ret = REMAP_JS( 2 ); break; case 3: // js_ne = 0 + 1; m_survival_sid_value = 0x80; ret = REMAP_JS( 0xa ); break; case 4: // js_w = 4 + 0 m_survival_sid_value = 0x80; m_survival_protection_value = 0xfe; ret = REMAP_JS( 4 ); break; case 5: // js_nw = 2 + 1 m_survival_sid_value = 0x80; ret = REMAP_JS( 0xc ); break; case 8: // js_s = 5 + 1 ret = REMAP_JS( 1 ); break; case 0xa: // js_se = 6 + 1 m_survival_sid_value = 0x80; ret = REMAP_JS( 3 ); break; case 0xc: // js_sw = 4 + 1 m_survival_sid_value = 0x80; ret = REMAP_JS( 5 ); break; default: break; } m_survival_input_latches[0] = m_survival_input_latches[1]; m_survival_input_latches[1] = ~ret; return m_survival_input_latches[0]; } READ8_MEMBER(phoenix_state::survival_protection_r) { return m_survival_protection_value; } READ_LINE_MEMBER(phoenix_state::survival_sid_callback) { return m_survival_sid_value ? ASSERT_LINE : CLEAR_LINE; } /*************************************************************************** Display refresh ***************************************************************************/ uint32_t phoenix_state::screen_update_phoenix(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { m_bg_tilemap->draw(screen, bitmap, cliprect, 0,0); m_fg_tilemap->draw(screen, bitmap, cliprect, 0,0); return 0; }