/***************************************************************************

	cdrom.c

	NEOGEO CD CD-ROM interface

***************************************************************************/

#include "ncdz.h"


/***************************************************************************
	Private structure
 **************************************************************************/

static struct filelist_t
{
	char name[16];
	u32  offset;
	int  bank;
	int  type;
	u32  length;
	u32  sectors;
} filelist[32];


/***************************************************************************
	Private variables
 **************************************************************************/

static u8  *cdrom_cache;
static u32 total_sectors;
static u32 loaded_sectors;
static int total_files;
static int internal_cache;


/***************************************************************************
	Global variables
 **************************************************************************/

int neogeo_loadscreen;
int neogeo_loadfinished;


/***************************************************************************
	Function prototypes
 **************************************************************************/

static int load_file(int fileno);
static void upload_file(int fileno, u32 offset, u32 length);
static int get_filetype(const char *ext);

static void init_loading_progress(void);
static void update_loading_progress(void);
static void loading_screen_start(void);
static void loading_screen_stop(void);
static int check_screen_data(u32 offset, int type);
static void loading_upload_fix(void);
static void loading_upload_palette(void);

static int hextodec(char c);


/*------------------------------------------------------
	CD-ROM initialize
 -----------------------------------------------------*/

void cdrom_init(void)
{
	internal_cache = 1;
	cdrom_cache = neogeo_prg_memory + 0x111204;
	neogeo_loadfinished = 0;
}


/*------------------------------------------------------
	CD-ROM shutdown
 -----------------------------------------------------*/

void cdrom_shutdown(void)
{
}


/*------------------------------------------------------
	Process IPL.TXT
 -----------------------------------------------------*/

int cdrom_process_ipl(void)
{
	struct filelist_t *file = &filelist[0];
    int i, j, f;
    u32 length;
    char linebuf[32], *buf, *p, *ext;
	char region_chr[3] = { 'J','U','E' };

	zip_open(game_dir);

	video_clear_screen();
	neogeo_loadfinished = 0;

	if (neogeo_loadscreen)
	{
		char fname[16];

		cdrom_cache = neogeo_prg_memory + 0x111204;

		for (i = neogeo_region & 3; i >= 0; i--)
		{
			sprintf(fname, "LOGO_%c.PRG", region_chr[i]);

			if ((f = zopen(fname)) != -1)
			{
				u8 *mem = neogeo_prg_memory + 0x120000;

				length = zread(f, mem, 0x10000);
				zclose(f);

				swab(mem, mem, length);
			}
		}
		internal_cache = 1;
	}
	else
	{
		internal_cache = 0;
		show_loading_image();
	}

	memset(cdrom_cache, 0, 0x800);
	length = zlength("IPL.TXT");

    if ((f = zopen("IPL.TXT")) == -1)
    {
		ErrorMsg("ERROR: Could not open IPL.TXT!");
		zip_close();
		return 0;
	}

	zread(f, cdrom_cache, length);
	zclose(f);

	total_sectors = 0;
	loaded_sectors = 0;
	total_files = 0;

	p = buf = (char *)cdrom_cache;

	while ((int)p - (int)cdrom_cache < length)
	{
		memset(linebuf, 0, 32);

		p = strchr(buf, 0x0a);
		if (p == NULL) break;

		*p++ = 0;

		strcpy(linebuf, buf);

		buf = p;

		if (strlen(linebuf) < 3) break;

		file->bank   = 0;
		file->offset = 0;

		i = 0;
		j = 0;
		while (linebuf[i] != ',')
		{
			file->name[j++] = toupper(linebuf[i++]);
		}
		file->name[j] = '\0';

		ext = &file->name[j - 3];
		if (ext[0] == 'O') strcpy(ext, "SPR");
		file->type = get_filetype(ext);

		i++;
		while (linebuf[i] != ',')
		{
			file->bank *= 10;
			file->bank += linebuf[i++] - '0';
		}
		file->bank &= 0x03;

		i++;
		while (linebuf[i] != 0x0d)
		{
			file->offset *= 16;
			file->offset += hextodec(linebuf[i++]);
		}

		file->length = zlength(file->name);
		file->sectors = (file->length + 0x7ff) / 0x800;

		total_sectors += file->sectors;
		total_files++;

		file++;
	}

	m68k_write_memory_8(0x108000 + 0x7ddc, 0x01);

	init_loading_progress();
	loading_screen_start();

	for (i = 0; i < total_files; i++)
	{
		if (bLoop != LOOP_EXEC)
			return 1;
		load_file(i);

//		update_inputport();
	}

	loading_screen_stop();

	m68k_write_memory_8(0x108000 + 0x7ddc, 0x00);
	m68k_write_memory_32(0x108000 + 0x7690, 0);

	video_clear_screen();

	if (!neogeo_loadscreen) neogeo_loadfinished = 1;

	init_autoframeskip();
	blit_fix_resetcache();

	zip_close();

	return 1;
}


