#include <clip.h>

//#define REDUCE

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

//#define DIR	"C:\\Home\\Emulator\\gb\\roms\\Nintendo GameBoy\\"
//#define FNAME	"Tetris_(JUE)_(V1.1)_[!].zip" /* OK(2005/03/03) */
//#define FNAME	"Dr._Mario_(JU)_(V1.1).zip" /* OK(2005/02/22) */
//#define FNAME	"Korodice_(J).zip" /* ゲーム開始しない。既存エミュレータでもダメ */
//#define FNAME	"Space_Invaders_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Penguin_Land_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Alleyway_(JUE)_[!].zip"
//#define FNAME	"Dragon_Slayer_I_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Q_Billion_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Yakyuuman_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Flipull_(J).zip"  /* OK(2005/02/22) */
//#define FNAME	"Tasmania_Story_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Palamedes_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Super_Mario_Land_(JUE)_(V1.1)_[!].zip" /* OK(2005/02/22) */
//#define FNAME	"Pac-Man_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Quarth_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"SolarStriker_(JU)_[!].zip" /* OK(2005/02/22) */
//#define FNAME	"Yoshi_no_Tamago_(J).zip" /* ダメ。Stop使ってる? */
//#define FNAME	"BattleCity_(J)_[!].zip" /* OK(2005/02/22) */
//#define FNAME	"Pinball_Party_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Makai_Toushi_SaGa_(J)_(V1.1).zip"  /* OK(2005/02/22) */
//#define FNAME	"Tennis_(JUE)_[!].zip" /* OK(2005/02/22) */
//#define FNAME	"Nemesis_(J).zip" /* OK(2005/02/22) */
//#define FNAME	"Puzzle_Boy_(J).zip" /* ダメ。面クリ後にパレット化ける */
//#define FNAME	"Pocket_Monsters_-_Blue_Version_(J)_[S].zip"
//#define FNAME	"Nettou_King_of_Fighters_%2797_(J)_[S][p1].zip" /* MBC5, ダメ */
//#define FNAME	"Donkey_Kong_(JU)_(V1.0)_[S][!].zip"
//#define FNAME	"Yu-Gi-Oh!_Duel_Monsters_(J)_[S].zip" /* OK(2005/03/03) */
//#define FNAME	"Zankurou_Musouken_(J)_[S][!].zip" /* OK(2005/03/03) */
//#define FNAME	"Donkey_Kong_Land_(J).zip" /* OK(2005/03/03) */
//#define FNAME	"Wario_Land_II_(UE)_[S][!].zip" /* MBC3, ダメ */
//#define FNAME	"Battle_Arena_Toshinden_(J)_[S].zip" /* OK(2005/03/03) */
//#define FNAME	"Namco_Gallery_Vol.3_(J)_[S].zip" /* OK(2005/03/03) */
//#define FNAME	"Kira_Kira_Kids_(J)_[S].zip" /* ダメ。開始しない */
//#define FNAME	"Bubble_Bobble_(J).zip"

#define DIR	"C:\\Home\\Emulator\\gb\\roms\\Nintendo GameBoy Color\\"
#define FNAME	"Star_Ocean_-_Blue_Sphere_(J)_[C][!].zip" /* Color/Classic, MBC5, OK(2005/03/03) */
//#define FNAME	"Grandia_-_Parallel_Trippers_(J)_[C][!].zip" /* Color only, MBC5, OK(2005/03/03) */
//#define FNAME	"Rockman_X2_-_Soul_Eraser_(J)_[C][!].zip" /* Color only, MBC5, OK(2005/03/03) */
//#define FNAME	"Tweety_Sekaiisshuu_(J)_[C][!].zip" /* Color only, MBC5, OK(2005/03/03) */
//#define FNAME	"Tokimeki_Memorial_Pocket_-_Sport_Hen_-_Koutei_no_Photograph_(J)_[C][!].zip"

/////////////////////////////////////////////////////////////////////////////
#define CLOCK	(1<<22)
#define FMSEC	(456*154*1000/CLOCK)

#define APPNAME	"GBWIN2"
#define WIDTH	160
#define HEIGHT	144
#define SCALE	2
unsigned char vbuff[WIDTH*HEIGHT];

