1 // license:BSD-3-Clause
2 // copyright-holders:Richard Davies
3 /***************************************************************************
4 
5   phoenix.cpp
6 
7   Functions to emulate the video hardware of the machine.
8 
9 ***************************************************************************/
10 
11 #include "emu.h"
12 #include "includes/phoenix.h"
13 
14 #include "video/resnet.h"
15 
16 
17 /***************************************************************************
18 
19   Convert the color PROMs into a more useable format.
20 
21   Phoenix has two 256x4 palette PROMs, one containing the high bits and the
22   other the low bits (2x2x2 color space).
23   The palette PROMs are connected to the RGB output this way:
24 
25   bit 3 --
26         -- 270 ohm resistor  -- GREEN
27         -- 270 ohm resistor  -- BLUE
28   bit 0 -- 270 ohm resistor  -- RED
29 
30   bit 3 --
31         -- GREEN
32         -- BLUE
33   bit 0 -- RED
34 
35   plus 270 ohm pulldown and 100 ohm pullup resistors on all lines
36 
37 ***************************************************************************/
38 
39 static const res_net_decode_info phoenix_decode_info =
40 {
41 	2,      // there may be two proms needed to construct color
42 	0,      // start at 0
43 	255,    // end at 255
44 	//  R,   G,   B,   R,   G,   B
45 	{   0,   0,   0, 256, 256, 256},        // offsets
46 	{   0,   2,   1,  -1,   1,   0},        // shifts
47 	{0x01,0x01,0x01,0x02,0x02,0x02}         // masks
48 };
49 
50 static const res_net_info phoenix_net_info =
51 {
52 	RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_OPEN_COL,
53 	{
54 		{ RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } },
55 		{ RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } },
56 		{ RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } }
57 	}
58 };
59 
60 static const res_net_info pleiades_net_info =
61 {
62 	RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_OPEN_COL,
63 	{
64 		{ RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } },
65 		{ RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } },
66 		{ RES_NET_AMP_NONE, 100, 270, 2, { 270, 1 } }
67 	}
68 };
69 
70 static const res_net_info survival_net_info =
71 {
72 	RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_OPEN_COL,
73 	{
74 		{ RES_NET_AMP_NONE, 100, 270, 2, { 180, 1 } },
75 		{ RES_NET_AMP_NONE, 100, 270, 2, { 180, 1 } },
76 		{ RES_NET_AMP_NONE, 100, 270, 2, { 180, 1 } }
77 	}
78 };
79 
phoenix_palette(palette_device & palette) const80 void phoenix_state::phoenix_palette(palette_device &palette) const
81 {
82 	uint8_t const *const color_prom = memregion("proms")->base();
83 
84 	std::vector<rgb_t> rgb;
85 	compute_res_net_all(rgb, color_prom, phoenix_decode_info, phoenix_net_info);
86 
87 	// native order
88 	for (int i = 0; i < 256; i++)
89 	{
90 		int const col = bitswap<7>(i, 6, 5, 1, 0, 4, 3, 2);
91 		palette.set_pen_color(i, rgb[col]);
92 	}
93 	palette.palette()->normalize_range(0, 255);
94 }
95 
survival_palette(palette_device & palette) const96 void phoenix_state::survival_palette(palette_device &palette) const
97 {
98 	uint8_t const *const color_prom = memregion("proms")->base();
99 
100 	std::vector<rgb_t> rgb;
101 	compute_res_net_all(rgb, color_prom, phoenix_decode_info, survival_net_info);
102 
103 	// native order
104 	for (int i = 0; i < 256; i++)
105 	{
106 		int const col = bitswap<7>(i, 6, 5, 1, 0, 4, 3, 2);
107 		palette.set_pen_color(i, rgb[col]);
108 	}
109 	palette.palette()->normalize_range(0, 255);
110 }
111 
pleiads_palette(palette_device & palette) const112 void phoenix_state::pleiads_palette(palette_device &palette) const
113 {
114 	uint8_t const *const color_prom = memregion("proms")->base();
115 
116 	std::vector<rgb_t> rgb;
117 	compute_res_net_all(rgb, color_prom, phoenix_decode_info, pleiades_net_info);
118 
119 	// native order
120 	for (int i = 0; i < 256; i++)
121 	{
122 		int const col = bitswap<8>(i, 7, 6, 5, 1, 0, 4, 3, 2);
123 		palette.set_pen_color(i, rgb[col]);
124 	}
125 	palette.palette()->normalize_range(0, 255);
126 }
127 
128 /***************************************************************************
129 
130   Callbacks for the TileMap code
131 
132 ***************************************************************************/
133 
TILE_GET_INFO_MEMBER(phoenix_state::get_fg_tile_info)134 TILE_GET_INFO_MEMBER(phoenix_state::get_fg_tile_info)
135 {
136 	int code, col;
137 
138 	code = m_videoram_pg[m_videoram_pg_index][tile_index];
139 	col = (code >> 5);
140 	col = col | 0x08 | (m_palette_bank << 4);
141 	tileinfo.set(1,
142 			code,
143 			col,
144 			0);
145 }
146 
TILE_GET_INFO_MEMBER(phoenix_state::get_bg_tile_info)147 TILE_GET_INFO_MEMBER(phoenix_state::get_bg_tile_info)
148 {
149 	int code, col;
150 
151 	code = m_videoram_pg[m_videoram_pg_index][tile_index + 0x800];
152 	col = (code >> 5);
153 	col = col | 0x00 | (m_palette_bank << 4);
154 	tileinfo.set(0,
155 			code,
156 			col,
157 			0);
158 }
159 
160 /***************************************************************************
161 
162   Start the video hardware emulation.
163 
164 ***************************************************************************/
165 
VIDEO_START_MEMBER(phoenix_state,phoenix)166 VIDEO_START_MEMBER(phoenix_state,phoenix)
167 {
168 	m_videoram_pg[0] = std::make_unique<uint8_t[]>(0x1000);
169 	memset(m_videoram_pg[0].get(), 0x00, 0x1000 * sizeof(uint8_t));
170 	m_videoram_pg[1] = std::make_unique<uint8_t[]>(0x1000);
171 	memset(m_videoram_pg[1].get(), 0x00, 0x1000 * sizeof(uint8_t));
172 
173 	membank("bank1")->configure_entry(0, m_videoram_pg[0].get());
174 	membank("bank1")->configure_entry(1, m_videoram_pg[1].get());
175 	membank("bank1")->set_entry(0);
176 
177 	m_videoram_pg_index = 0;
178 	m_palette_bank = 0;
179 	m_cocktail_mode = 0;
180 
181 	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);
182 	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);
183 
184 	m_fg_tilemap->set_transparent_pen(0);
185 
186 	save_pointer(NAME(m_videoram_pg[0]), 0x1000);
187 	save_pointer(NAME(m_videoram_pg[1]), 0x1000);
188 	save_item(NAME(m_videoram_pg_index));
189 	save_item(NAME(m_palette_bank));
190 	save_item(NAME(m_cocktail_mode));
191 
192 	/* some more candidates */
193 	m_pleiads_protection_question = 0;
194 	m_survival_protection_value = 0;
195 	m_survival_sid_value = 0;
196 	m_survival_input_readc = 0;
197 	m_survival_input_latches[0] = 0;
198 	m_survival_input_latches[1] = 0;
199 
200 	save_item(NAME(m_pleiads_protection_question));
201 	save_item(NAME(m_survival_protection_value));
202 	save_item(NAME(m_survival_sid_value));
203 	save_item(NAME(m_survival_input_readc));
204 	save_item(NAME(m_survival_input_latches));
205 
206 }
207 
208 /***************************************************************************
209 
210   Memory handlers
211 
212 ***************************************************************************/
213 
phoenix_videoram_w(offs_t offset,uint8_t data)214 void phoenix_state::phoenix_videoram_w(offs_t offset, uint8_t data)
215 {
216 	uint8_t *rom = memregion("maincpu")->base();
217 
218 	m_videoram_pg[m_videoram_pg_index][offset] = data;
219 
220 	if ((offset & 0x7ff) < 0x340)
221 	{
222 		if (offset & 0x800)
223 			m_bg_tilemap->mark_tile_dirty(offset & 0x3ff);
224 		else
225 			m_fg_tilemap->mark_tile_dirty(offset & 0x3ff);
226 	}
227 
228 	/* as part of the protection, Survival executes code from $43a4 */
229 	rom[offset + 0x4000] = data;
230 }
231 
232 
phoenix_videoreg_w(uint8_t data)233 void phoenix_state::phoenix_videoreg_w(uint8_t data)
234 {
235 	if (m_videoram_pg_index != (data & 1))
236 	{
237 		/* set memory bank */
238 		m_videoram_pg_index = data & 1;
239 		membank("bank1")->set_entry(m_videoram_pg_index);
240 
241 		m_cocktail_mode = m_videoram_pg_index && (ioport("CAB")->read() & 0x01);
242 
243 		machine().tilemap().set_flip_all(m_cocktail_mode ? (TILEMAP_FLIPX | TILEMAP_FLIPY) : 0);
244 		machine().tilemap().mark_all_dirty();
245 	}
246 
247 	/* Phoenix has only one palette select effecting both layers */
248 	if (m_palette_bank != ((data >> 1) & 1))
249 	{
250 		m_palette_bank = (data >> 1) & 1;
251 
252 		machine().tilemap().mark_all_dirty();
253 	}
254 }
255 
pleiads_videoreg_w(uint8_t data)256 void phoenix_state::pleiads_videoreg_w(uint8_t data)
257 {
258 	if (m_videoram_pg_index != (data & 1))
259 	{
260 		/* set memory bank */
261 		m_videoram_pg_index = data & 1;
262 		membank("bank1")->set_entry(m_videoram_pg_index);
263 
264 		m_cocktail_mode = m_videoram_pg_index && (ioport("CAB")->read() & 0x01);
265 
266 		machine().tilemap().set_flip_all(m_cocktail_mode ? (TILEMAP_FLIPX | TILEMAP_FLIPY) : 0);
267 		machine().tilemap().mark_all_dirty();
268 	}
269 
270 
271 	/* the palette table is at $0420-$042f and is set by $06bc.
272 	   Four palette changes by level.  The palette selection is
273 	   wrong, but the same palette is used for both layers. */
274 
275 	if (m_palette_bank != ((data >> 1) & 3))
276 	{
277 		m_palette_bank = ((data >> 1) & 3);
278 
279 		machine().tilemap().mark_all_dirty();
280 
281 		logerror("Palette: %02X\n", (data & 0x06) >> 1);
282 	}
283 
284 	m_pleiads_protection_question = data & 0xfc;
285 
286 	/* send two bits to sound control C (not sure if they are there) */
287 	m_pleiads_custom->control_c_w(data);
288 }
289 
290 
phoenix_scroll_w(uint8_t data)291 void phoenix_state::phoenix_scroll_w(uint8_t data)
292 {
293 	m_bg_tilemap->set_scrollx(0,data);
294 }
295 
296 
CUSTOM_INPUT_MEMBER(phoenix_state::player_input_r)297 CUSTOM_INPUT_MEMBER(phoenix_state::player_input_r)
298 {
299 	if (m_cocktail_mode)
300 		return (ioport("CTRL")->read() & 0xf0) >> 4;
301 	else
302 		return (ioport("CTRL")->read() & 0x0f) >> 0;
303 }
304 
READ_LINE_MEMBER(phoenix_state::pleiads_protection_r)305 READ_LINE_MEMBER(phoenix_state::pleiads_protection_r)
306 {
307 	/* handle Pleiads protection */
308 	switch (m_pleiads_protection_question)
309 	{
310 	case 0x00:
311 	case 0x20:
312 		/* Bit 3 is 0 */
313 		return 0;
314 	case 0x0c:
315 	case 0x30:
316 		/* Bit 3 is 1 */
317 		return 1;
318 	default:
319 		logerror("%s:Unknown protection question %02X\n", machine().describe_context(), m_pleiads_protection_question);
320 		return 0;
321 	}
322 }
323 
324 /*
325     Protection.  There is a 14 pin part connected to the 8910 Port B D0 labeled DL57S22
326 
327     Inputs are demangled at 0x1ae6-0x1b04 using the table at 0x1b26
328     and bit 0 of the data from the AY8910 port B. The equation is:
329     input = map[input] + ay_data + b@437c
330     (b@437c is set and cleared elsewhere in the code, but is
331     always 0 during the demangling.)
332 
333     A routine at 0x2f31 checks for incorrect AY8910 port B data.
334     Incorrect values increment an error counter at 0x4396 which
335     causes bad sprites and will kill the game after a specified
336     number of errors. For input & 0xf0 == 0 or 2 or 4, AY8910
337     port B must have bit 0 cleared. For all other joystick bits,
338     it must be set.
339 
340     Another  routine at 0x02bc checks for bad SID data, and
341     increments the same error counter and cancels certain joystick input.
342 
343     The hiscore data entry routine at 0x2fd8 requires unmangled inputs
344     at 0x3094. This could explain the significance of the loop where
345     the joystick inputs are read for gameplay at 0x2006-0x202a. The
346     code waits here for two consecutive identical reads from the AY8910.
347     This probably means there's a third read of raw data with some or all
348     of the otherwise unused bits 1-7 on the AY8910 port B set to
349     distinguish it from a gameplay read.
350 */
351 
352 #define REMAP_JS(js) ((ret & 0xf) | ( (js & 0xf)  << 4))
survival_input_port_0_r()353 uint8_t phoenix_state::survival_input_port_0_r()
354 {
355 	uint8_t ret;
356 
357 	if (m_cocktail_mode)
358 		ret = ~ioport("IN1")->read();
359 	else
360 		ret = ~ioport("IN0")->read();
361 
362 	if( m_survival_input_readc++ == 2 )
363 	{
364 		m_survival_input_readc = 0;
365 		m_survival_protection_value = 0;
366 		return ~ret;
367 	}
368 
369 	// Any value that remaps the joystick input to 0,2,4 must clear bit 0
370 	// on the AY8910 port B. All other remaps must set bit 0.
371 
372 	m_survival_protection_value = 0xff;
373 	m_survival_sid_value = 0;
374 
375 	switch( ( ret >> 4) & 0xf )
376 	{
377 		case 0: // js_nop = 7 + 1
378 			ret = REMAP_JS( 7 );
379 			break;
380 		case 1: // js_n = 1 + 1
381 			ret = REMAP_JS( 8 );
382 			break;
383 		case 2: // js_e = 0 + 0
384 			m_survival_sid_value = 0x80;
385 			m_survival_protection_value = 0xfe;
386 			ret = REMAP_JS( 2 );
387 			break;
388 		case 3: // js_ne = 0 + 1;
389 			m_survival_sid_value = 0x80;
390 			ret = REMAP_JS( 0xa );
391 			break;
392 		case 4: // js_w = 4 + 0
393 			m_survival_sid_value = 0x80;
394 			m_survival_protection_value = 0xfe;
395 			ret = REMAP_JS( 4 );
396 			break;
397 		case 5: // js_nw = 2 + 1
398 			m_survival_sid_value = 0x80;
399 			ret = REMAP_JS( 0xc );
400 			break;
401 		case 8: // js_s = 5 + 1
402 			ret = REMAP_JS( 1 );
403 			break;
404 		case 0xa: // js_se = 6 + 1
405 			m_survival_sid_value = 0x80;
406 			ret = REMAP_JS( 3 );
407 			break;
408 		case 0xc: // js_sw = 4 + 1
409 			m_survival_sid_value = 0x80;
410 			ret = REMAP_JS( 5 );
411 			break;
412 		default:
413 			break;
414 	}
415 
416 	m_survival_input_latches[0] = m_survival_input_latches[1];
417 	m_survival_input_latches[1] = ~ret;
418 
419 	return m_survival_input_latches[0];
420 }
421 
survival_protection_r()422 uint8_t phoenix_state::survival_protection_r()
423 {
424 	return m_survival_protection_value;
425 }
426 
READ_LINE_MEMBER(phoenix_state::survival_sid_callback)427 READ_LINE_MEMBER(phoenix_state::survival_sid_callback)
428 {
429 	return m_survival_sid_value ? ASSERT_LINE : CLEAR_LINE;
430 }
431 
432 
433 /***************************************************************************
434 
435   Display refresh
436 
437 ***************************************************************************/
438 
screen_update_phoenix(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)439 uint32_t phoenix_state::screen_update_phoenix(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
440 {
441 	m_bg_tilemap->draw(screen, bitmap, cliprect, 0,0);
442 	m_fg_tilemap->draw(screen, bitmap, cliprect, 0,0);
443 	return 0;
444 }
445