/*------------------------------------------------------
	load files
 -----------------------------------------------------*/

void cdrom_load_files(void)
{
	struct filelist_t *file = &filelist[0];
	u32 offset, src, dst, data, save1, save2;
	int i;

	neogeo_loadfinished = 0;

    src = m68000_get_reg(M68K_A0);

    if (m68k_read_memory_8(src) == 0)
		return;

	zip_open(game_dir);

	// setup CD-ROM cache (speedup hack)
	internal_cache = 0;

	if (with_image())
	{
		if (neogeo_loadscreen)
			internal_cache = 1;
		else
			show_loading_image();
	}

	save1 = m68k_read_memory_8(0x108000 + 0x7d80);
	save2 = m68k_read_memory_8(0x108000 + 0x7dc2);

    cdda_stop();
	neogeo_sound_mute(1);

	offset = 0x115a06;
	for (i = 0; i < 0x20; i++)
	{
		memset(neogeo_prg_memory + offset, 0, 0x20);
		m68k_write_memory_16(offset + 0x1c, 0xffff);
		offset += 0x20;
	}

	/*
		Following code is trace BIOS function.
		See BIOS $c0dca2-$c0dcfb
	*/

	offset = 0x115a06;

	do
	{
		dst = offset;

		// get filename
		while ((data = m68k_read_memory_8(src++)) != 0)
		{
			// If file name is lower case, be changed to upper case by BIOS.
			if (data >= 'a') data -= 0x20;

			m68k_write_memory_8(dst++, data);
		}

		// If file extension's first character is 'O', be replaced to "SPR" by BIOS.
		if (m68k_read_memory_8(dst - 3) == 'O')
		{
			m68k_write_memory_8(dst - 3, 'S');
			m68k_write_memory_8(dst - 2, 'P');
			m68k_write_memory_8(dst - 1, 'R');
		}

		// get bank number
		dst = offset + 0x10;
		data = m68k_read_memory_8(src++) & 0x03;
		m68k_write_memory_8(dst++, data);

		// align address to word boundary
		src = (src + 1) & 0xfffffe;
		dst = (dst + 1) & 0xfffffe;

		// get file load offset
		m68k_write_memory_32(dst, m68k_read_memory_32(src));

		// get next file information address
		src += 4;
		offset += 0x20;

	} while (m68k_read_memory_8(src) != 0);

	// load files
	offset = 0x115a06;

	total_sectors = 0;
	loaded_sectors = 0;
	total_files = 0;

	do
	{
		char *p;

		swab(neogeo_prg_memory + offset, file->name, 16);

		for (i = 0; file->name[i]; i++)
			if (!isprint(file->name[i]))
				break;

		if ((p = strchr(file->name, ';')) != NULL) *p = '\0';

		file->bank   = m68k_read_memory_8(offset + 0x10);
		file->offset = m68k_read_memory_32(offset + 0x12);
		file->type   = get_filetype(strrchr(file->name, '.') + 1);

		file->length = zlength(file->name);
		total_sectors += (file->length + 0x7ff) / 0x800;
		total_files++;

		offset += 0x20;
		file++;

	} while (neogeo_prg_memory[offset] != 0);

	init_loading_progress();
	loading_screen_start();

	for (i = 0; i < total_files; i++)
	{
		if (bLoop != LOOP_EXEC)
			return;
		load_file(i);

//		update_inputport();
	}

	loading_screen_stop();

	m68k_write_memory_8(0x108000 + 0x7e88, 0x00);	// unknown, need to clear by '0'
	m68k_write_memory_8(0x108000 + 0x7ddd, 0x00);	// unknown, need to clear by '0'
	m68k_write_memory_8(0x108000 + 0x76db, 0x01);	// unknown, need to set '1'
	m68k_write_memory_8(0x108000 + 0x7ec4, 0x01);	// unknown, need to set '1'

	m68k_write_memory_32(0x10f742, 0);	// unknown, need to clear by '0'
	m68k_write_memory_32(0x10f746, 0);	// unknown, need to clear by '0'

	m68k_write_memory_8(0x108000 + 0x7657, 0x00);	// CD-ROM loading flag
	m68k_write_memory_8(0x108000 + 0x76d9, 0x01);	// Input enable (?)
	m68k_write_memory_8(0x108000 + 0x76c3, 0x01);	// CDDA enable
	m68k_write_memory_8(0x108000 + 0x76f6, 0xff);	// CDDA command
	m68k_write_memory_8(0x108000 + 0x764b, 0xff);	// CDDA track

	m68k_write_memory_8(0x108000 + 0x7ddc, 0x00);	// with/without loading screen
	m68k_write_memory_8(0x108000 + 0x7e85, 0xff);	// unknown

	m68k_write_memory_8(0x108000 + 0x7dc2, save2);
	m68k_write_memory_8(0x108000 + 0x7d80, save1);

	if (with_image() && !neogeo_loadscreen)
		video_clear_screen();

	neogeo_sound_mute(0);

	init_autoframeskip();
	blit_fix_resetcache();

	if (!neogeo_loadscreen) neogeo_loadfinished = 1;

	zip_close();
}


