From: notaz <notasas@gmail.com>
Date: Mon, 2 Mar 2009 23:13:15 +0000 (+0000)
Subject: major menu unification, minor reorganization
X-Git-Tag: v1.85~361
X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=713c9224026b769b8238f898c85b63a17cd5bada;p=picodrive.git

major menu unification, minor reorganization

git-svn-id: file:///home/notaz/opt/svn/PicoDrive@639 be3aeb3a-fb24-0410-a615-afba39da0efa
---

diff --git a/pico/draw_arm.s b/pico/draw_arm.s
index c7e08972..8ecf85a4 100644
--- a/pico/draw_arm.s
+++ b/pico/draw_arm.s
@@ -206,7 +206,7 @@
     cmp     r4, #0xe
     ldrgeb  r4, [r1,#\ofs]
     orrlt   r4, r3, r4
-    orrge   r4, r3, 0x80
+    orrge   r4, r3, #0x80
     strb    r4, [r1,#\ofs]
 0:
 .endm
diff --git a/platform/common/config.c b/platform/common/config.c
index 63c8f00c..961ac92d 100644
--- a/platform/common/config.c
+++ b/platform/common/config.c
@@ -21,44 +21,8 @@ static char *mystrip(char *str);
 #include "emu.h"
 #include <pico/pico.h>
 
-extern menu_entry opt_entries[];
-extern menu_entry opt2_entries[];
-extern menu_entry cdopt_entries[];
-extern menu_entry ctrlopt_entries[];
-extern const int opt_entry_count;
-extern const int opt2_entry_count;
-extern const int cdopt_entry_count;
-extern const int ctrlopt_entry_count;
-#ifdef PSP
-extern menu_entry opt3_entries[];
-extern const int opt3_entry_count;
-#endif
-
-static menu_entry *cfg_opts[] =
-{
-	opt_entries,
-	opt2_entries,
-	cdopt_entries,
-	ctrlopt_entries,
-#ifdef PSP
-	opt3_entries,
-#endif
-};
-
-static const int *cfg_opt_counts[] =
-{
-	&opt_entry_count,
-	&opt2_entry_count,
-	&cdopt_entry_count,
-	&ctrlopt_entry_count,
-#ifdef PSP
-	&opt3_entry_count,
-#endif
-};
-
 #define NL "\r\n"
 
-
 static int seek_sect(FILE *f, const char *section)
 {
 	char line[128], *tmp;
@@ -82,79 +46,10 @@ static int seek_sect(FILE *f, const char *section)
 
 static void custom_write(FILE *f, const menu_entry *me, int no_def)
 {
-	char *str, str24[24];
+	char str24[24];
 
 	switch (me->id)
 	{
-		case MA_OPT_RENDERER:
-			if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_ALT_RENDERER) &&
-				!((defaultConfig.EmuOpt^currentConfig.EmuOpt)&0x80)) return;
-			if (PicoOpt&POPT_ALT_RENDERER)
-				str =
-#ifndef PSP
-				"8bit "
-#endif
-				"fast";
-			else if (currentConfig.EmuOpt&0x80)
-				str =
-#ifndef PSP
-				"16bit "
-#endif
-				"accurate";
-			else
-				str = "8bit accurate";
-			fprintf(f, "Renderer = %s", str);
-			break;
-
-		case MA_OPT_SCALING:
-			if (no_def && defaultConfig.scaling == currentConfig.scaling) return;
-#ifdef __GP2X__
-			switch (currentConfig.scaling) {
-				default: str = "OFF"; break;
-				case 1:  str = "hw horizontal";     break;
-				case 2:  str = "hw horiz. + vert."; break;
-				case 3:  str = "sw horizontal";     break;
-			}
-			fprintf(f, "Scaling = %s", str);
-#endif
-			break;
-		case MA_OPT_FRAMESKIP:
-			if (no_def && defaultConfig.Frameskip == currentConfig.Frameskip) return;
-			if (currentConfig.Frameskip < 0)
-			     strcpy(str24, "Auto");
-			else sprintf(str24, "%i", currentConfig.Frameskip);
-			fprintf(f, "Frameskip = %s", str24);
-			break;
-		case MA_OPT_SOUND_QUALITY:
-			if (no_def && !((defaultConfig.s_PicoOpt^PicoOpt)&POPT_EN_STEREO) &&
-				defaultConfig.s_PsndRate == PsndRate) return;
-			str = (PicoOpt&POPT_EN_STEREO)?"stereo":"mono";
-			fprintf(f, "Sound Quality = %i %s", PsndRate, str);
-			break;
-		case MA_OPT_REGION:
-			if (no_def && defaultConfig.s_PicoRegion == PicoRegionOverride &&
-				defaultConfig.s_PicoAutoRgnOrder == PicoAutoRgnOrder) return;
-			strncpy(str24, me_region_name(PicoRegionOverride, PicoAutoRgnOrder), 23); str24[23] = 0;
-			fprintf(f, "Region = %s", mystrip(str24));
-			break;
-		case MA_OPT_CONFIRM_STATES:
-			if (no_def && !((defaultConfig.EmuOpt^currentConfig.EmuOpt)&(5<<9))) return;
-			switch ((currentConfig.EmuOpt >> 9) & 5) {
-				default: str = "OFF";    break;
-				case 1:  str = "writes"; break;
-				case 4:  str = "loads";  break;
-				case 5:  str = "both";   break;
-			}
-			fprintf(f, "Confirm savestate = %s", str);
-			break;
-		case MA_OPT_CPU_CLOCKS:
-			if (no_def && defaultConfig.CPUclock == currentConfig.CPUclock) return;
-#ifdef __GP2X__
-			fprintf(f, "GP2X CPU clocks = %i", currentConfig.CPUclock);
-#elif defined(PSP)
-			fprintf(f, "PSP CPU clock = %i", currentConfig.CPUclock);
-#endif
-			break;
 		case MA_OPT2_GAMMA:
 			if (no_def && defaultConfig.gamma == currentConfig.gamma) return;
 			fprintf(f, "Gamma correction = %.3f", (double)currentConfig.gamma / 100.0);
@@ -299,7 +194,7 @@ int config_writesect(const char *fname, const char *section)
 	FILE *fo = NULL, *fn = NULL; // old and new
 	int no_defaults = 0; // avoid saving defaults
 	menu_entry *me;
-	int t, i, tlen, ret;
+	int t, tlen, ret;
 	char line[128], *tmp;
 
 	if (section != NULL)
@@ -368,30 +263,34 @@ write:
 	if (section != NULL)
 		fprintf(fn, "[%s]" NL, section);
 
-	for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]); t++)
+	me = me_list_get_first();
+	while (me != NULL)
 	{
-		me = cfg_opts[t];
-		tlen = *(cfg_opt_counts[t]);
-		for (i = 0; i < tlen; i++, me++)
-		{
-			if (!me->need_to_save) continue;
-			if ((me->beh != MB_ONOFF && me->beh != MB_RANGE) || me->name == NULL)
-				custom_write(fn, me, no_defaults);
-			else if (me->beh == MB_ONOFF) {
-				if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
-					fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
-			} else if (me->beh == MB_RANGE) {
-				if (!no_defaults || (*(int *)me->var ^ default_var(me)))
-					fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
-			}
-		}
+		int dummy;
+		if (!me->need_to_save)
+			goto next;
+		if (me->beh == MB_OPT_ONOFF) {
+			if (!no_defaults || ((*(int *)me->var ^ default_var(me)) & me->mask))
+				fprintf(fn, "%s = %i" NL, me->name, (*(int *)me->var & me->mask) ? 1 : 0);
+		} else if (me->beh == MB_OPT_RANGE) {
+			if (!no_defaults || (*(int *)me->var ^ default_var(me)))
+				fprintf(fn, "%s = %i" NL, me->name, *(int *)me->var);
+		} else if (me->name != NULL && me->generate_name != NULL) {
+			strncpy(line, me->generate_name(0, &dummy), sizeof(line));
+			line[sizeof(line) - 1] = 0;
+			mystrip(line);
+			fprintf(fn, "%s = %s" NL, me->name, line);
+		} else
+			custom_write(fn, me, no_defaults);
+next:
+		me = me_list_get_next();
 	}
 
 	/* input: save device names */
 	for (t = 0; t < IN_MAX_DEVS; t++)
 	{
 		const int  *binds = in_get_dev_binds(t);
-		const char *name =  in_get_dev_name(t, 0);
+		const char *name =  in_get_dev_name(t, 0, 0);
 		if (binds == NULL || name == NULL)
 			continue;
 
@@ -402,7 +301,7 @@ write:
 	for (t = 0; t < IN_MAX_DEVS; t++)
 	{
 		const int *binds = in_get_dev_binds(t);
-		const char *name = in_get_dev_name(t, 0);
+		const char *name = in_get_dev_name(t, 0, 0);
 		char strbind[16];
 		int count;
 
@@ -449,7 +348,7 @@ int config_writelrom(const char *fname)
 	int size;
 	FILE *f;
 
-	if (strlen(loadedRomFName) == 0) return -1;
+	if (strlen(rom_fname_loaded) == 0) return -1;
 
 	f = fopen(fname, "r");
 	if (f != NULL)
@@ -482,7 +381,7 @@ int config_writelrom(const char *fname)
 		fwrite(old_data, 1, optr - old_data, f);
 		free(old_data);
 	}
-	fprintf(f, "LastUsedROM = %s" NL, loadedRomFName);
+	fprintf(f, "LastUsedROM = %s" NL, rom_fname_loaded);
 	fclose(f);
 	return 0;
 }
@@ -513,9 +412,9 @@ int config_readlrom(const char *fname)
 		tmp++;
 		mystrip(tmp);
 
-		len = sizeof(loadedRomFName);
-		strncpy(loadedRomFName, tmp, len);
-		loadedRomFName[len-1] = 0;
+		len = sizeof(rom_fname_loaded);
+		strncpy(rom_fname_loaded, tmp, len);
+		rom_fname_loaded[len-1] = 0;
 		ret = 0;
 		break;
 	}
@@ -773,7 +672,7 @@ static unsigned char input_dev_map[IN_MAX_DEVS];
 static void parse(const char *var, const char *val)
 {
 	menu_entry *me;
-	int t, i, tlen, tmp, ret = 0;
+	int tmp, ret = 0;
 
 	if (strcasecmp(var, "LastUsedROM") == 0)
 		return; /* handled elsewhere */
@@ -814,30 +713,30 @@ static void parse(const char *var, const char *val)
 		return;
 	}
 
-	for (t = 0; t < sizeof(cfg_opts) / sizeof(cfg_opts[0]) && ret == 0; t++)
+	me = me_list_get_first();
+	while (me != NULL && ret == 0)
 	{
-		me = cfg_opts[t];
-		tlen = *(cfg_opt_counts[t]);
-		for (i = 0; i < tlen && ret == 0; i++, me++)
-		{
-			if (!me->need_to_save) continue;
-			if (me->name != NULL) {
-				if (strcasecmp(var, me->name) != 0) continue; // surely not this one
-				if (me->beh == MB_ONOFF) {
-					tmp = atoi(val);
-					if (tmp) *(int *)me->var |=  me->mask;
-					else     *(int *)me->var &= ~me->mask;
-					return;
-				} else if (me->beh == MB_RANGE) {
-					tmp = atoi(val);
-					if (tmp < me->min) tmp = me->min;
-					if (tmp > me->max) tmp = me->max;
-					*(int *)me->var = tmp;
-					return;
-				}
+		if (!me->need_to_save)
+			goto next;
+		if (me->name != NULL && me->name[0] != 0) {
+			if (strcasecmp(var, me->name) != 0)
+				goto next; /* surely not this one */
+			if (me->beh == MB_OPT_ONOFF) {
+				tmp = atoi(val);
+				if (tmp) *(int *)me->var |=  me->mask;
+				else     *(int *)me->var &= ~me->mask;
+				return;
+			} else if (me->beh == MB_OPT_RANGE) {
+				tmp = atoi(val);
+				if (tmp < me->min) tmp = me->min;
+				if (tmp > me->max) tmp = me->max;
+				*(int *)me->var = tmp;
+				return;
 			}
-			ret = custom_read(me, var, val);
 		}
+		ret = custom_read(me, var, val);
+next:
+		me = me_list_get_next();
 	}
 	if (!ret) lprintf("config_readsect: unhandled var: \"%s\"\n", var);
 }
diff --git a/platform/common/emu.c b/platform/common/emu.c
index 58fee619..7aac10b8 100644
--- a/platform/common/emu.c
+++ b/platform/common/emu.c
@@ -16,6 +16,7 @@
 #include "lprintf.h"
 #include "config.h"
 #include "common.h"
+#include "plat.h"
 
 #include <pico/pico_int.h>
 #include <pico/patch.h>
@@ -25,23 +26,21 @@
 
 char *PicoConfigFile = "config.cfg";
 currentConfig_t currentConfig, defaultConfig;
-int rom_loaded = 0;
 char noticeMsg[64] = { 0, };
 int state_slot = 0;
 int config_slot = 0, config_slot_current = 0;
-char loadedRomFName[512] = { 0, };
 int kb_combo_keys = 0, kb_combo_acts = 0;	// keys and actions which need button combos
 int pico_inp_mode = 0;
+int engineState = PGS_Menu;
+
+/* TODO: len checking */
+char rom_fname_reload[512] = { 0, };
+char rom_fname_loaded[512] = { 0, };
+int rom_loaded = 0;
 
 unsigned char *movie_data = NULL;
 static int movie_size = 0;
 
-// provided by platform code:
-extern void emu_noticeMsgUpdated(void);
-extern int  emu_getMainDir(char *dst, int len);
-extern void menu_romload_prepare(const char *rom_name);
-extern void menu_romload_end(void);
-
 
 // utilities
 static void strlwr_(char *string)
@@ -492,8 +491,8 @@ int emu_ReloadRom(char *rom_fname)
 	if (currentConfig.EmuOpt & EOPT_USE_SRAM)
 		emu_SaveLoadGame(1, 1);
 
-	strncpy(loadedRomFName, rom_fname, sizeof(loadedRomFName)-1);
-	loadedRomFName[sizeof(loadedRomFName)-1] = 0;
+	strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);
+	rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;
 	rom_loaded = 1;
 	return 1;
 
@@ -518,8 +517,8 @@ static void romfname_ext(char *dst, const char *prefix, const char *ext)
 	int prefix_len = 0;
 
 	// make save filename
-	p = loadedRomFName+strlen(loadedRomFName)-1;
-	for (; p >= loadedRomFName && *p != PATH_SEP_C; p--); p++;
+	p = rom_fname_loaded + strlen(rom_fname_loaded) - 1;
+	for (; p >= rom_fname_loaded && *p != PATH_SEP_C; p--); p++;
 	*dst = 0;
 	if (prefix) {
 		int len = emu_getMainDir(dst, 512);
@@ -527,7 +526,7 @@ static void romfname_ext(char *dst, const char *prefix, const char *ext)
 		prefix_len = len + strlen(prefix);
 	}
 #ifdef UIQ3
