#define VERBOSE 0
#define GX_DEBUG 0

/**************************************************************************
 *
 * machine/konamigx.c - contains various System GX hardware abstractions
 *
 * Currently includes: TMS57002 skipper/simulator to make the sound 68k happy.
 *
 */

#include "driver.h"
#include "vidhrdw/konamiic.h"
#include "machine/konamigx.h"
#include <math.h>


static struct
{
	unsigned char control;
	unsigned char program[1024];
	unsigned int tables[256];
	int curpos;
	int bytepos;
	unsigned int tmp;
} tms57002;

static int ldw = 0;


void tms57002_init(void)
{
	tms57002.control = 0;
}

static void chk_ldw(void)
{
	ldw = 0;
}

WRITE8_HANDLER( tms57002_control_w )
{
	chk_ldw();

	switch(tms57002.control)
	{
		case 0xf8:
		break;
		case 0xf0:
		break;
	}

	tms57002.control = data;

	switch(data)
	{
		case 0xf8: // Program write
		case 0xf0: // Table write
			tms57002.curpos = 0;
			tms57002.bytepos = 3;
			tms57002.tmp = 0;
		break;
		case 0xf4: // Entry write
			tms57002.curpos = -1;
			tms57002.bytepos = 3;
			tms57002.tmp = 0;
		break;
		case 0xfc: // Checksum (?) Status (?)
			tms57002.bytepos = 3;
			tms57002.tmp = 0;
		break;
		case 0xff: // Standby
		break;
		case 0xfe: // Irq
			/* (place ack of timer IRQ here) */
		break;
		default:
		break;
	}
}

READ8_HANDLER( tms57002_status_r )
{
	chk_ldw();
	return 1;
}

WRITE8_HANDLER( tms57002_data_w )
{
	switch(tms57002.control)
	{
		case 0xf8:
			tms57002.program[tms57002.curpos++] = data;
		break;
		case 0xf0:
			tms57002.tmp |= data << (8*tms57002.bytepos);
			tms57002.bytepos--;
			if (tms57002.bytepos < 0)
			{
				tms57002.bytepos = 3;
				tms57002.tables[tms57002.curpos++] =  tms57002.tmp;
				tms57002.tmp = 0;
			}
		break;
		case 0xf4:
			if (tms57002.curpos == -1)
	  			tms57002.curpos = data;
			else
			{
				tms57002.tmp |= data << (8*tms57002.bytepos);
				tms57002.bytepos--;

				if (tms57002.bytepos < 0)
				{
					tms57002.bytepos = 3;
					tms57002.tables[tms57002.curpos] =  tms57002.tmp;
					tms57002.tmp = 0;
					tms57002.curpos = -1;
				}
			}
		break;
		default:
			ldw++;
		break;
	}
}

READ8_HANDLER( tms57002_data_r )
{
	unsigned char res;

	chk_ldw();

	switch(tms57002.control)
	{
		case 0xfc:
			res = tms57002.tmp >> (8*tms57002.bytepos);
			tms57002.bytepos--;

			if(tms57002.bytepos < 0) tms57002.bytepos = 3;
		return res;
	}

	return 0;
}


READ16_HANDLER( tms57002_data_word_r )
{
	return(tms57002_data_r(0));
}

READ16_HANDLER( tms57002_status_word_r )
{
	return(tms57002_status_r(0));
}

WRITE16_HANDLER( tms57002_control_word_w )
{
	tms57002_control_w(0, data);
}

WRITE16_HANDLER( tms57002_data_word_w )
{
	tms57002_data_w(0, data);
}



/***************************************************************************/
/*                                                                         */
/*                     2nd-Tier GX/MW Graphics Functions                   */
/*                                                                         */
/***************************************************************************/

// Hardcoded constants and custom z-buffer
#define GX_BMPPW (512+32)

#if GX_DEBUG
	#define GX_ZBUFW     512
	#define GX_ZBUFH     384
	#define GX_ZPAGESIZE 0x300000
	#define GX_ZBUFSIZE  0x600000
#else
	#define GX_ZBUFW     384
	#define GX_ZBUFH     224
	#define GX_ZPAGESIZE 0x150000
	#define GX_ZBUFSIZE  0x2a0000
#endif

static UINT8 *gx_objzbuf, *gx_shdzbuf;


// Localized K053936/ROZ+
#define K053936_MAX_CHIPS 2

static rectangle K053936_cliprect[K053936_MAX_CHIPS] = {{0,0,0,0},{0,0,0,0}};
static int K053936_offset[K053936_MAX_CHIPS][2] = {{0,0},{0,0}};
static int K053936_clip_enabled[K053936_MAX_CHIPS] = {0,0};


void K053936GP_set_offset(int chip, int xoffs, int yoffs) { K053936_offset[chip][0] = xoffs; K053936_offset[chip][1] = yoffs; }

void K053936GP_clip_enable(int chip, int status) { K053936_clip_enabled[chip] = status; }

void K053936GP_set_cliprect(int chip, int minx, int maxx, int miny, int maxy)
{
	rectangle *cliprect = &K053936_cliprect[chip];
	cliprect->min_x = minx;
	cliprect->max_x = maxx;
	cliprect->min_y = miny;
	cliprect->max_y = maxy;
}

#define BLEND32_MACRO {  \
	eax = pal_base[eax]; \
	ebx = eax;           \
	edx = eax;           \
	eax >>= 8;           \
	ebx >>= 16;          \
	eax &= 0xff;         \
	edx &= 0xff;         \
	ebx = esi[ebx];      \
	eax = esi[eax];      \
	ebx <<= 16;          \
	eax <<= 8;           \
	ebx |= esi[edx];     \
	edx = dst_ptr[ecx];  \
	ebx |= eax;          \
	eax = edx;           \
	ebp = ebx;           \
	ebx = edx;           \
	edx >>= 8;           \
	eax >>= 16;          \
	edx &= 0xff;         \
	ebx &= 0xff;         \
	eax = edi[eax];      \
	edx = edi[edx];      \
	eax <<= 16;          \
	edx <<= 8;           \
	eax |= edi[ebx];     \
	edx += ebp;          \
	edx += eax;          \
	dst_ptr[ecx] = edx; }

INLINE void K053936GP_copyroz32clip( mame_bitmap *dst_bitmap, mame_bitmap *src_bitmap,
		const rectangle *dst_cliprect, const rectangle *src_cliprect,
		UINT32 _startx,UINT32 _starty,int _incxx,int _incxy,int _incyx,int _incyy,
		int tilebpp, int blend, int clip )
{
	static int colormask[8]={1,3,7,0xf,0x1f,0x3f,0x7f,0xff};

	int cy, cx;
	register int eax, ebx, edx, ecx;
	int src_pitch, incxy, incxx;
	int src_minx, src_maxx, src_miny, src_maxy, cmask, ebp;
	UINT16 *src_base;
	UINT32 *pal_base;
	const UINT8  *esi, *edi;
	UINT32 *dst_ptr;

	int tx, dst_pitch;
	UINT32 *dst_base;
	int starty, incyy, startx, incyx, ty, sx, sy;

	incxy = _incxy; incxx = _incxx; incyy = _incyy; incyx = _incyx;
	starty = _starty; startx = _startx;

	if (src_cliprect && clip) // set source clip range to some extreme values when disabled
	{
		src_minx = src_cliprect->min_x;
		src_maxx = src_cliprect->max_x;
		src_miny = src_cliprect->min_y;
		src_maxy = src_cliprect->max_y;
	}
	else { src_minx = src_miny = -0x10000; src_maxx = src_maxy = 0x10000; }

	if (dst_cliprect) // set target clip range
	{
		sx = dst_cliprect->min_x;
		tx = dst_cliprect->max_x - sx + 1;
		sy = dst_cliprect->min_y;
		ty = dst_cliprect->max_y - sy + 1;

		startx += sx * incxx + sy * incyx;
		starty += sx * incxy + sy * incyy;
	}
	else { sx = sy = 0; tx = dst_bitmap->width; ty = dst_bitmap->height; }

	// adjust entry points and other loop constants
	dst_pitch = dst_bitmap->rowpixels;
	dst_base = (UINT32*)dst_bitmap->base + sy * dst_pitch + sx + tx;
	ecx = tx = -tx;

	tilebpp = (tilebpp-1) & 7;
	pal_base = Machine->remapped_colortable;
	cmask = colormask[tilebpp];

	src_pitch = src_bitmap->rowpixels;
	src_base = src_bitmap->base;

	src_miny *= src_pitch;
	src_maxy *= src_pitch;

	dst_ptr = dst_base;
	cy = starty;
	cx = startx;

	if (blend >=0)
	{
		dst_base += dst_pitch;
		starty += incyy;
		startx += incyx;

		if (!blend)
			goto DRAW_SOLID;
		else
		{
			esi = drawgfx_alpha_cache.alphas;
			edi = drawgfx_alpha_cache.alphad;
			goto DRAW_ALPHA;
		}
	}
	else
	{
		if ((sy & 1) ^ (blend & 1))
		{
			if (ty <= 1) return;

			dst_ptr += dst_pitch;
			cy += incyy;
			cx += incyx;
		}

		if (ty > 1)
		{
			ty >>= 1;
			dst_pitch <<= 1;
			incyy <<= 1;
			incyx <<= 1;

			dst_base = dst_ptr + dst_pitch;
			starty = cy + incyy;
			startx = cx + incyx;
		}
	}


	DRAW_SOLID:
	do {
		do {
			eax = cy;      ebx = cx;
			eax >>= 16;    ebx >>= 16;
			eax &= 0x1fff; ebx &= 0x1fff;
			eax = (eax<<5) + (eax<<13); //eax *= src_pitch;
			cy += incxy;   cx += incxx;
			if (ebx < src_minx || ebx > src_maxx || eax < src_miny || eax > src_maxy) continue;

			eax = src_base[eax+ebx];
			if (!(eax & cmask)) continue;

			dst_ptr[ecx] = pal_base[eax];
		}
		while (++ecx);

		ecx = tx;
		dst_ptr = dst_base; dst_base += dst_pitch;
		cy = starty; starty += incyy;
		cx = startx; startx += incyx;
	} while (--ty);
	return;


	DRAW_ALPHA:
	do {
		do {
			eax = cy;      ebx = cx;
			eax >>= 16;    ebx >>= 16;
			eax &= 0x1fff; ebx &= 0x1fff;
			eax = (eax<<5) + (eax<<13); //eax *= src_pitch;
			cy += incxy;   cx += incxx;
			if (ebx < src_minx || ebx > src_maxx || eax < src_miny || eax > src_maxy) continue;

			eax = src_base[eax+ebx];
			if (!(eax & cmask)) continue;

			BLEND32_MACRO
		}
		while (++ecx);

		ecx = tx;
		dst_ptr = dst_base; dst_base += dst_pitch;
		cy = starty; starty += incyy;
		cx = startx; startx += incyx;
	} while (--ty);
}