/*------------------------------------------------------
	load file to CD-ROM chache area
 -----------------------------------------------------*/

static int load_file(int fileno)
{
	struct filelist_t *file = &filelist[fileno];
	int f;
	u32 length, next = 0;

	if (internal_cache)
		cdrom_cache = neogeo_prg_memory + 0x111204;
	else
		cdrom_cache = (u8 *)malloc(file->length + 4);

#ifdef SAVE_STATE
	switch (file->type)
	{
	case FIX_TYPE: state_fix_mark_loadfile(file->name, file->offset, file->length); break;
	case SPR_TYPE: state_spr_mark_loadfile(file->name, file->bank, file->offset, file->length); break;
	case PCM_TYPE: state_pcm_mark_loadfile(file->name, file->bank, file->offset, file->length); break;
	}
#endif

	if (file->type == Z80_TYPE)
		m68k_write_memory_8(0xff0183, 0);

	if ((f = zopen(file->name)) == -1)
	{
		ErrorMsg("ERROR: Could not open file \"%s\"", file->name);
		bLoop = LOOP_BROWSER;
		return -1;
	}

	if (neogeo_loadscreen && with_image())
	{
		/*
			MC68K memory $111204-$111a04 (size:$800 bytes, 1 CD-ROM sector)
			is CD-ROM cache area.
			all files are once loaded here, and upload to another memory.
		*/

		while ((length = zread(f, cdrom_cache, 0x800)) != 0)
		{
			upload_file(fileno, next, length);
			next += length;
			update_loading_progress();
		}
	}
	else
	{
		if (cdrom_cache)
		{
			zread(f, cdrom_cache, file->length);
			upload_file(fileno, 0, file->length);
		}
		else
		{
			cdrom_cache = neogeo_prg_memory + 0x111204;
			internal_cache = 1;

			while ((length = zread(f, cdrom_cache, 0x800)) != 0)
			{
				upload_file(fileno, next, length);
				next += length;
			}
		}
	}

	zclose(f);

	if (file->type == Z80_TYPE)
		m68k_write_memory_8(0xff0183, 0xff);

	if (!internal_cache)
		free(cdrom_cache);

	return 0;
}