DMG dmgcpu;
unsigned char dmgmem[0x10000];

FILE* rom_fp;

/////////////////////////////////////////////////////////////////////////////

#define NWHDR		8	/* これ以上増やすとwaveOutWriteでエラーになるみたい */
HWAVEOUT g_wout;
WAVEFORMATEX g_fmt;
WAVEHDR g_whdr[NWHDR];
short g_wbuff[NWHDR][DMGSOUND2BUFLEN];

CRITICAL_SECTION snd_cs;

void CALLBACK
waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	static int I;
	int retval;

	if(uMsg != WOM_DONE) return;

	EnterCriticalSection(&snd_cs);
	dmgsound2_mix(g_wbuff[I]);
	LeaveCriticalSection(&snd_cs);

	retval = waveOutWrite(hwo, &g_whdr[I], sizeof g_whdr[I]);
	assert(retval == 0);

	I = (I + 1) % NWHDR;
}

void
InitSnd()
{
	int retval;
	int i;

	InitializeCriticalSection(&snd_cs);

	/* init stream */
	memset(&g_fmt, 0, sizeof g_fmt);
	g_fmt.wFormatTag = WAVE_FORMAT_PCM;
	g_fmt.nChannels = 1;
	g_fmt.nSamplesPerSec = 16000;
	g_fmt.nAvgBytesPerSec = 16000 * 2;
	g_fmt.nBlockAlign = 2;
	g_fmt.wBitsPerSample = 16;
	retval = waveOutOpen(&g_wout, WAVE_MAPPER, &g_fmt, (DWORD)waveOutProc, 0, CALLBACK_FUNCTION);
	assert(retval == 0);
	for(i = 0; i < NWHDR; i++) {
		memset(&g_whdr[i], 0, sizeof g_whdr[i]);
		g_whdr[i].lpData = (char*)g_wbuff[i];
		g_whdr[i].dwBufferLength = sizeof g_wbuff[i];
		retval = waveOutPrepareHeader(g_wout, &g_whdr[i], sizeof g_whdr[i]);
		assert(retval == 0);
	}

	/* start playing */
	for(i = 0; i < NWHDR; i++) {
		retval = waveOutWrite(g_wout, &g_whdr[i], sizeof g_whdr[i]);
		assert(retval == 0);
	}
}

/////////////////////////////////////////////////////////////////////////////

unsigned char INPUT; /* MSB:{START,SELECT,B,A,DN,UP,LF,RI}:LSB */
int DIV;
int TIM;

/* $4000..$7FFFに対応するROMバンクの(フラッシュメモリ上のアドレス-0x4000)をポイントします。
 * - %addr=0x4000..0x7fffで直接オフセットできるように、Bank#0=&rom[-0x4000]としておきます。
 *   以下、Bank#1=&rom[0x0000], Bank#2=&rom[0x4000], Bank#3=&rom[0x8000],...と続きます。
 *
 *   rom+0x0000 +--------------+
 *              |    Bank#0    |
 *   rom+0x4000 +--------------+ <- たとえば、Bank#2が選択されている場合、ROM_BANKはここを指します。
 *              |    Bank#1    |
 *   rom+0x8000 +--------------+
 *              |    Bank#2    | <- そうしておけば、ROM_BANK[%addr=0x4000..0x7FFF]でBank#2の範囲をアドレッシングできます。
 *   rom+0xc000 +--------------+    (%addrから0x4000を引いたり、0x3fffでAndを取ったりする手間が省けるわけです。)
 *              |    Bank#3    |
 *              +--------------+
 *              |    Bank#4    |
 *              +--------------+
 *              |    Bank#...  |
 *              +--------------+
 *              |    Bank#N-1  |
 *              +--------------+
 */
const unsigned char* ROM_BANK;

/* $A000..$BFFFに対応するRAMバンクの(アドレス-0xa000)をポイントします。
 * - %addr=0xa000..0xbfffで直接オフセットできるように、Bank#0=&ram[-0xa000]としておきます。
 *   以下、Bank#1=&ram[-0x8000], Bank#2=&ram[-0x6000], Bank#3=&ram[-0x4000],...と続きます。
 */