-	else p = loadedRomFName; // backward compatibility
+	else p = rom_fname_loaded; // backward compatibility
 #endif
 	strncpy(dst + prefix_len, p, 511-prefix_len);
 	dst[511-8] = 0;
diff --git a/platform/common/emu.h b/platform/common/emu.h
index 15643b9d..516c77f6 100644
--- a/platform/common/emu.h
+++ b/platform/common/emu.h
@@ -50,11 +50,14 @@ extern char noticeMsg[64];
 extern int state_slot;
 extern int config_slot, config_slot_current;
 extern unsigned char *movie_data;
-extern char loadedRomFName[512];		// last loaded ROM filename
 extern int kb_combo_keys, kb_combo_acts;	// keys and actions which need button combos
 extern int pico_inp_mode;
 
+extern char rom_fname_reload[512];		// ROM to try loading on next PGS_ReloadRom
+extern char rom_fname_loaded[512];		// currently loaded ROM filename
+
 // engine states
+extern int engineState;
 enum TPicoGameState {
 	PGS_Paused = 1,
 	PGS_Running,
diff --git a/platform/common/input.c b/platform/common/input.c
index a8bad144..8474f360 100644
--- a/platform/common/input.c
+++ b/platform/common/input.c
@@ -380,14 +380,14 @@ int in_menu_wait_any(int timeout_ms)
 }
 
 /* wait for menu input, do autorepeat */
-int in_menu_wait(int interesting)
+int in_menu_wait(int interesting, int autorep_delay_ms)
 {
 	static int inp_prev = 0;
 	static int repeats = 0;
 	int ret, release = 0, wait = 666;
 
 	if (repeats)
-		wait = 33;
+		wait = autorep_delay_ms;
 
 	ret = in_menu_wait_any(wait);
 	if (ret == inp_prev)
@@ -439,14 +439,22 @@ int in_get_dev_bind_count(int dev_id)
 	return in_bind_count(in_devices[dev_id].drv_id);
 }
 
-const char *in_get_dev_name(int dev_id, int must_be_active)
+const char *in_get_dev_name(int dev_id, int must_be_active, int skip_pfix)
 {
+	const char *name, *tmp;
+
 	if (dev_id < 0 || dev_id >= IN_MAX_DEVS)
 		return NULL;
 
 	if (must_be_active && !in_devices[dev_id].probed)
 		return NULL;
-	return in_devices[dev_id].name;
+
+	name = in_devices[dev_id].name;
+	tmp = strchr(name, ':');
+	if (tmp != NULL)
+		name = tmp + 1;
+
+	return name;
 }
 
 /* never returns NULL */
diff --git a/platform/common/input.h b/platform/common/input.h
index 85723f19..5e62f59f 100644
--- a/platform/common/input.h
+++ b/platform/common/input.h
@@ -31,7 +31,7 @@ int  in_update(void);
 void in_set_blocking(int is_blocking);
 int  in_update_keycode(int *dev_id, int *is_down, int timeout_ms);
 int  in_menu_wait_any(int timeout_ms);
-int  in_menu_wait(int interesting);
+int  in_menu_wait(int interesting, int autorep_delay_ms);
 int  in_get_dev_bind_count(int dev_id);
 void in_config_start(void);
 int  in_config_parse_dev(const char *dev_name);
@@ -42,5 +42,5 @@ void in_debug_dump(void);
 
 const int  *in_get_dev_binds(int dev_id);
 const int  *in_get_dev_def_binds(int dev_id);
-const char *in_get_dev_name(int dev_id, int must_be_active);
+const char *in_get_dev_name(int dev_id, int must_be_active, int skip_pfix);
 const char *in_get_key_name(int dev_id, int keycode);
diff --git a/platform/common/menu.c b/platform/common/menu.c
index 8674897c..e3c8cef2 100644
--- a/platform/common/menu.c
+++ b/platform/common/menu.c
@@ -16,30 +16,16 @@
 #include "input.h"
 #include "emu.h"
 #include "plat.h"
+#include "posix.h"
 
+#include <pico/pico_int.h>
 #include <pico/patch.h>
+#include <zlib/zlib.h>
 
-char menuErrorMsg[64] = { 0, };
+#define array_size(x) (sizeof(x) / sizeof(x[0]))
 
-// PicoPad[] format: MXYZ SACB RLDU
-me_bind_action me_ctrl_actions[15] =
-{
-	{ "UP     ", 0x0001 },
-	{ "DOWN   ", 0x0002 },
-	{ "LEFT   ", 0x0004 },
-	{ "RIGHT  ", 0x0008 },
-	{ "A      ", 0x0040 },
-	{ "B      ", 0x0010 },
-	{ "C      ", 0x0020 },
-	{ "A turbo", 0x4000 },
-	{ "B turbo", 0x1000 },
-	{ "C turbo", 0x2000 },
-	{ "START  ", 0x0080 },
-	{ "MODE   ", 0x0800 },
-	{ "X      ", 0x0400 },
-	{ "Y      ", 0x0200 },
-	{ "Z      ", 0x0100 }
-};
+static char static_buff[64];
+char menuErrorMsg[64] = { 0, };
 
 
 #ifndef UIQ3
@@ -64,7 +50,12 @@ static void text_out16_(int x, int y, const char *text, int color)
 		len = 1;
 	}
 	else
-		len = strlen(text);
+	{
+		const char *p;
+		for (p = text; *p != 0 && *p != '\n'; p++)
+			;
+		len = p - text;
+	}
 
 	for (i = 0; i < len; i++)
 	{
@@ -115,7 +106,7 @@ void text_out16(int x, int y, const char *texto, ...)
 }
 
 
-void smalltext_out16(int x, int y, const char *texto, int color)
+static void smalltext_out16_(int x, int y, const char *texto, int color)
 {
 	int i;
 	unsigned char  *src;
@@ -147,19 +138,17 @@ void smalltext_out16(int x, int y, const char *texto, int color)
 	}
 }
 
-void smalltext_out16_lim(int x, int y, const char *texto, int color, int max)
+void smalltext_out16(int x, int y, const char *texto, int color)
 {
-	char    buffer[SCREEN_WIDTH/6+1];
+	char buffer[SCREEN_WIDTH/6+1];
 
-	strncpy(buffer, texto, SCREEN_WIDTH/6);
-	if (max > SCREEN_WIDTH/6) max = SCREEN_WIDTH/6;
-	if (max < 0) max = 0;
-	buffer[max] = 0;
+	strncpy(buffer, texto, sizeof(buffer));
+	buffer[sizeof(buffer) - 1] = 0;
 
-	smalltext_out16(x, y, buffer, color);
+	smalltext_out16_(x, y, buffer, color);
 }
 
-void menu_draw_selection(int x, int y, int w)
+static void menu_draw_selection(int x, int y, int w)
 {
 	int i, h;
 	unsigned short *dst, *dest;
@@ -253,7 +242,7 @@ void menu_init(void)
 }
 
 
-int me_id2offset(const menu_entry *ent, menu_id id)
+static int me_id2offset(const menu_entry *ent, menu_id id)
 {
 	int i;
 	for (i = 0; ent->name; ent++, i++)
@@ -263,13 +252,13 @@ int me_id2offset(const menu_entry *ent, menu_id id)
 	return 0;
 }
 
-void me_enable(menu_entry *entries, menu_id id, int enable)
+static void me_enable(menu_entry *entries, menu_id id, int enable)
 {
 	int i = me_id2offset(entries, id);
 	entries[i].enabled = enable;
 }
 
-int me_count(const menu_entry *ent)
+static int me_count(const menu_entry *ent)
 {
 	int ret;
 
@@ -279,54 +268,11 @@ int me_count(const menu_entry *ent)
 	return ret;
 }
 
-menu_id me_index2id(const menu_entry *ent, int index)
-{
-	const menu_entry *last;
-
-	for (; ent->name; ent++)
-	{
-		if (ent->enabled)
-		{
-			if (index == 0) break;
-			index--;
-		}
-		last = ent;
-	}
-	if (ent->name == NULL)
-		ent = last;
-	return ent->id;
-}
-
-/* TODO rm */
-void me_draw(const menu_entry *entries, int count, int x, int y, me_draw_custom_f *cust_draw, void *param)
-{
-	int i, y1 = y;
-
-	for (i = 0; i < count; i++)
-	{
-		if (!entries[i].enabled) continue;
-		if (entries[i].name == NULL)
-		{
-			if (cust_draw != NULL)
-				cust_draw(&entries[i], x, y1, param);
-			y1 += 10;
-			continue;
-		}
-		text_out16(x, y1, entries[i].name);
-		if (entries[i].beh == MB_OPT_ONOFF)
-			text_out16(x + 27*8, y1, (*(int *)entries[i].var & entries[i].mask) ? "ON" : "OFF");
-		else if (entries[i].beh == MB_OPT_RANGE)
-			text_out16(x + 27*8, y1, "%i", *(int *)entries[i].var);
-		y1 += 10;
-	}
-
-}
-
-static void me_draw2(const menu_entry *entries, int sel)
+static void me_draw(const menu_entry *entries, int sel)
 {
 	const menu_entry *ent;
 	int x, y, w = 0, h = 0;
-	int opt_offs = 27*8;
+	int offs, opt_offs = 27*8;
 	const char *name;
 	int asel = 0;
 	int i, n;
@@ -345,7 +291,7 @@ static void me_draw2(const menu_entry *entries, int sel)
 		name = NULL;
 		wt = strlen(ent->name) * 8;	/* FIXME: unhardcode font width */
 		if (wt == 0 && ent->generate_name)
-			name = ent->generate_name(1);
+			name = ent->generate_name(ent->id, &offs);
 		if (name != NULL)
 			wt = strlen(name) * 8;
 
@@ -360,11 +306,14 @@ static void me_draw2(const menu_entry *entries, int sel)
 			case MB_OPT_ONOFF:
 			case MB_OPT_RANGE: wt += 8*3; break;
 			case MB_OPT_CUSTOM:
+			case MB_OPT_CUSTONOFF:
+			case MB_OPT_CUSTRANGE:
 				name = NULL;
+				offs = 0;
 				if (ent->generate_name != NULL)
-					name = ent->generate_name(0);
+					name = ent->generate_name(ent->id, &offs);
 				if (name != NULL)
-					wt += strlen(name) * 8;
+					wt += (strlen(name) + offs) * 8;
 				break;
 			}
 		}
@@ -400,7 +349,7 @@ static void me_draw2(const menu_entry *entries, int sel)
 		name = ent->name;
 		if (strlen(name) == 0) {
 			if (ent->generate_name)
-				name = ent->generate_name(1);
+				name = ent->generate_name(ent->id, &offs);
 		}
 		if (name != NULL)
 			text_out16(x + 16, y, name);
@@ -415,39 +364,60 @@ static void me_draw2(const menu_entry *entries, int sel)
 			text_out16(x + 16 + opt_offs, y, "%i", *(int *)ent->var);
 			break;
 		case MB_OPT_CUSTOM:
+		case MB_OPT_CUSTONOFF:
+		case MB_OPT_CUSTRANGE:
 			name = NULL;
+			offs = 0;
 			if (ent->generate_name)
-				name = ent->generate_name(0);
+				name = ent->generate_name(ent->id, &offs);
 			if (name != NULL)
-				text_out16(x + 16 + opt_offs, y, "%s", name);
+				text_out16(x + 16 + opt_offs + offs * 8, y, "%s", name);
 			break;
 		}
 
 		y += 10;
 	}
 
+	/* display message if we have one */
+	if (menuErrorMsg[0] != 0) {
+		static int msg_redraws = 0;
+		if (SCREEN_HEIGHT - h >= 2*10)
+			text_out16(5, 226, menuErrorMsg);
+		else
+			lprintf("menu msg doesn't fit!\n");
+
+		if (++msg_redraws > 4) {
+			menuErrorMsg[0] = 0;
+			msg_redraws = 0;
+		}
+	}
+
 	plat_video_menu_end();
 }
 
-int me_process(menu_entry *entries, menu_id id, int is_next)
+static int me_process(menu_entry *entry, int is_next)
 {
-	int i = me_id2offset(entries, id);
-	menu_entry *entry = &entries[i];
 	switch (entry->beh)
 	{
 		case MB_OPT_ONOFF:
+		case MB_OPT_CUSTONOFF:
 			*(int *)entry->var ^= entry->mask;
 			return 1;
 		case MB_OPT_RANGE:
+		case MB_OPT_CUSTRANGE:
 			*(int *)entry->var += is_next ? 1 : -1;
-			if (*(int *)entry->var < (int)entry->min) *(int *)entry->var = (int)entry->min;
-			if (*(int *)entry->var > (int)entry->max) *(int *)entry->var = (int)entry->max;
+			if (*(int *)entry->var < (int)entry->min)
+				*(int *)entry->var = (int)entry->max;
+			if (*(int *)entry->var > (int)entry->max)
+				*(int *)entry->var = (int)entry->min;
 			return 1;
 		default:
 			return 0;
 	}
 }
 
+static void debug_menu_loop(void);
+
 static void me_loop(menu_entry *menu, int *menu_sel)
 {
 	int ret, inp, sel = *menu_sel, menu_sel_max;
@@ -462,20 +432,24 @@ static void me_loop(menu_entry *menu, int *menu_sel)
 		sel++;
 
 	/* make sure action buttons are not pressed on entering menu */
-	me_draw2(menu, sel);
+	me_draw(menu, sel);
 	while (in_menu_wait_any(50) & (PBTN_MOK|PBTN_MBACK|PBTN_MENU));
 
 	for (;;)
 	{
-		me_draw2(menu, sel);
-		inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_MOK|PBTN_MBACK|PBTN_MENU|PBTN_L|PBTN_R);
+		me_draw(menu, sel);
+		inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|
+					PBTN_MOK|PBTN_MBACK|PBTN_MENU|PBTN_L|PBTN_R, 70);
+		if (inp & (PBTN_MENU|PBTN_MBACK))
+			break;
+
 		if (inp & PBTN_UP  ) {
 			do {
 				sel--;
 				if (sel < 0)
 					sel = menu_sel_max;
 			}
-			while (!menu[sel].enabled);
+			while (!menu[sel].enabled || !menu[sel].selectable);
 		}
 		if (inp & PBTN_DOWN) {
 			do {
@@ -483,294 +457,1294 @@ static void me_loop(menu_entry *menu, int *menu_sel)
 				if (sel > menu_sel_max)
 					sel = 0;
 			}
-			while (!menu[sel].enabled);
+			while (!menu[sel].enabled || !menu[sel].selectable);
+		}
+
+		/* a bit hacky but oh well */
+		if ((inp & (PBTN_L|PBTN_R)) == (PBTN_L|PBTN_R))
+			debug_menu_loop();
+
+		if (inp & (PBTN_LEFT|PBTN_RIGHT)) { /* multi choice */
+			if (me_process(&menu[sel], (inp & PBTN_RIGHT) ? 1 : 0))
+				continue;
 		}