// adpoted from generic K053936_zoom_draw()
static void K053936GP_zoom_draw(int chip, UINT16 *ctrl, UINT16 *linectrl,
		mame_bitmap *bitmap, const rectangle *cliprect, tilemap *tmap,
		int tilebpp, int blend)
{
	mame_bitmap *src_bitmap;
	rectangle *src_cliprect;
	UINT16 *lineaddr;

	rectangle my_clip;
	UINT32 startx, starty;
	int incxx, incxy, incyx, incyy, y, maxy, clip;

	src_bitmap = tilemap_get_pixmap(tmap);
	src_cliprect = &K053936_cliprect[chip];
	clip = K053936_clip_enabled[chip];

	if (ctrl[0x07] & 0x0040)    /* "super" mode */
	{
		my_clip.min_x = cliprect->min_x;
		my_clip.max_x = cliprect->max_x;
		y = cliprect->min_y;
		maxy = cliprect->max_y;

		while (y <= maxy)
		{
			lineaddr = linectrl + ( ((y - K053936_offset[chip][1]) & 0x1ff) << 2);
			my_clip.min_y = my_clip.max_y = y;

			startx = (INT16)(lineaddr[0] + ctrl[0x00]) << 8;
			starty = (INT16)(lineaddr[1] + ctrl[0x01]) << 8;
			incxx  = (INT16)(lineaddr[2]);
			incxy  = (INT16)(lineaddr[3]);

			if (ctrl[0x06] & 0x8000) incxx <<= 8;
			if (ctrl[0x06] & 0x0080) incxy <<= 8;

			startx -= K053936_offset[chip][0] * incxx;
			starty -= K053936_offset[chip][0] * incxy;

			K053936GP_copyroz32clip(bitmap, src_bitmap, &my_clip, src_cliprect,
					startx<<5, starty<<5, incxx<<5, incxy<<5, 0, 0,
					tilebpp, blend, clip);
			y++;
		}
	}
	else    /* "simple" mode */
	{
		startx = (INT16)(ctrl[0x00]) << 8;
		starty = (INT16)(ctrl[0x01]) << 8;
		incyx  = (INT16)(ctrl[0x02]);
		incyy  = (INT16)(ctrl[0x03]);
		incxx  = (INT16)(ctrl[0x04]);
		incxy  = (INT16)(ctrl[0x05]);

		if (ctrl[0x06] & 0x4000) { incyx <<= 8; incyy <<= 8; }
		if (ctrl[0x06] & 0x0040) { incxx <<= 8; incxy <<= 8; }

		startx -= K053936_offset[chip][1] * incyx;
		starty -= K053936_offset[chip][1] * incyy;

		startx -= K053936_offset[chip][0] * incxx;
		starty -= K053936_offset[chip][0] * incxy;

		K053936GP_copyroz32clip(bitmap, src_bitmap, cliprect, src_cliprect,
				startx<<5, starty<<5, incxx<<5, incxy<<5, incyx<<5, incyy<<5,
				tilebpp, blend, clip);
	}
}

void K053936GP_0_zoom_draw(mame_bitmap *bitmap, const rectangle *cliprect,
		tilemap *tmap, int tilebpp, int blend)
{
	K053936GP_zoom_draw(0,K053936_0_ctrl,K053936_0_linectrl,bitmap,cliprect,tmap,tilebpp,blend);
}

void K053936GP_1_zoom_draw(mame_bitmap *bitmap, const rectangle *cliprect,
		tilemap *tmap, int tilebpp, int blend)
{
	K053936GP_zoom_draw(1,K053936_1_ctrl,K053936_1_linectrl,bitmap,cliprect,tmap,tilebpp,blend);
}



/*
    Parameter Notes
    ---------------
    clip    : *caller must supply a pointer to target clip rectangle
    alpha   : 0 = invisible, 255 = solid
    drawmode:
        0 = all pens solid
        1 = solid pens only
        2 = all pens solid with alpha blending
        3 = solid pens only with alpha blending
        4 = shadow pens only
        5 = all pens shadow
    zcode   : 0 = closest, 255 = furthest (pixel z-depth), -1 = disable depth buffers and shadows
    pri     : 0 = topmost, 255 = backmost (pixel priority)
*/