unsigned char* RAM_BANK;
unsigned char ram[0x8000]; /* $A000-$C000: 8KBx4bank=32KB */

/////////////////////////////////////////////////////////////////////////////
BITMAPINFO* pBMI;
HWND MainWnd;
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CHAR:
		if(wParam == 'q') {
			DestroyWindow(hWnd);
			return 0;
		}
		return 0;
	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}
int
DoMsg()
{
	MSG msg;

	while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		if(msg.message == WM_QUIT) {
			return 0;
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 1;
}
void
InitUI()
{
	int i;
	BITMAPINFOHEADER* bi;
	RGBQUAD* rgb;
	HINSTANCE hInst;
	WNDCLASS wc;
	RECT rcWin;
	RECT rcCli;
	int ncW;
	int ncH;

	pBMI = (BITMAPINFO*)calloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256, 1);
	bi = &pBMI->bmiHeader;
	rgb = &pBMI->bmiColors[0];
	bi->biSize = sizeof(BITMAPINFOHEADER);
#ifdef REDUCE
	bi->biWidth = 128;
	bi->biHeight = -88;
#else /*REDUCE*/
	bi->biWidth = WIDTH;
	bi->biHeight = -HEIGHT;
#endif /*REDUCE*/
	bi->biPlanes = 1;
	bi->biBitCount = 8;
	for(i = 0; i < 4; i++) {
		rgb[i].rgbRed   = 255 - (255 * i / 4);
		rgb[i].rgbGreen = 255 - (255 * i / 4);
		rgb[i].rgbBlue  = 255 - (255 * i / 4);
	}

	hInst = GetModuleHandle(NULL);
	//
	memset(&wc, 0, sizeof wc);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc;
	wc.hInstance = hInst;
	wc.hCursor = LoadCursor(NULL, IDC_CROSS);
	wc.lpszClassName = APPNAME;
	RegisterClass(&wc);
	//
	MainWnd = CreateWindow(APPNAME, APPNAME,
		WS_OVERLAPPED,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInst, NULL);
	//
	GetWindowRect(MainWnd, &rcWin);
	GetClientRect(MainWnd, &rcCli);
	ncW = (rcWin.right - rcWin.left) - (rcCli.right - rcCli.left);
	ncH = (rcWin.bottom - rcWin.top) - (rcCli.bottom - rcCli.top);
	MoveWindow(MainWnd, 0, 0,
		ncW + bi->biWidth  * SCALE,
		ncH - bi->biHeight * SCALE, 0);
	//
	ShowWindow(MainWnd, SW_SHOW);
	UpdateWindow(MainWnd);
}
/////////////////////////////////////////////////////////////////////////////
#include "mbc.c"
/////////////////////////////////////////////////////////////////////////////
void
InitROM()
{
	int retval;
	FILE* fp;
	int size;
	unsigned char* buff;
	unsigned char* rom;
	ZIP zip;
	ZIPDIR dir;
	char rom_path[_MAX_PATH];
	char rom_fname[_MAX_FNAME];

	fp = fopen(DIR FNAME, "rb");
	ASSERT(fp);
	fseek(fp, 0, SEEK_END);
	size = ftell(fp);
	rewind(fp);
	buff = malloc(size);
	ASSERT(buff);
	fread(buff, 1, size, fp);
	fclose(fp);
	//
	retval = zip_init(&zip, buff, size);
	ASSERT(retval == 0);
	retval = zip_dir(&zip, &dir);
	ASSERT(retval == 0);
	size = dir.uncompressed_size;
	rom = malloc(size);
	ASSERT(rom);
	retval = zip_uncompress(&zip, dir.filename, rom, size);
	ASSERT(retval == size);
	//
	_splitpath(DIR FNAME, NULL, NULL, rom_fname, NULL);
	_makepath(rom_path, NULL, NULL, rom_fname, "gb");
	fp = fopen(rom_path, "wb");
	fwrite(rom, 1, size, fp);
	fclose(fp);
	//
	free(rom);
	free(buff);

	//-------------------------------------------------------------------

	/* MBCを初期化します。 */
	if(mbc_init(rom_path) != 0) {
		goto L_ERR;
	}

	/* 入力、タイマをクリアします。 */
	INPUT = 0;
	DIV = 0;
	TIM = 0;

	/* Video、Soundをリセットします。
	 * - CPUリセットのGameBoy擬似IPLから、レジスタを初期化するので、
	 *   必ずCPUリセットよりも先に、周辺装置をリセットしてください。
	 */
	dmgvideo_reset(dmgmem);
	dmgsound2_reset();

	//-------------------------------------------------------------------
	return;
L_ERR:
	DIE();
}
/////////////////////////////////////////////////////////////////////////////
unsigned char gb_read_io(DMG* cpu, unsigned short addr);
unsigned char gb_read_rom_bank(DMG* cpu, unsigned short addr);
unsigned char gb_read_ram_bank(DMG* cpu, unsigned short addr);
unsigned char
gb_read(DMG* cpu, unsigned short addr)
{
	       if(addr < 0x4000) {	/* $0000-$3FFF: 16kB ROM bank #0 */
		/* FALLTHRU */
	} else if(addr < 0x8000) {	/* $4000-$7FFF: 16kB switchable ROM bank */
		return gb_read_rom_bank(cpu, addr);
	} else if(addr < 0xa000) {	/* $8000-$A000: 8kB Video RAM */
		/* FALLTHRU */
	} else if(addr < 0xc000) {	/* $A000-$C000: 8kB switchable RAM bank */
		return gb_read_ram_bank(cpu, addr);
	} else if(addr < 0xff00) {	/* $C000-$FF00: 8kB Internal RAM, Echo of 8kB Internal RAM, Sprite Attrib Memory (OAM), Empty but unusable for I/O */
		/* FALLTHRU */
	} else if(addr < 0xff80) {	/* $FF00-$FF7F: I/O ports, Empty but unusable for I/O */
		return gb_read_io(cpu, addr);
	} else if(addr < 0xffff) {	/* $FF80-$FFFE: Internal RAM */
		/* FALLTHRU */
	} else {			/* $FFFF      : Interrupt Enable Register */
		return gb_read_io(cpu, addr);
	}
	return dmgmem[addr];
}
unsigned char
gb_read_io(DMG* cpu, unsigned short addr)
{
	int data;
	switch(addr) {
	/*----- Joypad -----*/
	//case 0xff00: /* $FF00 (P1) */
	/*----- Serial -----*/
	//case 0xff01: /* $FF01 (SB) */
	//case 0xff02: /* $FF02 (SC) */
	/*----- Timer -----*/
	//case 0xff04: /* $FF04 (DIV) */
	//case 0xff05: /* $FF05 (TIMA) */
	//case 0xff06: /* $FF06 (TMA) */
	//case 0xff07: /* $FF07 (TAC) */
	/*----- Interrupt -----*/
	//case 0xff0f: /* $FF0F (IF) */
	//case 0xffff: /* $FFFF (IE) */
	/*----- Sound -----*/
	case 0xff10: /* $FF10 (NR10) */
	case 0xff11: /* $FF11 (NR11) */
	case 0xff12: /* $FF12 (NR12) */
	case 0xff13: /* $FF13 (NR13) */
	case 0xff14: /* $FF14 (NR14) */
	case 0xff16: /* $FF16 (NR21) */
	case 0xff17: /* $FF17 (NR22) */
	case 0xff18: /* $FF18 (NR23) */
	case 0xff19: /* $FF19 (NR24) */
	case 0xff1a: /* $FF1A (NR30) */
	case 0xff1b: /* $FF1B (NR31) */
	case 0xff1c: /* $FF1C (NR32) */
	case 0xff1d: /* $FF1D (NR33) */
	case 0xff1e: /* $FF1E (NR34) */
	case 0xff20: /* $FF20 (NR41) */
	case 0xff21: /* $FF21 (NR42) */
	case 0xff22: /* $FF22 (NR43) */
	case 0xff23: /* $FF23 (NR44) */
	case 0xff24: /* $FF24 (NR50) */
	case 0xff25: /* $FF25 (NR51) */
	case 0xff26: /* $FF26 (NR52) */
	case 0xff30: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff31: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff32: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff33: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff34: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff35: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff36: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff37: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff38: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff39: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3a: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3b: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3c: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3d: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3e: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3f: /* $FF30-$FF3F (Wave Pattern RAM) */
		{
			EnterCriticalSection(&snd_cs);
			data = dmgsound2_read(addr);
			LeaveCriticalSection(&snd_cs);
			return data;
		}
	/*----- LCDC -----*/
	case 0xff40: /* $FF40 (LCDC) */
	case 0xff41: /* $FF41 (STAT) */
	case 0xff42: /* $FF42 (SCY) */
	case 0xff43: /* $FF43 (SCX) */
	case 0xff44: /* $FF44 (LY) */
	case 0xff45: /* $FF45 (LYC) */
	case 0xff46: /* $FF46 (DMA) */
	case 0xff47: /* $FF47 (BGP) */
	case 0xff48: /* $FF48 (OBP0) */
	case 0xff49: /* $FF49 (OBP1) */
	case 0xff4a: /* $FF4A (WY) */
	case 0xff4b: /* $FF4B (WX) */
		{
			return dmgvideo_read(addr);
		}
	}
	return dmgmem[addr];
}
unsigned char
gb_read_rom_bank(DMG* cpu, unsigned short addr)
{
	return ROM_BANK[addr];
}
unsigned char
gb_read_ram_bank(DMG* cpu, unsigned short addr)
{
	return RAM_BANK[addr];
}

