//--------------------------------------------------------------
// File     : main.c
// Datum    : 01.04.2018
// Version  : 1.0
// Autor    : UB
// Web      : http://mikrocontroller.bplaced.net
// CPU      : STM32F746
// IDE      : OpenSTM32
// GCC      : 4.9 2015q2
// Module   : CubeHAL
// Funktion : Gameboy emulator (Main function)
//--------------------------------------------------------------


#include "stm32_ub_system.h"
#include "gameboy_ub.h"
#include "stm32_ub_uart.h"
#include "stm32_ub_lcd_480x272.h"
#include "stm32_ub_graphic2d.h"
#include "stm32_ub_font.h"
#include "stm32_ub_button.h"
#include "stm32_ub_led.h"
#include "stm32_ub_usb_hid_host.h"
#include "stm32_ub_fatfs.h"
#include "string.h"


char sbuffer[SBUFFER_SIZE];




//--------------------------------------------------------------
void p_init(void);
void p_clear_screen(void);
void p_redraw_screen(void);
void p_show_fps(void);
void p_show_gui(void);
void p_show_control(void);
void p_show_control2(void);
void p_show_cartridge_info(void);
void p_show_emu_status(void);
void p_show_load_status(void);
void p_show_error_nr(uint8_t nr);
void p_show_key_status(void);
void p_check_usb_keyboard(void);
void p_send_Screen(void);
void p_receive_data(void);
void p_sd_loop(void);
void p_sd_read_dir(void);
void p_sd_load_file(void);
void p_sd_load_ini(void);
void p_check_inistr(void);
uint8_t p_copy_str(uint8_t slen, uint8_t clen);




//--------------------------------------------------------------
// MAIN
//--------------------------------------------------------------
int main(void)
{
	Status_t old_status = EMULATOR_STOPPED;

	// init system
	p_init();

	// init gameboy
	gameboy_init();

	// load ini file from sdcard
	p_sd_load_ini();

	// draw gui
	p_clear_screen();
	p_show_gui();
	p_show_control();
	p_show_control2();

	// start cartridge nr.0 from flash
	gameboy_boot_flash(0);

	// show info
	p_show_cartridge_info();


	// MAIN loop
	while(1)
	{
		// check if status changed
		if(GB.status != old_status) {
			old_status = GB.status;
			// redraw status
			p_show_emu_status();
			p_show_load_status();
			if(GB.status == EMULATOR_RUNNING) p_show_error_nr(0);
		}

		if(GB.status == EMULATOR_RUNNING) {
			// execute single gameboy instruction
			gameboy_single_step();

			// show fps
			p_show_fps();
		}
		else if(GB.status == EMULATOR_ERROR) {
			GB.status = EMULATOR_STOPPED;
			p_show_error_nr(GB.err_nr);
		}
		else if(GB.status == EMULATOR_LOAD_UART) {
			p_receive_data();
		}
		else if(GB.status == EMULATOR_LOAD_SD) {
			p_sd_loop();
		}

		// check usb keyboard
		p_check_usb_keyboard();
	}
}



//--------------------------------------------------------------
// init system
//--------------------------------------------------------------
void p_init(void)
{
	// init hw
	UB_System_Init();
	UB_Uart_Init();
	UB_Button_Init();
	UB_Led_Init();

	// init lcd
	UB_LCD_Init();
	UB_LCD_LayerInit_Fullscreen();
	UB_LCD_SetLayer_1();
	UB_LCD_FillLayer(0);
	UB_LCD_SetLayer_2();
	UB_LCD_FillLayer(0);

	// init usb
	UB_USB_HID_HOST_Init();

	// init fatfs
	UB_Fatfs_Init();

	HAL_Delay(100);
}


//--------------------------------------------------------------
// clear screen
//--------------------------------------------------------------
void p_clear_screen(void)
{
	UB_LCD_SetLayer_1();
	UB_LCD_FillLayer(GB.ini.bgcol1);
	UB_LCD_SetLayer_2();
	UB_LCD_FillLayer(GB.ini.bgcol1);
}

//--------------------------------------------------------------
// redraw complete screen
//--------------------------------------------------------------
void p_redraw_screen(void)
{
	// clear screen
	UB_LCD_FillLayer(GB.ini.bgcol1);

	p_show_emu_status();

	p_show_gui();
	p_show_control();
	p_show_control2();
	p_show_cartridge_info();
	p_show_key_status();
}