INLINE void zdrawgfxzoom32GP( mame_bitmap *bitmap, const gfx_element *gfx, const rectangle *cliprect,
		unsigned int code, unsigned int color, int flipx, int flipy, int sx, int sy,
		int scalex, int scaley, int alpha, int drawmode, int zcode, int pri)
{
#define FP     19
#define FPONE  (1<<FP)
#define FPHALF (1<<(FP-1))
#define FPENT  0

	// inner loop
	UINT8  *src_ptr;
	int src_x;
	register int eax, ebx, edx, ecx;
	int src_fx, src_fdx;
	int shdpen, ebp;
	UINT8  z8, db0, p8, db1;
	UINT8  *ozbuf_ptr;
	UINT8  *szbuf_ptr;
	UINT32 *pal_base;
	UINT32 *shd_base;
	UINT32 *dst_ptr;
	const UINT8  *esi, *edi;

	// outter loop
	int src_fby, src_fdy, src_fbx;
	UINT8 *src_base;
	int dst_w, dst_h;

	// one-time
	int nozoom, granularity;
	int src_fw, src_fh;
	int dst_minx, dst_maxx, dst_miny, dst_maxy;
	int dst_skipx, dst_skipy, dst_x, dst_y, dst_lastx, dst_lasty;
	int src_pitch, dst_pitch;


	// cull illegal and transparent objects
	if (!scalex || !scaley) return;

	// find shadow pens and cull invisible shadows
	granularity = shdpen = gfx->color_granularity;
	shdpen--;

	if (zcode >= 0)
	{
		if (drawmode == 5) { drawmode = 4; shdpen = 1; }
	}
	else
		if (drawmode >= 4) return;

	// alpha blend necessary?
	if (drawmode & 2)
	{
		if (alpha <= 0) return;
		if (alpha >= 255) drawmode &= ~2;
	}

	esi = drawgfx_alpha_cache.alphas;
	edi = drawgfx_alpha_cache.alphad;

	// fill internal data structure with default values
	ozbuf_ptr  = gx_objzbuf;
	szbuf_ptr  = gx_shdzbuf;

	src_pitch = 16;
	src_fw    = 16;
	src_fh    = 16;
	src_base  = gfx->gfxdata + (code % gfx->total_elements) * gfx->char_modulo;

	pal_base  = gfx->colortable + (color % gfx->total_colors) * granularity;
	shd_base  = (UINT32 *)palette_shadow_table;

	dst_ptr   = bitmap->base;
	dst_pitch = GX_BMPPW;
	dst_minx  = cliprect->min_x;
	dst_maxx  = cliprect->max_x;
	dst_miny  = cliprect->min_y;
	dst_maxy  = cliprect->max_y;
	dst_x     = sx;
	dst_y     = sy;

	// cull off-screen objects
	if (dst_x > dst_maxx || dst_y > dst_maxy) return;
	if ((nozoom = (scalex == 0x10000 && scaley == 0x10000)))
	{
		dst_h = dst_w = 16;
		src_fdy = src_fdx = 1;
	}
	else
	{
		dst_w = ((scalex<<4)+0x8000)>>16;
		dst_h = ((scaley<<4)+0x8000)>>16;
		if (!dst_w || !dst_h) return;

		src_fw <<= FP;
		src_fh <<= FP;
		src_fdx = src_fw / dst_w;
		src_fdy = src_fh / dst_h;
	}
	dst_lastx = dst_x + dst_w - 1;
	if (dst_lastx < dst_minx) return;
	dst_lasty = dst_y + dst_h - 1;
	if (dst_lasty < dst_miny) return;

	// clip destination
	dst_skipx = 0;
	eax = dst_minx;  if ((eax -= dst_x) > 0) { dst_skipx = eax;  dst_w -= eax;  dst_x = dst_minx; }
	eax = dst_lastx; if ((eax -= dst_maxx) > 0) dst_w -= eax;
	dst_skipy = 0;
	eax = dst_miny;  if ((eax -= dst_y) > 0) { dst_skipy = eax;  dst_h -= eax;  dst_y = dst_miny; }
	eax = dst_lasty; if ((eax -= dst_maxy) > 0) dst_h -= eax;

	// calculate zoom factors and clip source
	if (nozoom)
	{
		if (!flipx) src_fbx = 0; else { src_fbx = src_fw - 1; src_fdx = -src_fdx; }
		if (!flipy) src_fby = 0; else { src_fby = src_fh - 1; src_fdy = -src_fdy; src_pitch = -src_pitch; }
	}
	else
	{
		if (!flipx) src_fbx = FPENT; else { src_fbx = src_fw - FPENT - 1; src_fdx = -src_fdx; }
		if (!flipy) src_fby = FPENT; else { src_fby = src_fh - FPENT - 1; src_fdy = -src_fdy; }
	}
	src_fbx += dst_skipx * src_fdx;
	src_fby += dst_skipy * src_fdy;

	// adjust insertion points and pre-entry constants
	eax = (dst_y - dst_miny) * GX_ZBUFW + (dst_x - dst_minx) + dst_w;
	db0 = z8 = (UINT8)zcode;
	db1 = p8 = (UINT8)pri;
	ozbuf_ptr += eax;
	szbuf_ptr += eax << 1;
	dst_ptr += dst_y * dst_pitch + dst_x + dst_w;
	dst_w = -dst_w;

	if (!nozoom)
	{
		ecx = src_fby;   src_fby += src_fdy;
		ecx >>= FP;      src_fx = src_fbx;
		src_x = src_fbx; src_fx += src_fdx;
		ecx <<= 4;       src_ptr = src_base;
		src_x >>= FP;    src_ptr += ecx;
		ecx = dst_w;

		if (zcode < 0) // no shadow and z-buffering
		{
			do {
				do {
					eax = src_ptr[src_x];
					src_x = src_fx;
					src_fx += src_fdx;
					src_x >>= FP;
					if (!eax || eax >= shdpen) continue;
					dst_ptr [ecx] = pal_base[eax];
				}
				while (++ecx);

				ecx = src_fby;   src_fby += src_fdy;
				dst_ptr += GX_BMPPW;
				ecx >>= FP;      src_fx = src_fbx;
				src_x = src_fbx; src_fx += src_fdx;
				ecx <<= 4;       src_ptr = src_base;
				src_x >>= FP;    src_ptr += ecx;
				ecx = dst_w;
			}
			while (--dst_h);
		}
		else
		{
			switch (drawmode)
			{
				case 0:	// all pens solid
					do {
						do {
							eax = src_ptr[src_x];
							src_x = src_fx;
							src_fx += src_fdx;
							src_x >>= FP;
							if (!eax || ozbuf_ptr[ecx] < z8) continue;
							eax = pal_base[eax];
							ozbuf_ptr[ecx] = z8;
							dst_ptr [ecx] = eax;
						}
						while (++ecx);

						ecx = src_fby;   src_fby += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx >>= FP;      src_fx = src_fbx;
						src_x = src_fbx; src_fx += src_fdx;
						ecx <<= 4;       src_ptr = src_base;
						src_x >>= FP;    src_ptr += ecx;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 1: // solid pens only
					do {
						do {
							eax = src_ptr[src_x];
							src_x = src_fx;
							src_fx += src_fdx;
							src_x >>= FP;
							if (!eax || eax >= shdpen || ozbuf_ptr[ecx] < z8) continue;
							eax = pal_base[eax];
							ozbuf_ptr[ecx] = z8;
							dst_ptr [ecx] = eax;
						}
						while (++ecx);

						ecx = src_fby;   src_fby += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx >>= FP;      src_fx = src_fbx;
						src_x = src_fbx; src_fx += src_fdx;
						ecx <<= 4;       src_ptr = src_base;
						src_x >>= FP;    src_ptr += ecx;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 2: // all pens solid with alpha blending
					do {
						do {
							eax = src_ptr[src_x];
							src_x = src_fx;
							src_fx += src_fdx;
							src_x >>= FP;
							if (!eax || ozbuf_ptr[ecx] < z8) continue;
							ozbuf_ptr[ecx] = z8;

							BLEND32_MACRO
						}
						while (++ecx);

						ecx = src_fby;   src_fby += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx >>= FP;      src_fx = src_fbx;
						src_x = src_fbx; src_fx += src_fdx;
						ecx <<= 4;       src_ptr = src_base;
						src_x >>= FP;    src_ptr += ecx;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 3: // solid pens only with alpha blending
					do {
						do {
							eax = src_ptr[src_x];
							src_x = src_fx;
							src_fx += src_fdx;
							src_x >>= FP;
							if (!eax || eax >= shdpen || ozbuf_ptr[ecx] < z8) continue;
							ozbuf_ptr[ecx] = z8;

							BLEND32_MACRO
						}
						while (++ecx);

						ecx = src_fby;   src_fby += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx >>= FP;      src_fx = src_fbx;
						src_x = src_fbx; src_fx += src_fdx;
						ecx <<= 4;       src_ptr = src_base;
						src_x >>= FP;    src_ptr += ecx;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 4: // shadow pens only
					do {
						do {
							eax = src_ptr[src_x];
							src_x = src_fx;
							src_fx += src_fdx;
							src_x >>= FP;
							if (eax < shdpen || szbuf_ptr[ecx*2] < z8 || szbuf_ptr[ecx*2+1] <= p8) continue;
							eax = dst_ptr[ecx];
							szbuf_ptr[ecx*2] = z8;
							szbuf_ptr[ecx*2+1] = p8;
							eax = (eax>>9&0x7c00) | (eax>>6&0x03e0) | (eax>>3&0x001f);
							dst_ptr[ecx] = shd_base[eax];
						}
						while (++ecx);

						ecx = src_fby;   src_fby += src_fdy;
						szbuf_ptr += (GX_ZBUFW<<1);
						dst_ptr += GX_BMPPW;
						ecx >>= FP;      src_fx = src_fbx;
						src_x = src_fbx; src_fx += src_fdx;
						ecx <<= 4;       src_ptr = src_base;
						src_x >>= FP;    src_ptr += ecx;
						ecx = dst_w;
					}
					while (--dst_h);
					break;
			}	// switch (drawmode)
		}	// if (zcode < 0)
	}	// if (!nozoom)
	else
	{
		src_ptr = src_base + (src_fby<<4) + src_fbx;
		src_fdy = src_fdx * dst_w + src_pitch;
		ecx = dst_w;

		if (zcode < 0) // no shadow and z-buffering
		{
			do {
				do {
					eax = *src_ptr;
					src_ptr += src_fdx;
					if (!eax || eax >= shdpen) continue;
					dst_ptr[ecx] = pal_base[eax];
				}
				while (++ecx);

				src_ptr += src_fdy;
				dst_ptr += GX_BMPPW;
				ecx = dst_w;
			}
			while (--dst_h);
		}
		else
		{
			switch (drawmode)
			{
				case 0: // all pens solid
					do {
						do {
							eax = *src_ptr;
							src_ptr += src_fdx;
							if (!eax || ozbuf_ptr[ecx] < z8) continue;
							eax = pal_base[eax];
							ozbuf_ptr[ecx] = z8;
							dst_ptr[ecx] = eax;
						}
						while (++ecx);

						src_ptr += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 1:  // solid pens only
					do {
						do {
							eax = *src_ptr;
							src_ptr += src_fdx;
							if (!eax || eax >= shdpen || ozbuf_ptr[ecx] < z8) continue;
							eax = pal_base[eax];
							ozbuf_ptr[ecx] = z8;
							dst_ptr[ecx] = eax;
						}
						while (++ecx);

						src_ptr += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 2: // all pens solid with alpha blending
					do {
						do {
							eax = *src_ptr;
							src_ptr += src_fdx;
							if (!eax || ozbuf_ptr[ecx] < z8) continue;
							ozbuf_ptr[ecx] = z8;

							BLEND32_MACRO
						}
						while (++ecx);

						src_ptr += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 3: // solid pens only with alpha blending
					do {
						do {
							eax = *src_ptr;
							src_ptr += src_fdx;
							if (!eax || eax >= shdpen || ozbuf_ptr[ecx] < z8) continue;
							ozbuf_ptr[ecx] = z8;

							BLEND32_MACRO
						}
						while (++ecx);

						src_ptr += src_fdy;
						ozbuf_ptr += GX_ZBUFW;
						dst_ptr += GX_BMPPW;
						ecx = dst_w;
					}
					while (--dst_h);
					break;

				case 4: // shadow pens only
					do {
						do {
							eax = *src_ptr;
							src_ptr += src_fdx;
							if (eax < shdpen || szbuf_ptr[ecx*2] < z8 || szbuf_ptr[ecx*2+1] <= p8) continue;
							eax = dst_ptr[ecx];
							szbuf_ptr[ecx*2] = z8;
							szbuf_ptr[ecx*2+1] = p8;
							eax = (eax>>9&0x7c00) | (eax>>6&0x03e0) | (eax>>3&0x001f);
							dst_ptr[ecx] = shd_base[eax];
						}
						while (++ecx);

						src_ptr += src_fdy;
						szbuf_ptr += (GX_ZBUFW<<1);
						dst_ptr += GX_BMPPW;
						ecx = dst_w;
					}
					while (--dst_h);
					break;
			}
		}
	}
#undef FP
#undef FPONE
#undef FPHALF
#undef FPENT
}



/***************************************************************************/
/*                                                                         */
/*                 1st-Tier GX/MW Variables and Functions                  */
/*                                                                         */
/***************************************************************************/

// global system ports access
UINT8  konamigx_wrport1_0, konamigx_wrport1_1;
UINT16 konamigx_wrport2;

// frequently used registers
static int K053246_objset1;
static int K053247_vrcbk[4];
static int K053247_coreg, K053247_coregshift, K053247_opset;
static int opri, oinprion;
static int vcblk[6], ocblk;
static int vinmix, vmixon, osinmix, osmixon;


static void konamigx_precache_registers(void)
{
	// (see sprite color coding scheme on p.46 & 47)
	static int coregmasks[5] = {0xf,0xe,0xc,0x8,0x0};
	static int coregshifts[5]= {4,5,6,7,8};
	int i;

	K053246_objset1 = K053246_read_register(5);

	i = K053247_read_register(0x8/2);
	K053247_vrcbk[0] = (i & 0x000f) << 14;
	K053247_vrcbk[1] = (i & 0x0f00) << 6;
	i = K053247_read_register(0xa/2);
	K053247_vrcbk[2] = (i & 0x000f) << 14;
	K053247_vrcbk[3] = (i & 0x0f00) << 6;

	// COREG == OBJSET2+1C == bit8-11 of OPSET ??? (see p.50 last table, needs p.49 to confirm)
	K053247_opset = K053247_read_register(0xc/2);

	i = K053247_opset & 7; if (i > 4) i = 4;

	K053247_coreg = K053247_read_register(0xc/2)>>8 & 0xf;
	K053247_coreg =(K053247_coreg & coregmasks[i]) << 12;

	K053247_coregshift = coregshifts[i];

	opri     = K055555_read_register(K55_PRIINP_8);
	oinprion = K055555_read_register(K55_OINPRI_ON);
	vcblk[0] = K055555_read_register(K55_PALBASE_A);
	vcblk[1] = K055555_read_register(K55_PALBASE_B);
	vcblk[2] = K055555_read_register(K55_PALBASE_C);
	vcblk[3] = K055555_read_register(K55_PALBASE_D);
	vcblk[4] = K055555_read_register(K55_PALBASE_SUB1);
	vcblk[5] = K055555_read_register(K55_PALBASE_SUB2);
	ocblk    = K055555_read_register(K55_PALBASE_OBJ);
	vinmix   = K055555_read_register(K55_BLEND_ENABLES);
	vmixon   = K055555_read_register(K55_VINMIX_ON);
	osinmix  = K055555_read_register(K55_OSBLEND_ENABLES);
	osmixon  = K055555_read_register(K55_OSBLEND_ON);
}

INLINE int K053247GX_combine_c18(int attrib) // (see p.46)
{
	int c18;

	c18 = (attrib & 0xff)<<K053247_coregshift | K053247_coreg;

	if (konamigx_wrport2 & 4) c18 &= 0x3fff; else
	if (!(konamigx_wrport2 & 8)) c18 = (c18 & 0x3fff) | (attrib<<6 & 0xc000);

	return(c18);
}

INLINE int K055555GX_decode_objcolor(int c18) // (see p.59 7.2.2)
{
	int ocb, opon;

	opon  = oinprion<<8 | 0xff;
	ocb   = (ocblk & 7) << 10;
	c18  &= opon;
	ocb  &=~opon;

	return((ocb | c18) >> K053247_coregshift);
}

INLINE int K055555GX_decode_inpri(int c18) // (see p.59 7.2.2)
{
	int op = opri;

	c18 >>= 8;
	op   &= oinprion;
	c18  &=~oinprion;

	return(c18 | op);
}

void konamigx_type2_sprite_callback(int *code, int *color, int *priority)
{
	int num = *code;
	int c18 = *color;

	*code = K053247_vrcbk[num>>14] | (num & 0x3fff);
	c18 = K053247GX_combine_c18(c18);
	*color = K055555GX_decode_objcolor(c18);
	*priority = K055555GX_decode_inpri(c18);
}

void konamigx_dragoonj_sprite_callback(int *code, int *color, int *priority)
{
	int num, op, pri, c18;

	num = *code;
	*code = K053247_vrcbk[num>>14] | (num & 0x3fff);

	c18  = pri = *color;
	op   = opri;
	pri  = (pri & 0x200) ? 4 : pri>>4 & 0xf;
	op  &= oinprion;
	pri &=~oinprion;
	*priority = pri | op;

	c18 = K053247GX_combine_c18(c18);
	*color = K055555GX_decode_objcolor(c18);
}

void konamigx_salmndr2_sprite_callback(int *code, int *color, int *priority)
{
	int num, op, pri, c18;

	num = *code;
	*code = K053247_vrcbk[num>>14] | (num & 0x3fff);

	c18  = pri = *color;
	op   = opri;
	pri  = pri>>4 & 0x3f;
	op  &= oinprion;
	pri &=~oinprion;
	*priority = pri | op;

	c18 = K053247GX_combine_c18(c18);
	*color = K055555GX_decode_objcolor(c18);
}

void konamigx_le2_sprite_callback(int *code, int *color, int *priority)
{
	int num, op, pri;

	num = *code;
	*code = K053247_vrcbk[num>>14] | (num & 0x3fff);

	pri = *color;
	*color &= 0x1f;

	op   = opri;
	pri &= 0xf0;
	op  &= oinprion;
	pri &=~oinprion;
	*priority = pri | op;
}

int K055555GX_decode_vmixcolor(int layer, int *color) // (see p.62 7.2.6 and p.27 3.3)
{
	int vcb, shift, pal, vmx, von, pl45, emx;

	vcb    =  vcblk[layer]<<6;
	shift  =  layer<<1;
	pal    =  *color;
	vmx    =  vinmix>>shift & 3;
	von    =  vmixon>>shift & 3;
	emx    =  pl45 = pal>>4 & 3;
	pal   &=  0xf;
	pl45  &=  von;
	vmx   &=  von;
	pl45 <<=  4;
	emx   &= ~von;
	pal   |=  pl45;
	emx   |=  vmx;
	pal   |=  vcb;

	if (von == 3) emx = -1; // invalidate external mix code if all bits are from internal
	*color =  pal;

	return(emx);
}

int K055555GX_decode_osmixcolor(int layer, int *color) // (see p.63, p.49-50 and p.27 3.3)
{
	int scb, shift, pal, osmx, oson, pl45, emx;

	shift  =  layer<<1;
	pal    =  *color;
	osmx   =  osinmix>>shift & 3;
	oson   =  osmixon>>shift & 3;

	if (layer)
	{
		// layer 1-3 are external tile layers
		scb    =  vcblk[layer+3]<<6;
		emx    =  pl45 = pal>>4 & 3;
		pal   &=  0xf;
		pl45  &=  oson;
		osmx  &=  oson;
		pl45 <<=  4;
		emx   &= ~oson;
		pal   |=  pl45;
		emx   |=  osmx;
		pal   |=  scb;

		if (oson == 3) emx = -1; // invalidate external mix code if all bits are from internal
		*color =  pal;
	}
	else
	{
		// layer 0 is the sprite layer with different attributes decode; detail on p.49 (missing)
		emx   = 0; // K053247_read_register(??)>>? & 3;
		osmx &= oson;
		emx  &=~oson;
		emx  |= osmx;
	}

	return(emx);
}

static void gx_wipezbuf(int noshadow)
{
	UINT8  *zptr;
	int w, h;
	register int ecx;

	w = Machine->visible_area.max_x - Machine->visible_area.min_x + 1;
	h = Machine->visible_area.max_y - Machine->visible_area.min_y + 1;

	zptr = gx_objzbuf;
	ecx = h;
	do { memset(zptr, -1, w); zptr += GX_ZBUFW; } while (--ecx);

	if (!noshadow)
	{
		zptr = gx_shdzbuf;
		w <<= 1;
		ecx = h;
		do { memset(zptr, -1, w); zptr += (GX_ZBUFW<<1); } while (--ecx);
	}
}

/*
 * Sprite Format
 * ------------------
 *
 * Word | Bit(s)           | Use
 * -----+-fedcba9876543210-+----------------
 *   0  | x--------------- | active (show this sprite)
 *   0  | -x-------------- | maintain aspect ratio (when set, zoom y acts on both axis)
 *   0  | --x------------- | flip y
 *   0  | ---x------------ | flip x
 *   0  | ----xxxx-------- | sprite size (see below)
 *   0  | --------xxxxxxxx | zcode
 *   1  | xxxxxxxxxxxxxxxx | sprite code
 *   2  | ------xxxxxxxxxx | y position
 *   3  | ------xxxxxxxxxx | x position
 *   4  | xxxxxxxxxxxxxxxx | zoom y (0x40 = normal, <0x40 = enlarge, >0x40 = reduce)
 *   5  | xxxxxxxxxxxxxxxx | zoom x (0x40 = normal, <0x40 = enlarge, >0x40 = reduce)
 *   6  | x--------------- | mirror y (top half is drawn as mirror image of the bottom)
 *   6  | -x-------------- | mirror x (right half is drawn as mirror image of the left)
 *   6  | --xx------------ | reserved (sprites with these two bits set don't seem to be graphics data at all)
 *   6  | ----xx---------- | shadow code: 0=off, 0x400=preset1, 0x800=preset2, 0xc00=preset3
 *   6  | ------xx-------- | effect code: flicker, upper palette, full shadow...etc. (game dependent)
 *   6  | --------xxxxxxxx | "color", but depends on external connections (implies priority)
 *   7  | xxxxxxxxxxxxxxxx | game dependent
 *
 * shadow enables transparent shadows. Note that it applies to the last sprite pen ONLY.
 * The rest of the sprite remains normal.
 */
#define GX_MAX_SPRITES 512
#define GX_MAX_LAYERS  6
#define GX_MAX_OBJECTS (GX_MAX_SPRITES + GX_MAX_LAYERS)

static struct GX_OBJ { int order, offs, code, color; } *gx_objpool;
static UINT16 *gx_spriteram;
static int gx_objdma, gx_primode;

// mirrored K053247 and K054338 settings
static UINT16 *K053247_ram;
static gfx_element *K053247_gfx;
static void (*K053247_callback)(int *code,int *color,int *priority);
static int K053247_dx, K053247_dy;

static int *K054338_shdRGB;


void K053247GP_set_SpriteOffset(int offsx, int offsy)
{
	K053247_dx = offsx;
	K053247_dy = offsy;
}

int konamigx_mixer_init(int objdma)
{
	gx_objdma = 0;
	gx_primode = 0;

	gx_objzbuf = (UINT8 *)priority_bitmap->base;
	gx_shdzbuf = auto_malloc(GX_ZBUFSIZE);
	gx_objpool = auto_malloc(sizeof(struct GX_OBJ) * (GX_MAX_OBJECTS));

	K053247_export_config(&K053247_ram, &K053247_gfx, &K053247_callback, &K053247_dx, &K053247_dy);
	K054338_export_config(&K054338_shdRGB);

	if (objdma)
	{
		gx_spriteram = auto_malloc(0x1000);
		gx_objdma = 1;
	}
	else
		gx_spriteram = K053247_ram;

	palette_set_shadow_dRGB32(3,-80,-80,-80, 0);
	K054338_invert_alpha(1);

	return(0);
}

void konamigx_mixer_primode(int mode)
{
	gx_primode = mode;
}

void konamigx_objdma(void)
{
	if (gx_objdma && gx_spriteram && K053247_ram) memcpy(gx_spriteram, K053247_ram, 0x1000);
}

void konamigx_mixer(mame_bitmap *bitmap, const rectangle *cliprect,
					tilemap *sub1, int sub1flags,
					tilemap *sub2, int sub2flags,
					int mixerflags)
{
	static int xoffset[8] = { 0, 1, 4, 5, 16, 17, 20, 21 };
	static int yoffset[8] = { 0, 2, 8, 10, 32, 34, 40, 42 };
	static int parity = 0;

	int objbuf[GX_MAX_OBJECTS];
	int shadowon[3], shdpri[3], layerid[6], layerpri[6];

	struct GX_OBJ *objpool, *objptr;
	int wrapsize, xwraplim, ywraplim, cltc_shdpri, prflp, disp;
	int xa,ya,ox,oy,zw,zh,flipx,flipy,mirrorx,mirrory,zoomx,zoomy,scalex,scaley,nozoom;
	int screenwidth, flipscreenx, flipscreeny, offx, offy;
	int nobj, i, j, k, l, temp, temp1, temp2, temp3, temp4, count;
	int order, offs, code, color, zcode, pri = 0, spri, spri_min, shdprisel, shadow, alpha, drawmode;


	// abort if object database failed to initialize
	if (!(objpool = gx_objpool)) return;

	// clear screen with backcolor and update flicker pulse
	K054338_fill_backcolor(bitmap, konamigx_wrport1_0 & 0x20);
	parity ^= 1;

	// abort if video has been disabled
	if (!(disp = K055555_read_register(K55_INPUT_ENABLES))) return;
	cltc_shdpri = K054338_read_register(K338_REG_CONTROL);
	if (!(cltc_shdpri & K338_CTL_KILL)) return;

	// demote shadows by one layer when this bit is set??? (see p.73 8.6)
	cltc_shdpri &= K338_CTL_SHDPRI;

	// wipe z-buffer
	if (mixerflags & GXMIX_NOZBUF)
		mixerflags |= GXMIX_NOSHADOW;
	else
		gx_wipezbuf(mixerflags & GXMIX_NOSHADOW);

	// cache global parameters
	konamigx_precache_registers();

	// init OBJSET1 parameters (see p.47 6.2)
	flipscreenx = K053246_objset1 & 1;
	flipscreeny = K053246_objset1 & 2;

	// get "display window" offsets
	offx = (K053246_read_register(0)<<8 | K053246_read_register(1)) & 0x3ff;
	offy = (K053246_read_register(2)<<8 | K053246_read_register(3)) & 0x3ff;

	// init OBJSET2 and mixer parameters (see p.51 and chapter 7)
	layerid[0] = 0; layerid[1] = 1; layerid[2] = 2; layerid[3] = 3; layerid[4] = 4; layerid[5] = 5;

	if (K053247_opset & 0x40)
	{
		wrapsize = 512;
		xwraplim = 512 - 64;
		ywraplim = 512 - 128;
	}
	else
	{
		wrapsize  = 1024;
		xwraplim  = 1024 - 384;
		ywraplim  = 1024 - 512;
	}

	// invert layer priority when this flag is set (not used by any GX game?)
	prflp = K055555_read_register(K55_CONTROL) & K55_CTL_FLIPPRI;

	layerpri[0] = K055555_read_register(K55_PRIINP_0);
	layerpri[1] = K055555_read_register(K55_PRIINP_3);
	layerpri[3] = K055555_read_register(K55_PRIINP_7);
	layerpri[4] = K055555_read_register(K55_PRIINP_9);
	layerpri[5] = K055555_read_register(K55_PRIINP_10);

	if (gx_primode == -1)
	{
		// Lethal Enforcer hack (requires pixel color comparison)
		layerpri[2] = K055555_read_register(K55_PRIINP_3) + 0x20;
		shdprisel = 0x3f;
	}
	else
	{
		layerpri[2] = K055555_read_register(K55_PRIINP_6);
		shdprisel = K055555_read_register(K55_SHD_PRI_SEL);
	}

	// SHDPRISEL filters shadows by different priority comparison methods (UNIMPLEMENTED, see detail on p.66)
	if (!(shdprisel & 0x03)) shadowon[0] = 0;
	if (!(shdprisel & 0x0c)) shadowon[1] = 0;
	if (!(shdprisel & 0x30)) shadowon[2] = 0;

	shdpri[0]   = K055555_read_register(K55_SHAD1_PRI);
	shdpri[1]   = K055555_read_register(K55_SHAD2_PRI);
	shdpri[2]   = K055555_read_register(K55_SHAD3_PRI);

	spri_min = 0;
	shadowon[2] = shadowon[1] = shadowon[0] = 0;

	if (!(mixerflags & GXMIX_NOSHADOW))
	{
		// only enable shadows beyond a +/-7 RGB threshold
		for (j=0,i=0; i<3; j+=3,i++)
		{
			k = K054338_shdRGB[j  ]; if (k < -7 || k > 7) { shadowon[i] = 1; continue; }
			k = K054338_shdRGB[j+1]; if (k < -7 || k > 7) { shadowon[i] = 1; continue; }
			k = K054338_shdRGB[j+2]; if (k < -7 || k > 7) { shadowon[i] = 1; }
		}

		// SHDON specifies layers on which shadows can be projected (see detail on p.65 7.2.8)
		temp = K055555_read_register(K55_SHD_ON);
		for (i=0; i<4; i++) if (!(temp>>i & 1) && spri_min < layerpri[i]) spri_min = layerpri[i]; // HACK

		// update shadows status
		K054338_update_all_shadows();
	}

	// pre-sort layers
	for (j=0; j<5; j++)
	{
		temp1 = layerpri[j];
		for (i=j+1; i<6; i++)
		{
			temp2 = layerpri[i];
			if ((unsigned int)temp1 <= (unsigned int)temp2)
			{
				layerpri[i] = temp1; layerpri[j] = temp1 = temp2;
				temp2 = layerid[i]; layerid[i] = layerid[j]; layerid[j] = temp2;
			}
		}
	}

	// build object database and create indices
	objptr = objpool;
	nobj = 0;

	for (i=5; i>=0; i--)
	{
		code = layerid[i];
		switch (code)
		{
			/*
                Background layers are represented by negative offset values as follow:

                0+ : normal sprites
                -1 : tile layer A - D
                -2 : K053936 ROZ+ layer 1
                -3 : K053936 ROZ+ layer 2
                -4 : K053250 LVC layer 1
                -5 : K053250 LVC layer 2
            */
			case 4 :
				offs = -128;
				if (sub1flags & 0xf) { if (sub1flags & GXSUB_K053250) offs = -4; else if (sub1) offs = -2; }
			break;
			case 5 :
				offs = -128;
				if (sub2flags & 0xf) { if (sub2flags & GXSUB_K053250) offs = -5; else if (sub2) offs = -3; }
			break;
			default: offs = -1;
		}

		if (offs != -128)
		{
			objptr->order = layerpri[i]<<24;
			objptr->code  = code;
			objptr->offs = offs;
			objptr++;

			objbuf[nobj] = nobj;
			nobj++;
		}
	}

	i = j = 0xff;

	for (offs=0; offs<0x800; offs+=8)
	{
		if (!(gx_spriteram[offs] & 0x8000)) continue;

		zcode = gx_spriteram[offs] & 0xff;

		// invert z-order when opset_pri is set (see p.51 OPSET PRI)
		if (K053247_opset & 0x10) zcode = 0xff - zcode;

		code  = gx_spriteram[offs+1];
		color = k = gx_spriteram[offs+6];
		l     = gx_spriteram[offs+7];

		(*K053247_callback)(&code, &color, &pri);

		/*
            shadow = shadow code
            spri   = shadow priority
            temp1  = add solid object
            temp2  = solid pens draw mode
            temp3  = add shadow object
            temp4  = shadow pens draw mode
        */
		temp4 = temp3 = temp2 = temp1 = spri = shadow = 0;

		if (color & K055555_FULLSHADOW)
		{
			shadow = 3; // use default intensity and color
			spri = pri; // retain host priority
			temp3 = 1; // add shadow
			temp4 = 5; // draw full shadow
		}
		else
		{
			if ((shadow = k>>10 & 3)) // object has shadow?
			{
				if (shadow != 1 || K053246_objset1 & 0x20)
				{
					shadow--;
					temp1 = 1; // add solid
					temp2 = 1; // draw partial solid
					if (shadowon[shadow])
					{
						temp3 = 1; // add shadow
						temp4 = 4; // draw partial shadow
					}
				}
				else
				{
					// drop the entire sprite to shadow if its shadow code is 1 and SD0EN is off (see p.48)
					shadow = 0;
					if (!shadowon[0]) continue;
					temp3 = 1; // add shadow
					temp4 = 5; // draw full shadow
				}
			}
			else
			{
				temp1 = 1; // add solid
				temp2 = 0; // draw full solid
			}

			if (temp1)
			{
				// tag sprite for alpha blending
				if (color>>K055555_MIXSHIFT & 3) temp2 |= 2;
			}

			if (temp3)
			{
				// determine shadow priority
				spri = (K053247_opset & 0x20) ? pri : shdpri[shadow]; // (see p.51 OPSET SDSEL)
			}
		}

		switch (gx_primode & 0xf)
		{
			// Dadandarn zcode suppression
			case  1:
				zcode = 0;
			break;

			// Daisukiss bad shadow filter
			case  4:
				if (k & 0x3000 || k == 0x0800) continue;

			// Tokkae shadow masking (INACCURATE)
			case  5:
				if (spri < spri_min) spri = spri_min;
			break;
		}

		/*
            default sort order:
            fedcba9876543210fedcba9876543210
            xxxxxxxx------------------------ (priority)
            --------xxxxxxxx---------------- (zcode)
            ----------------xxxxxxxx-------- (offset)
            ------------------------xxxx---- (shadow mode)
            ----------------------------xxxx (shadow code)
        */
		if (temp1)
		{
			// add objects with solid or alpha pens
			order = pri<<24 | zcode<<16 | offs<<(8-3) | temp2<<4;
			objptr->order = order;
			objptr->offs  = offs;
			objptr->code  = code;
			objptr->color = color;
			objptr++;

			objbuf[nobj] = nobj;
			nobj++;
		}

		if (temp3 && !(color & K055555_SKIPSHADOW) && !(mixerflags & GXMIX_NOSHADOW))
		{
			// add objects with shadows if enabled
			order = spri<<24 | zcode<<16 | offs<<(8-3) | temp4<<4 | shadow;
			objptr->order = order;
			objptr->offs  = offs;
			objptr->code  = code;
			objptr->color = color;
			objptr++;

			objbuf[nobj] = nobj;
			nobj++;
		}
	}

	// sort objects in decending order (SLOW)
	k = nobj;
	l = nobj - 1;

	for (j=0; j<l; j++)
	{
		temp1 = objbuf[j];
		temp2 = objpool[temp1].order;
		for (i=j+1; i<k; i++)
		{
			temp3 = objbuf[i];
			temp4 = objpool[temp3].order;
			if ((unsigned int)temp2 <= (unsigned int)temp4) { temp2 = temp4; objbuf[i] = temp1; objbuf[j] = temp1 = temp3; }
		}
	}

	// traverse draw list
	screenwidth = Machine->drv->screen_width;

	for (count=0; count<nobj; count++)
	{
		objptr = objpool + objbuf[count];
		order  = objptr->order;
		offs   = objptr->offs;
		code   = objptr->code;
		color  = objptr->color;

		if (offs >= 0)
		{
			if (!(disp & K55_INP_OBJ)) continue;
		}
		else
		{
			i = code<<1;
			j = mixerflags>>i & 3;
			k = 0;

			switch (offs)
			{
				case -1:
				if (disp & (1<<code))
				{
					if (j == GXMIX_BLEND_NONE)  { temp1 = 0xff; temp2 = temp3 = 0; } else
					if (j == GXMIX_BLEND_FORCE) { temp1 = 0x00; temp2 = mixerflags>>(i+16); temp3 = 3; }
					else
					{
						temp1 = vinmix;
						temp2 = vinmix>>i & 3;
						temp3 = vmixon>>i & 3;
					}

					/* blend layer only when:
                        1) vinmix != 0xff
                        2) its internal mix code is set
                        3) all mix code bits are internal(overriden until tile blending has been implemented)
                        4) 0 > alpha < 255;
                    */
					if (temp1!=0xff && temp2 /*&& temp3==3*/)
					{
						temp4 = K054338_set_alpha_level(temp2);

						if (temp4 <= 0) continue;
						if (temp4 < 255) k = TILEMAP_ALPHA;
					}

					if (mixerflags & 1<<(code+12)) k |= TILE_LINE_DISABLED;

					K056832_tilemap_draw(bitmap, cliprect, code, k, 0);
				}
				continue;
				case -2:
				case -4:
				if (disp & K55_INP_SUB1)
				{
					if (j == GXMIX_BLEND_NONE)  { temp1 = 0xff; temp2 = temp3 = 0; } else
					if (j == GXMIX_BLEND_FORCE) { temp1 = 0x00; temp2 = mixerflags>>24; temp3 = 3; }
					else
					{
						temp1 = osinmix;
						temp2 = osinmix>>2 & 3;
                        temp3 = osmixon>>2 & 3;
					}

					if (temp1!=0xff && temp2 /*&& temp3==3*/)
					{
						temp4 = K054338_set_alpha_level(temp2);

						if (temp4 <= 0) continue;
						if (temp4 < 255) k = (j == GXMIX_BLEND_FAST) ? ~parity : 1;
					}

					l = sub1flags & 0xf;

					if (offs == -2)
						K053936GP_0_zoom_draw(bitmap, cliprect, sub1, l, k);
					else
						K053250_draw(bitmap, cliprect, 0, vcblk[4]<<l, 0, 0);
				}
				continue;
				case -3:
				case -5:
				if (disp & K55_INP_SUB2)
				{
					if (j == GXMIX_BLEND_NONE)  { temp1 = 0xff; temp2 = temp3 = 0; } else
					if (j == GXMIX_BLEND_FORCE) { temp1 = 0x00; temp2 = mixerflags>>26; temp3 = 3; }
					else
					{
						temp1 = osinmix;
						temp2 = osinmix>>4 & 3;
                        temp3 = osmixon>>4 & 3;
					}

					if (temp1!=0xff && temp2 /*&& temp3==3*/)
					{
						temp4 = K054338_set_alpha_level(temp2);

						if (temp4 <= 0) continue;
						if (temp4 < 255) k = (j == GXMIX_BLEND_FAST) ? ~parity : 1;
					}

					l = sub2flags & 0xf;

					if (offs == -3)
						K053936GP_1_zoom_draw(bitmap, cliprect, sub2, l, k);
					else
						K053250_draw(bitmap, cliprect, 1, vcblk[5]<<l, 0, 0);
				}
				continue;
			}
			continue;
		}

		drawmode = order>>4 & 0xf;

		alpha = 255;
		if (drawmode & 2)
		{
			if ((alpha = color>>K055555_MIXSHIFT & 3)) alpha = K054338_set_alpha_level(alpha);
			if (alpha <= 0) continue;
		}
		color &= K055555_COLORMASK;

		if (drawmode >= 4) palette_set_shadow_mode(order & 0x0f);

		if (!(mixerflags & GXMIX_NOZBUF))
		{
			zcode = order>>16 & 0xff;
			pri = order>>24 & 0xff;
		}
		else
			zcode = -1; // negative zcode values turn off z-buffering

		xa = ya = 0;
		if (code & 0x01) xa += 1;
		if (code & 0x02) ya += 1;
		if (code & 0x04) xa += 2;
		if (code & 0x08) ya += 2;
		if (code & 0x10) xa += 4;
		if (code & 0x20) ya += 4;
		code &= ~0x3f;

		temp4 = gx_spriteram[offs];

		// mask off the upper 6 bits of coordinate and zoom registers
		oy = gx_spriteram[offs+2] & 0x3ff;
		ox = gx_spriteram[offs+3] & 0x3ff;

		scaley = zoomy = gx_spriteram[offs+4] & 0x3ff;
		if (zoomy) zoomy = (0x400000+(zoomy>>1)) / zoomy;
		else zoomy = 0x800000;
		if (!(temp4 & 0x4000))
		{
			scalex = zoomx = gx_spriteram[offs+5] & 0x3ff;
			if (zoomx) zoomx = (0x400000+(zoomx>>1)) / zoomx;
			else zoomx = 0x800000;
		}
		else { zoomx = zoomy; scalex = scaley; }

		nozoom = (scalex == 0x40 && scaley == 0x40);

		flipx = temp4 & 0x1000;
		flipy = temp4 & 0x2000;

		temp = gx_spriteram[offs+6];
		mirrorx = temp & 0x4000;
		if (mirrorx) flipx = 0; // only applies to x mirror, proven
		mirrory = temp & 0x8000;

		// for Escape Kids (GX975)
		if ( K053246_objset1 & 8 ) // Check only "Bit #3 is '1'?"
		{
			zoomx = zoomx>>1; // Fix sprite width to HALF size
			ox = (ox>>1) + 1; // Fix sprite draw position

			if (flipscreenx) ox += screenwidth;
		}

		if (flipscreenx) { ox = -ox; if (!mirrorx) flipx = !flipx; }
		if (flipscreeny) { oy = -oy; if (!mirrory) flipy = !flipy; }

		// apply wrapping and global offsets
		temp = wrapsize-1;
		ox = ( ox - offx) & temp;
		oy = (-oy - offy) & temp;
		if (ox >= xwraplim) ox -= wrapsize;
		if (oy >= ywraplim) oy -= wrapsize;
		ox += K053247_dx;
		oy += K053247_dy;


		temp = temp4>>8 & 0x0f;
		k = 1 << (temp & 3);
		l = 1 << (temp>>2 & 3);

		ox -= (zoomx * k) >> 13;
		oy -= (zoomy * l) >> 13;

		// substitutes: i=x, j=y, k=w, l=h, temp=code, temp1=fx, temp2=fy, temp3=sx, temp4=sy;
		for (j=0; j<l; j++)
		{
			temp4 = oy + ((zoomy * j + (1<<11)) >> 12);
			zh = (oy + ((zoomy * (j+1) + (1<<11)) >> 12)) - temp4;

			for (i=0; i<k; i++)
			{
				temp3 = ox + ((zoomx * i + (1<<11)) >> 12);
				zw = (ox + ((zoomx * (i+1) + (1<<11)) >> 12)) - temp3;
				temp = code;

				if (mirrorx)
				{
					if ((!flipx)^((i<<1)<k))
					{
						/* mirror left/right */
						temp += xoffset[(k-1-i+xa)&7];
						temp1 = 1;
					}
					else
					{
						temp += xoffset[(i+xa)&7];
						temp1 = 0;
					}
				}
				else
				{
					if (flipx) temp += xoffset[(k-1-i+xa)&7];
					else temp += xoffset[(i+xa)&7];
					temp1 = flipx;
				}

				if (mirrory)
				{
					if ((!flipy)^((j<<1)>=l))
					{
						/* mirror top/bottom */
						temp += yoffset[(l-1-j+ya)&7];
						temp2 = 1;
					}
					else
					{
						temp += yoffset[(j+ya)&7];
						temp2 = 0;
					}
				}
				else
				{
					if (flipy) temp += yoffset[(l-1-j+ya)&7];
					else temp += yoffset[(j+ya)&7];
					temp2 = flipy;
				}

				if (nozoom) { scaley = scalex = 0x10000; } else { scalex = zw << 12; scaley = zh << 12; };

				zdrawgfxzoom32GP(bitmap, K053247_gfx, cliprect,
						temp,
						color,
						temp1,temp2,
						temp3,temp4,
						scalex, scaley, alpha, drawmode, zcode, pri);
			}
		}
	}
}



/***************************************************************************/
/*                                                                         */
/*                           GX/MW Protections                             */
/*                                                                         */
/***************************************************************************/

// K055550/K053990 protection chips, perform simple memset() and other game logic operations
static UINT16 prot_data[0x20];

READ16_HANDLER( K055550_word_r )
{
	return(prot_data[offset]);
}

WRITE16_HANDLER( K055550_word_w )
{
	UINT32 adr, bsize, count, i, lim;
	int src, tgt, srcend, tgtend, skip, cx1, sx1, wx1, cy1, sy1, wy1, cz1, sz1, wz1, c2, s2, w2;
	int dx, dy, angle;

	COMBINE_DATA(prot_data+offset);

	if (offset == 0 && ACCESSING_MSB)
	{
		data >>= 8;
		switch (data)
		{
			case 0x97: // memset() (Dadandarn at 0x639dc)
			case 0x9f: // memset() (Violent Storm at 0x989c)
				adr   = (prot_data[7] << 16) | prot_data[8];
				bsize = (prot_data[10] << 16) | prot_data[11];
				count = (prot_data[0] & 0xff) + 1;

				lim = adr+bsize*count;
				for(i=adr; i<lim; i+=2)
					program_write_word(i, prot_data[0x1a/2]);
			break;

			// WARNING: The following cases are speculation based with questionable accuracy!(AAT)

			case 0x87: // unknown memory write (Violent Storm at 0x00b6ea)
				// Violent Storm writes the following data to the 55550 in mode 0x87.
				// All values are hardcoded and the write happens each frame during
				// gameplay. It refers to a 32x8-word list at 0x210e00 and seems to
				// be tied with another 13x128-byte table at 0x205080.
				// Both tables appear "check-only" and have little effect on gameplay.
				count =(prot_data[0] & 0xff) + 1;          // unknown ( byte 0x00)
				i     = prot_data[1];                      // unknown ( byte 0x1f)
				adr   = prot_data[7]<<16 | prot_data[8];   // address (dword 0x210e00)
				lim   = prot_data[9];                      // unknown ( word 0x0010)
				src   = prot_data[10]<<16 | prot_data[11]; // unknown (dword zero)
				tgt   = prot_data[12]<<16 | prot_data[13]; // unknown (dword zero)
			break;

			case 0xa0: // update collision detection table (Violent Storm at 0x018b42)
				count = prot_data[0] & 0xff;             // number of objects - 1
				skip  = prot_data[1]>>(8-1);             // words to skip in each entry to reach the "hit list"
				adr   = prot_data[2]<<16 | prot_data[3]; // where the table is located
				bsize = prot_data[5]<<16 | prot_data[6]; // object entry size in bytes

				srcend = adr + bsize * count;
				tgtend = srcend + bsize;

				// let's hope GCC will inline the mem24bew calls
				for (src=adr; src<srcend; src+=bsize)
				{
					cx1 = (short)program_read_word(src);
					sx1 = (short)program_read_word(src + 2);
					wx1 = (short)program_read_word(src + 4);

					cy1 = (short)program_read_word(src + 6);
					sy1 = (short)program_read_word(src + 8);
					wy1 = (short)program_read_word(src +10);

					cz1 = (short)program_read_word(src +12);
					sz1 = (short)program_read_word(src +14);
					wz1 = (short)program_read_word(src +16);

					count = i = src + skip;
					tgt = src + bsize;

					for (; count<tgt; count++) program_write_byte(count, 0);

					for (; tgt<tgtend; i++, tgt+=bsize)
					{
						c2 = (short)program_read_word(tgt);
						s2 = (short)program_read_word(tgt + 2);
						w2 = (short)program_read_word(tgt + 4);
						if (abs((cx1+sx1)-(c2+s2))>=wx1+w2) continue; // X rejection

						c2 = (short)program_read_word(tgt + 6);
						s2 = (short)program_read_word(tgt + 8);
						w2 = (short)program_read_word(tgt +10);
						if (abs((cy1+sy1)-(c2+s2))>=wy1+w2) continue; // Y rejection

						c2 = (short)program_read_word(tgt +12);
						s2 = (short)program_read_word(tgt +14);
						w2 = (short)program_read_word(tgt +16);
						if (abs((cz1+sz1)-(c2+s2))>=wz1+w2) continue; // Z rejection

						program_write_byte(i, 0x80); // collision confirmed
					}
				}
			break;

			case 0xc0: // calculate object "homes-in" vector (Violent Storm at 0x03da9e)
				dx = (short)prot_data[0xc];
				dy = (short)prot_data[0xd];

				// it's not necessary to use lookup tables because Violent Storm
				// only calls the service once per enemy per frame.
				if (dx)
				{
					if (dy)
					{
						angle = (atan((double)dy / dx) * 128.0) / M_PI;
						if (dx < 0) angle += 128;
						i = (angle - 0x40) & 0xff;
					}
					else
						i = (dx > 0) ? 0xc0 : 0x40;
				}
				else
					if (dy > 0) i = 0;
				else
					if (dy < 0) i = 0x80;
				else
					i = mame_rand() & 0xff; // vector direction indeterminate

				prot_data[0x10] = i;
			break;

			default:
//              logerror("%06x: unknown K055550 command %02x\n", activecpu_get_pc(), data);
			break;
		}
	}
}

WRITE16_HANDLER( K053990_martchmp_word_w )
{
	int src_addr, src_count, src_skip;
	int dst_addr, dst_count, dst_skip;
	int mod_addr, mod_count, mod_skip, mod_offs;
	int mode, i, element_size = 1;
	UINT16 mod_val, mod_data;

	COMBINE_DATA(prot_data+offset);

	if (offset == 0x0c && ACCESSING_MSB)
	{
		mode  = (prot_data[0x0d]<<8 & 0xff00) | (prot_data[0x0f] & 0xff);

		switch (mode)
		{
			case 0xffff: // word copy
				element_size = 2;
			case 0xff00: // byte copy
				src_addr  = prot_data[0x0];
				src_addr |= prot_data[0x1]<<16 & 0xff0000;
				dst_addr  = prot_data[0x2];
				dst_addr |= prot_data[0x3]<<16 & 0xff0000;
				src_count = prot_data[0x8]>>8;
				dst_count = prot_data[0x9]>>8;
				src_skip  = prot_data[0xa] & 0xff;
				dst_skip  = prot_data[0xb] & 0xff;

				if ((prot_data[0x8] & 0xff) == 2) src_count <<= 1;
				src_skip += element_size;
				dst_skip += element_size;

				if (element_size == 1)
				for (i=src_count; i; i--)
				{
					program_write_byte(dst_addr, program_read_byte(src_addr));
					src_addr += src_skip;
					dst_addr += dst_skip;
				}
				else for (i=src_count; i; i--)
				{
					program_write_word(dst_addr, program_read_word(src_addr));
					src_addr += src_skip;
					dst_addr += dst_skip;
				}
			break;

			case 0x00ff: // sprite list modifier
				src_addr  = prot_data[0x0];
				src_addr |= prot_data[0x1]<<16 & 0xff0000;
				src_skip  = prot_data[0x1]>>8;
				dst_addr  = prot_data[0x2];
				dst_addr |= prot_data[0x3]<<16 & 0xff0000;
				dst_skip  = prot_data[0x3]>>8;
				mod_addr  = prot_data[0x4];
				mod_addr |= prot_data[0x5]<<16 & 0xff0000;
				mod_skip  = prot_data[0x5]>>8;
				mod_offs  = prot_data[0x8] & 0xff;
				mod_offs<<= 1;
				mod_count = 0x100;

				src_addr += mod_offs;
				dst_addr += mod_offs;

				for (i=mod_count; i; i--)
				{
					mod_val  = program_read_word(mod_addr);
					mod_addr += mod_skip;

					mod_data = program_read_word(src_addr);
					src_addr += src_skip;

					mod_data += mod_val;

					program_write_word(dst_addr, mod_data);
					dst_addr += dst_skip;
				}
			break;

			default:
			break;
		}
	}
}

void konamigx_esc_alert(UINT32 *srcbase, int srcoffs, int count, int mode) // (WARNING: assumed big endianess)
{

// hand-filled but should be close
static UINT8 ztable[7][8] =
{
	{5,4,3,2,1,7,6,0},
	{4,3,2,1,0,7,6,5},
	{4,3,2,1,0,7,6,5},
	{3,2,1,0,5,7,4,6},
	{6,5,1,4,3,7,0,2},
	{5,4,3,2,1,7,6,0},
	{5,4,3,2,1,7,6,0}
};

static UINT8 ptable[7][8] =
{
	{0x00,0x00,0x00,0x10,0x20,0x00,0x00,0x30},
	{0x20,0x20,0x20,0x20,0x20,0x00,0x20,0x20},
	{0x00,0x00,0x00,0x20,0x20,0x00,0x00,0x00},
	{0x10,0x10,0x10,0x20,0x00,0x00,0x10,0x00},
	{0x00,0x00,0x20,0x00,0x10,0x00,0x20,0x20},
	{0x00,0x00,0x00,0x10,0x10,0x00,0x00,0x10},
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10}
};

	INT32 data1, data2, i, j, vpos, hpos, voffs, hoffs, vcorr, hcorr, vmask, hmask, magicid;
	UINT32 *src, *srcend, *obj, *objend;
	UINT16 *dst;
	UINT8  *zcode, *pcode;

	if (!count || !srcbase) return;

	if (mode == 0)
	{
		src = srcbase + srcoffs;
		dst = K053247_ram;
		data1 = count<<2;
		data2 = count<<3;
		src += data1; dst += data2; i = -data1; j = -data2;
		do
		{
			data1 = src[i];
			data2 = src[i+1];
			i += 2;
			dst[j+1] = data1;
			dst[j+3] = data2;
			data1  >>= 16;
			data2  >>= 16;
			dst[j]   = data1;
			dst[j+2] = data2;
		}
		while (j += 4);
	}
	else
	{

#define EXTRACT_ODD         \
if((data1=obj[0])&0x8000)   \
{                           \
  i      = data1 & 7;       \
  data1 &= 0xff00;          \
  dst[0] = data1 | zcode[i];\
  data1  = obj[1];          \
  dst[1] = data1>>16;       \
  vpos   = data1 & 0xffff;  \
  data1  = obj[2];          \
  vpos  += voffs;           \
  dst[4] = data1;           \
  vpos  &= vmask;           \
  hpos   = data1>>16;       \
  data1  = obj[3];          \
  hpos  += hoffs;           \
  dst[2] = vpos;            \
  dst[3] = hpos;            \
  dst[5] = data1>>16;       \
  i      = pcode[i];        \
  dst[6] = data1| i<<4;     \
  dst += 8;                 \
  if (!(--j)) return;       \
}

#define EXTRACT_EVEN         \
if((data1=obj[0])&0x80000000)\
{                            \
  dst[1] = data1;            \
  data1>>= 16;               \
  i      = data1 & 7;        \
  data1 &= 0xff00;           \
  dst[0] = data1 | zcode[i]; \
  data1  = obj[1];           \
  hpos   = data1 & 0xffff;   \
  vpos   = data1>>16;        \
  hpos  += hoffs;            \
  vpos  += voffs;            \
  data1  = obj[2];           \
  vpos  &= vmask;            \
  dst[3] = hpos;             \
  dst[2] = vpos;             \
  dst[5] = data1;            \
  dst[4] = data1>>16;        \
  data1  = obj[3]>>16;       \
  i      = pcode[i];         \
  dst[6] = data1 | i<<4;     \
  dst += 8;                  \
  if (!(--j)) return;        \
}

		// These suspecious looking flags might tell the ESC chip what zcode/priority combos to use.
		// At the beginning of each sprite chunk there're at least three pointers to the main ROM but
		// I can't make out anything meaningful.
		magicid = srcbase[0x71f0/4];

		hmask = vmask = 0x3ff;
		if (magicid != 0x11010111)
		{
			switch (magicid)
			{
				case 0x10010801: i = 6; break;
				case 0x11010010: i = 5; vmask = 0x1ff; break;
				case 0x01111018: i = 4; break;
				case 0x10010011: i = 3;
					if ((srcbase[0x1c75]&0xff)==32) K055555_write_reg(K55_BLEND_ENABLES,36); // (TEMPORARY)
				break;
				case 0x11010811: i = 2; break;
				case 0x10000010: i = 1; break;
				default:         i = 0;
			}
			vcorr = srcbase[0x26a0/4] & 0xffff;
			hcorr = srcbase[0x26a4/4] >> 16;
			hcorr -= 10;
		}
		else
			hcorr = vcorr = i = 0;

		zcode = ztable[i];
		pcode = ptable[i];

		dst = K053247_ram;
		j = 256;

		// decode Vic-Viper
		if (srcbase[0x049c/4] & 0xffff0000)
		{
			hoffs = srcbase[0x0502/4] & 0xffff;
			voffs = srcbase[0x0506/4] & 0xffff;
			hoffs -= hcorr;
			voffs -= vcorr;
			obj = &srcbase[0x049e/4];
			EXTRACT_ODD
			obj = &srcbase[0x04ae/4];
			EXTRACT_ODD
			obj = &srcbase[0x04be/4];
			EXTRACT_ODD
		}

		// decode Lord British (the designer must be a Richard Garriot fan too:)
		if (srcbase[0x0848/4] & 0x0000ffff)
		{
			hoffs = srcbase[0x08b0/4]>>16;
			voffs = srcbase[0x08b4/4]>>16;
			hoffs -= hcorr;
			voffs -= vcorr;
			obj = &srcbase[0x084c/4];
			EXTRACT_EVEN
			obj = &srcbase[0x085c/4];
			EXTRACT_EVEN
			obj = &srcbase[0x086c/4];
			EXTRACT_EVEN
		}

		// decode common sprites
		src = srcbase + srcoffs;
		srcend = src + count * 0x30;
		do
		{
			if (!src[0] || !(i = src[7] & 0xf)) continue; // reject retired or zero-element groups
			i <<= 2;
			hoffs = src[5]>>16;
			voffs = src[6]>>16;
			hoffs -= hcorr;
			voffs -= vcorr;
			obj = src + 8;
			objend = obj + i;

			do
			{
				EXTRACT_EVEN
			}
			while ((obj+=4)<objend);
		}
		while ((src+=0x30)<srcend);

		// clear residual data
		if (j) do { *dst = 0; dst += 8; } while (--j);
	}

#undef EXTRACT_ODD
#undef EXTRACT_EVEN
}