void gb_write_io(DMG* cpu, unsigned short addr, unsigned char data);
void gb_write_rom_bank(DMG* cpu, unsigned short addr, unsigned char data);
void gb_write_ram_bank(DMG* cpu, unsigned short addr, unsigned char data);
void
gb_write(DMG* cpu, unsigned short addr, unsigned char data)
{
	       if(addr < 0x8000) {	/* $0000-$7FFF: 16kB ROM bank #0, 16kB switchable ROM bank */
		gb_write_rom_bank(cpu, addr, data);
		return;
	} else if(addr < 0xa000) {	/* $8000-$A000: 8kB Video RAM */
		/* FALLTHRU */
	} else if(addr < 0xc000) {	/* $A000-$C000: 8kB switchable RAM bank */
		gb_write_ram_bank(cpu, addr, data);
		return;
	} else if(addr < 0xff00) {	/* $C000-$FF00: 8kB Internal RAM, Echo of 8kB Internal RAM, Sprite Attrib Memory (OAM), Empty but unusable for I/O */
		/* FALLTHRU */
	} else if(addr < 0xff80) {	/* $FF00-$FF7F: I/O ports, Empty but unusable for I/O */
		gb_write_io(cpu, addr, data);
		return;
	} else if(addr < 0xffff) {	/* $FF80-$FFFE: Internal RAM */
		/* FALLTHRU */
	} else {			/* $FFFF      : Interrupt Enable Register */
		gb_write_io(cpu, addr, data);
		return;
	}
	dmgmem[addr] = data;
}
void
gb_write_io(DMG* cpu, unsigned short addr, unsigned char data)
{
	switch(addr) {
	/*----- Joypad -----*/
	case 0xff00: /* $FF00 (P1) */
		{
			/* P1読み出しのD7:3は$Fx固定でなければいけないようです。
			 * (そうしないと、ポケモン青が操作できません。)
			 */
			if(!(data & (1<<4))) {
				data = ~(INPUT & 15);
			} else if(!(data & (1<<5))) {
				data = ~(INPUT >> 4);
			} else {
				data = ~0; /* $Fx=GameBoy, $3x=SuperGameBoy */
			}
			dmgmem[addr] = data;
			return;
		}
	/*----- Serial -----*/
	//case 0xff01: /* $FF01 (SB) */
	case 0xff02: /* $FF02 (SC) */
		{
			if((data & (1<<7)) &&	/* Start transfer */
			   (data & (1<<0))) {	/* Internal Clock (=マスタ) */
				data &= ~(1<<7);	/* 送信完了 */
				dmgmem[0xff01] = 0xff;	/* 受信データ無し */
				dmgmem[0xff0f] |= DMG_SERIAL_TRANSFER_COMPLETION;
				dmg_check_pending(cpu);
			}
			dmgmem[addr] = data;
			return;
		}
	/*----- Timer -----*/
	case 0xff04: /* $FF04 (DIV) */
		{
			dmgmem[addr] = DIV = 0; /* カウンタリセット(仕様) */
			return;
		}
	//case 0xff05: /* $FF05 (TIMA) */
	//case 0xff06: /* $FF06 (TMA) */
	case 0xff07: /* $FF07 (TAC) */
		{
			if(dmgmem[addr] != data) {
				dmgmem[addr] = data;
				TIM = 0; /* 端数カウンタリセット */
			}
			return;
		}
	/*----- Interrupt -----*/
	case 0xff0f: /* $FF0F (IF) */
	case 0xffff: /* $FFFF (IE) */
		{
			if(dmgmem[addr] != data) {
				dmgmem[addr] = data;
				dmg_check_pending(cpu);
			}
			return;
		}
	/*----- Sound -----*/
	case 0xff10: /* $FF10 (NR10) */
	case 0xff11: /* $FF11 (NR11) */
	case 0xff12: /* $FF12 (NR12) */
	case 0xff13: /* $FF13 (NR13) */
	case 0xff14: /* $FF14 (NR14) */
	case 0xff16: /* $FF16 (NR21) */
	case 0xff17: /* $FF17 (NR22) */
	case 0xff18: /* $FF18 (NR23) */
	case 0xff19: /* $FF19 (NR24) */
	case 0xff1a: /* $FF1A (NR30) */
	case 0xff1b: /* $FF1B (NR31) */
	case 0xff1c: /* $FF1C (NR32) */
	case 0xff1d: /* $FF1D (NR33) */
	case 0xff1e: /* $FF1E (NR34) */
	case 0xff20: /* $FF20 (NR41) */
	case 0xff21: /* $FF21 (NR42) */
	case 0xff22: /* $FF22 (NR43) */
	case 0xff23: /* $FF23 (NR44) */
	case 0xff24: /* $FF24 (NR50) */
	case 0xff25: /* $FF25 (NR51) */
	case 0xff26: /* $FF26 (NR52) */
	case 0xff30: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff31: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff32: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff33: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff34: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff35: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff36: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff37: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff38: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff39: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3a: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3b: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3c: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3d: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3e: /* $FF30-$FF3F (Wave Pattern RAM) */
	case 0xff3f: /* $FF30-$FF3F (Wave Pattern RAM) */
		{
			EnterCriticalSection(&snd_cs);
			dmgsound2_write(addr, data);
			LeaveCriticalSection(&snd_cs);
			return;
		}
	/*----- LCDC -----*/
	case 0xff40: /* $FF40 (LCDC) */
	case 0xff41: /* $FF41 (STAT) */
	case 0xff42: /* $FF42 (SCY) */
	case 0xff43: /* $FF43 (SCX) */
	case 0xff44: /* $FF44 (LY) */
	case 0xff45: /* $FF45 (LYC) */
	case 0xff46: /* $FF46 (DMA) */
	case 0xff47: /* $FF47 (BGP) */
	case 0xff48: /* $FF48 (OBP0) */
	case 0xff49: /* $FF49 (OBP1) */
	case 0xff4a: /* $FF4A (WY) */
	case 0xff4b: /* $FF4B (WX) */
		{
			dmgvideo_write(addr, data);
			return;
		}
	}
	dmgmem[addr] = data;
}
void
gb_write_rom_bank(DMG* cpu, unsigned short addr, unsigned char data)
{
	mbc_write(addr, data);
}
void
gb_write_ram_bank(DMG* cpu, unsigned short addr, unsigned char data)
{
	RAM_BANK[addr] = data;
}