-//		if ((inp & (PBTN_L|PBTN_R)) == (PBTN_L|PBTN_R)) debug_menu_loop(); // TODO
-		if (inp & (PBTN_MENU|PBTN_MBACK))
-			break;
 
-		if (inp & PBTN_MOK)
+		if (inp & (PBTN_MOK|PBTN_LEFT|PBTN_RIGHT))
 		{
-			if (menu[sel].submenu_handler != NULL) {
-				ret = menu[sel].submenu_handler(menu[sel].id);
+			if (menu[sel].handler != NULL) {
+				ret = menu[sel].handler(menu[sel].id, inp);
 				if (ret) break;
+				menu_sel_max = me_count(menu) - 1; /* might change */
 			}
 		}
-//		menuErrorMsg[0] = 0; // TODO: clear error msg
 	}
 	*menu_sel = sel;
 }
 
 /* ***************************************** */
 
-/* TODO s */
-int menu_loop_tray(void) { return 0; }
-void menu_romload_prepare(const char *rom_name) {}
-void menu_romload_end(void) {}
-me_bind_action emuctrl_actions[1];
-menu_entry opt_entries[1];
-menu_entry opt2_entries[1];
-menu_entry cdopt_entries[1];
-menu_entry ctrlopt_entries[1];
-const int opt_entry_count = 0;
-const int opt2_entry_count = 0;
-const int cdopt_entry_count = 0;
-const int ctrlopt_entry_count = 0;
-
-extern int engineState;
-
-int savestate_menu_loop(int a) { return 1; }
-int menu_loop_options() { return 1; }
-void kc_sel_loop() {}
-void draw_menu_credits() {}
-void patches_menu_loop() {}
+static void draw_menu_credits(void)
+{
+	const char *creds, *p;
+	int x, y, h, w, wt;
 
-// ------------ main menu ------------
+	p = creds = plat_get_credits();
 
-static int main_menu_handler(menu_id id)
-{
-	int ret;
+	for (h = 1, w = 0; *p != 0; h++) {
+		for (wt = 0; *p != 0 && *p != '\n'; p++)
+			wt++;
 
-	switch (id)
-	{
-	case MA_MAIN_RESUME_GAME:
-		if (rom_loaded) {
-			while (in_menu_wait_any(50) & PBTN_MOK);
-			engineState = PGS_Running;
-			return 1;
-		}
-		break;
-	case MA_MAIN_SAVE_STATE:
-		if (rom_loaded) {
-			if (savestate_menu_loop(0))
-				break;
-			engineState = PGS_Running;
-			return 1;
-		}
-		break;
-	case MA_MAIN_LOAD_STATE:
-		if (rom_loaded) {
-			if (savestate_menu_loop(1))
-				break;
-			while (in_menu_wait_any(50) & PBTN_MOK);
-			engineState = PGS_Running;
-			return 1;
-		}
-		break;
-	case MA_MAIN_RESET_GAME:
-		if (rom_loaded) {
-			emu_ResetGame();
-			while (in_menu_wait_any(50) & PBTN_MOK);
-			engineState = PGS_Running;
-			return 1;
-		}
-		break;
-	case MA_MAIN_LOAD_ROM:
-		{
-/*			char curr_path[PATH_MAX], *selfname;
-			FILE *tstf;
-			if ( (tstf = fopen(loadedRomFName, "rb")) )
-			{
-				fclose(tstf);
-				strcpy(curr_path, loadedRomFName);
-			}
-			else
-				getcwd(curr_path, PATH_MAX);
-			selfname = romsel_loop(curr_path);
-			if (selfname) {
-				printf("selected file: %s\n", selfname);
-				engineState = PGS_ReloadRom;
-				return;
-			}*/
+		if (wt > w)
+			w = wt;
+		if (*p == 0)
 			break;
-		}
-	case MA_MAIN_OPTIONS:
-		ret = menu_loop_options();
-		if (ret == 1) break; // status update
-		if (engineState == PGS_ReloadRom)
-			return 1; // BIOS test
-		break;
-	case MA_MAIN_CONTROLS:
-		kc_sel_loop();
-		break;
-	case MA_MAIN_CREDITS:
-		draw_menu_credits();
-		usleep(500*1000); /* FIXME */
-		in_menu_wait(PBTN_MOK|PBTN_MBACK);
-		break;
-	case MA_MAIN_EXIT:
-		engineState = PGS_Quit;
-		return 1;
-	case MA_MAIN_PATCHES:
-		if (rom_loaded && PicoPatches) {
-			patches_menu_loop();
-			PicoPatchApply();
-			strcpy(menuErrorMsg, "Patches applied");
-		}
-		break;
-	default:
-		lprintf("%s: something unknown selected\n", __FUNCTION__);
-		break;
+		p++;
 	}
 
-	return 0;
+	x = SCREEN_WIDTH  / 2 - w *  8 / 2;
+	y = SCREEN_HEIGHT / 2 - h * 10 / 2;
+	if (x < 0) x = 0;
+	if (y < 0) y = 0;
+
+	plat_video_menu_begin();
+
+	for (p = creds; *p != 0 && y <= SCREEN_HEIGHT - 10; y += 10) {
+		text_out16(x, y, p);
+
+		for (; *p != 0 && *p != '\n'; p++)
+			;
+		if (*p != 0)
+			p++;
+	}
+
+	plat_video_menu_end();
 }
 
-menu_entry e_main_menu[] =
+// --------- loading ROM screen ----------
+
+static int cdload_called = 0;
+
+static void load_progress_cb(int percent)
 {
-	mee_submenu_id("Resume game",        MA_MAIN_RESUME_GAME, main_menu_handler),
-	mee_submenu_id("Save State",         MA_MAIN_SAVE_STATE,  main_menu_handler),
-	mee_submenu_id("Load State",         MA_MAIN_LOAD_STATE,  main_menu_handler),
-	mee_submenu_id("Reset game",         MA_MAIN_RESET_GAME,  main_menu_handler),
-	mee_submenu_id("Load new ROM/ISO",   MA_MAIN_LOAD_ROM,    main_menu_handler),
-	mee_submenu_id("Change options",     MA_MAIN_OPTIONS,     main_menu_handler),
-	mee_submenu_id("Credits",            MA_MAIN_CREDITS,     main_menu_handler),
-	mee_submenu_id("Patches / GameGenie",MA_MAIN_PATCHES,     main_menu_handler),
-	mee_submenu_id("Exit",               MA_MAIN_EXIT,        main_menu_handler),
-	mee_end,
-};
+	int ln, len = percent * SCREEN_WIDTH / 100;
+	unsigned short *dst = (unsigned short *)SCREEN_BUFFER + SCREEN_WIDTH * 10 * 2;
 
-void menu_loop(void)
+	if (len > SCREEN_WIDTH)
+		len = SCREEN_WIDTH;
+	for (ln = 10 - 2; ln > 0; ln--, dst += SCREEN_WIDTH)
+		memset(dst, 0xff, len * 2);
+	plat_video_menu_end();
+}
+
+static void cdload_progress_cb(int percent)
 {
-	static int sel = 0;
+	int ln, len = percent * SCREEN_WIDTH / 100;
+	unsigned short *dst = (unsigned short *)SCREEN_BUFFER + SCREEN_WIDTH * 10 * 2;
 
-	me_enable(e_main_menu, MA_MAIN_RESUME_GAME, rom_loaded);
-	me_enable(e_main_menu, MA_MAIN_SAVE_STATE,  rom_loaded);
-	me_enable(e_main_menu, MA_MAIN_LOAD_STATE,  rom_loaded);
-	me_enable(e_main_menu, MA_MAIN_RESET_GAME,  rom_loaded);
-	me_enable(e_main_menu, MA_MAIN_PATCHES, PicoPatches != NULL);
+	memset(dst, 0xff, SCREEN_WIDTH * (10 - 2) * 2);
 
-	plat_video_menu_enter(rom_loaded);
-	in_set_blocking(1);
-	me_loop(e_main_menu, &sel);
-	in_set_blocking(0);
+	smalltext_out16(1, 3 * 10, "Processing CD image / MP3s", 0xffff);
+	smalltext_out16(1, 4 * 10, rom_fname_loaded, 0xffff);
+	dst += SCREEN_WIDTH * 30;
 
-	if (rom_loaded) {
-		while (in_menu_wait_any(50) & (PBTN_MENU|PBTN_MBACK)); // wait until select is released
-		engineState = PGS_Running;
-	}
+	if (len > SCREEN_WIDTH)
+		len = SCREEN_WIDTH;
+	for (ln = (10 - 2); ln > 0; ln--, dst += SCREEN_WIDTH)
+		memset(dst, 0xff, len * 2);
 
+	plat_video_menu_end();
+	cdload_called = 1;
 }
 
-// ------------ debug menu ------------
+void menu_romload_prepare(const char *rom_name)
+{
+	const char *p = rom_name + strlen(rom_name);
 
-#include <sys/stat.h>
-#include <sys/types.h>
+	plat_video_menu_begin();
 
-#include <pico/pico.h>
-#include <pico/debug.h>
+	while (p > rom_name && *p != '/')
+		p--;
 
-void SekStepM68k(void);
+	/* fill both buffers, callbacks won't update in full */
+	smalltext_out16(1, 1, "Loading", 0xffff);
+	smalltext_out16(1, 10, p, 0xffff);
+	plat_video_menu_end();
 
-static void mplayer_loop(void)
+	smalltext_out16(1, 1, "Loading", 0xffff);
+	smalltext_out16(1, 10, p, 0xffff);
+	plat_video_menu_end();
+
+	PicoCartLoadProgressCB = load_progress_cb;
+	PicoCDLoadProgressCB = cdload_progress_cb;
+	cdload_called = 0;
+}
+
+void menu_romload_end(void)
 {
-	emu_startSound();
+	PicoCartLoadProgressCB = PicoCDLoadProgressCB = NULL;
+	smalltext_out16(1, (cdload_called ? 6 : 3) * 10,
+		"Starting emulation...", 0xffff);
+	plat_video_menu_end();
+}
 
-	while (1)
-	{
-		PDebugZ80Frame();
-		if (in_menu_wait_any(0) & PBTN_NORTH) break;
-		emu_waitSound();
-	}
+// -------------- ROM selector --------------
 
-	emu_endSound();
+// rrrr rggg gggb bbbb
+static unsigned short file2color(const char *fname)
+{
+	const char *ext = fname + strlen(fname) - 3;
+	static const char *rom_exts[]   = { "zip", "bin", "smd", "gen", "iso", "cso", "cue" };
+	static const char *other_exts[] = { "gmv", "pat" };
+	int i;
+
+	if (ext < fname) ext = fname;
+	for (i = 0; i < array_size(rom_exts); i++)
+		if (strcasecmp(ext, rom_exts[i]) == 0) return 0xbdff; // FIXME: mk defines
+	for (i = 0; i < array_size(other_exts); i++)
+		if (strcasecmp(ext, other_exts[i]) == 0) return 0xaff5;
+	return 0xffff;
 }
 
-static void draw_text_debug(const char *str, int skip, int from)
+static void draw_dirlist(char *curdir, struct dirent **namelist, int n, int sel)
 {
-	const char *p;
-	int len, line;
+	int max_cnt, start, i, pos;
 
-	p = str;
-	while (skip-- > 0)
-	{
-		while (*p && *p != '\n') p++;
-		if (*p == 0 || p[1] == 0) return;
-		p++;
-	}
+	max_cnt = SCREEN_HEIGHT / 10;
+	start = max_cnt / 2 - sel;
+	n--; // exclude current dir (".")
 
-	str = p;
-	for (line = from; line < SCREEN_HEIGHT/10; line++)
-	{
-		while (*p && *p != '\n') p++;
-		len = p - str;
-		if (len > 55) len = 55;
-		smalltext_out16_lim(1, line*10, str, 0xffff, len);
-		if (*p == 0) break;
-		p++; str = p;
+	plat_video_menu_begin();
+
+//	if (!rom_loaded)
+//		menu_darken_bg(gp2x_screen, 320*240, 0);
+
+	menu_darken_bg((short *)SCREEN_BUFFER + SCREEN_WIDTH * max_cnt/2 * 10, SCREEN_WIDTH * 8, 0);
+
+	if (start - 2 >= 0)
+		smalltext_out16(14, (start - 2)*10, curdir, 0xffff);
+	for (i = 0; i < n; i++) {
+		pos = start + i;
+		if (pos < 0)  continue;
+		if (pos >= max_cnt) break;
+		if (namelist[i+1]->d_type == DT_DIR) {
+			smalltext_out16(14,   pos*10, "/", 0xfff6);
+			smalltext_out16(14+6, pos*10, namelist[i+1]->d_name, 0xfff6);
+		} else {
+			unsigned short color = file2color(namelist[i+1]->d_name);
+			smalltext_out16(14,   pos*10, namelist[i+1]->d_name, color);
+		}
 	}
+	text_out16(5, max_cnt/2 * 10, ">");
+	plat_video_menu_end();
 }
 
-static void draw_frame_debug(void)
+static int scandir_cmp(const void *p1, const void *p2)
 {
-	char layer_str[48] = "layers:             ";
-	if (PicoDrawMask & PDRAW_LAYERB_ON)      memcpy(layer_str +  8, "B", 1);
-	if (PicoDrawMask & PDRAW_LAYERA_ON)      memcpy(layer_str + 10, "A", 1);
-	if (PicoDrawMask & PDRAW_SPRITES_LOW_ON) memcpy(layer_str + 12, "spr_lo", 6);
-	if (PicoDrawMask & PDRAW_SPRITES_HI_ON)  memcpy(layer_str + 19, "spr_hi", 6);
+	struct dirent **d1 = (struct dirent **)p1, **d2 = (struct dirent **)p2;
+	if ((*d1)->d_type == (*d2)->d_type) return alphasort(d1, d2);
+	if ((*d1)->d_type == DT_DIR) return -1; // put before
+	if ((*d2)->d_type == DT_DIR) return  1;
+	return alphasort(d1, d2);
+}
 
-	clear_screen();
-	emu_forcedFrame(0);
-	smalltext_out16(4, SCREEN_HEIGHT-8, layer_str, 0xffff);
+static const char *filter_exts[] = {
+	".mp3", ".MP3", ".srm", ".brm", "s.gz", ".mds",	"bcfg", ".txt", ".htm", "html",
+	".jpg", ".gpe"
+};
+
+static int scandir_filter(const struct dirent *ent)
+{
+	const char *p;
+	int i;
+
+	if (ent == NULL || ent->d_name == NULL) return 0;
+	if (strlen(ent->d_name) < 5) return 1;
+
+	p = ent->d_name + strlen(ent->d_name) - 4;
+
+	for (i = 0; i < array_size(filter_exts); i++)
+		if (strcmp(p, filter_exts[i]) == 0)
+			return 0;
+
+	return 1;
 }
 
-void debug_menu_loop(void)
+static char *menu_loop_romsel(char *curr_path, int len)
 {
-	int inp, mode = 0;
-	int spr_offs = 0, dumped = 0;
-	char *tmp;
+	struct dirent **namelist;
+	int n, inp, sel = 0;
+	char *ret = NULL, *fname = NULL;
+
+rescan:
+	// is this a dir or a full path?
+	if (!plat_is_dir(curr_path)) {
+		char *p = curr_path + strlen(curr_path) - 1;
+		for (; p > curr_path && *p != '/'; p--)
+			;
+		*p = 0;
+		fname = p+1;
+	}
 
-	while (1)
-	{
-		switch (mode)
-		{
-			case 0: plat_video_menu_begin();
-				tmp = PDebugMain();
-				emu_platformDebugCat(tmp);
-				draw_text_debug(tmp, 0, 0);
-				if (dumped) {
-					smalltext_out16(SCREEN_WIDTH-6*10, SCREEN_HEIGHT-8, "dumped", 0xffff);
-					dumped = 0;
-				}
-				break;
-			case 1: draw_frame_debug(); break;
-			case 2: clear_screen();
-				emu_forcedFrame(0);
-				darken_screen();
-				PDebugShowSpriteStats((unsigned short *)SCREEN_BUFFER + (SCREEN_HEIGHT/2 - 240/2)*SCREEN_WIDTH +
-					SCREEN_WIDTH/2 - 320/2, SCREEN_WIDTH); break;
-			case 3: clear_screen();
-				PDebugShowPalette(SCREEN_BUFFER, SCREEN_WIDTH);
-				PDebugShowSprite((unsigned short *)SCREEN_BUFFER + SCREEN_WIDTH*120+SCREEN_WIDTH/2+16,
-					SCREEN_WIDTH, spr_offs);
-				draw_text_debug(PDebugSpriteList(), spr_offs, 6);
+	n = scandir(curr_path, &namelist, scandir_filter, scandir_cmp);
+	if (n < 0) {
+		lprintf("menu_loop_romsel failed, dir: %s\n", curr_path);
+
+		// try root
+		getcwd(curr_path, len);
+		n = scandir(curr_path, &namelist, scandir_filter, scandir_cmp);
+		if (n < 0) {
+			// oops, we failed
+			lprintf("menu_loop_romsel failed, dir: %s\n", curr_path);
+			return NULL;
+		}
+	}
+
+	// try to find sel
+	if (fname != NULL) {
+		int i;
+		for (i = 1; i < n; i++) {
+			if (strcmp(namelist[i]->d_name, fname) == 0) {
+				sel = i - 1;
 				break;
+			}
 		}
-		plat_video_menu_end();
+	}
 
-		inp = in_menu_wait(PBTN_EAST|PBTN_MBACK|PBTN_WEST|PBTN_NORTH|PBTN_L|PBTN_R|PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT);
-		if (inp & PBTN_MBACK) return;
-		if (inp & PBTN_L) { mode--; if (mode < 0) mode = 3; }
-		if (inp & PBTN_R) { mode++; if (mode > 3) mode = 0; }
-		switch (mode)
+	for (;;)
+	{
+		draw_dirlist(curr_path, namelist, n, sel);
+		inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|
+			PBTN_L|PBTN_R|PBTN_WEST|PBTN_MOK|PBTN_MBACK|PBTN_MENU, 33); // TODO L R
+		if (inp & PBTN_UP  )  { sel--;   if (sel < 0)   sel = n-2; }
+		if (inp & PBTN_DOWN)  { sel++;   if (sel > n-2) sel = 0; }
+		if (inp & PBTN_LEFT)  { sel-=10; if (sel < 0)   sel = 0; }
+		if (inp & PBTN_L)     { sel-=24; if (sel < 0)   sel = 0; }
+		if (inp & PBTN_RIGHT) { sel+=10; if (sel > n-2) sel = n-2; }
+		if (inp & PBTN_R)     { sel+=24; if (sel > n-2) sel = n-2; }
+		if ((inp & PBTN_MOK) || (inp & (PBTN_MENU|PBTN_WEST)) == (PBTN_MENU|PBTN_WEST)) // enter dir/select || delete
 		{
-			case 0:
-				if (inp & PBTN_EAST) SekStepM68k();
-				if (inp & PBTN_NORTH) {
-					while (inp & PBTN_NORTH) inp = in_menu_wait_any(-1);
-					mplayer_loop();
+			again:
+			if (namelist[sel+1]->d_type == DT_REG)
+			{
+				strcpy(rom_fname_reload, curr_path);
+				strcat(rom_fname_reload, "/");
+				strcat(rom_fname_reload, namelist[sel+1]->d_name);
+				if (inp & PBTN_MOK) { // return sel
+					ret = rom_fname_reload;
+					break;
 				}
-				if ((inp & (PBTN_WEST|PBTN_LEFT)) == (PBTN_WEST|PBTN_LEFT)) {
-					mkdir("dumps", 0777);
-					PDebugDumpMem();
-					while (inp & PBTN_WEST) inp = in_menu_wait_any(-1);
-					dumped = 1;
+//				do_delete(rom_fname_reload, namelist[sel+1]->d_name); // TODO
+				if (n > 0) {
+					while (n--) free(namelist[n]);
+					free(namelist);
+				}
+				goto rescan;
+			}
+			else if (namelist[sel+1]->d_type == DT_DIR)
+			{
+				int newlen;
+				char *p, *newdir;
+				if (!(inp & PBTN_MOK)) continue;
+				newlen = strlen(curr_path) + strlen(namelist[sel+1]->d_name) + 2;
+				newdir = malloc(newlen);
+				if (strcmp(namelist[sel+1]->d_name, "..") == 0) {
+					char *start = curr_path;
+					p = start + strlen(start) - 1;
+					while (*p == '/' && p > start) p--;
+					while (*p != '/' && p > start) p--;
+					if (p <= start) strcpy(newdir, "/");
+					else { strncpy(newdir, start, p-start); newdir[p-start] = 0; }
+				} else {
+					strcpy(newdir, curr_path);
+					p = newdir + strlen(newdir) - 1;
+					while (*p == '/' && p >= newdir) *p-- = 0;
+					strcat(newdir, "/");
+					strcat(newdir, namelist[sel+1]->d_name);
 				}
+				ret = menu_loop_romsel(newdir, newlen);
+				free(newdir);
 				break;
-			case 1:
-				if (inp & PBTN_LEFT)  PicoDrawMask ^= PDRAW_LAYERB_ON;
-				if (inp & PBTN_RIGHT) PicoDrawMask ^= PDRAW_LAYERA_ON;
-				if (inp & PBTN_DOWN)  PicoDrawMask ^= PDRAW_SPRITES_LOW_ON;
+			}
+			else
+			{
+				// unknown file type, happens on NTFS mounts. Try to guess.
+				FILE *tstf; int tmp;
+				strcpy(rom_fname_reload, curr_path);
+				strcat(rom_fname_reload, "/");
+				strcat(rom_fname_reload, namelist[sel+1]->d_name);
+				tstf = fopen(rom_fname_reload, "rb");
+				if (tstf != NULL)
+				{
+					if (fread(&tmp, 1, 1, tstf) > 0 || ferror(tstf) == 0)
+						namelist[sel+1]->d_type = DT_REG;
+					else	namelist[sel+1]->d_type = DT_DIR;
+					fclose(tstf);
+					goto again;
+				}
+			}
+		}
+		if (inp & PBTN_MBACK)
+			break;
+	}
+
+	if (n > 0) {
+		while (n--) free(namelist[n]);
+		free(namelist);
+	}
+
+	return ret;
+}
+
+// ------------ patch/gg menu ------------
+
+static void draw_patchlist(int sel)
+{
+	int max_cnt, start, i, pos, active;
+
+	max_cnt = SCREEN_HEIGHT / 10;
+	start = max_cnt / 2 - sel;
+
+	plat_video_menu_begin();
+
+	for (i = 0; i < PicoPatchCount; i++) {
+		pos = start + i;
+		if (pos < 0) continue;
+		if (pos >= max_cnt) break;
+		active = PicoPatches[i].active;
+		smalltext_out16(14,     pos*10, active ? "ON " : "OFF", active ? 0xfff6 : 0xffff);
+		smalltext_out16(14+6*4, pos*10, PicoPatches[i].name,    active ? 0xfff6 : 0xffff);
+	}
+	pos = start + i;
+	if (pos < max_cnt)
+		smalltext_out16(14, pos * 10, "done", 0xffff);
+
+	text_out16(5, max_cnt / 2 * 10, ">");
+	plat_video_menu_end();
+}
+
+static void menu_loop_patches(void)
+{
+	static int menu_sel = 0;
+	int inp;
+
+	for (;;)
+	{
+		draw_patchlist(menu_sel);
+		inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_L|PBTN_R|PBTN_MOK|PBTN_MBACK, 33);
+		if (inp & PBTN_UP  ) { menu_sel--; if (menu_sel < 0) menu_sel = PicoPatchCount; }
+		if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > PicoPatchCount) menu_sel = 0; }
+		if (inp &(PBTN_LEFT|PBTN_L))  { menu_sel-=10; if (menu_sel < 0) menu_sel = 0; }
+		if (inp &(PBTN_RIGHT|PBTN_R)) { menu_sel+=10; if (menu_sel > PicoPatchCount) menu_sel = PicoPatchCount; }
+		if (inp & PBTN_MOK) { // action
+			if (menu_sel < PicoPatchCount)
+				PicoPatches[menu_sel].active = !PicoPatches[menu_sel].active;
+			else 	break;
+		}
+		if (inp & PBTN_MBACK)
+			break;
+	}
+}
+
+// ------------ savestate loader ------------
+
+static int state_slot_flags = 0;
+
+static void state_check_slots(void)
+{
+	int slot;
+
+	state_slot_flags = 0;
+
+	for (slot = 0; slot < 10; slot++) {
+		if (emu_checkSaveFile(slot))
+			state_slot_flags |= 1 << slot;
+	}
+}
+
+static void draw_savestate_bg(int slot)
+{
+	struct PicoVideo tmp_pv;
+	unsigned short tmp_cram[0x40];
+	unsigned short tmp_vsram[0x40];
+	void *tmp_vram, *file;
+	char *fname;
+
+	fname = emu_GetSaveFName(1, 0, slot);
+	if (!fname) return;
+
+	tmp_vram = malloc(sizeof(Pico.vram));
+	if (tmp_vram == NULL) return;
+
+	memcpy(tmp_vram, Pico.vram, sizeof(Pico.vram));
+	memcpy(tmp_cram, Pico.cram, sizeof(Pico.cram));
+	memcpy(tmp_vsram, Pico.vsram, sizeof(Pico.vsram));
+	memcpy(&tmp_pv, &Pico.video, sizeof(Pico.video));
+
+	if (strcmp(fname + strlen(fname) - 3, ".gz") == 0) {
+		file = gzopen(fname, "rb");
+		emu_setSaveStateCbs(1);
+	} else {
+		file = fopen(fname, "rb");
+		emu_setSaveStateCbs(0);
+	}
+
+	if (file) {
+		if (PicoAHW & PAHW_MCD) {
+			PicoCdLoadStateGfx(file);
+		} else {
+			areaSeek(file, 0x10020, SEEK_SET);  // skip header and RAM in state file
+			areaRead(Pico.vram, 1, sizeof(Pico.vram), file);
+			areaSeek(file, 0x2000, SEEK_CUR);
+			areaRead(Pico.cram, 1, sizeof(Pico.cram), file);
+			areaRead(Pico.vsram, 1, sizeof(Pico.vsram), file);
+			areaSeek(file, 0x221a0, SEEK_SET);
+			areaRead(&Pico.video, 1, sizeof(Pico.video), file);
+		}
+		areaClose(file);
+	}
+
+	/* do a frame and fetch menu bg */
+	emu_forcedFrame(POPT_EN_SOFTSCALE);
+	plat_video_menu_enter(1);
+
+	memcpy(Pico.vram, tmp_vram, sizeof(Pico.vram));
+	memcpy(Pico.cram, tmp_cram, sizeof(Pico.cram));
+	memcpy(Pico.vsram, tmp_vsram, sizeof(Pico.vsram));
+	memcpy(&Pico.video, &tmp_pv,  sizeof(Pico.video));
+	free(tmp_vram);
+}
+
+static void draw_savestate_menu(int menu_sel, int is_loading)
+{
+	int i, x, y, w, h;
+
+	if (state_slot_flags & (1 << menu_sel))
+		draw_savestate_bg(menu_sel);
+
+	w = 13 * 8 + 16;
+	h = (1+2+10+1) * 10;
+	x = SCREEN_WIDTH / 2 - w / 2;
+	if (x < 0) x = 0;
+	y = SCREEN_HEIGHT / 2 - h / 2;
+	if (y < 0) y = 0;
+
+	plat_video_menu_begin();
+
+	text_out16(x, y, is_loading ? "Load state" : "Save state");
+	y += 3*10;
+
+	menu_draw_selection(x - 16, y + menu_sel * 10, 13 * 8 + 4);
+
+	/* draw all 10 slots */
+	y += 10;
+	for (i = 0; i < 10; i++, y += 10)
+	{
+		text_out16(x, y, "SLOT %i (%s)", i, (state_slot_flags & (1 << i)) ? "USED" : "free");
+	}
+	text_out16(x, y, "back");
+
+	plat_video_menu_end();
+}
+
+static int menu_loop_savestate(int is_loading)
+{
+	static int menu_sel = 10;
+	int menu_sel_max = 10;
+	unsigned long inp = 0;
+
+	state_check_slots();
+
+	for (;;)
+	{
+		draw_savestate_menu(menu_sel, is_loading);
+		inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_MOK|PBTN_MBACK, 100);
+		if (inp & PBTN_UP) {
+			do {
+				menu_sel--;
+				if (menu_sel < 0)
+					menu_sel = menu_sel_max;
+			} while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading);
+		}
+		if (inp & PBTN_DOWN) {
+			do {
+				menu_sel++;
+				if (menu_sel > menu_sel_max)
+					menu_sel = 0;
+			} while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading);
+		}
+		if (inp & PBTN_MOK) { // save/load
+			if (menu_sel < 10) {
+				state_slot = menu_sel;
+				if (emu_SaveLoadGame(is_loading, 0)) {
+					strcpy(menuErrorMsg, is_loading ? "Load failed" : "Save failed");
+					return 0;
+				}
+				return 1;
+			}
+			return 0;
+		}
+		if (inp & PBTN_MBACK)
+			return 0;
+	}
+}
+
+// -------------- key config --------------
+
+static char *action_binds(int player_idx, int action_mask, int dev_id)
+{
+	const int *binds;
+	int k, count;
+
+	static_buff[0] = 0;
+
+	binds = in_get_dev_binds(dev_id);
+	if (binds == NULL)
+		return static_buff;
+
+	count = in_get_dev_bind_count(dev_id);
+	for (k = 0; k < count; k++)
+	{
+		const char *xname;
+		if (!(binds[k] & action_mask))
+			continue;
+
+		if (player_idx >= 0 && ((binds[k] >> 16) & 3) != player_idx)
+			continue;
+
+		xname = in_get_key_name(dev_id, k);
+		if (static_buff[0])
+			strncat(static_buff, " + ", sizeof(static_buff));
+		strncat(static_buff, xname, sizeof(static_buff));
+	}
+
+	return static_buff;
+}
+
+static int count_bound_keys(int dev_id, int action_mask, int player_idx)
+{
+	const int *binds;
+	int k, keys = 0;
+	int count;
+
+	binds = in_get_dev_binds(dev_id);
+	if (binds == NULL)
+		return 0;
+
+	count = in_get_dev_bind_count(dev_id);
+	for (k = 0; k < count; k++)
+	{
+		if (!(binds[k] & action_mask))
+			continue;
+
+		if (player_idx >= 0 && ((binds[k] >> 16) & 3) != player_idx)
+			continue;
+
+		keys++;
+	}
+
+	return keys;
+}
+
+static void draw_key_config(const me_bind_action *opts, int opt_cnt, int player_idx,
+		int sel, int dev_id, int dev_count, int is_bind)
+{
+	int x, y = 30, w, i;
+	const char *dev_name;
+
+	x = SCREEN_WIDTH / 2 - 32*8 / 2;
+	if (x < 0) x = 0;
+
+	plat_video_menu_begin();
+	if (player_idx >= 0)
+		text_out16(x, 10, "Player %i controls", player_idx + 1);
+	else
+		text_out16(x, 10, "Emulator controls");
+
+	menu_draw_selection(x - 16, y + sel*10, (player_idx >= 0) ? 66 : 140);
+
+	for (i = 0; i < opt_cnt; i++, y+=10)
+		text_out16(x, y, "%s : %s", opts[i].name,
+			action_binds(player_idx, opts[i].mask, dev_id));
+
+	dev_name = in_get_dev_name(dev_id, 1, 1);
+	w = strlen(dev_name) * 8;
+	if (w < 30 * 8)
+		w = 30 * 8;
+	if (w > SCREEN_WIDTH)
+		w = SCREEN_WIDTH;
+
+	x = SCREEN_WIDTH / 2 - w / 2;
+
+	if (dev_count > 1) {
+		text_out16(x, SCREEN_HEIGHT - 4*10, "Viewing binds for:");
+		text_out16(x, SCREEN_HEIGHT - 3*10, dev_name);
+	}
+
+	if (is_bind)
+		text_out16(x, SCREEN_HEIGHT - 2*10, "Press a button to bind/unbind");
+	else if (dev_count > 1)
+		text_out16(x, SCREEN_HEIGHT - 2*10, "Press left/right for other devs");
+
+	plat_video_menu_end();
+}
+
+static void key_config_loop(const me_bind_action *opts, int opt_cnt, int player_idx)
+{
+	int i, sel = 0, menu_sel_max = opt_cnt - 1;
+	int dev_id, dev_count, kc, is_down, mkey, unbind;
+
+	for (i = 0, dev_id = -1, dev_count = 0; i < IN_MAX_DEVS; i++) {
+		if (in_get_dev_name(i, 1, 0) != NULL) {
+			dev_count++;
+			if (dev_id < 0)
+				dev_id = i;
+		}
+	}
+
+	if (dev_id == -1) {
+		lprintf("no devs, can't do config\n");
+		return;
+	}
+
+	for (;;)
+	{
+		draw_key_config(opts, opt_cnt, player_idx, sel, dev_id, dev_count, 0);
+		mkey = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_MBACK|PBTN_MOK, 100);
+		switch (mkey) {
+			case PBTN_UP:   sel--; if (sel < 0) sel = menu_sel_max; continue;
+			case PBTN_DOWN: sel++; if (sel > menu_sel_max) sel = 0; continue;
+			case PBTN_LEFT:
+				for (i = 0, dev_id--; i < IN_MAX_DEVS; i++, dev_id--) {
+					if (dev_id < 0)
+						dev_id = IN_MAX_DEVS - 1;
+					if (in_get_dev_name(dev_id, 1, 0) != NULL)
+						break;
+				}
+				continue;
+			case PBTN_RIGHT:
+				for (i = 0, dev_id++; i < IN_MAX_DEVS; i++, dev_id++) {
+					if (dev_id >= IN_MAX_DEVS)
+						dev_id = 0;
+					if (in_get_dev_name(dev_id, 1, 0) != NULL)
+						break;
+				}
+				continue;
+			case PBTN_MBACK: return;
+			case PBTN_MOK:
+				if (sel >= opt_cnt)
+					return;
+				while (in_menu_wait_any(30) & PBTN_MOK);
+				break;
+			default:continue;
+		}
+
+		draw_key_config(opts, opt_cnt, player_idx, sel, dev_id, dev_count, 1);
+
+		/* wait for some up event */
+		for (is_down = 1; is_down; )
+			kc = in_update_keycode(&dev_id, &is_down, -1);
+
+		unbind = count_bound_keys(dev_id, opts[sel].mask, player_idx) >= 2;
+
+		in_bind_key(dev_id, kc, opts[sel].mask, unbind);
+		if (player_idx >= 0) {
+			/* FIXME */
+			in_bind_key(dev_id, kc, 3 << 16, 1);
+			in_bind_key(dev_id, kc, player_idx << 16, 0);
+		}
+	}
+}
+
+// PicoPad[] format: MXYZ SACB RLDU
+me_bind_action me_ctrl_actions[15] =
+{
+	{ "UP     ", 0x0001 },
+	{ "DOWN   ", 0x0002 },
+	{ "LEFT   ", 0x0004 },
+	{ "RIGHT  ", 0x0008 },
+	{ "A      ", 0x0040 },
+	{ "B      ", 0x0010 },
+	{ "C      ", 0x0020 },
+	{ "A turbo", 0x4000 },
+	{ "B turbo", 0x1000 },
+	{ "C turbo", 0x2000 },
+	{ "START  ", 0x0080 },
+	{ "MODE   ", 0x0800 },
+	{ "X      ", 0x0400 },
+	{ "Y      ", 0x0200 },
+	{ "Z      ", 0x0100 }
+};
+
+// player2_flag, reserved, ?, ?,
+// ?, ?, fast forward, menu
+// "NEXT SAVE SLOT", "PREV SAVE SLOT", "SWITCH RENDERER", "SAVE STATE",
+// "LOAD STATE", "VOLUME UP", "VOLUME DOWN", "DONE"
+me_bind_action emuctrl_actions[] =
+{
+	{ "Load State       ", 1<<28 },
+	{ "Save State       ", 1<<27 },
+	{ "Prev Save Slot   ", 1<<25 },
+	{ "Next Save Slot   ", 1<<24 },
+	{ "Switch Renderer  ", 1<<26 },
+	{ "Volume Down      ", 1<<30 },
+	{ "Volume Up        ", 1<<29 },
+	{ "Fast forward     ", 1<<22 },
+	{ "Enter Menu       ", 1<<23 },
+	{ "Pico Next page   ", 1<<21 },
+	{ "Pico Prev page   ", 1<<20 },
+	{ "Pico Switch input", 1<<19 },
+	{ NULL,                0     }
+};
+
+static int key_config_loop_wrap(menu_id id, int keys)
+{
+	switch (id) {
+		case MA_CTRL_PLAYER1:
+			key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions), 0);
+			break;
+		case MA_CTRL_PLAYER2:
+			key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions), 1);
+			break;
+		case MA_CTRL_EMU:
+			key_config_loop(emuctrl_actions, array_size(emuctrl_actions) - 1, -1);
+			break;
+		default:
+			break;
+	}
+	return 0;
+}
+
+static const char *mgn_dev_name(menu_id id, int *offs)
+{
+	const char *name = NULL;
+	static int it = 0;
+
+	if (id == MA_CTRL_DEV_FIRST)
+		it = 0;
+
+	for (; it < IN_MAX_DEVS; it++) {
+		name = in_get_dev_name(it, 1, 1);
+		if (name != NULL)
+			break;
+	}
+
+	it++;
+	return name;
+}
+
+static int mh_saveloadcfg(menu_id id, int keys);
+static const char *mgn_savecfg(menu_id id, int *offs);
+
+static menu_entry e_menu_keyconfig[] =
+{
+	mee_handler_id("Player 1",          MA_CTRL_PLAYER1,    key_config_loop_wrap),
+	mee_handler_id("Player 2",          MA_CTRL_PLAYER2,    key_config_loop_wrap),
+	mee_handler_id("Emulator controls", MA_CTRL_EMU,        key_config_loop_wrap),
+	mee_onoff     ("6 button pad",      MA_OPT_6BUTTON_PAD, PicoOpt, POPT_6BTN_PAD),
+	mee_range     ("Turbo rate",        MA_CTRL_TURBO_RATE, currentConfig.turbo_rate, 1, 30),
+	mee_handler_mkname_id(MA_OPT_SAVECFG, mh_saveloadcfg, mgn_savecfg),
+	mee_handler_id("Save cfg for loaded game", MA_OPT_SAVECFG_GAME, mh_saveloadcfg),
+	mee_label     (""),
+	mee_label     ("Input devices:"),
+	mee_label_mk  (MA_CTRL_DEV_FIRST, mgn_dev_name),
+	mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
+	mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
+	mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
+	mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
+	mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
+	mee_label_mk  (MA_CTRL_DEV_NEXT,  mgn_dev_name),
+	mee_end,
+};
+
+static int menu_loop_keyconfig(menu_id id, int keys)
+{
+	static int sel = 0;
+	me_loop(e_menu_keyconfig, &sel);
+	return 0;
+}
+
+// ------------ SCD options menu ------------
+
+static const char *mgn_cdopt_ra(menu_id id, int *offs)
+{
+	*offs = -5;
+	if (PicoCDBuffers <= 0)
+		return "     OFF";
+	sprintf(static_buff, "%5iK", PicoCDBuffers * 2);
+	return static_buff;
+}
+
+static int mh_cdopt_ra(menu_id id, int keys)
+{
+	if (keys & PBTN_LEFT) {
+		PicoCDBuffers >>= 1;
+		if (PicoCDBuffers < 2)
+			PicoCDBuffers = 0;
+	} else {
+		if (PicoCDBuffers <= 0)
+			PicoCDBuffers = 1;
+		PicoCDBuffers <<= 1;
+		if (PicoCDBuffers > 8*1024)
+			PicoCDBuffers = 8*1024; // 16M
+	}
+	return 0;
+}
+
+static menu_entry e_menu_cd_options[] =
+{
+	mee_onoff("CD LEDs",              MA_CDOPT_LEDS,          currentConfig.EmuOpt, 0x0400),
+	mee_onoff("CDDA audio",           MA_CDOPT_CDDA,          PicoOpt, POPT_EN_MCD_CDDA),
+	mee_onoff("PCM audio",            MA_CDOPT_PCM,           PicoOpt, POPT_EN_MCD_PCM),
+	mee_cust ("ReadAhead buffer",     MA_CDOPT_READAHEAD,     mh_cdopt_ra, mgn_cdopt_ra),
+	mee_onoff("SaveRAM cart",         MA_CDOPT_SAVERAM,       PicoOpt, POPT_EN_MCD_RAMCART),
+	mee_onoff("Scale/Rot. fx (slow)", MA_CDOPT_SCALEROT_CHIP, PicoOpt, POPT_EN_MCD_GFX),
+	mee_onoff("Better sync (slow)",   MA_CDOPT_BETTER_SYNC,   PicoOpt, POPT_EN_MCD_PSYNC),
+	mee_end,
+};
+
+static int menu_loop_cd_options(menu_id id, int keys)
+{
+	static int sel = 0;
+	me_loop(e_menu_cd_options, &sel);
+	return 0;
+}
+
+// ------------ adv options menu ------------
+
+// TODO FIXME fix if and mv
+static const char *mgn_aopt_sqhack(menu_id id, int *offs)
+{
+	*offs = -10;
+	sprintf(static_buff, "%s, %s", 111 ? "  active" : "inactive",
+		(currentConfig.EmuOpt & 0x10) ? "ON" : "OFF");
+	return static_buff;
+}
+
+static menu_entry e_menu_adv_options[] =
+{
+	mee_onoff     ("SRAM/BRAM saves",          MA_OPT_SRAM_STATES,    currentConfig.EmuOpt, EOPT_USE_SRAM),
+	mee_onoff     ("Disable sprite limit",     MA_OPT2_NO_SPRITE_LIM, PicoOpt, POPT_DIS_SPRITE_LIM),
+	mee_onoff     ("Use second CPU for sound", MA_OPT_ARM940_SOUND,   PicoOpt, POPT_EXT_FM),
+	mee_onoff     ("Emulate Z80",              MA_OPT2_ENABLE_Z80,    PicoOpt, POPT_EN_Z80),
+	mee_onoff     ("Emulate YM2612 (FM)",      MA_OPT2_ENABLE_YM2612, PicoOpt, POPT_EN_FM),
+	mee_onoff     ("Emulate SN76496 (PSG)",    MA_OPT2_ENABLE_SN76496,PicoOpt, POPT_EN_PSG),
+	mee_onoff     ("gzip savestates",          MA_OPT2_GZIP_STATES,   currentConfig.EmuOpt, EOPT_GZIP_SAVES),
+	mee_onoff     ("Don't save last used ROM", MA_OPT2_NO_LAST_ROM,   currentConfig.EmuOpt, EOPT_NO_AUTOSVCFG),
+	mee_label     ("- needs restart -"),
+	mee_onoff     ("craigix's RAM timings",    MA_OPT2_RAMTIMINGS,    currentConfig.EmuOpt, 0x0100),
+	mee_onoff_cust("Squidgehack",              MA_OPT2_SQUIDGEHACK,   currentConfig.EmuOpt, 0x0010, mgn_aopt_sqhack),
+	mee_onoff     ("SVP dynarec",              MA_OPT2_SVP_DYNAREC,   PicoOpt, POPT_EN_SVP_DRC),
+	mee_onoff     ("Disable idle loop patching",MA_OPT2_NO_IDLE_LOOPS,PicoOpt, POPT_DIS_IDLE_DET),
+	mee_end,
+};
+
+static int menu_loop_adv_options(menu_id id, int keys)
+{
+	static int sel = 0;
+	me_loop(e_menu_adv_options, &sel);
+	return 0;
+}
+
+// ------------ gfx options menu ------------
+
+static const char *mgn_opt_scaling(menu_id id, int *offs)
+{
+	*offs = -12;
+	switch (currentConfig.scaling) {
+		default: return "            OFF";
+		case 1:  return "hw horizontal";
+		case 2:  return "hw horiz. + vert.";
+		case 3:  return "sw horizontal";
+	}
+}
+
+static const char *mgn_aopt_gamma(menu_id id, int *offs)
+{
+	sprintf(static_buff, "%i.%02i", currentConfig.gamma / 100, currentConfig.gamma%100);
+	return static_buff;
+}
+
+static menu_entry e_menu_gfx_options[] =
+{
+	mee_range_cust("Scaling",                  MA_OPT_SCALING,        currentConfig.scaling, 0, 3, mgn_opt_scaling),
+	mee_range_cust("Gamma correction",         MA_OPT2_GAMMA,         currentConfig.gamma, 1, 300, mgn_aopt_gamma),
+	mee_onoff     ("A_SN's gamma curve",       MA_OPT2_A_SN_GAMMA,    currentConfig.EmuOpt, 0x1000),
+	mee_onoff     ("Perfect vsync",            MA_OPT2_VSYNC,         currentConfig.EmuOpt, 0x2000),
+	mee_end,
+};
+
+static int menu_loop_gfx_options(menu_id id, int keys)
+{
+	static int sel = 0;
+	me_loop(e_menu_gfx_options, &sel);
+	return 0;
+}
+
+// ------------ options menu ------------
+
+static menu_entry e_menu_options[];
+
+/* TODO: move to plat */
+static int mh_opt_render(menu_id id, int keys)
+{
+	if (keys & PBTN_LEFT) {
+		if      (PicoOpt&0x10) PicoOpt&= ~0x10;
+		else if (!(currentConfig.EmuOpt &0x80))currentConfig.EmuOpt |=  0x80;
+	} else {
+		if      (PicoOpt&0x10) return 0;
+		else if (!(currentConfig.EmuOpt &0x80))PicoOpt|=  0x10;
+		else if (  currentConfig.EmuOpt &0x80) currentConfig.EmuOpt &= ~0x80;
+	}
+	return 0;
+}
+
+static int sndrate_prevnext(int rate, int dir)
+{
+	static const int rates[] = { 8000, 11025, 16000, 22050, 44100 };
+	int i;
+
+	for (i = 0; i < 5; i++)
+		if (rates[i] == rate) break;
+
+	i += dir ? 1 : -1;
+	if (i > 4) {
+		if (!(PicoOpt & POPT_EN_STEREO)) {
+			PicoOpt |= POPT_EN_STEREO;
+			return rates[0];
+		}
+		return rates[4];
+	}
+	if (i < 0) {
+		if (PicoOpt & POPT_EN_STEREO) {
+			PicoOpt &= ~POPT_EN_STEREO;
+			return rates[4];
+		}
+		return rates[0];
+	}
+	return rates[i];
+}
+
+static void region_prevnext(int right)
+{
+	// jp_ntsc=1, jp_pal=2, usa=4, eu=8
+	static const int rgn_orders[] = { 0x148, 0x184, 0x814, 0x418, 0x841, 0x481 };
+	int i;
+
+	if (right) {
+		if (!PicoRegionOverride) {
+			for (i = 0; i < 6; i++)
+				if (rgn_orders[i] == PicoAutoRgnOrder) break;
+			if (i < 5) PicoAutoRgnOrder = rgn_orders[i+1];
+			else PicoRegionOverride=1;
+		}
+		else
+			PicoRegionOverride <<= 1;
+		if (PicoRegionOverride > 8)
+			PicoRegionOverride = 8;
+	} else {
+		if (!PicoRegionOverride) {
+			for (i = 0; i < 6; i++)
+				if (rgn_orders[i] == PicoAutoRgnOrder) break;
+			if (i > 0) PicoAutoRgnOrder = rgn_orders[i-1];
+		}
+		else
+			PicoRegionOverride >>= 1;
+	}
+}
+
+static int mh_opt_misc(menu_id id, int keys)
+{
+	int i;
+
+	switch (id) {
+	case MA_OPT_SOUND_QUALITY:
+		PsndRate = sndrate_prevnext(PsndRate, keys & PBTN_RIGHT);
+		break;
+	case MA_OPT_REGION:
+		region_prevnext(keys & PBTN_RIGHT);
+		break;
+	case MA_OPT_CONFIRM_STATES:
+		i = ((currentConfig.EmuOpt>>9)&1) | ((currentConfig.EmuOpt>>10)&2);
+		i += (keys & PBTN_LEFT) ? -1 : 1;
+		if (i < 0) i = 0; else if (i > 3) i = 3;
+		i |= i << 1; i &= ~2;
+		currentConfig.EmuOpt &= ~0xa00;
+		currentConfig.EmuOpt |= i << 9;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int mh_saveloadcfg(menu_id id, int keys)
+{
+	int ret;
+
+	if (keys & (PBTN_LEFT|PBTN_RIGHT)) { // multi choice
+		config_slot += (keys & PBTN_LEFT) ? -1 : 1;
+		if (config_slot < 0) config_slot = 9;
+		else if (config_slot > 9) config_slot = 0;
+		me_enable(e_menu_options, MA_OPT_LOADCFG, config_slot != config_slot_current);
+		return 0;
+	}
+
+	switch (id) {
+	case MA_OPT_SAVECFG:
+	case MA_OPT_SAVECFG_GAME:
+		if (emu_WriteConfig(id == MA_OPT_SAVECFG_GAME ? 1 : 0))
+			strcpy(menuErrorMsg, "config saved");
+		else
+			strcpy(menuErrorMsg, "failed to write config");
+		break;
+	case MA_OPT_LOADCFG:
+		ret = emu_ReadConfig(1, 1);
+		if (!ret) ret = emu_ReadConfig(0, 1);
+		if (ret)  strcpy(menuErrorMsg, "config loaded");
+		else      strcpy(menuErrorMsg, "failed to load config");
+		break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
+static const char *mgn_opt_renderer(menu_id id, int *offs)
+{
+	*offs = -6;
+	if (PicoOpt & POPT_ALT_RENDERER)
+		return " 8bit fast";
+	else if (currentConfig.EmuOpt & 0x80)
+		return "16bit accurate";
+	else
+		return " 8bit accurate";
+}
+
+static const char *mgn_opt_fskip(menu_id id, int *offs)
+{
+	if (currentConfig.Frameskip < 0)
+		return "Auto";
+	sprintf(static_buff, "%d", currentConfig.Frameskip);
+	return static_buff;
+}
+
+static const char *mgn_opt_sound(menu_id id, int *offs)
+{
+	const char *str2;
+	*offs = -8;
+	str2 = (PicoOpt & POPT_EN_STEREO) ? "stereo" : "mono";
+	sprintf(static_buff, "%5iHz %s", PsndRate, str2);
+	return static_buff;
+}
+
+static const char *mgn_opt_region(menu_id id, int *offs)
+{
+	static const char *names[] = { "Auto", "      Japan NTSC", "      Japan PAL", "      USA", "      Europe" };
+	static const char *names_short[] = { "", " JP", " JP", " US", " EU" };
+	int code = PicoRegionOverride;
+	int u, i = 0;
+
+	*offs = -6;
+	if (code) {
+		code <<= 1;
+		while ((code >>= 1)) i++;
+		if (i > 4)
+			return "unknown";
+		return names[i];
+	} else {
+		strcpy(static_buff, "Auto:");
+		for (u = 0; u < 3; u++) {
+			code = (PicoAutoRgnOrder >> u*4) & 0xf;
+			for (i = 0; code; code >>= 1, i++)
+				;
+			strcat(static_buff, names_short[i]);
+		}
+		return static_buff;
+	}
+}
+
+static const char *mgn_opt_c_saves(menu_id id, int *offs)
+{
+	switch ((currentConfig.EmuOpt >> 9) & 5) {
+		default: return "OFF";
+		case 1:  return "writes";
+		case 4:  return "loads";
+		case 5:  return "both";
+	}
+}
+
+static const char *mgn_savecfg(menu_id id, int *offs)
+{
+	strcpy(static_buff, "Save global config");
+	if (config_slot != 0)
+		sprintf(static_buff + strlen(static_buff), " (profile: %i)", config_slot);
+	return static_buff;
+}
+
+static const char *mgn_loadcfg(menu_id id, int *offs)
+{
+	sprintf(static_buff, "Load cfg from profile %i", config_slot);
+	return static_buff;
+}
+
+static menu_entry e_menu_options[] =
+{
+	mee_range     ("Save slot",                MA_OPT_SAVE_SLOT,     state_slot, 0, 9),
+	mee_range_cust("Frameskip",                MA_OPT_FRAMESKIP,     currentConfig.Frameskip, -1, 16, mgn_opt_fskip),
+	mee_cust      ("Region",                   MA_OPT_REGION,        mh_opt_misc, mgn_opt_region),
+	mee_cust      ("Renderer",                 MA_OPT_RENDERER,      mh_opt_render, mgn_opt_renderer),
+	mee_onoff     ("Show FPS",                 MA_OPT_SHOW_FPS,      currentConfig.EmuOpt, 0x002),
+	mee_onoff     ("Enable sound",             MA_OPT_ENABLE_SOUND,  currentConfig.EmuOpt, 0x004),
+	mee_cust      ("Sound Quality",            MA_OPT_SOUND_QUALITY, mh_opt_misc, mgn_opt_sound),
+	mee_cust      ("Confirm savestate",        MA_OPT_CONFIRM_STATES,mh_opt_misc, mgn_opt_c_saves),
+#if   defined(__GP2X__)
+	mee_range     ("GP2X CPU clocks",          MA_OPT_CPU_CLOCKS,    currentConfig.CPUclock, 20, 400),
+#elif defined(PSP)
+	mee_range     ("PSP CPU clock",            MA_OPT_CPU_CLOCKS,    currentConfig.CPUclock, )
+#endif
+	mee_handler   ("[Display options]",        menu_loop_gfx_options),
+	mee_handler   ("[Advanced options]",       menu_loop_adv_options),
+	mee_handler   ("[Sega/Mega CD options]",   menu_loop_cd_options),
+	mee_handler_mkname_id(MA_OPT_SAVECFG, mh_saveloadcfg, mgn_savecfg),
+	mee_handler_id("Save cfg for current game only", MA_OPT_SAVECFG_GAME, mh_saveloadcfg),
+	mee_handler_mkname_id(MA_OPT_LOADCFG, mh_saveloadcfg, mgn_loadcfg),
+	mee_end,
+};
+
+static int menu_loop_options(menu_id id, int keys)
+{
+	static int sel = 0;
+
+	me_enable(e_menu_options, MA_OPT_SAVECFG_GAME, rom_loaded);
+	me_enable(e_menu_options, MA_OPT_LOADCFG, config_slot != config_slot_current);
+
+	me_loop(e_menu_options, &sel);
+
+	if (PicoRegionOverride)
+		// force setting possibly changed..
+		Pico.m.pal = (PicoRegionOverride == 2 || PicoRegionOverride == 8) ? 1 : 0;
+
+	return 0;
+}
+
+// ------------ debug menu ------------
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <pico/debug.h>
+
+extern void SekStepM68k(void);
+
+static void mplayer_loop(void)
+{
+	emu_startSound();
+
+	while (1)
+	{
+		PDebugZ80Frame();
+		if (in_menu_wait_any(0) & PBTN_NORTH) break;
+		emu_waitSound();
+	}
+
+	emu_endSound();
+}
+
+static void draw_text_debug(const char *str, int skip, int from)
+{
+	const char *p;
+	int line;
+
+	p = str;
+	while (skip-- > 0)
+	{
+		while (*p && *p != '\n') p++;
+		if (*p == 0 || p[1] == 0) return;
+		p++;
+	}
+
+	str = p;
+	for (line = from; line < SCREEN_HEIGHT/10; line++)
+	{
+		while (*p && *p != '\n') p++;
+		smalltext_out16(1, line*10, str, 0xffff);
+		if (*p == 0) break;
+		p++; str = p;
+	}
+}
+
+static void draw_frame_debug(void)
+{
+	char layer_str[48] = "layers:             ";
+	if (PicoDrawMask & PDRAW_LAYERB_ON)      memcpy(layer_str +  8, "B", 1);
+	if (PicoDrawMask & PDRAW_LAYERA_ON)      memcpy(layer_str + 10, "A", 1);
+	if (PicoDrawMask & PDRAW_SPRITES_LOW_ON) memcpy(layer_str + 12, "spr_lo", 6);
+	if (PicoDrawMask & PDRAW_SPRITES_HI_ON)  memcpy(layer_str + 19, "spr_hi", 6);
+
+	clear_screen();
+	emu_forcedFrame(0);
+	smalltext_out16(4, SCREEN_HEIGHT-8, layer_str, 0xffff);
+}
+
+static void debug_menu_loop(void)
+{
+	int inp, mode = 0;
+	int spr_offs = 0, dumped = 0;
+	char *tmp;
+
+	while (1)
+	{
+		switch (mode)
+		{
+			case 0: plat_video_menu_begin();
+				tmp = PDebugMain();
+				emu_platformDebugCat(tmp);
+				draw_text_debug(tmp, 0, 0);
+				if (dumped) {
+					smalltext_out16(SCREEN_WIDTH-6*10, SCREEN_HEIGHT-8, "dumped", 0xffff);
+					dumped = 0;
+				}
+				break;
+			case 1: draw_frame_debug(); break;
+			case 2: clear_screen();
+				emu_forcedFrame(0);
+				darken_screen();
+				PDebugShowSpriteStats((unsigned short *)SCREEN_BUFFER + (SCREEN_HEIGHT/2 - 240/2)*SCREEN_WIDTH +
+					SCREEN_WIDTH/2 - 320/2, SCREEN_WIDTH); break;
+			case 3: clear_screen();
+				PDebugShowPalette(SCREEN_BUFFER, SCREEN_WIDTH);
+				PDebugShowSprite((unsigned short *)SCREEN_BUFFER + SCREEN_WIDTH*120+SCREEN_WIDTH/2+16,
+					SCREEN_WIDTH, spr_offs);
+				draw_text_debug(PDebugSpriteList(), spr_offs, 6);
+				break;
+		}
+		plat_video_menu_end();
+
+		inp = in_menu_wait(PBTN_EAST|PBTN_MBACK|PBTN_WEST|PBTN_NORTH|PBTN_L|PBTN_R |
+					PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT, 70);
+		if (inp & PBTN_MBACK) return;
+		if (inp & PBTN_L) { mode--; if (mode < 0) mode = 3; }
+		if (inp & PBTN_R) { mode++; if (mode > 3) mode = 0; }
+		switch (mode)
+		{
+			case 0:
+				if (inp & PBTN_EAST) SekStepM68k();
+				if (inp & PBTN_NORTH) {
+					while (inp & PBTN_NORTH) inp = in_menu_wait_any(-1);
+					mplayer_loop();
+				}
+				if ((inp & (PBTN_WEST|PBTN_LEFT)) == (PBTN_WEST|PBTN_LEFT)) {
+					mkdir("dumps", 0777);
+					PDebugDumpMem();
+					while (inp & PBTN_WEST) inp = in_menu_wait_any(-1);
+					dumped = 1;
+				}
+				break;
+			case 1:
+				if (inp & PBTN_LEFT)  PicoDrawMask ^= PDRAW_LAYERB_ON;
+				if (inp & PBTN_RIGHT) PicoDrawMask ^= PDRAW_LAYERA_ON;
+				if (inp & PBTN_DOWN)  PicoDrawMask ^= PDRAW_SPRITES_LOW_ON;
 				if (inp & PBTN_UP)    PicoDrawMask ^= PDRAW_SPRITES_HI_ON;
 				if (inp & PBTN_EAST) {
 					PsndOut = NULL; // just in case
@@ -789,32 +1763,177 @@ void debug_menu_loop(void)
 	}
 }
 
-#endif // !UIQ3
+// ------------ main menu ------------
 
-// ------------ util ------------
+static char *romsel_run(void)
+{
+	char *ret, *sel_name;
+
+	sel_name = malloc(sizeof(rom_fname_loaded));
+	if (sel_name == NULL)
+		return NULL;
+	strcpy(sel_name, rom_fname_loaded);
+
+	ret = menu_loop_romsel(sel_name, sizeof(rom_fname_loaded));
+	free(sel_name);
+	return ret;
+}
 
-const char *me_region_name(unsigned int code, int auto_order)
+static int main_menu_handler(menu_id id, int keys)
 {
-	static const char *names[] = { "Auto", "      Japan NTSC", "      Japan PAL", "      USA", "      Europe" };
-	static const char *names_short[] = { "", " JP", " JP", " US", " EU" };
-	int u, i = 0;
-	if (code) {
-		code <<= 1;
-		while((code >>= 1)) i++;
-		if (i > 4) return "unknown";
-		return names[i];
-	} else {
-		static char name[24];
-		strcpy(name, "Auto:");
-		for (u = 0; u < 3; u++) {
-			i = 0; code = ((auto_order >> u*4) & 0xf) << 1;
-			while((code >>= 1)) i++;
-			strcat(name, names_short[i]);
+	char *ret_name;
+
+	switch (id)
+	{
+	case MA_MAIN_RESUME_GAME:
+		if (rom_loaded)
+			return 1;
+		break;
+	case MA_MAIN_SAVE_STATE:
+		if (rom_loaded)
+			return menu_loop_savestate(0);
+		break;
+	case MA_MAIN_LOAD_STATE:
+		if (rom_loaded)
+			return menu_loop_savestate(1);
+		break;
+	case MA_MAIN_RESET_GAME:
+		if (rom_loaded) {
+			emu_ResetGame();
+			return 1;
+		}
+		break;
+	case MA_MAIN_LOAD_ROM:
+		ret_name = romsel_run();
+		if (ret_name != NULL) {
+			lprintf("selected file: %s\n", ret_name);
+			engineState = PGS_ReloadRom;
+			return 1;
+		}
+		break;
+	case MA_MAIN_CREDITS:
+		draw_menu_credits();
+		in_menu_wait(PBTN_MOK|PBTN_MBACK, 70);
+		break;
+	case MA_MAIN_EXIT:
+		engineState = PGS_Quit;
+		return 1;
+	case MA_MAIN_PATCHES:
+		if (rom_loaded && PicoPatches) {
+			menu_loop_patches();
+			PicoPatchApply();
+			strcpy(menuErrorMsg, "Patches applied");
 		}
-		return name;
+		break;
+	default:
+		lprintf("%s: something unknown selected\n", __FUNCTION__);
+		break;
+	}
+
+	return 0;
+}
+
+static menu_entry e_menu_main[] =
+{
+	mee_handler_id("Resume game",        MA_MAIN_RESUME_GAME, main_menu_handler),
+	mee_handler_id("Save State",         MA_MAIN_SAVE_STATE,  main_menu_handler),
+	mee_handler_id("Load State",         MA_MAIN_LOAD_STATE,  main_menu_handler),
+	mee_handler_id("Reset game",         MA_MAIN_RESET_GAME,  main_menu_handler),
+	mee_handler_id("Load new ROM/ISO",   MA_MAIN_LOAD_ROM,    main_menu_handler),
+	mee_handler_id("Change options",     MA_MAIN_OPTIONS,     menu_loop_options),
+	mee_handler_id("Configure controls", MA_MAIN_OPTIONS,     menu_loop_keyconfig),
+	mee_handler_id("Credits",            MA_MAIN_CREDITS,     main_menu_handler),
+	mee_handler_id("Patches / GameGenie",MA_MAIN_PATCHES,     main_menu_handler),
+	mee_handler_id("Exit",               MA_MAIN_EXIT,        main_menu_handler),
+	mee_end,
+};
+
+void menu_loop(void)
+{
+	static int sel = 0;
+
+	me_enable(e_menu_main, MA_MAIN_RESUME_GAME, rom_loaded);
+	me_enable(e_menu_main, MA_MAIN_SAVE_STATE,  rom_loaded);
+	me_enable(e_menu_main, MA_MAIN_LOAD_STATE,  rom_loaded);
+	me_enable(e_menu_main, MA_MAIN_RESET_GAME,  rom_loaded);
+	me_enable(e_menu_main, MA_MAIN_PATCHES, PicoPatches != NULL);
+
+	plat_video_menu_enter(rom_loaded);
+	in_set_blocking(1);
+	me_loop(e_menu_main, &sel);
+	in_set_blocking(0);
+
+	if (rom_loaded && engineState == PGS_Menu) {
+		/* wait until menu, ok, back is released */
+		while (in_menu_wait_any(50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK));
+		engineState = PGS_Running;
+	}
+}
+
+// --------- CD tray close menu ----------
+
+static int mh_tray_load_cd(menu_id id, int keys)
+{
+	cd_img_type cd_type;
+	char *ret_name;
+	int ret = -1;
+
+	ret_name = romsel_run();
+	if (ret_name == NULL)
+		return 0;
+
+	cd_type = emu_cdCheck(NULL, ret_name);
+	if (cd_type != CIT_NOT_CD)
+		ret = Insert_CD(ret_name, cd_type);
+	if (ret != 0) {
+		sprintf(menuErrorMsg, "Load failed, invalid CD image?");
+		lprintf("%s\n", menuErrorMsg);
+		return 0;
+	}
+
+	engineState = PGS_RestartRun;
+	return 1;
+}
+
+static int mh_tray_nothing(menu_id id, int keys)
+{
+	return 1;
+}
+
+static menu_entry e_menu_tray[] =
+{
+	mee_label  ("The unit is about to"),
+	mee_label  ("close the CD tray."),
+	mee_label  (""),
+	mee_label  (""),
+	mee_handler("Load CD image",  mh_tray_load_cd),
+	mee_handler("Insert nothing", mh_tray_nothing),
+};
+
+int menu_loop_tray(void)
+{
+	int ret = 1, sel = 0;
+
+	plat_video_menu_enter(rom_loaded);
+
+	in_set_blocking(1);
+	me_loop(e_menu_tray, &sel);
+	in_set_blocking(0);
+
+	if (engineState != PGS_RestartRun) {
+		engineState = PGS_RestartRun;
+		ret = 0; /* no CD inserted */
 	}
+
+	while (in_menu_wait_any(50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK));
+
+	return ret;
 }
 
+#endif // !UIQ3
+
+// ------------ util ------------
+
 /* TODO: rename */
 void menu_darken_bg(void *dst, int pixels, int darker)
 {
@@ -838,3 +1957,49 @@ void menu_darken_bg(void *dst, int pixels, int darker)
 	}
 }
 
+/* hidden options for config engine only */
+static menu_entry e_menu_hidden[] =
+{
+	mee_onoff("Accurate sprites", MA_OPT_ACC_SPRITES, PicoOpt, 0x080),
+	mee_end,
+};
+
+static menu_entry *e_menu_table[] =
+{
+	e_menu_options,
+	e_menu_gfx_options,
+	e_menu_adv_options,
+	e_menu_cd_options,
+	e_menu_keyconfig,
+	e_menu_hidden,
+};
+
+static menu_entry *me_list_table = NULL;
+static menu_entry *me_list_i = NULL;
+
+menu_entry *me_list_get_first(void)
+{
+	me_list_table = me_list_i = e_menu_table[0];
+	return me_list_i;
+}
+
+menu_entry *me_list_get_next(void)
+{
+	int i;
+
+	me_list_i++;
+	if (me_list_i->name != NULL)
+		return me_list_i;
+
+	for (i = 0; i < array_size(e_menu_table); i++)
+		if (me_list_table == e_menu_table[i])
+			break;
+
+	if (i + 1 < array_size(e_menu_table))
+		me_list_table = me_list_i = e_menu_table[i + 1];
+	else
+		me_list_table = me_list_i = NULL;
+
+	return me_list_i;
+}
+
diff --git a/platform/common/menu.h b/platform/common/menu.h
index 0227d52c..c4aaaf00 100644
--- a/platform/common/menu.h
+++ b/platform/common/menu.h
@@ -4,9 +4,6 @@
 void menu_init(void);
 void text_out16(int x, int y, const char *texto, ...);
 void smalltext_out16(int x, int y, const char *texto, int color);
-void smalltext_out16_lim(int x, int y, const char *texto, int color, int max);
-void menu_draw_selection(int x, int y, int w);
-void debug_menu_loop(void);
 
 extern char menuErrorMsg[64];
 
@@ -15,7 +12,9 @@ typedef enum
 	MB_NONE = 1,		/* no auto processing */
 	MB_OPT_ONOFF,		/* ON/OFF setting */
 	MB_OPT_RANGE,		/* [min-max] setting */
-	MB_OPT_CUSTOM,
+	MB_OPT_CUSTOM,		/* custom value */
+	MB_OPT_CUSTONOFF,
+	MB_OPT_CUSTRANGE,
 } menu_behavior;
 
 typedef enum
@@ -97,6 +96,8 @@ typedef enum
 	MA_CTRL_PLAYER2,
 	MA_CTRL_EMU,
 	MA_CTRL_TURBO_RATE,
+	MA_CTRL_DEV_FIRST,
+	MA_CTRL_DEV_NEXT,
 	MA_CTRL_DONE,
 } menu_id;
 
@@ -107,22 +108,47 @@ typedef struct
 	menu_id id;
 	void *var;		/* for on-off/range settings */
 	int mask;		/* bit to toggle for on/off */
-	signed char min;	/* for ranged integer settings, to be sign-extended */
-	signed char max;
-	char enabled;
-	char need_to_save;
-	int (*submenu_handler)(menu_id id);
-	const char * (*generate_name)(int is_left);
+	signed short min;	/* for ranged integer settings, to be sign-extended */
+	signed short max;
+	int enabled:1;
+	int need_to_save:1;
+	int selectable:1;
+	int (*handler)(menu_id id, int keys);
+	const char * (*generate_name)(menu_id id, int *offs);
 } menu_entry;
 
-#define mee_submenu_id(name, id, handler) \
-	{ name, MB_NONE, id, NULL, 0, 0, 0, 1, 0, handler, NULL }
+#define mee_handler_id(name, id, handler) \
+	{ name, MB_NONE, id, NULL, 0, 0, 0, 1, 0, 1, handler, NULL }
 
-#define mee_submenu(name, handler) \
-	mee_submenu_id(name, MA_NONE, handler)
+#define mee_handler(name, handler) \
+	mee_handler_id(name, MA_NONE, handler)
+
+#define mee_handler_mkname_id(id, handler, name_func) \
+	{ "", MB_NONE, id, NULL, 0, 0, 0, 1, 0, 1, handler, name_func }
+
+#define mee_label(name) \
+	{ name, MB_NONE, MA_NONE, NULL, 0, 0, 0, 1, 0, 0, NULL, NULL }
+
+#define mee_label_mk(id, name_func) \
+	{ "", MB_NONE, id, NULL, 0, 0, 0, 1, 0, 0, NULL, name_func }
+
+#define mee_onoff(name, id, var, mask) \
+	{ name, MB_OPT_ONOFF, id, &(var), mask, 0, 0, 1, 1, 1, NULL, NULL }
+
+#define mee_range(name, id, var, min, max) \
+	{ name, MB_OPT_RANGE, id, &(var), 0, min, max, 1, 1, 1, NULL, NULL }
+
+#define mee_cust(name, id, handler, name_func) \
+	{ name, MB_OPT_CUSTOM, id, NULL, 0, 0, 0, 1, 1, 1, handler, name_func }
+
+#define mee_onoff_cust(name, id, var, mask, name_func) \
+	{ name, MB_OPT_CUSTONOFF, id, &(var), mask, 0, 0, 1, 1, 1, NULL, name_func }
+
+#define mee_range_cust(name, id, var, min, max, name_func) \
+	{ name, MB_OPT_CUSTRANGE, id, &(var), 0, min, max, 1, 1, 1, NULL, name_func }
 
 #define mee_end \
-	{ NULL, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, NULL }
+	{ NULL, 0, 0, NULL, 0, 0, 0, 0, 0, 0, NULL, NULL }
 
 typedef struct
 {
@@ -133,18 +159,8 @@ typedef struct
 extern me_bind_action me_ctrl_actions[15];
 extern me_bind_action emuctrl_actions[];	// platform code
 
-
-typedef void (me_draw_custom_f)(const menu_entry *entry, int x, int y, void *param);
-
-/* TODO: move? */
-int     me_id2offset(const menu_entry *entries, menu_id id);
-void    me_enable(menu_entry *entries, menu_id id, int enable);
-int     me_count_enabled(const menu_entry *ent);
-menu_id me_index2id(const menu_entry *entries, int index);
-void    me_draw(const menu_entry *entries, int count, int x, int y, me_draw_custom_f *cust_draw, void *param);
-int     me_process(menu_entry *entries, menu_id id, int is_next);
-
-const char *me_region_name(unsigned int code, int auto_order);
+menu_entry *me_list_get_first(void);
+menu_entry *me_list_get_next(void);
 
 void menu_darken_bg(void *dst, int pixels, int darker);
 
diff --git a/platform/common/plat.h b/platform/common/plat.h
index 35ceaeb5..27f331fe 100644
--- a/platform/common/plat.h
+++ b/platform/common/plat.h
@@ -1,22 +1,32 @@
-/* stuff to be implemented by platform code */
-
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern const char * const keyNames[];
+/* stuff to be implemented by platform code */
+/* TODO rename all these */
+extern const char * const keyNames[]; // TODO rm
 void  emu_prepareDefaultConfig(void);
 void  emu_platformDebugCat(char *str);
 void  emu_forcedFrame(int opts);
 void  emu_startSound(void);
 void  emu_endSound(void);
 void  emu_waitSound(void);
+void  emu_ResetGame(void); // TODO mv rm?
+
+void emu_noticeMsgUpdated(void);
+int  emu_getMainDir(char *dst, int len);
+void menu_romload_prepare(const char *rom_name);
+void menu_romload_end(void);
 
 /* menu: enter (switch bpp, etc), begin/end drawing */
 void plat_video_menu_enter(int is_rom_loaded);
 void plat_video_menu_begin(void);
 void plat_video_menu_end(void);
 
+int  plat_is_dir(const char *path);
+
+const char *plat_get_credits(void);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/platform/common/posix.h b/platform/common/posix.h
new file mode 100644
index 00000000..91001418
--- /dev/null
+++ b/platform/common/posix.h
@@ -0,0 +1,13 @@
+/* define POSIX stuff: dirent, scandir, getcwd */
+#if defined(__linux__)
+
+#include <dirent.h>
+#include <unistd.h>
+
+#else
+
+#error "must define dirent"
+
+#endif
+
+
diff --git a/platform/gizmondo/emu.c b/platform/gizmondo/emu.c
index 5469c562..f4ce7649 100644
--- a/platform/gizmondo/emu.c
+++ b/platform/gizmondo/emu.c
@@ -26,8 +26,6 @@
 
 // main 300K gfx-related buffer. Used by menu and renderers.
 unsigned char gfx_buffer[321*240*2*2];
-char romFileName[MAX_PATH];
-int engineState;
 
 unsigned char *PicoDraw2FB = gfx_buffer;  // temporary buffer for alt renderer ( (8+320)*(8+240+8) )
 int reset_timing = 0;
diff --git a/platform/gizmondo/emu.h b/platform/gizmondo/emu.h
index dcff7a9b..6f0322a8 100644
--- a/platform/gizmondo/emu.h
+++ b/platform/gizmondo/emu.h
@@ -4,9 +4,6 @@
 // For commercial use, separate licencing terms must be obtained.
 
 extern unsigned char gfx_buffer[321*240*2*2];
-extern char romFileName[];
-extern int engineState;
-
 
 void emu_Init(void);
 void emu_Deinit(void);
diff --git a/platform/gp2x/Makefile b/platform/gp2x/Makefile
index ed4f9286..625e679a 100644
--- a/platform/gp2x/Makefile
+++ b/platform/gp2x/Makefile
@@ -31,7 +31,7 @@ else
 use_cyclone = 1
 endif
 
-DEFINC = -I../.. -I. -DARM -D__GP2X__ # -DBENCHMARK
+DEFINC = -I../.. -I. -DARM -D__GP2X__ -DIN_GP2X # -DBENCHMARK
 COPT_COMMON = -static -Wall -Winline
 ifeq ($(DEBUG),)
 COPT_COMMON += -O3 -ftracer -fstrength-reduce -fomit-frame-pointer -fstrict-aliasing -ffast-math
@@ -54,14 +54,15 @@ LD = $(CROSS)ld
 OBJCOPY = $(CROSS)objcopy
 
 # frontend
-OBJS += main.o menu.o gp2x.o emu.o squidgehack.o cpuctrl.o
+OBJS += main.o gp2x.o emu.o in_gp2x.o squidgehack.o cpuctrl.o
 # 940 core control
 OBJS += 940ctl.o
 
 # common
 OBJS += platform/common/emu.o platform/common/menu.o platform/common/fonts.o platform/common/config.o \
 	platform/common/arm_utils.o platform/common/arm_linux.o platform/common/readpng.o \
-	platform/common/mp3_helix.o platform/linux/usbjoy.o platform/linux/sndout_oss.o
+	platform/common/mp3_helix.o platform/common/input.o platform/linux/usbjoy.o \
+	platform/linux/sndout_oss.o platform/linux/plat.o
 
 # Pico
 ifeq "$(amalgamate)" "1"
diff --git a/platform/gp2x/emu.c b/platform/gp2x/emu.c
index b9079a92..f5052dbd 100644
--- a/platform/gp2x/emu.c
+++ b/platform/gp2x/emu.c
@@ -25,6 +25,7 @@
 #include "../common/input.h"
 #include "../linux/sndout_oss.h"
 #include "cpuctrl.h"
+#include "version.h"
 
 #include <pico/pico_int.h>
 #include <pico/patch.h>
@@ -40,11 +41,8 @@
 #endif
 
 
-int engineState;
 int select_exits = 0;
 
-char romFileName[PATH_MAX];
-
 extern int crashed_940;
 
 static short __attribute__((aligned(4))) sndBuffer[2*44100/50];
@@ -1065,3 +1063,24 @@ void emu_ResetGame(void)
 	reset_timing = 1;
 }
 
+const char *plat_get_credits(void)
+{
+	return "PicoDrive v" VERSION " (c) notaz, 2006-2009\n\n\n"
+		"Credits:\n"
+		"fDave: Cyclone 68000 core,\n"
+		"      base code of PicoDrive\n"
+		"Reesy & FluBBa: DrZ80 core\n"
+		"MAME devs: YM2612 and SN76496 cores\n"
+		"rlyeh and others: minimal SDK\n"
+		"Squidge: squidgehack\n"
+		"Dzz: ARM940 sample\n"
+		"GnoStiC / Puck2099: USB joy code\n"
+		"craigix: GP2X hardware\n"
+		"ketchupgun: skin design\n"
+		"\n"
+		"special thanks (for docs, ideas):\n"
+		" Charles MacDonald, Haze,\n"
+		" Stephane Dallongeville,\n"
+		" Lordus, Exophase, Rokas,\n"
+		" Nemesis, Tasco Deluxe";
+}
diff --git a/platform/gp2x/emu.h b/platform/gp2x/emu.h
index 21190c0f..20342d1b 100644
--- a/platform/gp2x/emu.h
+++ b/platform/gp2x/emu.h
@@ -4,14 +4,9 @@
 // For commercial use, separate licencing terms must be obtained.
 
 
-extern char romFileName[];
-extern int engineState;
-
-
 void emu_Init(void);
 void emu_Deinit(void);
 void emu_Loop(void);
-void emu_ResetGame(void);
 
 void osd_text(int x, int y, const char *text);
 
diff --git a/platform/gp2x/in_gp2x.c b/platform/gp2x/in_gp2x.c
index 489bbf7d..78ee92ef 100644
--- a/platform/gp2x/in_gp2x.c
+++ b/platform/gp2x/in_gp2x.c
@@ -103,7 +103,9 @@ static int in_gp2x_menu_translate(int keycode)
 		case BTN_RIGHT:	return PBTN_RIGHT;
 		case BTN_B:	return PBTN_MOK;
 		case BTN_X:	return PBTN_MBACK;
-		case BTN_START:	return PBTN_MENU;
+		case BTN_SELECT:return PBTN_MENU;
+		case BTN_L:	return PBTN_L;
+		case BTN_R:	return PBTN_R;
 		default:	return 0;
 	}
 }
diff --git a/platform/gp2x/main.c b/platform/gp2x/main.c
index d8d82afe..95a181c9 100644
--- a/platform/gp2x/main.c
+++ b/platform/gp2x/main.c
@@ -16,6 +16,7 @@
 #include "../common/emu.h"
 #include "../common/config.h"
 #include "../common/input.h"
+#include "../common/plat.h"
 #include "emu.h"
 #include "940ctl.h"
 #include "version.h"
@@ -61,9 +62,9 @@ void parse_cmd_line(int argc, char *argv[])
 		} else {
 			/* External Frontend: ROM Name */
 			FILE *f;
-			strncpy(romFileName, argv[x], PATH_MAX);
-			romFileName[PATH_MAX-1] = 0;
-			f = fopen(romFileName, "rb");
+			strncpy(rom_fname_reload, argv[x], PATH_MAX);
+			rom_fname_reload[PATH_MAX-1] = 0;
+			f = fopen(rom_fname_reload, "rb");
 			if (f) fclose(f);
 			else unrecognized = 1;
 			engineState = PGS_ReloadRom;
@@ -122,7 +123,7 @@ int main(int argc, char *argv[])
 
 	if (engineState == PGS_ReloadRom)
 	{
-		if (emu_ReloadRom(romFileName)) {
+		if (emu_ReloadRom(rom_fname_reload)) {
 			engineState = PGS_Running;
 			if (load_state_slot >= 0) {
 				state_slot = load_state_slot;
@@ -140,7 +141,7 @@ int main(int argc, char *argv[])
 				break;
 
 			case PGS_ReloadRom:
-				if (emu_ReloadRom(romFileName))
+				if (emu_ReloadRom(rom_fname_reload))
 					engineState = PGS_Running;
 				else {
 					printf("PGS_ReloadRom == 0\n");
diff --git a/platform/linux/Makefile b/platform/linux/Makefile
index f7b6827e..006e982d 100644
--- a/platform/linux/Makefile
+++ b/platform/linux/Makefile
@@ -28,7 +28,7 @@ LDFLAGS += `pkg-config --libs gthread-2.0`
 
 # frontend
 OBJS += platform/gp2x/main.o platform/gp2x/emu.o platform/gp2x/plat.o usbjoy.o blit.o \
-		in_evdev.o sndout_oss.o gp2x.o 940ctl_ym2612.o log_io.o
+		in_evdev.o plat.o sndout_oss.o gp2x.o 940ctl_ym2612.o log_io.o
 # platform/gp2x/menu.o
 
 ifeq "$(fake_in_gp2x)" "1"
diff --git a/platform/linux/plat.c b/platform/linux/plat.c
new file mode 100644
index 00000000..102af6e3
--- /dev/null
+++ b/platform/linux/plat.c
@@ -0,0 +1,14 @@
+#include <dirent.h>
+#include "../common/plat.h"
+
+
+int plat_is_dir(const char *path)
+{
+	DIR *dir;
+	if ((dir = opendir(path))) {
+		closedir(dir);
+		return 1;
+	}
+	return 0;
+}
+
diff --git a/platform/pandora/emu.c b/platform/pandora/emu.c
index 8bc76be6..2d06d98f 100644
--- a/platform/pandora/emu.c
+++ b/platform/pandora/emu.c
@@ -42,11 +42,8 @@
 #endif
 
 
-int engineState;
 int select_exits = 0;
 
-char romFileName[PATH_MAX];
-
 static short __attribute__((aligned(4))) sndBuffer[2*44100/50];
 static struct timeval noticeMsgTime = { 0, 0 };	// when started showing
 static int osd_fps_x;
diff --git a/platform/pandora/main.c b/platform/pandora/main.c
index 4690f067..51f7f8e8 100644
--- a/platform/pandora/main.c
+++ b/platform/pandora/main.c
@@ -50,9 +50,9 @@ void parse_cmd_line(int argc, char *argv[])
 		} else {
 			/* External Frontend: ROM Name */
 			FILE *f;
-			strncpy(romFileName, argv[x], PATH_MAX);
-			romFileName[PATH_MAX-1] = 0;
-			f = fopen(romFileName, "rb");
+			strncpy(rom_fname_reload, argv[x], PATH_MAX);
+			rom_fname_reload[PATH_MAX-1] = 0;
+			f = fopen(rom_fname_reload, "rb");
 			if (f) fclose(f);
 			else unrecognized = 1;
 			engineState = PGS_ReloadRom;
@@ -96,7 +96,7 @@ int main(int argc, char *argv[])
 
 	if (engineState == PGS_ReloadRom)
 	{
-		if (emu_ReloadRom(romFileName)) {
+		if (emu_ReloadRom(rom_fname_reload)) {
 			engineState = PGS_Running;
 			if (load_state_slot >= 0) {
 				state_slot = load_state_slot;
@@ -114,7 +114,7 @@ int main(int argc, char *argv[])
 				break;
 
 			case PGS_ReloadRom:
-				if (emu_ReloadRom(romFileName))
+				if (emu_ReloadRom(rom_fname_reload))
 					engineState = PGS_Running;
 				else {
 					printf("PGS_ReloadRom == 0\n");
diff --git a/platform/psp/emu.c b/platform/psp/emu.c
index a9acac78..bbf07e5d 100644
--- a/platform/psp/emu.c
+++ b/platform/psp/emu.c
@@ -33,9 +33,8 @@ int sceAudio_E0727056(int volume, void *buffer);	// blocking output
 int sceAudioOutput2GetRestSample();
 
 
-char romFileName[PATH_MAX];
 unsigned char *PicoDraw2FB = (unsigned char *)VRAM_CACHED_STUFF + 8; // +8 to be able to skip border with 1 quadword..
-int engineState = PGS_Menu, engineStateSuspend;
+int engineStateSuspend;
 
 static unsigned int noticeMsgTime = 0;
 int reset_timing = 0; // do we need this?
@@ -1127,13 +1126,13 @@ void emu_HandleResume(void)
 	// reopen first CD track
 	if (Pico_mcd->TOC.Tracks[0].F != NULL)
 	{
-		char *fname = romFileName;
-		int len = strlen(romFileName);
+		char *fname = rom_fname_reload;
+		int len = strlen(rom_fname_reload);
 		cue_data_t *cue_data = NULL;
 
 		if (len > 4 && strcasecmp(fname + len - 4,  ".cue") == 0)
 		{
-			cue_data = cue_parse(romFileName);
+			cue_data = cue_parse(rom_fname_reload);
 			if (cue_data != NULL)
 				fname = cue_data->tracks[1].fname;
 		}
diff --git a/platform/psp/emu.h b/platform/psp/emu.h
index fc0a36fa..dc53ac15 100644
--- a/platform/psp/emu.h
+++ b/platform/psp/emu.h
@@ -4,10 +4,7 @@
 // For commercial use, separate licencing terms must be obtained.
 
 
-
-extern char romFileName[];
-extern int engineState, engineStateSuspend;
-
+extern int engineStateSuspend;
 
 void emu_Init(void);
 void emu_Deinit(void);
diff --git a/platform/psp/main.c b/platform/psp/main.c
index e0698d5e..e96eacc5 100644
--- a/platform/psp/main.c
+++ b/platform/psp/main.c
@@ -23,7 +23,7 @@
 
 void dummy(void)
 {
-	engineState = atoi(romFileName);
+	engineState = atoi(rom_fname_reload);
 	setbuf(NULL, NULL);
 	getenv(NULL);
 }
@@ -52,13 +52,13 @@ int pico_main(void)
 #ifndef GPROF
 				menu_loop();
 #else
-				strcpy(romFileName, loadedRomFName);
+				strcpy(rom_fname_reload, rom_fname_loaded);
 				engineState = PGS_ReloadRom;
 #endif
 				break;
 
 			case PGS_ReloadRom:
-				if (emu_ReloadRom(romFileName)) {
+				if (emu_ReloadRom(rom_fname_reload)) {
 					engineState = PGS_Running;
 					if (mp3_last_error != 0)
 						engineState = PGS_Menu; // send to menu to display mp3 error
diff --git a/platform/uiq3/App.cpp b/platform/uiq3/App.cpp
index 7497c166..4eaee3a0 100644
--- a/platform/uiq3/App.cpp
+++ b/platform/uiq3/App.cpp
@@ -216,7 +216,7 @@ void CPicolAppView::DisplayOpenROMDialogL()
 	CleanupStack::PushL(fileArray);
 	_LIT16(KDlgTitle, "Select a ROM file");
 
-	TPtrC8 text8((TUint8*) loadedRomFName);
+	TPtrC8 text8((TUint8*) rom_fname_loaded);
 	iCurrentConfig.iLastROMFile.Copy(text8);
 
 	if( CQikSelectFileDlg::RunDlgLD( *mimeArray, *fileArray, &KDlgTitle, &iCurrentConfig.iLastROMFile) )