/*------------------------------------------------------
	Upload file from CD-ROM chache to another memory
 -----------------------------------------------------*/

static void upload_file(int fileno, u32 offset, u32 length)
{
	struct filelist_t *file = &filelist[fileno];
	u32 base;

	switch (file->type)
	{
	case PRG_TYPE:
	case AXX_TYPE:
		swab(cdrom_cache, neogeo_prg_memory + file->offset + offset, length);
		break;

	case FIX_TYPE:
		if (neogeo_loadscreen && with_image())
		{
			u32 offset1, offset2, start, end;
			u32 length1, length2;

			start = (file->offset >> 1) + offset;
			end   = (start + length) - 1;

			if (start >= 0x6000)
			{
				offset1 = 0;
				length1 = 0;
				offset2 = start << 1;
				length2 = length;
			}
			else if (end < 0x6000)
			{
				offset1 = start << 1;
				length1 = length;
				offset2 = 0;
				length2 = 0;
			}
			else
			{
				offset1 = start << 1;
				length1 = 0x6000 - start;
				offset2 = 0x6000 << 1;
				length2 = length - length1;
			}

			if (length1)
			{
				memcpy(neogeo_prg_memory + 0x115e06 + (offset1 >> 1), cdrom_cache, length1);
			}

			if (length2)
			{
				memcpy(neogeo_fix_memory + (offset2 >> 1), cdrom_cache + length1, length2);
				neogeo_decode_fix(neogeo_fix_memory, offset2, length2);
			}
		}
		else
		{
			base = file->offset >> 1;
			memcpy(neogeo_fix_memory + base + offset, cdrom_cache, length);
			neogeo_decode_fix(neogeo_fix_memory, file->offset + (offset << 1), length);
		}
		break;

	case SPR_TYPE:
		base = (file->bank << 20) + file->offset;
		memcpy(neogeo_spr_memory + base + offset, cdrom_cache, length);
		neogeo_decode_spr(neogeo_spr_memory, base + offset, length);
		break;

	case Z80_TYPE:
		base = file->offset >> 1;
		memcpy(neogeo_z80_memory + base + offset, cdrom_cache, length);
		break;

	case PAT_TYPE:
		swab(cdrom_cache, cdrom_cache, length);
		neogeo_apply_patch((u16 *)cdrom_cache, file->bank, file->offset);
		break;

	case PCM_TYPE:
		base = (file->bank << 19) + (file->offset >> 1);
		memcpy(neogeo_pcm_memory + base + offset, cdrom_cache, length);
		break;
	}
}


/*------------------------------------------------------
	Check file type
 -----------------------------------------------------*/