/////////////////////////////////////////////////////////////////////////////

void
input_update()
{
	INPUT = 0;
	if(GetAsyncKeyState(VK_RIGHT )) INPUT |= (1<<0);
	if(GetAsyncKeyState(VK_LEFT  )) INPUT |= (1<<1);
	if(GetAsyncKeyState(VK_UP    )) INPUT |= (1<<2);
	if(GetAsyncKeyState(VK_DOWN  )) INPUT |= (1<<3);
	if(GetAsyncKeyState('X'      )) INPUT |= (1<<4);
	if(GetAsyncKeyState('Z'      )) INPUT |= (1<<5);
	if(GetAsyncKeyState(VK_BACK  )) INPUT |= (1<<6);
	if(GetAsyncKeyState(VK_RETURN)) INPUT |= (1<<7);
}

void
timer_update()
{
	int tima;

	/* カウンタ更新。(16384[Hz]=256[Cycle/Count]) */
	DIV += 456;
	dmgmem[0xff04] = DIV >> 8;			/* $FF04 (DIV) */

	/* 割り込みタイマ更新。 */
	if(dmgmem[0xff07] & (1<<2)) {			/* $FF07 (TAC) */
		TIM += 456;
		tima = dmgmem[0xff05];			/* $FF05 (TIMA) */
		switch(dmgmem[0xff07] & 3) {		/* $FF07 (TAC) */
		case 1:       tima += (TIM>> 4); TIM &= (1<< 4)-1; break;
		case 2:       tima += (TIM>> 6); TIM &= (1<< 6)-1; break;
		case 3:       tima += (TIM>> 8); TIM &= (1<< 8)-1; break;
		default/*0*/: tima += (TIM>>10); TIM &= (1<<10)-1; break;
		}
		if(tima >= 0x100) {
			/* このエミュレータでは、1ライン2回以上のタイマ割り込みは生成できません。
			 * 実際のプログラムでも、そんな高速なタイマ割り込みは使わないと思います。
			 */
			do {
				tima -= 0x100;
				tima += dmgmem[0xff06];	/* $FF06 (TMA) */
			} while(tima >= 0x100);
			dmgmem[0xff0f] |= DMG_TIMER_OVERFLOW;
			dmg_check_pending(&dmgcpu);
		}
		dmgmem[0xff05] = tima;			/* $FF05 (TIMA) */
	}
}

