/***************************************************************************
Atari System 1 hardware
****************************************************************************/
#include "driver.h"
#include "machine/atarigen.h"
#include "atarisy1.h"
/*************************************
*
* Constants
*
*************************************/
/* the color and remap PROMs are mapped as follows */
#define PROM1_BANK_4 0x80 /* active low */
#define PROM1_BANK_3 0x40 /* active low */
#define PROM1_BANK_2 0x20 /* active low */
#define PROM1_BANK_1 0x10 /* active low */
#define PROM1_OFFSET_MASK 0x0f /* postive logic */
#define PROM2_BANK_6_OR_7 0x80 /* active low */
#define PROM2_BANK_5 0x40 /* active low */
#define PROM2_PLANE_5_ENABLE 0x20 /* active high */
#define PROM2_PLANE_4_ENABLE 0x10 /* active high */
#define PROM2_PF_COLOR_MASK 0x0f /* negative logic */
#define PROM2_BANK_7 0x08 /* active low, plus PROM2_BANK_6_OR_7 low as well */
#define PROM2_MO_COLOR_MASK 0x07 /* negative logic */
/*************************************
*
* Globals we own
*
*************************************/
UINT16 *atarisy1_bankselect;
/*************************************
*
* Statics
*
*************************************/
/* playfield parameters */
static UINT16 playfield_lookup[256];
static UINT8 playfield_tile_bank;
static UINT16 playfield_priority_pens;
static emu_timer *yscroll_reset_timer;
/* INT3 tracking */
static int next_timer_scanline;
static emu_timer *scanline_timer;
static emu_timer *int3off_timer;
/* graphics bank tracking */
static UINT8 bank_gfx[3][8];
static UINT8 bank_color_shift[MAX_GFX_ELEMENTS];
/* basic form of a graphics bank */
static gfx_layout objlayout =
{
8,8, /* 8*8 sprites */
4096, /* 4096 of them */
6, /* 6 bits per pixel */
{ 5*8*0x10000, 4*8*0x10000, 3*8*0x10000, 2*8*0x10000, 1*8*0x10000, 0*8*0x10000 },
{ 0, 1, 2, 3, 4, 5, 6, 7 },
{ 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 },
8*8 /* every sprite takes 8 consecutive bytes */
};
/*************************************
*
* Prototypes
*
*************************************/
static void update_timers(int scanline);
static void decode_gfx(running_machine *machine, UINT16 *pflookup, UINT16 *molookup);
static int get_bank(running_machine *machine, UINT8 prom1, UINT8 prom2, int bpp);
static TIMER_CALLBACK( int3_callback );
static TIMER_CALLBACK( int3off_callback );
static TIMER_CALLBACK( reset_yscroll_callback );
/*************************************
*
* Tilemap callbacks
*
*************************************/
static TILE_GET_INFO( get_alpha_tile_info )
{
UINT16 data = atarigen_alpha[tile_index];
int code = data & 0x3ff;
int color = (data >> 10) & 0x07;
int opaque = data & 0x2000;
SET_TILE_INFO(0, code, color, opaque ? TILE_FORCE_LAYER0 : 0);
}
static TILE_GET_INFO( get_playfield_tile_info )
{
UINT16 data = atarigen_playfield[tile_index];
UINT16 lookup = playfield_lookup[((data >> 8) & 0x7f) | (playfield_tile_bank << 7)];
int gfxindex = (lookup >> 8) & 15;
int code = ((lookup & 0xff) << 8) | (data & 0xff);
int color = 0x20 + (((lookup >> 12) & 15) << bank_color_shift[gfxindex]);
SET_TILE_INFO(gfxindex, code, color, (data >> 15) & 1);
}
/*************************************
*
* Generic video system start
*
*************************************/
VIDEO_START( atarisy1 )
{
static const struct atarimo_desc modesc =
{
0, /* index to which gfx system */
8, /* number of motion object banks */
1, /* are the entries linked? */
1, /* are the entries split? */
0, /* render in reverse order? */
0, /* render in swapped X/Y order? */
0, /* does the neighbor bit affect the next object? */
0, /* pixels per SLIP entry (0 for no-slip) */
0, /* pixel offset for SLIPs */
0x38, /* maximum number of links to visit/scanline (0=all) */
0x100, /* base palette entry */
0x100, /* maximum number of colors */
0, /* transparent pen index */
{{ 0,0,0,0x003f }}, /* mask for the link */
{{ 0,0xff00,0,0 }}, /* mask for the graphics bank */
{{ 0,0xffff,0,0 }}, /* mask for the code index */
{{ 0 }}, /* mask for the upper code index */
{{ 0,0xff00,0,0 }}, /* mask for the color */
{{ 0,0,0x3fe0,0 }}, /* mask for the X position */
{{ 0x3fe0,0,0,0 }}, /* mask for the Y position */
{{ 0 }}, /* mask for the width, in tiles*/
{{ 0x000f,0,0,0 }}, /* mask for the height, in tiles */
{{ 0x8000,0,0,0 }}, /* mask for the horizontal flip */
{{ 0 }}, /* mask for the vertical flip */
{{ 0,0,0x8000,0 }}, /* mask for the priority */
{{ 0 }}, /* mask for the neighbor */
{{ 0 }}, /* mask for absolute coordinates */
{{ 0,0xffff,0,0 }}, /* mask for the special value */
0xffff, /* resulting value to indicate "special" */
0 /* callback routine for special entries */
};
UINT16 motable[256];
UINT16 *codelookup;
UINT8 *colorlookup, *gfxlookup;
int i, size;
/* first decode the graphics */
decode_gfx(machine, playfield_lookup, motable);
/* initialize the playfield */
atarigen_playfield_tilemap = tilemap_create(get_playfield_tile_info, tilemap_scan_rows, TILEMAP_TYPE_PEN, 8,8, 64,64);
/* initialize the motion objects */
atarimo_init(machine, 0, &modesc);
/* initialize the alphanumerics */
atarigen_alpha_tilemap = tilemap_create(get_alpha_tile_info, tilemap_scan_rows, TILEMAP_TYPE_PEN, 8,8, 64,32);
tilemap_set_transparent_pen(atarigen_alpha_tilemap, 0);
/* modify the motion object code lookup */
codelookup = atarimo_get_code_lookup(0, &size);
for (i = 0; i < size; i++)
codelookup[i] = (i & 0xff) | ((motable[i >> 8] & 0xff) << 8);
/* modify the motion object color and gfx lookups */
colorlookup = atarimo_get_color_lookup(0, &size);
gfxlookup = atarimo_get_gfx_lookup(0, &size);
for (i = 0; i < size; i++)
{
colorlookup[i] = ((motable[i] >> 12) & 15) << 1;
gfxlookup[i] = (motable[i] >> 8) & 15;
}
/* reset the statics */
atarimo_set_yscroll(0, 256);
next_timer_scanline = -1;
scanline_timer = timer_alloc(int3_callback);
int3off_timer = timer_alloc(int3off_callback);
yscroll_reset_timer = timer_alloc(reset_yscroll_callback);
}
/*************************************
*
* Graphics bank selection
*
*************************************/
WRITE16_HANDLER( atarisy1_bankselect_w )
{
UINT16 oldselect = *atarisy1_bankselect;
UINT16 newselect = oldselect, diff;
int scanline = video_screen_get_vpos(0);
/* update memory */
COMBINE_DATA(&newselect);
diff = oldselect ^ newselect;
/* sound CPU reset */
if (diff & 0x0080)
{
cpunum_set_input_line(1, INPUT_LINE_RESET, (newselect & 0x0080) ? CLEAR_LINE : ASSERT_LINE);
if (!(newselect & 0x0080)) atarigen_sound_reset();
}
/* if MO or playfield banks change, force a partial update */
if (diff & 0x003c)
video_screen_update_partial(0, scanline);
/* motion object bank select */
atarimo_set_bank(0, (newselect >> 3) & 7);
update_timers(scanline);
/* playfield bank select */
if (diff & 0x0004)
{
playfield_tile_bank = (newselect >> 2) & 1;
tilemap_mark_all_tiles_dirty(atarigen_playfield_tilemap);
}
/* stash the new value */
*atarisy1_bankselect = newselect;
}
/*************************************
*
* Playfield priority pens
*
*************************************/
WRITE16_HANDLER( atarisy1_priority_w )
{
UINT16 oldpens = playfield_priority_pens;
UINT16 newpens = oldpens;
/* force a partial update in case this changes mid-screen */
COMBINE_DATA(&newpens);
if (oldpens != newpens)
video_screen_update_partial(0, video_screen_get_vpos(0));
playfield_priority_pens = newpens;
}
/*************************************
*
* Playfield horizontal scroll
*
*************************************/
WRITE16_HANDLER( atarisy1_xscroll_w )
{
UINT16 oldscroll = *atarigen_xscroll;
UINT16 newscroll = oldscroll;
/* force a partial update in case this changes mid-screen */
COMBINE_DATA(&newscroll);
if (oldscroll != newscroll)
video_screen_update_partial(0, video_screen_get_vpos(0));
/* set the new scroll value */
tilemap_set_scrollx(atarigen_playfield_tilemap, 0, newscroll);
/* update the data */
*atarigen_xscroll = newscroll;
}
/*************************************
*
* Playfield vertical scroll
*
*************************************/
static TIMER_CALLBACK( reset_yscroll_callback )
{
tilemap_set_scrolly(atarigen_playfield_tilemap, 0, param);
}
WRITE16_HANDLER( atarisy1_yscroll_w )
{
UINT16 oldscroll = *atarigen_yscroll;
UINT16 newscroll = oldscroll;
int scanline = video_screen_get_vpos(0);
int adjusted_scroll;
/* force a partial update in case this changes mid-screen */
COMBINE_DATA(&newscroll);
video_screen_update_partial(0, scanline);
/* because this latches a new value into the scroll base,
we need to adjust for the scanline */
adjusted_scroll = newscroll;
if (scanline <= Machine->screen[0].visarea.max_y)
adjusted_scroll -= (scanline + 1);
tilemap_set_scrolly(atarigen_playfield_tilemap, 0, adjusted_scroll);
/* but since we've adjusted it, we must reset it to the normal value
once we hit scanline 0 again */
timer_adjust(yscroll_reset_timer, video_screen_get_time_until_pos(0, 0, 0), newscroll, attotime_zero);
/* update the data */
*atarigen_yscroll = newscroll;
}
/*************************************
*
* Sprite RAM write handler
*
*************************************/
WRITE16_HANDLER( atarisy1_spriteram_w )
{
int active_bank = atarimo_get_bank(0);
int oldword = atarimo_0_spriteram[offset];
int newword = oldword;
COMBINE_DATA(&newword);
/* if the data changed, and it modified the live sprite bank, do some extra work */
if (oldword != newword && (offset >> 8) == active_bank)
{
/* if modifying a timer, beware */
if (((offset & 0xc0) == 0x00 && atarimo_0_spriteram[offset | 0x40] == 0xffff) ||
((offset & 0xc0) == 0x40 && (newword == 0xffff || oldword == 0xffff)))
{
/* if the timer is in the active bank, update the display list */
atarimo_0_spriteram_w(offset, data, 0);
update_timers(video_screen_get_vpos(0));
}
/* if we're about to modify data in the active sprite bank, make sure the video is up-to-date */
/* Road Runner needs this to work; note the +2 kludge -- +1 would be correct since the video */
/* renders the next scanline's sprites to the line buffers, but Road Runner still glitches */
/* without the extra +1 */
else
video_screen_update_partial(0, video_screen_get_vpos(0) + 2);
}
/* let the MO handler do the basic work */
atarimo_0_spriteram_w(offset, data, 0);
}
/*************************************
*
* MO interrupt handlers
*
*************************************/
static TIMER_CALLBACK( int3off_callback )
{
/* clear the state */
atarigen_scanline_int_ack_w(0, 0, 0);
}
static TIMER_CALLBACK( int3_callback )
{
int scanline = param;
/* update the state */
atarigen_scanline_int_gen();
/* set a timer to turn it off */
timer_adjust(int3off_timer, video_screen_get_scan_period(0), 0, attotime_zero);
/* determine the time of the next one */
next_timer_scanline = -1;
update_timers(scanline);
}
/*************************************
*
* MO interrupt state read
*
*************************************/
READ16_HANDLER( atarisy1_int3state_r )
{
return atarigen_scanline_int_state ? 0x0080 : 0x0000;
}
/*************************************
*
* Timer updater
*
*************************************/
static void update_timers(int scanline)
{
UINT16 *base = &atarimo_0_spriteram[atarimo_get_bank(0) * 64 * 4];
int link = 0, best = scanline, found = 0;
UINT8 spritevisit[64];
/* track which ones we've visited */
memset(spritevisit, 0, sizeof(spritevisit));
/* walk the list until we loop */
while (!spritevisit[link])
{
/* timers are indicated by 0xffff in entry 2 */
if (base[link + 0x40] == 0xffff)
{
int data = base[link];
int vsize = (data & 15) + 1;
int ypos = (256 - (data >> 5) - vsize * 8 - 1) & 0x1ff;
/* note that we found something */
found = 1;
/* is this a better entry than the best so far? */
if (best <= scanline)
{
if ((ypos <= scanline && ypos < best) || ypos > scanline)
best = ypos;
}
else
{
if (ypos < best)
best = ypos;
}
}
/* link to the next */
spritevisit[link] = 1;
link = base[link + 0xc0] & 0x3f;
}
/* if nothing was found, use scanline -1 */
if (!found)
best = -1;
/* update the timer */
if (best != next_timer_scanline)
{
next_timer_scanline = best;
/* set a new one */
if (best != -1)
timer_adjust(scanline_timer, video_screen_get_time_until_pos(0, best, 0), best, attotime_zero);
else
timer_adjust(scanline_timer, attotime_never, 0, attotime_zero);
}
}
/*************************************
*
* Main refresh
*
*************************************/
VIDEO_UPDATE( atarisy1 )
{
struct atarimo_rect_list rectlist;
mame_bitmap *mobitmap;
int x, y, r;
/* draw the playfield */
tilemap_draw(bitmap, cliprect, atarigen_playfield_tilemap, 0, 0);
/* draw and merge the MO */
mobitmap = atarimo_render(machine, 0, cliprect, &rectlist);
for (r = 0; r < rectlist.numrects; r++, rectlist.rect++)
for (y = rectlist.rect->min_y; y <= rectlist.rect->max_y; y++)
{
UINT16 *mo = (UINT16 *)mobitmap->base + mobitmap->rowpixels * y;
UINT16 *pf = (UINT16 *)bitmap->base + bitmap->rowpixels * y;
for (x = rectlist.rect->min_x; x <= rectlist.rect->max_x; x++)
if (mo[x])
{
/* high priority MO? */
if (mo[x] & ATARIMO_PRIORITY_MASK)
{
/* only gets priority if MO pen is not 1 */
if ((mo[x] & 0x0f) != 1)
pf[x] = 0x300 + ((pf[x] & 0x0f) << 4) + (mo[x] & 0x0f);
}
/* low priority */
else
{
/* priority pens for playfield color 0 */
if ((pf[x] & 0xf8) != 0 || !(playfield_priority_pens & (1 << (pf[x] & 0x07))))
pf[x] = mo[x];
}
/* erase behind ourselves */
mo[x] = 0;
}
}
/* add the alpha on top */
tilemap_draw(bitmap, cliprect, atarigen_alpha_tilemap, 0, 0);
return 0;
}
/*************************************
*
* Graphics decoding
*
*************************************/
static void decode_gfx(running_machine *machine, UINT16 *pflookup, UINT16 *molookup)
{
UINT8 *prom1 = &memory_region(REGION_PROMS)[0x000];
UINT8 *prom2 = &memory_region(REGION_PROMS)[0x200];
int obj, i;
/* reset the globals */
memset(&bank_gfx[0][0], 0, sizeof(bank_gfx));
/* loop for two sets of objects */
for (obj = 0; obj < 2; obj++)
{
/* loop for 256 objects in the set */
for (i = 0; i < 256; i++, prom1++, prom2++)
{
int bank, bpp, color, offset;
/* determine the bpp */
bpp = 4;
if (*prom2 & PROM2_PLANE_4_ENABLE)
{
bpp = 5;
if (*prom2 & PROM2_PLANE_5_ENABLE)
bpp = 6;
}
/* determine the offset */
offset = *prom1 & PROM1_OFFSET_MASK;
/* determine the bank */
bank = get_bank(machine, *prom1, *prom2, bpp);
/* set the value */
if (obj == 0)
{
/* playfield case */
color = (~*prom2 & PROM2_PF_COLOR_MASK) >> (bpp - 4);
if (bank == 0)
{
bank = 1;
offset = color = 0;
}
pflookup[i] = offset | (bank << 8) | (color << 12);
}
else
{
/* motion objects (high bit ignored) */
color = (~*prom2 & PROM2_MO_COLOR_MASK) >> (bpp - 4);
molookup[i] = offset | (bank << 8) | (color << 12);
}
}
}
}
/*************************************
*
* Graphics bank mapping
*
*************************************/
static int get_bank(running_machine *machine, UINT8 prom1, UINT8 prom2, int bpp)
{
int bank_index, i, gfx_index;
/* determine the bank index */
if ((prom1 & PROM1_BANK_1) == 0)
bank_index = 1;
else if ((prom1 & PROM1_BANK_2) == 0)
bank_index = 2;
else if ((prom1 & PROM1_BANK_3) == 0)
bank_index = 3;
else if ((prom1 & PROM1_BANK_4) == 0)
bank_index = 4;
else if ((prom2 & PROM2_BANK_5) == 0)
bank_index = 5;
else if ((prom2 & PROM2_BANK_6_OR_7) == 0)
{
if ((prom2 & PROM2_BANK_7) == 0)
bank_index = 7;
else
bank_index = 6;
}
else
return 0;
/* find the bank */
if (bank_gfx[bpp - 4][bank_index])
return bank_gfx[bpp - 4][bank_index];
/* if the bank is out of range, call it 0 */
if (0x80000 * (bank_index - 1) >= memory_region_length(REGION_GFX2))
return 0;
/* don't have one? let's make it ... first find any empty slot */
for (gfx_index = 0; gfx_index < MAX_GFX_ELEMENTS; gfx_index++)
if (machine->gfx[gfx_index] == NULL)
break;
assert(gfx_index != MAX_GFX_ELEMENTS);
/* tweak the structure for the number of bitplanes we have */
objlayout.planes = bpp;
for (i = 0; i < bpp; i++)
objlayout.planeoffset[i] = (bpp - i - 1) * 0x10000 * 8;
/* decode the graphics */
machine->gfx[gfx_index] = allocgfx(&objlayout);
decodegfx(machine->gfx[gfx_index], &memory_region(REGION_GFX2)[0x80000 * (bank_index - 1)], 0, machine->gfx[gfx_index]->total_elements);
/* set the color information */
machine->gfx[gfx_index]->color_base = 256;
machine->gfx[gfx_index]->color_granularity = 8;
machine->gfx[gfx_index]->total_colors = 0x40;
bank_color_shift[gfx_index] = bpp - 3;
/* set the entry and return it */
return bank_gfx[bpp - 4][bank_index] = gfx_index;
}