static int get_filetype(const char *ext)
{
	int type = UNKNOWN_TYPE;

	/*
		The file type number defined in BIOS $c0e792-$c0e819

		 $00: PRG or TXT
		 $01: FIX
		 $02: SPR
		 $03: Z80
		 $04: PCM
		 $05: PAT
		 $06: N/A (used as "palette data" number in system check)
		 $07: OBJ (not used)
		 $08: ARG, AIX, APR, ACM, AAT ABJ(not used)
		 $09: N/A
		 $0a: N/A
		 $0b: N/A
		 $0c: N/A
		 $0d: N/A
		 $0e: N/A
		 $0f: N/A (default value)
	*/
	if (stricmp(ext, "PRG") == 0) type = PRG_TYPE;
	if (stricmp(ext, "FIX") == 0) type = FIX_TYPE;
	if (stricmp(ext, "SPR") == 0) type = SPR_TYPE;
	if (stricmp(ext, "Z80") == 0) type = Z80_TYPE;
	if (stricmp(ext, "PCM") == 0) type = PCM_TYPE;
	if (stricmp(ext, "PAT") == 0) type = PAT_TYPE;
	if (ext[0] == 'A')
	{
		/*
			The file extension "Axx" is resident program that need to
			load to MC68K memory.
			These are uploaded to another memory by program if necessary.
		*/
		type = AXX_TYPE;
	}

	return type;
}


/*------------------------------------------------------
	Initialize loading progress
 -----------------------------------------------------*/

static void init_loading_progress(void)
{
	if (neogeo_loadscreen && with_image())
	{
		m68k_write_memory_32(0x108000 + 0x7694, total_sectors);
		m68k_write_memory_32(0x108000 + 0x7690, 0);
	}
}


/*------------------------------------------------------
	Update loading progress
 -----------------------------------------------------*/

static void update_loading_progress(void)
{
	if (neogeo_loadscreen && with_image())
	{
		u32 progress;

		loaded_sectors++;

		progress = ((loaded_sectors * 0x8000) / total_sectors) << 8;

		m68k_write_memory_32(0x108000 + 0x7690, progress);

		if (progress >= 0x800000)
			m68k_write_memory_32(0x108000 + 0x7690, 0x800000);

		m68000_interpreter(m68k_read_memory_32(0x11c80c), 0);

		if (with_image() || m68k_read_memory_16(0x108) == 0x0212)
		{
			m68k_write_memory_8(0x108000 + 0x7793, 0x00);
			m68000_interpreter(0xc0c8b2, 0);
			neogeo_loading_screenrefresh();
		}
	}
}


/*--------------------------------------------------------
	Start loading screen
 -------------------------------------------------------*/

static void loading_screen_start(void)
{
	if (neogeo_loadscreen)
	{
		if (with_image())
		{
			int i;
			u32 offset;

			video_clear_screen();

			// Save FIX plane
			for (i = 0; i < 0x500; i++)
				m68k_write_memory_16(0x110804 + i * 2, neogeo_vidram16[0x7000 + i]);

			// Setup load screen
			m68000_interpreter(m68k_read_memory_32(0x11c808), 0);

			loading_upload_fix();
			loading_upload_palette();

			offset = m68k_read_memory_32(0x11c80c);
			if (offset != 0xc0c814)
			{
				i = 64;	 // for safety

				if (NGH_NUMBER(0x0096))	// aof3
				{
					while (i--)
					{
						offset = m68k_read_memory_32(0x11c80c);
						m68000_interpreter(offset, 0);

						if (offset == 0x1244f0 || offset == 0x124534
						||  offset == 0x1245b2 || offset == 0x1245ce)
							break;
					}
				}
				else if (NGH_NUMBER(0x0234))	// lastblad
				{
					while (i--)
					{
						offset = m68k_read_memory_32(0x11c80c);
						m68000_interpreter(offset, 0);

						if (offset == 0x124550 || offset == 0x124b28)
							break;
					}
				}
				else if (NGH_NUMBER(0x0243))	// lastbld2
				{
					while (i--)
					{
						offset = m68k_read_memory_32(0x11c80c);
						m68000_interpreter(offset, 0);

						if (offset == 0x1244aa || offset == 0x124d9c)
							break;
					}
				}
			}

			fix_disable_w(0);
			spr_disable_w(1);
			video_enable_w(1);
		}
		else if (NGH_NUMBER(0x0212))	// overtop
		{
			u8 *src, *dst;

			video_clear_screen();

			src = neogeo_prg_memory + 0xe0000;
			dst = neogeo_fix_memory;
			swab(src, dst, 0x20000);
			neogeo_decode_fix(dst, 0, 0x20000);

			m68k_write_memory_32(0x11c80c, 0x8db0);
		}
	}
}


