// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria
/***************************************************************************
Ms.Pac-Man/Galaga - 20 Year Reunion hardware
driver by Nicola Salmoria
***************************************************************************/
#include "emu.h"
#include "includes/20pacgal.h"
#include "screen.h"
#define SCREEN_HEIGHT (224)
#define SCREEN_WIDTH (288)
#define NUM_PENS (0x1000)
#define NUM_STAR_PENS (64)
/*************************************
*
* Palette handling
*
*************************************/
void _20pacgal_state::get_pens()
{
// TODO : Accurate palette when prom isn't exists
offs_t offs;
uint8_t *color_prom = m_proms->base() + (NUM_PENS * m_game_selected);
for (offs = 0; offs < NUM_PENS ;offs++)
{
int bit0,bit1,bit2,r,g,b;
/* red component */
bit0 = (*color_prom >> 0) & 0x01;
bit1 = (*color_prom >> 1) & 0x01;
bit2 = (*color_prom >> 2) & 0x01;
r = 0x21 * bit0 + 0x47 * bit1 + 0x97 * bit2;
/* green component */
bit0 = (*color_prom >> 3) & 0x01;
bit1 = (*color_prom >> 4) & 0x01;
bit2 = (*color_prom >> 5) & 0x01;
g = 0x21 * bit0 + 0x47 * bit1 + 0x97 * bit2;
/* blue component */
bit0 = 0;
bit1 = (*color_prom >> 6) & 0x01;
bit2 = (*color_prom >> 7) & 0x01;
b = 0x21 * bit0 + 0x47 * bit1 + 0x97 * bit2;
m_palette->set_pen_color(offs, rgb_t(r, g, b));
color_prom++;
}
}
PALETTE_INIT_MEMBER(_20pacgal_state,starpal_init)
{
/* Star field */
/* palette for the stars */
for (offs_t offs = 0;offs < 64;offs++)
{
int bits,r,g,b;
static const int map[4] = { 0x00, 0x47, 0x97 ,0xde };
bits = (offs >> 0) & 0x03;
r = map[bits];
bits = (offs >> 2) & 0x03;
g = map[bits];
bits = (offs >> 4) & 0x03;
b = map[bits];
palette.set_pen_color(NUM_PENS + offs, rgb_t(r, g, b));
}
}
void _20pacgal_state::do_pen_lookup(bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
const pen_t *pen = m_palette->pens();
for (int y = cliprect.min_y; y <= cliprect.max_y; y++)
for(int x = cliprect.min_x; x <= cliprect.max_x; x++)
bitmap.pix32(y, x) = pen[bitmap.pix32(y, x)];
}
/*************************************
*
* Sprite drawing
*
*************************************/
void _20pacgal_state::draw_sprite(bitmap_rgb32 &bitmap, const rectangle &cliprect, int y, int x,
uint8_t code, uint8_t color, int flip_y, int flip_x)
{
int sy;
offs_t pen_base = (color & 0x1f) << 2;
pen_base += m_sprite_pal_base;
if (flip_y)
y = y + 0x0f;
if (flip_x)
x = x + 0x0f;
/* for each row in the sprite */
for (sy = 0; sy < 0x10; sy++)
{
int x_sav = x;
if ((y >= cliprect.min_y) && (y <= cliprect.max_y))
{
int sx;
uint32_t data;
offs_t gfx_offs = ((code & 0x7f) << 6) | (sy << 2);
/* address mangling */
gfx_offs = (gfx_offs & 0x1f83) | ((gfx_offs & 0x003c) << 1) | ((gfx_offs & 0x0040) >> 4);
data = (m_sprite_gfx_ram[gfx_offs + 0] << 24) |
(m_sprite_gfx_ram[gfx_offs + 1] << 16) |
(m_sprite_gfx_ram[gfx_offs + 2] << 8) |
(m_sprite_gfx_ram[gfx_offs + 3] << 0);
/* for each pixel in the row */
for (sx = 0; sx < 0x10; sx++)
{
if ((x >= cliprect.min_x) && (x <= cliprect.max_x))
{
offs_t pen = (data & 0xc0000000) >> 30;
uint8_t col;
col = m_sprite_color_lookup[pen_base | pen] & 0x0f;
/* pen bits A0-A3 */
if (col)
bitmap.pix32(y, x) = (bitmap.pix32(y, x) & 0xff0) | col;
}
/* next pixel */
if (flip_x)
x = x - 1;
else
x = x + 1;
data = data << 2;
}
}
/* next row */
if (flip_y)
y = y - 1;
else
y = y + 1;
x = x_sav;
}
}
void _20pacgal_state::draw_sprites(bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
int offs;
for (offs = 0x80 - 2; offs >= 0; offs -= 2)
{
static const int code_offs[2][2] =
{
{ 0, 1 },
{ 2, 3 }
};
int x, y;
uint8_t code = m_sprite_ram[offs + 0x000];
uint8_t color = m_sprite_ram[offs + 0x001];
int sx = m_sprite_ram[offs + 0x081] - 41 + 0x100*(m_sprite_ram[offs + 0x101] & 3);
int sy = 256 - m_sprite_ram[offs + 0x080] + 1;
int flip_x = (m_sprite_ram[offs + 0x100] & 0x01) >> 0;
int flip_y = (m_sprite_ram[offs + 0x100] & 0x02) >> 1;
int size_x = (m_sprite_ram[offs + 0x100] & 0x04) >> 2;
int size_y = (m_sprite_ram[offs + 0x100] & 0x08) >> 3;
sy = sy - (16 * size_y);
sy = (sy & 0xff) - 32; /* fix wraparound */
/* only Galaga appears to be effected by the global flip state */
if (m_game_selected && (m_flip[0] & 0x01))
{
flip_x = !flip_x;
flip_y = !flip_y;
}
for (y = 0; y <= size_y; y++)
for (x = 0; x <= size_x; x++)
draw_sprite(bitmap, cliprect,
sy + (16 * y), sx + (16 * x),
code + code_offs[y ^ (size_y * flip_y)][x ^ (size_x * flip_x)],
color,
flip_y, flip_x);
}
}
/*************************************
*
* Character map drawing
*
*************************************/
void _20pacgal_state::draw_chars(bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
offs_t offs;
int flip = m_flip[0] & 0x01;
/* for each byte in the video RAM */
for (offs = 0; offs < 0x400; offs++)
{
int sy;
int y, x;
uint8_t *gfx = &m_char_gfx_ram.target()[m_video_ram[0x0000 | offs] << 4];
uint32_t color_base = (m_video_ram[0x0400 | offs] & 0x3f) << 2;
/* map the offset to (x, y) character coordinates */
if ((offs & 0x03c0) == 0)
{
y = (offs & 0x1f) - 2;
x = (offs >> 5) + 34;
}
else if ((offs & 0x03c0) == 0x3c0)
{
y = (offs & 0x1f) - 2;
x = (offs >> 5) - 30;
}
else
{
y = (offs >> 5) - 2;
x = (offs & 0x1f) + 2;
}
if ((y < 0) || (y > 27)) continue;
/* conver to pixel coordinates */
y = y << 3;
x = x << 3;
if (flip)
{
y = SCREEN_HEIGHT - 1 - y;
x = SCREEN_WIDTH - 1 - x;
}
/* for each row in the character */
for (sy = 0; sy < 8; sy++)
{
int sx;
int x_sav = x;
if ((y >= cliprect.min_y) && (y <= cliprect.max_y))
{
uint16_t data = (gfx[8] << 8) | gfx[0];
/* for each pixel in the row */
for (sx = 0; sx < 8; sx++)
{
if ((x >= cliprect.min_x) && (x <= cliprect.max_x))
{
uint32_t col = ((data & 0x8000) >> 14) | ((data & 0x0800) >> 11);
/* pen bits A4-A11 */
if ( col != 0 )
bitmap.pix32(y, x) = (color_base | col) << 4;
/* next pixel */
if (flip)
x = x - 1;
else
x = x + 1;
if (sx == 0x03)
data = data << 5;
else
data = data << 1;
}
}
}
/* next row */
if (flip)
y = y - 1;
else
y = y + 1;
x = x_sav;
gfx = gfx + 1;
}
}
}
/*************************************
*
* Draw stars
*
*************************************/
/* starfield lfsr code is at d616 and at d648
*
* Code at d616:
*
* bit 6 (0x40) from 4be4 (port 8A) is rotated into top bit of 586D/586C
* Based on bit 4 (0x10) and the carry out , bit 6 of 4be4 is set to:
*
* Carry out bit 4 Result
* 1 1 1
* 1 0 0
* 0 1 0
* 0 0 1
*
* Code at d648 is opposite direction.
*
* The two videos show, that the starfield is covering the whole screen.
* Galaga is different, the 256H signal is delivered with 12 pixels
* delay ( NAND of 1H,2H,4H) to the starfield generator 05xx.
*
* http://www.youtube.com/watch?v=c1zIitLkpGs
*
* http://www.youtube.com/watch?v=bS2Zcin6OwM&feature=PlayList&p=649FD471A1803ABB&playnext_from=PL&playnext=1&index=8
*
* Galaga (for comparison)
*
* http://www.youtube.com/watch?v=-R4CCB2g5bE
*
* The typical lfsr star count pattern is
*
* 132 stars
* 125 stars
* 129 stars
* 68 stars
* 132 stars
* 125 stars
* 129 stars
* 68 stars
*
* This is close to Galaga's 63/126
*
*/
void _20pacgal_state::draw_stars(bitmap_rgb32 &bitmap, const rectangle &cliprect )
{
if ( (m_stars_ctrl[0] >> 5) & 1 )
{
int clock;
uint16_t lfsr = m_stars_seed[0] + m_stars_seed[1]*256;
uint8_t feedback = (m_stars_ctrl[0] >> 6) & 1;
uint16_t star_seta = (m_stars_ctrl[0] >> 3) & 0x01;
uint16_t star_setb = (m_stars_ctrl[0] >> 3) & 0x02;
int cnt = 0;
/* This is a guess based on galaga star sets */
star_seta = 0x3fc0 | (star_seta << 14);
star_setb = 0x3fc0 | (star_setb << 14);
for (clock=0; clock < 288*224; clock++)
{
int x, y;
int carryout;
x = clock % 288;
y = clock / 288;
/* code at d616 translates into:
* carryout = lfsr & 1;
* lfsr = lfsr>>1;
* lfsr = (feedback << 15) | lfsr;
* feedback = (((lfsr>>4) & 1) ^ (carryout & 1)) ^ 1;
*
* and needs a Hack:
* x = 288 - x;
*
*/
/* code at d648 */
carryout = ((lfsr >> 4) ^ feedback ^ 1) & 1;
feedback = (lfsr >> 15) & 1;
lfsr = (lfsr << 1) | carryout;
if (((lfsr & 0xffc0) == star_seta) || ((lfsr & 0xffc0) == star_setb))
{
if (y >= cliprect.min_y && y <= cliprect.max_y)
bitmap.pix32(y, x) = NUM_PENS + (lfsr & 0x3f);
cnt++;
}
}
}
}
/*************************************
*
* Video update
*
*************************************/
uint32_t _20pacgal_state::screen_update_20pacgal(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
bitmap.fill(0, cliprect);
draw_stars(bitmap,cliprect);
draw_chars(bitmap, cliprect);
draw_sprites(bitmap, cliprect);
do_pen_lookup(bitmap, cliprect);
return 0;
}
void _20pacgal_state::video_start()
{
m_sprite_gfx_ram = make_unique_clear<uint8_t[]>(0x2000);
m_sprite_ram = make_unique_clear<uint8_t[]>(0x180);
m_sprite_color_lookup = make_unique_clear<uint8_t[]>(0x100);
save_pointer(NAME(m_sprite_gfx_ram), 0x2000);
save_pointer(NAME(m_sprite_ram), 0x180);
save_pointer(NAME(m_sprite_color_lookup), 0x100);
if (m_proms != nullptr)
get_pens();
}
/*************************************
*
* Machine driver
*
*************************************/
MACHINE_CONFIG_START(_20pacgal_state::_20pacgal_video)
MCFG_SCREEN_ADD("screen", RASTER)
MCFG_SCREEN_REFRESH_RATE(60)
MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC(0))
MCFG_SCREEN_SIZE(SCREEN_WIDTH, SCREEN_HEIGHT)
MCFG_SCREEN_VISIBLE_AREA(0, SCREEN_WIDTH - 1, 0, SCREEN_HEIGHT - 1)
MCFG_SCREEN_UPDATE_DRIVER(_20pacgal_state, screen_update_20pacgal)
MCFG_SCREEN_VBLANK_CALLBACK(WRITELINE(*this, _20pacgal_state, vblank_irq))
MCFG_PALETTE_ADD("palette", NUM_PENS + NUM_STAR_PENS)
MCFG_PALETTE_INIT_OWNER(_20pacgal_state,starpal_init)
MACHINE_CONFIG_END