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