/*--------------------------------------------------------
	Stop loading screen
 -------------------------------------------------------*/

static void loading_screen_stop(void)
{
	if (neogeo_loadscreen)
	{
		if (with_image())
		{
			u8 *src, *dst;
			u32 src_offs, dst_offs;
			int i;

			// Restore palettes
			src_offs = 0x11be06;
			dst_offs = 0x400000;

			for (i = 0; i < 0x200; i += 4)
			{
				m68k_write_memory_32(dst_offs, m68k_read_memory_32(src_offs));
				src_offs += 4;
				dst_offs += 4;
			}

			// Restore FIX data
			src = neogeo_prg_memory + 0x115e06;
			dst = neogeo_fix_memory;
			memcpy(dst, src, 0x6000);
			neogeo_decode_fix(dst, 0, 0x6000);

			// Restore FIX plane
			for (i = 0; i < 0x500; i++)
				neogeo_vidram16[0x7000 + i] = m68k_read_memory_16(0x110804 + i * 2);

			fix_disable_w(0);
			spr_disable_w(0);
			video_enable_w(1);
		}
		else if (NGH_NUMBER(0x0212))	// overtop
		{
			m68k_write_memory_32(0x11c80c, 0x8854);
		}
	}
}


/*--------------------------------------------------------
	Check loading screen data header (BIOS function)
 -------------------------------------------------------*/

static u32 check_offset;

static int check_screen_data(u32 offset, int type)
{
	u32 data_offset;
	int data_type;

	check_offset = offset;

	do
	{
		data_type = m68k_read_memory_32(check_offset);
		check_offset += 4;

		data_offset = m68k_read_memory_32(check_offset);
		check_offset += 4;

		if (data_type == type + 1)
			return -1;

		if (data_type == -1)
			return -1;

	} while (data_type != type);

	return data_offset;
}


/*--------------------------------------------------------
	Upload FIX data (BIOS function)
 -------------------------------------------------------*/

static void loading_upload_fix(void)
{
	u8 *src, *dst;
	u32 offset;

	// Save current FIX data
	src = neogeo_fix_memory;
	dst = neogeo_prg_memory + 0x115e06;
	memcpy(dst, src, 0x6000);
	neogeo_undecode_fix(dst, 0, 0x6000);

	// Upload BIOS loading screen FIX data
	src = neogeo_rom_memory + 0x7c000;
	dst = neogeo_fix_memory;
	memcpy(dst, src, 0x4000);
	neogeo_decode_fix(dst, 0, 0x4000);

	offset = 0x120002;

	// Upload game loading screen FIX data
	while (1)
	{
		u16 fix_offs, size;

		offset = check_screen_data(offset, 1);
		if (offset == -1)
			break;

		fix_offs = m68k_read_memory_32(offset);
		offset += 4;

		size = m68k_read_memory_32(offset);
		offset += 4;

		src = neogeo_prg_memory;
		dst = neogeo_fix_memory;
		memcpy(dst + (fix_offs >> 1), src + offset, size);
		neogeo_decode_fix(dst, fix_offs, size);

		offset = check_offset;
	}
}


/*--------------------------------------------------------
	Upload loading screen palette data (BIOS function)
 -------------------------------------------------------*/