//--------------------------------------------------------------
// draw fps
//--------------------------------------------------------------
void p_show_fps(void)
{
	uint16_t posx;
	uint16_t posy;

	if(GB.frame.flag != 0) {
		GB.frame.flag = 0;

		if(GB.screen.mode <= 1) {
			posx = GB_LCD_START_X;
			posy = GB_LCD_START_Y + GB_LCD_HEIGHT + 10;
		}
		else if(GB.screen.mode == 2) {
			posx = GB_LCD_START_X + GB_LCD_WIDTH + 10;
			posy = GB_LCD_START_Y + (GB_LCD_HEIGHT<<1) - 50;
		}
		else {
			posx = GB_LCD_START_X + (GB_LCD_WIDTH<<1) + 20;
			posy = GB_LCD_START_Y + 10;
		}

		sprintf(sbuffer,"FPS:%03d",GB.frame.fps);
		UB_Font_DrawString(posx, posy, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
}

//--------------------------------------------------------------
// show umulator status
//--------------------------------------------------------------
void p_show_emu_status(void)
{
	uint16_t posx;
	uint16_t posy;

	if(GB.screen.mode <= 1) {
		posx = GB_LCD_START_X;
		posy = GB_LCD_START_Y + GB_LCD_HEIGHT + 30;
	}
	else if(GB.screen.mode == 2) {
		posx = GB_LCD_START_X + GB_LCD_WIDTH + 10;
		posy = GB_LCD_START_Y + (GB_LCD_HEIGHT<<1) - 30;
	}
	else {
		posx = GB_LCD_START_X + (GB_LCD_WIDTH<<1) + 20;
		posy = GB_LCD_START_Y + 30;
	}

	if(GB.status == EMULATOR_RUNNING) {
		UB_Font_DrawString(posx, posy, "running", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else if((GB.status == EMULATOR_LOAD_UART) || (GB.status == EMULATOR_LOAD_SD)) {
		UB_Font_DrawString(posx, posy, "loading", &Arial_7x10, GB.ini.bgcol1, GB.ini.fontcol1);
	}
	else {
		UB_Font_DrawString(posx, posy, "stopped", &Arial_7x10, GB.ini.bgcol1, GB.ini.fontcol1);
	}
}

//--------------------------------------------------------------
// draw gui
//--------------------------------------------------------------
void p_show_gui(void)
{
	uint16_t posx = GB_LCD_START_X + GB_LCD_WIDTH + 20;
	uint16_t posy = GB_LCD_START_Y + 5;

	if((GB.screen.mode & 0x01) != 0) return;

	UB_Font_DrawString(posx, posy, "STM32F746 Gameboy emulator", &Arial_10x15, GB.ini.fontcol2, GB.ini.bgcol1);

	sprintf(sbuffer,"Version:%s",GB_EMULATOR_VERSION);
	UB_Font_DrawString(posx, posy+20, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);

	UB_Font_DrawString(posx, posy+30, "Author: Uwe Becker", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);

	sprintf(sbuffer,"Date:%s",GB_EMULATOR_DATE);
	UB_Font_DrawString(posx, posy+40, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
}

//--------------------------------------------------------------
// draw controls
//--------------------------------------------------------------
void p_show_control(void)
{
	uint16_t posx = GB_LCD_START_X + GB_LCD_WIDTH + 20;
	uint16_t posy = GB_LCD_START_Y + 70;

	if((GB.screen.mode & 0x01) != 0) return;


	UB_Font_DrawString(posx, posy, "Controls-I", &Arial_10x15, GB.ini.fontcol2, GB.ini.bgcol1);

	UB_Font_DrawString(posx, posy+20, "Cursor: w,a,s,d", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+30, "Btn-A: o", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+40, "Btn-B: p", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+50, "START: return", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+60, "SELECT: space", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
}

//--------------------------------------------------------------
// draw controlls2
//--------------------------------------------------------------
void p_show_control2(void)
{
	uint16_t posx = GB_LCD_START_X + GB_LCD_WIDTH + 175;
	uint16_t posy = GB_LCD_START_Y + 70;

	UB_Font_DrawString(posx, posy, "Controls-II", &Arial_10x15, GB.ini.fontcol2, GB.ini.bgcol1);

	UB_Font_DrawString(posx, posy+20, "Games: F1..F4", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+30, "start/stop: ESC", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+40, "load [SD]: F8", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+50, "BG color: F9", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+60, "OBJ color: F10", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+70, "screenshot: F11", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+80, "load [UART]: F12", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	UB_Font_DrawString(posx, posy+90, "screen size: TAB", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	sprintf(sbuffer,"speed: +/- [%03d]",GB.timing.target_fps);
	UB_Font_DrawString(posx, posy+100, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
}

//--------------------------------------------------------------
// draw cartridge info
//--------------------------------------------------------------
void p_show_cartridge_info(void)
{
	uint16_t posx = GB_LCD_START_X + GB_LCD_WIDTH + 20;
	uint16_t posy = GB_LCD_START_Y + 160;

	if(GB.screen.mode == 3) return;

	UB_Font_DrawString(posx, posy, "Game infos", &Arial_10x15, GB.ini.fontcol2, GB.ini.bgcol1);

	if(GB.mem_ctrl.logo_check == 0) {
		UB_Font_DrawString(posx, posy+20, "Nintendo Logo: ok   ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else {
		UB_Font_DrawString(posx, posy+20, "Nintendo Logo: error", &Arial_7x10, GB.ini.bgcol1, GB.ini.fontcol1);
	}

	sprintf(sbuffer,"Title: %s                ",GB.mem_ctrl.title);
	UB_Font_DrawString(posx, posy+30, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);

	sprintf(sbuffer,"MBC type: %d",GB.mem_ctrl.type);
	UB_Font_DrawString(posx, posy+40, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);

	sprintf(sbuffer,"Rom size: %d",GB.mem_ctrl.rom_size);
	UB_Font_DrawString(posx, posy+50, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);

	sprintf(sbuffer,"Ram size: %d",GB.mem_ctrl.ram_size);
	UB_Font_DrawString(posx, posy+60, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);

	if(SUPPORTED_MBC_VERSION == 0) {
		UB_Font_DrawString(300, 260, "without MBC support", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else {
		sprintf(sbuffer,"MBC type: %d support",SUPPORTED_MBC_VERSION);
		UB_Font_DrawString(300, 260, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
}


//--------------------------------------------------------------
// draw loading status
//--------------------------------------------------------------
void p_show_load_status(void)
{
	uint16_t posx = GB_LCD_START_X;
	uint16_t posy = GB_LCD_START_Y + GB_LCD_HEIGHT + 50;

	if(GB.screen.mode > 1) return;

	if(GB.load.status == LOADING_WAITING) {
		UB_Font_DrawString(posx, posy, "start transfer at CN14", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else if(GB.load.status == LOADING_RUNNING) {
		UB_Font_DrawString(posx, posy, "receiving data...     ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else if(GB.load.status == LOADING_TIMEOUT) {
		UB_Font_DrawString(posx, posy, "UART timeout error    ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
		GB.load.status = LOADING_DISABLE;
	}
	else if(GB.load.status == LOADING_ERROR) {
		UB_Font_DrawString(posx, posy, "UART data size error  ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
		GB.load.status = LOADING_DISABLE;
	}
	else if(GB.load.status == LOADING_DISABLE) {
		UB_Font_DrawString(posx, posy, "                      ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
}


//--------------------------------------------------------------
// draw error msg
//--------------------------------------------------------------
void p_show_error_nr(uint8_t nr)
{
	uint16_t posx = GB_LCD_START_X;
	uint16_t posy = GB_LCD_START_Y + GB_LCD_HEIGHT + 70;

	if(GB.screen.mode > 1) return;

	if(nr == ERROR_NONE) {
		UB_Font_DrawString(posx, posy, "                       ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else if(nr == ERROR_OPCODE) {
		sprintf(sbuffer,"opcode err adr: %04x",z80.reg.pc);
		UB_Font_DrawString(posx, posy, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else if(nr == ERROR_LOGO) {
		UB_Font_DrawString(posx, posy, "logo error             ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else if(nr == ERROR_MBC) {
		UB_Font_DrawString(posx, posy, "MBC not supported      ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
}

//--------------------------------------------------------------
// draw keyboard status
//--------------------------------------------------------------
void p_show_key_status(void)
{
	uint16_t posx = GB_LCD_START_X;
	uint16_t posy = 260;

	if(GB.screen.mode > 1) return;

	if(GB.key.status == USB_HID_KEYBOARD_CONNECTED) {
		UB_Font_DrawString(posx, posy, "USB keyboard connected             ", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol1);
	}
	else {
		UB_Font_DrawString(posx, posy, "please connect USB keyboard at CN13", &Arial_7x10, GB.ini.bgcol1, GB.ini.fontcol1);
	}
}

//--------------------------------------------------------------
// handle usb keyboard
//--------------------------------------------------------------
void p_check_usb_keyboard(void)
{
	USB_HID_HOST_STATUS_t status;
	uint8_t key_anz;
	uint8_t key1, key2 = 0;
	uint8_t a_key1, a_key2 = 0;
	static uint8_t key_blocked = 0;
	uint8_t keys_pressed = 0;

	if(GB.key.delay_cnt >= 5) {
		GB.key.delay_cnt = 0;

		// execute usb handler
		status = UB_USB_HID_HOST_Do();

		// check connection status
		if(status == USB_HID_KEYBOARD_CONNECTED) {
			if(GB.key.status != status) {
				// new connection
				GB.key.status = status;
				p_show_key_status();
			}

			// check how many keys pressed
			key_anz = UB_USB_HID_HOST_GetKeyAnz();
			if(key_anz > 0) {
				if(GB.ini.dbg_msg != 0) {
					key1 = USB_KEY_DATA.akt_key1;
					sprintf(sbuffer,"Keycode = %d",key1);
					UB_Uart_SendString(COM_1, sbuffer,CRLF);
				}

				// read key codes
				key1 = USB_KEY_DATA.akt_key1;
				a_key1 = USB_KEY_DATA.ascii_key1;
				if(key_anz == 2) {
					key2 = USB_KEY_DATA.akt_key2;
					a_key2 = USB_KEY_DATA.ascii_key2;
				}

				//--------------------------------------------------------------
				// gameboy cursor + buttons
				//--------------------------------------------------------------
				GB.key.code_cursor=KEYCODE_CURSOR_NONE;
				GB.key.code_btn=KEYCODE_BTN_NONE;

				if((key1 == GB.ini.keytable[2]) || (key2 == GB.ini.keytable[2])) {GB.key.code_cursor=KEYCODE_CURSOR_LEFT;keys_pressed++;}
				if((key1 == GB.ini.keytable[3]) || (key2 == GB.ini.keytable[3])) {GB.key.code_cursor=KEYCODE_CURSOR_RIGHT;keys_pressed++;}
				if((key1 == GB.ini.keytable[0]) || (key2 == GB.ini.keytable[0])) {GB.key.code_cursor=KEYCODE_CURSOR_UP;keys_pressed++;}
				if((key1 == GB.ini.keytable[1]) || (key2 == GB.ini.keytable[1])) {GB.key.code_cursor=KEYCODE_CURSOR_DOWN;keys_pressed++;}

				if((key1 == GB.ini.keytable[6]) || (key2 == GB.ini.keytable[6])) {GB.key.code_btn=KEYCODE_BTN_START;keys_pressed++;}
				if((key1 == GB.ini.keytable[7]) || (key2 == GB.ini.keytable[7])) {GB.key.code_btn=KEYCODE_BTN_SELECT;keys_pressed++;}
				if((key1 == GB.ini.keytable[4]) || (key2 == GB.ini.keytable[4])) {GB.key.code_btn=KEYCODE_BTN_A;keys_pressed++;}
				if((key1 == GB.ini.keytable[5]) || (key2 == GB.ini.keytable[5])) {GB.key.code_btn=KEYCODE_BTN_B;keys_pressed++;}

				if((key_anz == keys_pressed) && (GB.status == EMULATOR_RUNNING)) return; // exit function if no other key is pressed

				//--------------------------------------------------------------
				// user interface keys
				//--------------------------------------------------------------

				if((key1 == 66) && (key_blocked == 0)) { // F9
					// change background palette
					key_blocked = 1;
					GB.ini.use_sdcard_colors = 0;
					GB.col.bg_col_index++;
					if(GB.col.bg_col_index>7) GB.col.bg_col_index=0;
					gameboy_set_palette(0, GB.col.bg_col_value);
				}
				else if((key1 == 67) && (key_blocked == 0)) { // F10
					// change sprite palette
					key_blocked = 1;
					GB.ini.use_sdcard_colors = 0;
					GB.col.obj_col_index++;
					if(GB.col.obj_col_index>7) GB.col.obj_col_index=0;
					gameboy_set_palette(1, GB.col.obj0_col_value);
					gameboy_set_palette(2, GB.col.obj1_col_value);
				}
				else if((key1 == 58) && (key_blocked == 0)) { // F1
					// start rom nr.0
					key_blocked = 1;
					gameboy_boot_flash(0);
					p_show_cartridge_info();
				}
				else if((key1 == 59) && (key_blocked == 0)) { // F2
					// start rom nr.1
					key_blocked = 1;
					gameboy_boot_flash(1);
					p_show_cartridge_info();
				}
				else if((key1 == 60) && (key_blocked == 0)) { //F3
					// start rom nr.2
					key_blocked = 1;
					gameboy_boot_flash(2);
					p_show_cartridge_info();
				}
				else if((key1 == 61) && (key_blocked == 0)) { //F4
					// start rom nr.3
					key_blocked = 1;
					gameboy_boot_flash(3);
					p_show_cartridge_info();
				}
				else if((key1 == 41) && (key_blocked == 0)) { // ESC
					// start/stop
					key_blocked = 1;
					if(GB.status == EMULATOR_RUNNING) {
						GB.status = EMULATOR_STOPPED;
					}
					else {
						GB.status = EMULATOR_RUNNING;
					}
				}
				else if((key1 == 65) && (key_blocked == 0)) { //F8
					// load rom from SD-card
					key_blocked = 1;
					GB.status = EMULATOR_LOAD_SD;
					GB.load_sd.status = SD_LOADING_DISABLE;
					GB.load_sd.current_item = 0;
					GB.load_sd.first_line = 0;
					GB.load_sd.items = 0;
					GB.load_sd.flags |= 0x01; // refresh loading screen
				}
				else if((key1 == 81) && (key_blocked == 0)) { // down
					if(GB.load_sd.status == SD_LOADING_SELECT) {
						key_blocked = 1;
						if(GB.load_sd.current_item+1 < GB.load_sd.items) {
							GB.load_sd.current_item++;
							if(GB.load_sd.current_item > 5) {
								GB.load_sd.first_line++;
							}
							GB.load_sd.flags |= 0x01; // refresh loading screen
						}
					}
				}
				else if((key1 == 82) && (key_blocked == 0)) { // up
					if(GB.load_sd.status == SD_LOADING_SELECT) {
						key_blocked = 1;
						if(GB.load_sd.current_item > 0) {
							GB.load_sd.current_item--;
							if(GB.load_sd.first_line > 0) {
								GB.load_sd.first_line--;
							}
							GB.load_sd.flags |= 0x01; // refresh loading screen
						}
					}
				}
				else if((key1 == 79) && (key_blocked == 0)) { // right
					if(GB.load_sd.status == SD_LOADING_SELECT) {
						key_blocked = 1;
						GB.load_sd.status = SD_LOADING_COPY;
					}
				}
				else if((key1 == 43) && (key_blocked == 0)) { // TAB
					key_blocked = 1;
					// change screen mode
					GB.screen.mode++;
					if(GB.screen.mode>3) GB.screen.mode=0;
					gameboy_set_screenmode();
					p_redraw_screen();
				}
				else if((key1 == 48) && (key_blocked == 0)) { // +
					key_blocked = 1;
					// increase eumulation speed
					GB.timing.target_fps += 5;
					if(GB.timing.target_fps > 200) GB.timing.target_fps = 200;
					p_show_control2();
				}
				else if((key1 == 56) && (key_blocked == 0)) { // -
					key_blocked = 1;
					// decrease eumulation speed
					GB.timing.target_fps -= 5;
					if(GB.timing.target_fps < 10) GB.timing.target_fps = 10;
					p_show_control2();
				}
				else if((key1 == 68) && (key_blocked == 0)) { // F11
					key_blocked = 1;
					GB.status = EMULATOR_STOPPED;
					p_send_Screen();
				}
				else if((key1 == 69) && (key_blocked == 0)) { // F12
					// load rom from UART
					key_blocked = 1;
					GB.status = EMULATOR_LOAD_UART;
					UB_Uart_ReceiveCartridge(0); // enable receiving mode
					GB.load.status = LOADING_WAITING;
					GB.load.timeout_cnt = 0;
					GB.load.timeout_ovf = 50000000;
					GB.load.rom_size = 0;
				}
			}
			else {
				// no key pressed
				GB.key.code_cursor=KEYCODE_CURSOR_NONE;
				GB.key.code_btn=KEYCODE_BTN_NONE;
				key_blocked = 0;
			}
		}
		else {
			// usb keyboard not connected
			if(GB.key.status != status) {
				GB.key.status = status;
				p_show_key_status();
				GB.key.code_cursor=KEYCODE_CURSOR_NONE;
				GB.key.code_btn=KEYCODE_BTN_NONE;
				key_blocked = 0;
			}
		}
	}
}


//--------------------------------------------------------------
// receiving cartridge data from uart
//--------------------------------------------------------------
void p_receive_data(void)
{
	uint32_t rx_adr = 0;

	rx_adr = UB_Uart_ReceiveCartridge(1); // poll received bytes
	if(rx_adr != GB.load.rom_size) {
		// new data received
		if(GB.load.status == LOADING_WAITING) {
			GB.load.status = LOADING_RUNNING;
			GB.load.timeout_ovf = 5000000;
			p_show_load_status();
		}
		GB.load.rom_size = rx_adr;
		GB.load.timeout_cnt = 0;

		if(GB.load.rom_size > ROM_SIZE) {
			GB.load.status = LOADING_ERROR;
			UB_Uart_ReceiveCartridge(2); // disable receiving mode
			GB.status = EMULATOR_STOPPED;
		}
	}
	else {
		GB.load.timeout_cnt++;
	}

	// check timeout
	if(GB.load.timeout_cnt > GB.load.timeout_ovf) {
		GB.load.timeout_cnt = 0;
		UB_Uart_ReceiveCartridge(2); // disable receiving mode
		if(GB.load.status == LOADING_RUNNING) {
			GB.load.status = LOADING_DISABLE;
			gameboy_boot_ram();
			p_show_cartridge_info();
		}
		else {
			GB.load.status = LOADING_TIMEOUT;
			GB.status = EMULATOR_STOPPED;
		}
	}
}

//--------------------------------------------------------------
// send screenshot
//--------------------------------------------------------------
void p_send_Screen(void)
{
  uint32_t n,adr;
  uint16_t x,y,color;
  uint8_t r,g,b;

  // send bmp header
  for(n=0;n<54;n++) {
    UB_Uart_SendByte(COM_1,BMP_HEADER[n]);
  }

  // set screen buffer to send
  adr=LCD_CurrentFrameBuffer;

  // send picture data
  for(y=0;y<LCD_MAXY;y++) {
    for(x=0;x<LCD_MAXX;x++) {
      n=(LCD_MAXY-y-1)*(LCD_MAXX*2)+(x*2);
      color=*(volatile uint16_t*)(adr+n);
      r=((color&0xF800)>>8);  // 5bit red
      g=((color&0x07E0)>>3);  // 6bit green
      b=((color&0x001F)<<3);  // 5bit blue
      UB_Uart_SendByte(COM_1,b);
      UB_Uart_SendByte(COM_1,g);
      UB_Uart_SendByte(COM_1,r);
    }
  }
}

//--------------------------------------------------------------
// sd-card functions
//--------------------------------------------------------------
void p_sd_loop(void)
{
	uint16_t posx = GB_LCD_START_X;
	uint16_t posy = GB_LCD_START_Y;
	FATFS_t status;

	if((GB.load_sd.flags & 0x01) != 0) {
		// refresh directory
		GB.load_sd.flags &= ~0x01;

		UB_Graphic2D_DrawFullRectDMA(posx, posy, GB_LCD_WIDTH, GB_LCD_HEIGHT, GB.ini.bgcol2);
		status = UB_Fatfs_CheckMedia(MMC_1);
		if(status != FATFS_OK) {
			UB_Font_DrawString(posx, posy+10, "insert sd card", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
			UB_Font_DrawString(posx, posy+20, "try again...", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
			GB.load_sd.status = SD_LOADING_ERROR;
			return;
		}
		status = UB_Fatfs_Mount(MMC_1);
		if(status != FATFS_OK) {
			UB_Font_DrawString(posx, posy+10, "mounting error", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
			UB_Font_DrawString(posx, posy+20, "try again...", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
			UB_Fatfs_UnMount(MMC_1);
			GB.load_sd.status = SD_LOADING_ERROR;
			return;
		}
		p_sd_read_dir();
	}
	else if(GB.load_sd.status == SD_LOADING_COPY) {
		// load file from sdcard and copy into sdram
		if((GB.load_sd.flags & 0x02) != 0) {
			GB.load_sd.flags &= ~0x02;
			p_sd_load_file();
		}
		else {
			GB.load_sd.status = SD_LOADING_SELECT;
		}
	}
}

//--------------------------------------------------------------
// read directory from sdcard
//--------------------------------------------------------------
void p_sd_read_dir(void)
{
	FRESULT rc;
	DIR dir;
	FILINFO fno;
	uint8_t ok = 0;
	uint8_t is_file = 0;
	uint16_t posx = GB_LCD_START_X;
	uint16_t posy = GB_LCD_START_Y +10;
	uint8_t lines = 0;
	uint32_t items = 0;
	uint8_t timeout = 0;



	rc = f_opendir(&dir, "0:");
	if(rc == FR_OK) {
		do {
			// read entry
			rc = f_readdir(&dir, &fno);
			if(rc != FR_OK || fno.fname[0] == 0) {
				// end of directory
				ok=1;
			}
			else {
				if(fno.fname[0] != '.') {
					if(items >= GB.load_sd.first_line) {
						if(fno.fattrib & AM_DIR) {
							sprintf(sbuffer,"<DIR> %s",fno.fname);
							is_file = 0;
						}
						else {
							sprintf(sbuffer,"      %s",fno.fname);
							is_file = 1;
						}
						if(items == GB.load_sd.current_item) {
							UB_Font_DrawString(posx, posy, sbuffer, &Arial_7x10, GB.ini.bgcol2, GB.ini.fontcol1);
							if(is_file == 1) {
								sprintf(GB.load_sd.name, "%s", fno.fname);
								GB.load_sd.flags |= 0x02; // selected obj is a file
							}
							else {
								GB.load_sd.flags &= ~0x02; // selected obj is a directory
							}
						}
						else {
							UB_Font_DrawString(posx, posy, sbuffer, &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
						}
						posy+=10;
						lines++;
						if(lines > 10) ok=1;
					}
					items++;
				}
				else {
					timeout++;
					if(timeout>100) ok=1;
				}
			}
		}while(ok==0);
		if(items > 0) {
			GB.load_sd.items = items;
			GB.load_sd.status = SD_LOADING_SELECT;
			UB_Font_DrawString(posx, posy+10, "cursor: up,down,right", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
		}
		else {
			UB_Font_DrawString(posx, posy+10, "drive empty", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
			UB_Font_DrawString(posx, posy+20, "try again...", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
			GB.load_sd.status = SD_LOADING_ERROR;
		}
	}
	else {
		UB_Font_DrawString(posx, posy+10, "drive empty", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
		UB_Font_DrawString(posx, posy+20, "try again...", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
		GB.load_sd.status = SD_LOADING_ERROR;
	}

    UB_Fatfs_UnMount(MMC_1);
}

//--------------------------------------------------------------
// load file from sdcard into ram or sdram
//--------------------------------------------------------------
void p_sd_load_file(void)
{
	FATFS_t status;
	FIL fp;
	uint32_t fsize;
	uint32_t lsize = 0;
	uint32_t ram_pos = 0;
	uint32_t rlen;
	uint8_t ok = 0;
	uint32_t BLOCK_SIZE = 512;
	uint8_t *sdptr;
	uint16_t posx = GB_LCD_START_X;
	uint16_t posy = GB_LCD_START_Y;


	status = UB_Fatfs_CheckMedia(MMC_1);
	if(status == FATFS_OK) {
		status = UB_Fatfs_Mount(MMC_1);
		if(status == FATFS_OK) {
			status = UB_Fatfs_OpenFile(&fp, GB.load_sd.name, F_RD);
			if(status == FATFS_OK) {
				fsize = UB_Fatfs_FileSize(&fp);
				if(fsize > 0) {
					if(fsize <= ROM_SIZE) {
						// load file from sdcard and copy into memory
						do {
							status = UB_Fatfs_ReadBlock(&fp, &z80.memory[ram_pos], BLOCK_SIZE, &rlen);
							ram_pos += rlen;
							lsize += rlen;
							if(status != FATFS_OK) ok=1;
							if(rlen < BLOCK_SIZE) ok=1;
						}while(ok==0);

						if(lsize == fsize) {
							// complete
							GB.load_sd.status = SD_LOADING_DISABLE;
							gameboy_boot_ram();
							p_show_cartridge_info();
							if(GB.status == EMULATOR_RUNNING) ok=2;
						}
						else {
							GB.status = EMULATOR_ERROR;
							p_show_emu_status();
						}
					}
					else if(fsize < SDRAM_CARTRIDGE_SIZE) {
						// load file from sdcard and copy into sdram
						sdptr = GB.mem_ctrl.sdram;
						do {
							status = UB_Fatfs_ReadBlock(&fp, sdptr, BLOCK_SIZE, &rlen);
							sdptr += rlen;
							lsize += rlen;
							if(status != FATFS_OK) ok=1;
							if(rlen < BLOCK_SIZE) ok=1;
						}while(ok==0);

						if(lsize == fsize) {
							// complete
							GB.load_sd.status = SD_LOADING_DISABLE;
							gameboy_boot_sdram();
							p_show_cartridge_info();
							if(GB.status == EMULATOR_RUNNING) ok=2;
						}
						else {
							GB.status = EMULATOR_ERROR;
							p_show_emu_status();
						}
					}
				}
			}
			UB_Fatfs_CloseFile(&fp);
		}
		UB_Fatfs_UnMount(MMC_1);
	}

	if(ok!=2) {
		UB_Graphic2D_DrawFullRectDMA(posx, posy, GB_LCD_WIDTH, GB_LCD_HEIGHT, GB.ini.bgcol2);
		UB_Font_DrawString(posx, posy+10, "loading error", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
		UB_Font_DrawString(posx, posy+20, "try again...", &Arial_7x10, GB.ini.fontcol1, GB.ini.bgcol2);
		GB.load_sd.status = SD_LOADING_ERROR;
	}
}

//--------------------------------------------------------------
// load ini file from sdcard
//--------------------------------------------------------------
void p_sd_load_ini(void)
{
	FATFS_t status;
	FIL fp;
	uint32_t fsize;
	uint8_t ok = 0;

	status = UB_Fatfs_CheckMedia(MMC_1);
	if(status == FATFS_OK) {
		status = UB_Fatfs_Mount(MMC_1);
		if(status == FATFS_OK) {
			status = UB_Fatfs_OpenFile(&fp, GB_INI_FILE, F_RD);
			if(status == FATFS_OK) {
				fsize = UB_Fatfs_FileSize(&fp);
				if(fsize > 0) {
					do {
						status = UB_Fatfs_ReadString(&fp, sbuffer, SBUFFER_SIZE);
						if(status != FATFS_OK) {
							ok=1;
						}
						else {
							p_check_inistr();
						}
					}while(ok==0);
				}
			}
			UB_Fatfs_CloseFile(&fp);
		}
		UB_Fatfs_UnMount(MMC_1);
	}
}

//--------------------------------------------------------------
// parse ini file
//--------------------------------------------------------------
void p_check_inistr(void)
{
	uint8_t l;

	l=strlen(sbuffer);
	if(l>6) {
		//---------------------------------------------
		if(strstr(sbuffer, "<start>") == sbuffer) {
			if(p_copy_str(l, strlen("<bgcol>")) != 0) {
				if(strstr((const char *)(GB.ini.buf), "gb_ub")) {
					GB.ini.ok = 1;
				}
			}
		}

		if(GB.ini.ok == 0) return;

		if(strstr(sbuffer, "<bg_col_1>") == sbuffer) {
			if(p_copy_str(l, strlen("<bg_col_1>")) != 0) {
				GB.ini.bgcol1 = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<bg_col_2>") == sbuffer) {
			if(p_copy_str(l, strlen("<bg_col_2>")) != 0) {
				GB.ini.bgcol2 = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<font_col_1>") == sbuffer) {
			if(p_copy_str(l, strlen("<font_col_1>")) != 0) {
				GB.ini.fontcol1 = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<font_col_2>") == sbuffer) {
			if(p_copy_str(l, strlen("<font_col_2>")) != 0) {
				GB.ini.fontcol2 = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_bg_col_0>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_bg_col_0>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.bg_table[0] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_bg_col_1>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_bg_col_1>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.bg_table[1] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_bg_col_2>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_bg_col_2>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.bg_table[2] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_bg_col_3>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_bg_col_3>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.bg_table[3] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_spr_col_0>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_spr_col_0>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.obj_table[0] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_spr_col_1>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_spr_col_1>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.obj_table[1] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_spr_col_2>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_spr_col_2>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.obj_table[2] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<gb_spr_col_3>") == sbuffer) {
			if(p_copy_str(l, strlen("<gb_spr_col_3>")) != 0) {
				GB.ini.use_sdcard_colors = 1;
				GB.ini.obj_table[3] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_up>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_up>")) != 0) {
				GB.ini.keytable[0] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_down>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_down>")) != 0) {
				GB.ini.keytable[1] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_left>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_left>")) != 0) {
				GB.ini.keytable[2] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_right>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_right>")) != 0) {
				GB.ini.keytable[3] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_btn_a>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_btn_a>")) != 0) {
				GB.ini.keytable[4] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_btn_b>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_btn_b>")) != 0) {
				GB.ini.keytable[5] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_btn_start>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_btn_start>")) != 0) {
				GB.ini.keytable[6] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<key_btn_select>") == sbuffer) {
			if(p_copy_str(l, strlen("<key_btn_select>")) != 0) {
				GB.ini.keytable[7] = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<dbg_msg>") == sbuffer) {
			if(p_copy_str(l, strlen("<dbg_msg>")) != 0) {
				GB.ini.dbg_msg = atoi((const char *)(GB.ini.buf));
			}
		}
		else if(strstr(sbuffer, "<screenmode>") == sbuffer) {
			if(p_copy_str(l, strlen("<screenmode>")) != 0) {
				GB.screen.mode = atoi((const char *)(GB.ini.buf));
				if(GB.screen.mode > 3) GB.screen.mode = 0;
				gameboy_set_screenmode();
			}
		}
	}
}

//--------------------------------------------------------------
// copy TAG value in string buffer
//--------------------------------------------------------------
uint8_t p_copy_str(uint8_t slen, uint8_t clen)
{
	uint8_t n,m;

	m=slen-((clen+1)*2);

	if(m==0) return 0;

	for(n=0;n<m;n++) {
		GB.ini.buf[n] = sbuffer[n+clen];
	}
	GB.ini.buf[n] = 0;

	return 1;
}