int
main(int argc, char* argv[])
{
	RECT rc;
	HDC hDC;
	int irq;
	int t0;
	int t1;
	int i;

	InitUI();
	InitSnd();
	InitROM();

	dmg_reset(&dmgcpu, gb_read, gb_write, 1);

	t0 = GetTickCount();
	hDC = GetDC(MainWnd);
	GetClientRect(MainWnd, &rc);
	for(;;) {
		if(!DoMsg()) {
			break;
		}

		/* 入力更新。 */
		input_update();

		/* 456[Cycle/Line]×(画面内144[Line]+画面外10[Line]) */
		irq = dmgvideo_new_frame(vbuff);
		if(irq) {
			dmgmem[0xff0f] |= irq;
			dmg_check_pending(&dmgcpu);
		}
		for(i = 0; i < 154; i++) {

			//dmgcpu_run(cpu->cycle + 456);
			//↓実際には、周辺装置のメモリアクセスや割り込みアクノリッジサイクルのために、
			//↓CPUは456サイクルをフルに利用できないはずです。
			//↓実行サイクル数をやや少なめにして、エミュレーション速度を稼ぐことにします。
			//↓もしも動作に問題が出たら、実行サイクル数を調整してください。
			dmg_run(&dmgcpu, dmgcpu.cycle + (456 - 16));
			if(dmgcpu.halt) dmgcpu.cycle = 0;

			irq = dmgvideo_next_line();
			if(irq) {
				dmgmem[0xff0f] |= irq;
				dmg_check_pending(&dmgcpu);
			}

			/* カウンタ、タイマ更新。 */
			timer_update();
		}

#ifdef REDUCE
		dmgvideo_reduce(vbuff, vbuff);
#endif /*REDUCE*/

		StretchDIBits(hDC,
			rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
			0, 0, pBMI->bmiHeader.biWidth, -pBMI->bmiHeader.biHeight,
			vbuff,
			pBMI,
			DIB_RGB_COLORS,
			SRCCOPY);

		for(;;) {
			t1 = GetTickCount();
			if(t1 - t0 >= FMSEC) break;
			Sleep(0);
		}
		t0 += FMSEC;
	}
	ReleaseDC(MainWnd, hDC);

	mbc_exit();
}