static void loading_upload_palette(void)
{
	int i;
	u32 src, dst, offset;

	// Save current palette data
	src = 0x400000;
	dst = 0x11be06;

	for (i = 0; i < 0x200; i += 4)
	{
		m68k_write_memory_32(dst, m68k_read_memory_32(src));
		src += 4;
		dst += 4;
	}

	// Upload BIOS palette data
	src = 0xc1701c;
	dst = 0x400000;

	for (i = 0; i < 0x200; i += 4)
	{
		m68k_write_memory_32(dst, m68k_read_memory_32(src));
		src += 4;
		dst += 4;
	}

	offset = 0x120002;

	// Upload game palette data
	while (1)
	{
		u16 palno;

		src = check_screen_data(offset, 2);
		if (src == -1)
			break;

		palno = m68k_read_memory_16(src);
		src += 2;

		dst = 0x400000 + (palno << 5);

		for (i = 0; i < 16; i++)
		{
			m68k_write_memory_16(dst, m68k_read_memory_16(src));
			src += 2;
			dst += 2;
		}

		offset = check_offset;
	}
}


/*------------------------------------------------------
	Convert hex charctor to decimal data
 -----------------------------------------------------*/

static int hextodec(char c)
{
	switch (tolower(c))
	{
	case '0': return 0;
	case '1': return 1;
	case '2': return 2;
	case '3': return 3;
	case '4': return 4;
	case '5': return 5;
	case '6': return 6;
	case '7': return 7;
	case '8': return 8;
	case '9': return 9;
	case 'a': return 10;
	case 'b': return 11;
	case 'c': return 12;
	case 'd': return 13;
	case 'e': return 14;
	case 'f': return 15;
	default: return 0;
	}
}


/****************************************************************************
	Decode GFX
 ***************************************************************************/

/*------------------------------------------------------
	Decode sprite (SPR)
 -----------------------------------------------------*/

#define decode_spr(n, s)						\
{												\
	*dst |= (*(s) & 0x01) << ((n +  0) - 0);	\
	*dst |= (*(s) & 0x02) << ((n +  4) - 1);	\
	*dst |= (*(s) & 0x04) << ((n +  8) - 2);	\
	*dst |= (*(s) & 0x08) << ((n + 12) - 3);	\
	*dst |= (*(s) & 0x10) << ((n + 16) - 4);	\
	*dst |= (*(s) & 0x20) << ((n + 20) - 5);	\
	*dst |= (*(s) & 0x40) << ((n + 24) - 6);	\
	*dst |= (*(s) & 0x80) << ((n + 28) - 7);	\
	(s)++;										\
}

void neogeo_decode_spr(u8 *mem, u32 offset, u32 length)
{
	u8 buf[128], *usage;
	u32 *dst;
	int i, j, k;

	dst = (u32 *)(mem + offset);

	usage = video_spr_usage + (offset >> 7);

	for (i = 0; i < length; i += 128)
	{
		int opaque = 0;
		u8 *src, *src2;

		memcpy(buf, dst, 128);

		src = buf;
		src2 = buf + 64;

		for (j = 0; j < 16; j++)
		{
			*dst = 0;
			decode_spr(1, src2);
			decode_spr(0, src2);
			decode_spr(3, src2);
			decode_spr(2, src2);
			for (k = 0; k < 32; k += 4)
				opaque += (*dst & (0x0f << k)) != 0;
			dst++;

			*dst = 0;
			decode_spr(1, src);
			decode_spr(0, src);
			decode_spr(3, src);
			decode_spr(2, src);
			for (k = 0; k < 32; k += 4)
				opaque += (*dst & (0x0f << k)) != 0;
			dst++;
		}

		if (opaque)
			*usage = (opaque == 256) ? 1 : 2;
		else
			*usage = 0;
		usage++;
	}

    blit_spr_resetcache();
}


/*------------------------------------------------------
	Decode fixed sprite (FIX)
 -----------------------------------------------------*/

#define decode_fix(n)				\
{									\
	tile = buf[n];					\
	*mem++ = tile;					\
	opaque += (tile & 0x0f) != 0;	\
	opaque += (tile >> 4) != 0;		\
}

void neogeo_decode_fix(u8 *mem, u32 offset, u32 length)
{
	int i, j;
	u8 tile, opaque;
	u8 buf[32], *usage;

	mem += (offset >> 1);
	usage = video_fix_usage + (offset >> 6);

	for (i = 0; i < length; i += 32)
	{
		opaque  = 0;

		memcpy(buf, mem, 32);

		for (j = 0; j < 8; j++)
		{
			decode_fix(j + 16);
			decode_fix(j + 24);
			decode_fix(j +  0);
			decode_fix(j +  8);
		}

		if (opaque)
			*usage = (opaque == 64) ? 1 : 2;
		else
			*usage = 0;
		*usage++;
	}

	blit_fix_resetcache();
}


/*------------------------------------------------------
	Restore FIX data to original format
 -----------------------------------------------------*/

#define undecode_fix(n)				\
{									\
	tile = *(mem2 + (ofs++));		\
	buf[n] = tile;					\
}

void neogeo_undecode_fix(u8 *mem, u32 offset, u32 length)
{
	int i, j, ofs;
	u8 tile;
	u8 buf[32];
	u8 *mem2 = mem + offset;

	for (i = 0; i < length; i += 32)
	{
		ofs = 0;

		for (j = 0; j < 8; j++)
		{
			undecode_fix(j + 16);
			undecode_fix(j + 24);
			undecode_fix(j +  0);
			undecode_fix(j +  8);
		}

		memcpy(mem2, buf, 32);

		mem2 += 32;
	}

	blit_fix_resetcache();
}


/****************************************************************************
	Z80 patch operation
 ***************************************************************************/

/*------------------------------------------------------
	Patch Z80 memory
 -----------------------------------------------------*/

#define PATCH_Z80(a, b)					\
{										\
	dst[((a) + 0)] =  (b)       & 0xff;	\
	dst[((a) + 1)] = ((b) >> 8) & 0xff;	\
}

void neogeo_apply_patch(u16 *src, int bank, u32 offset)
{
	u8 *dst = neogeo_z80_memory;

	offset = (((bank * 0x100000) + offset) >> 8) & 0xffff;

	while (*src)
	{
		PATCH_Z80(src[0] + 0,  (src[1] + offset) >> 1);
		PATCH_Z80(src[0] + 2, ((src[2] + offset) >> 1) - 1);

		if (src[3] && src[4])
		{
			PATCH_Z80(src[0] + 5,  (src[3] + offset) >> 1);
			PATCH_Z80(src[0] + 7, ((src[4] + offset) >> 1) - 1);
		}

		src += 5;
	}
}


#ifdef SAVE_STATE
/*------------------------------------------------------
	Load files using history
 -----------------------------------------------------*/

void state_load_file(int type, const char *fname, int bank, u32 offset, u32 length)
{
	int f;

	if ((cdrom_cache = (u8 *)malloc(length + 4)) == NULL)
	{
		ErrorMsg("ERROR: Could not allocate CD-ROM cache.");
		bLoop = LOOP_BROWSER;
		return;
	}

	if ((f = zopen(fname)) == -1)
	{
		ErrorMsg("ERROR: Could not open file \"%s\"", fname);
		bLoop = LOOP_BROWSER;
		return;
	}

	zread(f, cdrom_cache, length);
	zclose(f);

	switch (type)
	{
	case FIX_TYPE:
		memcpy(neogeo_fix_memory + (offset >> 1), cdrom_cache, length);
		neogeo_decode_fix(neogeo_fix_memory, offset, length);
		break;

	case SPR_TYPE:
		memcpy(neogeo_spr_memory + offset + (bank << 20), cdrom_cache, length);
		neogeo_decode_spr(neogeo_spr_memory, offset + (bank << 20), length);
		break;

	case PCM_TYPE:
		memcpy(neogeo_pcm_memory + (offset >> 1) + (bank << 19), cdrom_cache, length);
		break;
	}

	free(cdrom_cache);
}
#endif
