Browse Source

Merge pull request #9 from DrUm78/FunKey

All PicoDrive changes since PicoDrive 1.3.1.
Vincent-FK 2 years ago
parent
commit
50f973723c
82 changed files with 7816 additions and 3288 deletions
  1. 1 1
      .gitignore
  2. 1 1
      .gitmodules
  3. 3 1
      Makefile
  4. 1 4
      Makefile.libretro
  5. 1 1
      cpu/DrZ80/drz80.S
  6. 1 1
      cpu/cyclone
  7. 47 58
      cpu/cz80/cz80.c
  8. 22 17
      cpu/cz80/cz80.h
  9. 24 10
      cpu/cz80/cz80_op.c
  10. 33 30
      cpu/cz80/cz80_opED.c
  11. 4 2
      cpu/cz80/cz80_opXY.c
  12. 1 1
      cpu/cz80/cz80macro.h
  13. 10 6
      cpu/sh2/compiler.c
  14. 1 0
      cpu/sh2/mame/sh2.c
  15. 4 2
      cpu/sh2/mame/sh2pico.c
  16. 3 0
      cpu/sh2/sh2.h
  17. BIN
      icons/picodrive.png
  18. 46 0
      package
  19. 7 8
      pico/32x/32x.c
  20. 14 11
      pico/32x/draw.c
  21. 21 6
      pico/32x/draw_arm.S
  22. 26 25
      pico/32x/memory.c
  23. 24 1
      pico/cart.c
  24. 18 0
      pico/carthw.cfg
  25. 240 1
      pico/carthw/carthw.c
  26. 3 0
      pico/carthw/carthw.h
  27. 12 0
      pico/carthw_cfg.c
  28. 1 1
      pico/cd/libchdr
  29. 2 2
      pico/cd/memory.c
  30. 3 2
      pico/cd/pcm.c
  31. 1 1
      pico/debug.c
  32. 101 62
      pico/draw.c
  33. 436 42
      pico/draw_arm.S
  34. 2 1
      pico/m68kif_cyclone.s
  35. 20 6
      pico/media.c
  36. 96 25
      pico/memory.c
  37. 5 5
      pico/memory.h
  38. 602 201
      pico/mode4.c
  39. 15 3
      pico/pico.h
  40. 33 10
      pico/pico_int.h
  41. 1 1
      pico/pico_port.h
  42. 458 74
      pico/sms.c
  43. 1 1
      pico/sound/emu2413
  44. 1 1
      pico/sound/mix.c
  45. 36 50
      pico/sound/sn76496.c
  46. 29 28
      pico/sound/sound.c
  47. 20 4
      pico/sound/ym2612.c
  48. 7 4
      pico/sound/ym2612.h
  49. 22 12
      pico/sound/ym2612_arm.S
  50. 37 27
      pico/videoport.c
  51. 9 0
      picodrive.sh
  52. 1 1
      platform/common/arm_utils.h
  53. 29 31
      platform/common/arm_utils.s
  54. 0 12
      platform/common/config_file.c
  55. 1 1
      platform/common/dr_libs
  56. 1930 1922
      platform/common/emu.c
  57. 24 5
      platform/common/emu.h
  58. 7 10
      platform/common/inputmap_kbd.c
  59. 37 3
      platform/common/menu_pico.c
  60. 6 7
      platform/common/menu_pico.h
  61. 1 1
      platform/common/minimp3
  62. 73 33
      platform/common/plat_sdl.c
  63. 609 0
      platform/common/upscale.c
  64. 650 0
      platform/common/upscale.h
  65. 1 1
      platform/common/version.h
  66. 71 45
      platform/gp2x/emu.c
  67. 1 1
      platform/libpicofe
  68. 803 2
      platform/libretro/libretro-common/include/libretro.h
  69. 180 87
      platform/libretro/libretro.c
  70. 454 171
      platform/libretro/libretro_core_options.h
  71. 44 12
      platform/libretro/libretro_core_options_intl.h
  72. 2 2
      platform/linux/blit.c
  73. 241 40
      platform/linux/emu.c
  74. 9 4
      platform/linux/menu.c
  75. 1 1
      platform/opendingux/data/default.gcw0.desktop
  76. 1 1
      platform/opendingux/data/default.retrofw.desktop
  77. 6 7
      platform/pandora/plat.c
  78. 78 59
      platform/psp/emu.c
  79. 10 76
      platform/psp/menu.c
  80. 3 3
      platform/win32/plat.c
  81. 32 0
      submodule_update.sh
  82. 5 0
      tools/mkoffsets.sh

+ 1 - 1
.gitignore

@@ -9,6 +9,7 @@
 *.dylib
 *.so
 *.a
+*.opk
 tags
 cscope.out
 config.mak
@@ -26,7 +27,6 @@ libs/
 obj/
 .opk_data
 PicoDrive
-PicoDrive.opk
 pico_int_offs.h
 amalgamate
 textfilter

+ 1 - 1
.gitmodules

@@ -1,6 +1,6 @@
 [submodule "platform/libpicofe"]
 	path = platform/libpicofe
-	url = https://github.com/FunKey-Project/picofe-irixxxx.git
+	url = https://github.com/DrUm78/picofe-irixxxx
 [submodule "cpu/cyclone"]
 	path = cpu/cyclone
 	url = https://github.com/irixxxx/cyclone68000.git

+ 3 - 1
Makefile

@@ -60,7 +60,9 @@ endif
 use_libchdr ?= 1
 ifeq "$(ARCH)" "arm"
 use_cyclone ?= 1
+use_fame ?= 0
 use_drz80 ?= 1
+use_cz80 ?= 0
 use_sh2drc ?= 1
 use_svpdrc ?= 1
 
@@ -210,7 +212,7 @@ endif
 ifeq "$(USE_FRONTEND)" "1"
 
 # common
-OBJS += platform/common/main.o platform/common/configfile_fk.o platform/common/emu.o \
+OBJS += platform/common/main.o platform/common/configfile_fk.o platform/common/emu.o platform/common/upscale.o \
 	platform/common/menu_pico.o platform/common/config_file.o
 
 # libpicofe

+ 1 - 4
Makefile.libretro

@@ -281,12 +281,9 @@ else ifeq ($(platform), wiiu)
 	CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT)
 	CXX = $(DEVKITPPC)/bin/powerpc-eabi-g++$(EXE_EXT)
 	AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT)
-	CFLAGS += -DGEKKO -DWIIU -DHW_RVL -mwup -mcpu=750 -meabi -mhard-float -D__ppc__ -DMSB_FIRST
+	CFLAGS += -DGEKKO -DWIIU -DHW_RVL -DHW_WUP -mwup -mcpu=750 -meabi -mhard-float -D__ppc__ -DMSB_FIRST
 	STATIC_LINKING = 1
 	NO_MMAP = 1
-	# Wii U has memory mapped in a way not suitable for DRC
-	use_sh2drc = 0
-	use_svpdrc = 0
 
 # Nintendo Switch (libtransistor)
 else ifeq ($(platform), switch)

+ 1 - 1
cpu/DrZ80/drz80.S

@@ -20,7 +20,7 @@
       .equiv DRZ80_XMAP_MORE_INLINE, 1
 
 .if DRZ80_XMAP
-      .equ Z80_MEM_SHIFT, 13
+      .equ Z80_MEM_SHIFT, 10
 .endif
 
 .if INTERRUPT_MODE

+ 1 - 1
cpu/cyclone

@@ -1 +1 @@
-Subproject commit 94a9d9a888e72cbfa4db12113cd113cf5e154f7f
+Subproject commit 1f03ed6a49252bfb900c4d3932094308e1c5b375

+ 47 - 58
cpu/cz80/cz80.c

@@ -196,10 +196,9 @@ void Cz80_Init(cz80_struc *CPU)
 	CPU->pzR16[0] = pzBC;
 	CPU->pzR16[1] = pzDE;
 	CPU->pzR16[2] = pzHL;
-	CPU->pzR16[3] = pzAF;
+	CPU->pzR16[3] = pzFA;
 
 	zIX = zIY = 0xffff;
-	zF = ZF;
 
 	CPU->Interrupt_Callback = Cz80_Interrupt_Callback;
 }
@@ -211,7 +210,8 @@ void Cz80_Init(cz80_struc *CPU)
 
 void Cz80_Reset(cz80_struc *CPU)
 {
-	memset(CPU, 0, (FPTR)&CPU->BasePC - (FPTR)CPU);
+	// I, R, CPU and interrupts logic is reset, registers are untouched
+	memset(&CPU->R, 0, (FPTR)&CPU->BasePC - (FPTR)&CPU->R);
 	Cz80_Set_Reg(CPU, CZ80_PC, 0);
 }
 
@@ -244,7 +244,6 @@ INT32 Cz80_Exec(cz80_struc *CPU, INT32 cycles)
 	UINT32 adr = 0;
 	UINT32 res;
 	UINT32 val;
-	int afterEI = 0;
 	union16 *data;
 
 	PC = CPU->PC;
@@ -254,33 +253,36 @@ INT32 Cz80_Exec(cz80_struc *CPU, INT32 cycles)
 	CPU->ICount = cycles - CPU->ExtraCycles;
 	CPU->ExtraCycles = 0;
 
-	if (!CPU->HaltState)
-	{
 Cz80_Exec:
-		if (CPU->ICount > 0)
+	if (CPU->Status)
+	{
+		if (CPU->Status & CZ80_HAS_NMI)
 		{
+			zIFF1 = 0;
+			CPU->Status &= ~(CZ80_HALTED | CZ80_HAS_NMI);
+			CPU->ExtraCycles += 11;
+			PUSH_16(zRealPC);
+			SET_PC(0x66);
+		} else if (CPU->Status & CZ80_HAS_INT)
+		{
+			CHECK_INT
+		} else if (CPU->Status & CZ80_HALTED)
+		{
+			goto Cz80_Exec_End;
+		}
+		CPU->ICount -= CPU->ExtraCycles;
+		CPU->ExtraCycles = 0;
+	}
+
+	if (CPU->ICount > 0)
+	{
 Cz80_Exec_nocheck:
-			data = pzHL;
-			Opcode = READ_OP();
+		data = pzHL;
+		Opcode = READ_OP();
 #if CZ80_EMULATE_R_EXACTLY
-			zR++;
+		zR++;
 #endif
-			#include "cz80_op.c"
-		}
-
-		if (afterEI)
-		{
-			afterEI = 0;
-Cz80_Check_Interrupt:
-			if (CPU->IRQState != CLEAR_LINE)
-			{
-				CHECK_INT
-			}
-			CPU->ICount -= CPU->ExtraCycles;
-			CPU->ExtraCycles = 0;
-			if (!CPU->HaltState)
-				goto Cz80_Exec;
-		}
+		#include "cz80_op.c"
 	}
 
 Cz80_Exec_End:
@@ -288,7 +290,7 @@ Cz80_Exec_End:
 #if CZ80_ENCRYPTED_ROM
 	CPU->OPBase = OPBase;
 #endif
-	if (!(CPU->HaltState && CPU->ICount > 0))
+	if (!((CPU->Status & CZ80_HALTED) && CPU->ICount > 0))
 		cycles -= CPU->ICount;
 	CPU->ICount = 0;
 #if !CZ80_EMULATE_R_EXACTLY
@@ -307,36 +309,23 @@ void Cz80_Set_IRQ(cz80_struc *CPU, INT32 line, INT32 state)
 {
 	if (line == IRQ_LINE_NMI)
 	{
-		zIFF1 = 0;
-		CPU->ExtraCycles += 11;
-		CPU->HaltState = 0;
-		PUSH_16(CPU->PC - CPU->BasePC)
-		Cz80_Set_Reg(CPU, CZ80_PC, 0x66);
-	}
-	else
+		if (state)
+			CPU->Status |= CZ80_HAS_NMI;
+		else
+			CPU->Status &= ~CZ80_HAS_NMI;
+	} else
 	{
+		CPU->IRQLine = line;
 		CPU->IRQState = state;
-
-		if (state != CLEAR_LINE)
+		if (state)
 		{
-			FPTR PC = CPU->PC;
-#if CZ80_ENCRYPTED_ROM
-			FPTR OPBase = CPU->OPBase;
-#endif
-
-			CPU->IRQLine = line;
-			CHECK_INT
-			CPU->PC = PC;
-#if CZ80_ENCRYPTED_ROM
-			CPU->OPBase = OPBase;
-#endif
+			if (zIFF1)
+				CPU->Status |= CZ80_HAS_INT;
+		} else
+		{
+			CPU->Status &= ~CZ80_HAS_INT;
 		}
 	}
-	if (CPU->ICount > 0)
-	{
-		CPU->ICount -= CPU->ExtraCycles;
-		CPU->ExtraCycles  = 0;
-	}
 }
 
 
@@ -350,13 +339,13 @@ UINT32 Cz80_Get_Reg(cz80_struc *CPU, INT32 regnum)
 	{
 	case CZ80_PC:   return (CPU->PC - CPU->BasePC);
 	case CZ80_SP:   return zSP;
-	case CZ80_AF:   return zAF;
+	case CZ80_FA:   return zFA;
 	case CZ80_BC:   return zBC;
 	case CZ80_DE:   return zDE;
 	case CZ80_HL:   return zHL;
 	case CZ80_IX:   return zIX;
 	case CZ80_IY:   return zIY;
-	case CZ80_AF2:  return zAF2;
+	case CZ80_FA2:  return zFA2;
 	case CZ80_BC2:  return zBC2;
 	case CZ80_DE2:  return zDE2;
 	case CZ80_HL2:  return zHL2;
@@ -365,7 +354,7 @@ UINT32 Cz80_Get_Reg(cz80_struc *CPU, INT32 regnum)
 	case CZ80_IM:   return zIM;
 	case CZ80_IFF1: return zIFF1;
 	case CZ80_IFF2: return zIFF2;
-	case CZ80_HALT: return CPU->HaltState;
+	case CZ80_HALT: return CPU->Status & CZ80_HALTED;
 	case CZ80_IRQ:  return CPU->IRQState;
 	default: return 0;
 	}
@@ -389,13 +378,13 @@ void Cz80_Set_Reg(cz80_struc *CPU, INT32 regnum, UINT32 val)
 		break;
 
 	case CZ80_SP:   zSP = val; break;
-	case CZ80_AF:   zAF = val; break;
+	case CZ80_FA:   zFA = val; break;
 	case CZ80_BC:   zBC = val; break;
 	case CZ80_DE:   zDE = val; break;
 	case CZ80_HL:   zHL = val; break;
 	case CZ80_IX:   zIX = val; break;
 	case CZ80_IY:   zIY = val; break;
-	case CZ80_AF2:  zAF2 = val; break;
+	case CZ80_FA2:  zFA2 = val; break;
 	case CZ80_BC2:  zBC2 = val; break;
 	case CZ80_DE2:  zDE2 = val; break;
 	case CZ80_HL2:  zHL2 = val; break;
@@ -404,7 +393,7 @@ void Cz80_Set_Reg(cz80_struc *CPU, INT32 regnum, UINT32 val)
 	case CZ80_IM:   zIM = val; break;
 	case CZ80_IFF1: zIFF1 = val ? (1 << 2) : 0; break;
 	case CZ80_IFF2: zIFF2 = val ? (1 << 2) : 0; break;
-	case CZ80_HALT: CPU->HaltState = val; break;
+	case CZ80_HALT: CPU->Status = !!val * CZ80_HALTED; break;
 	case CZ80_IRQ:  CPU->IRQState = val; break;
 	default: break;
 	}

+ 22 - 17
cpu/cz80/cz80.h

@@ -59,7 +59,8 @@ extern "C" {
 /* Z80 core Structures & definitions */
 /*************************************/
 
-#define CZ80_FETCH_BITS			4   // [4-12]   default = 8
+// NB this must have at least the value of (16-Z80_MEM_SHIFT)
+#define CZ80_FETCH_BITS			6   // [4-12]   default = 8
 
 #define CZ80_FETCH_SFT			(16 - CZ80_FETCH_BITS)
 #define CZ80_FETCH_BANK			(1 << CZ80_FETCH_BITS)
@@ -78,12 +79,12 @@ extern "C" {
 #define zR8(A)		(*CPU->pzR8[A])
 #define zR16(A)		(CPU->pzR16[A]->W)
 
-#define pzAF		&(CPU->AF)
-#define zAF			CPU->AF.W
-#define zlAF		CPU->AF.B.L
-#define zhAF		CPU->AF.B.H
-#define zA			zhAF
-#define zF			zlAF
+#define pzFA		&(CPU->FA)
+#define zFA			CPU->FA.W
+#define zlFA		CPU->FA.B.L
+#define zhFA		CPU->FA.B.H
+#define zA			zlFA
+#define zF			zhFA
 
 #define pzBC		&(CPU->BC)
 #define zBC			CPU->BC.W
@@ -106,11 +107,11 @@ extern "C" {
 #define zH			zhHL
 #define zL			zlHL
 
-#define zAF2		CPU->AF2.W
-#define zlAF2		CPU->AF2.B.L
-#define zhAF2		CPU->AF2.B.H
-#define zA2			zhAF2
-#define zF2			zlAF2
+#define zFA2		CPU->FA2.W
+#define zlFA2		CPU->FA2.B.L
+#define zhFA2		CPU->FA2.B.H
+#define zA2			zhFA2
+#define zF2			zlFA2
 
 #define zBC2		CPU->BC2.W
 #define zDE2		CPU->DE2.W
@@ -169,6 +170,10 @@ extern "C" {
 #define CZ80_IFF_SFT	CZ80_PF_SFT
 #define CZ80_IFF		CZ80_PF
 
+#define	CZ80_HAS_INT	0x1
+#define	CZ80_HAS_NMI	0x2
+#define	CZ80_HALTED	0x4
+
 #ifndef IRQ_LINE_STATE
 #define IRQ_LINE_STATE
 #define CLEAR_LINE		0		/* clear (a fired, held or pulsed) line */
@@ -182,13 +187,13 @@ enum
 {
 	CZ80_PC = 1,
 	CZ80_SP,
-	CZ80_AF,
+	CZ80_FA,
 	CZ80_BC,
 	CZ80_DE,
 	CZ80_HL,
 	CZ80_IX,
 	CZ80_IY,
-	CZ80_AF2,
+	CZ80_FA2,
 	CZ80_BC2,
 	CZ80_DE2,
 	CZ80_HL2,
@@ -227,7 +232,7 @@ typedef struct cz80_t
 			union16 BC;
 			union16 DE;
 			union16 HL;
-			union16 AF;
+			union16 FA;
 		};
 	};
 
@@ -239,14 +244,14 @@ typedef struct cz80_t
 	union16 BC2;
 	union16 DE2;
 	union16 HL2;
-	union16 AF2;
+	union16 FA2;
 
 	union16 R;
 	union16 IFF;
 
 	UINT8 I;
 	UINT8 IM;
-	UINT8 HaltState;
+	UINT8 Status;
 	UINT8 dummy;
 
 	INT32 IRQLine;

+ 24 - 10
cpu/cz80/cz80_op.c

@@ -200,9 +200,15 @@ OP_LD_mNN_xx:
  POP
 -----------------------------------------*/
 
+	OP(0xf1):   // POP  AF
+OP_POP_AF:
+		POP_16(res)
+		zA = res >> 8;
+		zF = res & 0xFF;
+		RET(10)
+
 	OP(0xc1):   // POP  BC
 	OP(0xd1):   // POP  DE
-	OP(0xf1):   // POP  AF
 OP_POP_RR:
 		data = CPU->pzR16[(Opcode >> 4) & 3];
 
@@ -215,9 +221,14 @@ OP_POP:
  PUSH
 -----------------------------------------*/
 
+	OP(0xf5):   // PUSH AF
+OP_PUSH_AF:
+		PUSH_16((zA << 8) | zF);
+		RET(11)
+
+
 	OP(0xc5):   // PUSH BC
 	OP(0xd5):   // PUSH DE
-	OP(0xf5):   // PUSH AF
 OP_PUSH_RR:
 		data = CPU->pzR16[(Opcode >> 4) & 3];
 
@@ -232,9 +243,9 @@ OP_PUSH:
 
 	OP(0x08):   // EX   AF,AF'
 OP_EX_AF_AF2:
-		res = zAF;
-		zAF = zAF2;
-		zAF2 = res;
+		res = zFA;
+		zFA = zFA2;
+		zFA2 = res;
 		RET(4)
 
 	OP(0xeb):   // EX   DE,HL
@@ -686,8 +697,8 @@ OP_CCF:
 
 	OP(0x76):   // HALT
 OP_HALT:
-		CPU->HaltState = 1;
-		goto Cz80_Check_Interrupt;
+		CPU->Status |= CZ80_HALTED;
+		RET(4)
 
 	OP(0xf3):   // DI
 OP_DI:
@@ -709,9 +720,12 @@ OP_EI:
 				zR++;
 #endif
 			}
-			afterEI = 1;
-			CPU->ExtraCycles += 1 - CPU->ICount;
-			CPU->ICount = 1;
+			if (CPU->IRQState)
+			{
+				CPU->Status |= CZ80_HAS_INT;
+				CPU->ExtraCycles -= CPU->ICount;
+				CPU->ICount = 0;
+			}
 		}
 		else zIFF2 = (1 << 2);
 		goto Cz80_Exec_nocheck;

+ 33 - 30
cpu/cz80/cz80_opED.c

@@ -421,8 +421,7 @@ OP_SBC16:
 			zIFF1 = (1 << 2);
 			if (CPU->IRQState)
 			{
-				USE_CYCLES(10)
-				goto Cz80_Check_Interrupt;
+				CPU->Status |= CZ80_HAS_INT;
 			}
 		}
 		else zIFF1 = zIFF2;
@@ -497,8 +496,8 @@ OP_LDX:
 			val = READ_MEM8(zHL++);
 			WRITE_MEM8(zDE++, val);
 			zBC--;
-			USE_CYCLES(17)
-		} while (zBC && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zBC && (CPU->ICount > -4) && !CPU->Status);
 		goto OP_LDXR;
 
 	OPED(0xb8): // LDDR
@@ -507,8 +506,8 @@ OP_LDX:
 			val = READ_MEM8(zHL--);
 			WRITE_MEM8(zDE--, val);
 			zBC--;
-			USE_CYCLES(17)
-		} while (zBC && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zBC && (CPU->ICount > -4) && !CPU->Status);
 
 OP_LDXR:
 		F = zF & (SF | ZF | CF);
@@ -521,10 +520,11 @@ OP_LDXR:
 #if CZ80_EMULATE_R_EXACTLY
 			zR--;
 #endif
-			goto Cz80_Exec_End;
+			ADD_CYCLES(4)
+			goto Cz80_Exec;
 		}
 		zF = F;
-		ADD_CYCLES(5)
+		ADD_CYCLES(4+5)
 		goto Cz80_Exec;
 
 /*-----------------------------------------
@@ -564,8 +564,8 @@ OP_CPX:
 			if (res & 0x08) F |= XF;
 			if (zBC) F |= VF;
 			zF = F;
-			USE_CYCLES(17)
-		} while (zBC && !(F & ZF) && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zBC && !(F & ZF) && (CPU->ICount > -4) && !CPU->Status);
 		goto OP_CPXR;
 
 	OPED(0xb9): // CPDR
@@ -580,8 +580,8 @@ OP_CPX:
 			if (res & 0x08) F |= XF;
 			if (zBC) F |= VF;
 			zF = F;
-			USE_CYCLES(17)
-		} while (zBC && !(F & ZF) && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zBC && !(F & ZF) && (CPU->ICount > -4) && !CPU->Status);
 
 OP_CPXR:
 		if (zBC && !(F & ZF))
@@ -590,9 +590,10 @@ OP_CPXR:
 #if CZ80_EMULATE_R_EXACTLY
 			zR--;
 #endif
-			goto Cz80_Exec_End;
+			ADD_CYCLES(4)
+			goto Cz80_Exec;
 		}
-		ADD_CYCLES(5)
+		ADD_CYCLES(4+5)
 		goto Cz80_Exec;
 
 /*-----------------------------------------
@@ -614,7 +615,7 @@ OP_INX:
 		F = SZ[zB];
 		res = ((UINT32)(zC - 1) & 0xff) + (UINT32)val;
 		if (val & SF) F |= NF;
-		if (res & 0x100) F |= HF | CF;
+		if (res < val) F |= HF | CF;
 		F |= SZP[(UINT8)(res & 0x07) ^ zB] & PF;
 		zF = F;
 		RET(12)
@@ -629,8 +630,8 @@ OP_INX:
 			val = IN(zBC);
 			zB--;
 			WRITE_MEM8(zHL++, val);
-			USE_CYCLES(17)
-		} while (zB && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zB && (CPU->ICount > -4) && !CPU->Status);
 		goto OP_INXR;
 
 	OPED(0xba): // INDR
@@ -639,14 +640,14 @@ OP_INX:
 			val = IN(zBC);
 			zB--;
 			WRITE_MEM8(zHL--, val);
-			USE_CYCLES(17)
-		} while (zB && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zB && (CPU->ICount > -4) && !CPU->Status);
 
 OP_INXR:
 		F = SZ[zB];
 		res = ((UINT32)(zC - 1) & 0xff) + (UINT32)val;
 		if (val & SF) F |= NF;
-		if (res & 0x100) F |= HF | CF;
+		if (res < val) F |= HF | CF;
 		F |= SZP[(UINT8)(res & 0x07) ^ zB] & PF;
 		zF = F;
 		if (zB)
@@ -655,9 +656,10 @@ OP_INXR:
 #if CZ80_EMULATE_R_EXACTLY
 			zR--;
 #endif
-			goto Cz80_Exec_End;
+			ADD_CYCLES(4)
+			goto Cz80_Exec;
 		}
-		ADD_CYCLES(5);
+		ADD_CYCLES(4+5);
 		goto Cz80_Exec;
 
 /*-----------------------------------------
@@ -679,7 +681,7 @@ OP_OUTX:
 		F = SZ[zB];
 		res = (UINT32)zL + (UINT32)val;
 		if (val & SF) F |= NF;
-		if (res & 0x100) F |= HF | CF;
+		if (res < val) F |= HF | CF;
 		F |= SZP[(UINT8)(res & 0x07) ^ zB] & PF;
 		zF = F;
 		RET(12)
@@ -694,8 +696,8 @@ OP_OUTX:
 			val = READ_MEM8(zHL++);
 			zB--;
 			OUT(zBC, val);
-			USE_CYCLES(17)
-		} while (zB && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zB && (CPU->ICount > -4) && !CPU->Status);
 		goto OP_OTXR;
 
 	OPED(0xbb): // OTDR
@@ -704,14 +706,14 @@ OP_OUTX:
 			val = READ_MEM8(zHL--);
 			zB--;
 			OUT(zBC, val);
-			USE_CYCLES(17)
-		} while (zB && (CPU->ICount > 0));
+			USE_CYCLES(21)
+		} while (zB && (CPU->ICount > -4) && !CPU->Status);
 
 OP_OTXR:
 		F = SZ[zB];
 		res = (UINT32)zL + (UINT32)val;
 		if (val & SF) F |= NF;
-		if (res & 0x100) F |= HF | CF;
+		if (res < val) F |= HF | CF;
 		F |= SZP[(UINT8)(res & 0x07) ^ zB] & PF;
 		zF = F;
 		if (zB)
@@ -720,9 +722,10 @@ OP_OTXR:
 #if CZ80_EMULATE_R_EXACTLY
 			zR--;
 #endif
-			goto Cz80_Exec_End;
+			ADD_CYCLES(4)
+			goto Cz80_Exec;
 		}
-		ADD_CYCLES(5)
+		ADD_CYCLES(4+5)
 		goto Cz80_Exec;
 	}
 

+ 4 - 2
cpu/cz80/cz80_opXY.c

@@ -194,8 +194,9 @@ switch (Opcode)
 
 	OPXY(0xc1): // POP  BC
 	OPXY(0xd1): // POP  DE
-	OPXY(0xf1): // POP  AF
 		goto OP_POP_RR;
+	OPXY(0xf1): // POP  AF
+		goto OP_POP_AF;
 
 	OPXY(0xe1): // POP  IX
 		goto OP_POP;
@@ -206,8 +207,9 @@ switch (Opcode)
 
 	OPXY(0xc5): // PUSH BC
 	OPXY(0xd5): // PUSH DE
-	OPXY(0xf5): // PUSH AF
 		goto OP_PUSH_RR;
+	OPXY(0xf5): // PUSH AF
+		goto OP_PUSH_AF;
 
 	OPXY(0xe5): // PUSH IX
 		goto OP_PUSH;

+ 1 - 1
cpu/cz80/cz80macro.h

@@ -90,7 +90,7 @@
 		if (CPU->IRQState == HOLD_LINE)				\
 			CPU->IRQState = CLEAR_LINE;			\
 									\
-		CPU->HaltState = 0;					\
+		CPU->Status &= ~(CZ80_HALTED|CZ80_HAS_INT);		\
 		zIFF1 = zIFF2 = 0;					\
 		IntVect = CPU->Interrupt_Callback(CPU->IRQLine);	\
 									\

+ 10 - 6
cpu/sh2/compiler.c

@@ -68,7 +68,6 @@
 // 200 - compare trace
 // 400 - block entry backtrace on exit
 // 800 - state dump on exit
-// {
 #ifndef DRC_DEBUG
 #define DRC_DEBUG 0//x847
 #endif
@@ -218,7 +217,7 @@ static void REGPARM(3) *sh2_drc_log_entry(void *block, SH2 *sh2, u32 sr)
   if (block != NULL) {
 #if defined PDB
     dbg(8, "= %csh2 enter %08x %p, c=%d", sh2->is_slave?'s':'m',
-      sh2->pc, block, (signed int)sr >> 12);
+      sh2->pc, block, ((signed int)sr >> 12)+1);
     pdb_step(sh2, sh2->pc);
 #elif (DRC_DEBUG & 8)
     if (lastpc != sh2->pc) {
@@ -2612,10 +2611,14 @@ static uptr split_address(uptr la, uptr mask, s32 *offs)
   uptr sign = (mask>>1) + 1; // sign bit in offset
   *offs = (la & mask) | (la & sign ? ~mask : 0); // offset part, sign extended
   la = (la & ~mask) + ((la & sign) << 1); // base part, corrected for offs sign
-  if (~mask && la == ~mask && *offs > 0) { // special case la=-1&~mask && offs>0
-    *offs -= mask+1;
-    la = 0;
+#ifdef __arm__
+  // arm32 offset has an add/sub flag and an unsigned 8 bit value, which only
+  // allows values of [-255...255]. the value -256 thus can't be used.
+  if (*offs + sign == 0) {
+    la -= sign;
+    *offs += sign;
   }
+#endif
   return la;
 }
 
@@ -4414,6 +4417,7 @@ static void REGPARM(2) *sh2_translate(SH2 *sh2, int tcache_id)
           EMITH_HINT_COND(DCOND_EQ);
           emith_subf_r_r_imm(tmp, tmp2, 1);
           emith_set_t_cond(sr, DCOND_EQ);
+          emith_or_r_imm(sr, SH2_NO_POLLING);
           goto end_op;
         }
         goto default_;
@@ -5013,7 +5017,7 @@ end_op:
         // can't resolve branch locally, make a block exit
         bl = dr_prepare_ext_branch(block->entryp, target_pc, sh2->is_slave, tcache_id);
         if (cond != -1) {
-#if 1
+#ifndef __arm__
           if (bl && blx_target_count < ARRAY_SIZE(blx_targets)) {
             // conditional jumps get a blx stub for the far jump
             bl->type = BL_JCCBLX;

+ 1 - 0
cpu/sh2/mame/sh2.c

@@ -800,6 +800,7 @@ INLINE void DT(sh2_state *sh2, UINT32 n)
 		sh2->sr |= T;
 	else
 		sh2->sr &= ~T;
+	sh2->no_polling = SH2_NO_POLLING;
 #if BUSY_LOOP_HACKS
 	{
 		UINT32 next_opcode = (UINT32)(UINT16)RW( sh2, sh2->ppc & AM );

+ 4 - 2
cpu/sh2/mame/sh2pico.c

@@ -24,9 +24,10 @@ typedef u8  UINT8;
 static __inline unsigned int name(SH2 *sh2, unsigned int a) \
 { \
 	unsigned int ret; \
-	sh2->sr |= sh2->icount << 12; \
+	sh2->sr |= (sh2->icount << 12) | (sh2->no_polling); \
 	ret = cname(a, sh2); \
 	sh2->icount = (signed int)sh2->sr >> 12; \
+	sh2->no_polling = (sh2->sr & SH2_NO_POLLING); \
 	sh2->sr &= 0x3f3; \
 	return ret; \
 }
@@ -34,9 +35,10 @@ static __inline unsigned int name(SH2 *sh2, unsigned int a) \
 #define MAKE_WRITEFUNC(name, cname) \
 static __inline void name(SH2 *sh2, unsigned int a, unsigned int d) \
 { \
-	sh2->sr |= sh2->icount << 12; \
+	sh2->sr |= (sh2->icount << 12) | (sh2->no_polling); \
 	cname(a, d, sh2); \
 	sh2->icount = (signed int)sh2->sr >> 12; \
+	sh2->no_polling = (sh2->sr & SH2_NO_POLLING); \
 	sh2->sr &= 0x3f3; \
 }
 

+ 3 - 0
cpu/sh2/sh2.h

@@ -56,6 +56,9 @@ typedef struct SH2_
 	uint32_t	poll_addr;
 	int		poll_cycles;
 	int		poll_cnt;
+// NB MUST be a bit unused in SH2 SR, see also cpu/sh2/compiler.c!
+#define SH2_NO_POLLING	(1 << 10)	// poll detection control
+	int		no_polling;
 
 	// DRC branch cache. size must be 2^n and <=128
 	int rts_cache_idx;

BIN
icons/picodrive.png


+ 46 - 0
package

@@ -0,0 +1,46 @@
+#!/bin/sh
+
+mkdir opk/
+#git pull
+make clean
+git fetch --all
+git merge upstream/FunKey
+./submodule_update.sh
+cp icons/picodrive.png opk/
+cp AUTHORS COPYING README opk/
+cp picodrive.sh opk/
+
+cd /opt/FunKey-OS
+rm -f br.log
+rm -rf FunKey/output/build/picodrive-*
+sed -i 's|PICODRIVE_OVERRIDE_SRCDIR.*|PICODRIVE_OVERRIDE_SRCDIR = /opt/picodrive-funkey|' /opt/FunKey-OS/FunKey/output/local.mk
+git pull
+make
+cp FunKey/output/target/usr/games/PicoDrive /opt/picodrive-funkey/opk
+cd -
+
+# https://unix.stackexchange.com/questions/219268/how-to-add-new-lines-when-using-echo
+print()
+	case    ${IFS- } in
+	(\ *)   printf  %b\\n "$*";;
+	(*)     IFS=\ $IFS
+	printf  %b\\n "$*"
+	IFS=${IFS#?}
+esac
+
+# Create GMenu2X entry file plus other things
+
+print '[Desktop Entry]
+Name=PicoDrive
+Comment=SMS/GG/MD/MCD/32X emulator
+Exec=picodrive.sh %f
+Icon=picodrive
+Categories=emulators
+SelectorFilter=zip,bin,md,smd,sms,gg,32x,cue,cso,chd' > opk/picodrive.funkey-s.desktop
+
+rm -f *.opk
+mksquashfs opk/ picodrive_v1.99_$(git rev-parse HEAD | cut -c1-7)_funkey-s.opk -all-root -noappend -no-exports -no-xattrs
+md5sum opk/PicoDrive
+rm -rf opk/
+
+sed -i 's|PICODRIVE_OVERRIDE_SRCDIR.*|PICODRIVE_OVERRIDE_SRCDIR = /opt/picodrive-irixxxx|' /opt/FunKey-OS/FunKey/output/local.mk

+ 7 - 8
pico/32x/32x.c

@@ -145,13 +145,13 @@ void p32x_reset_sh2s(void)
       idl_src = CPU_BE2(*(u32 *)(Pico.rom + 0x3d4)) & ~0xf0000000;
       idl_dst = CPU_BE2(*(u32 *)(Pico.rom + 0x3d8)) & ~0xf0000000;
       idl_size= CPU_BE2(*(u32 *)(Pico.rom + 0x3dc));
-      if (idl_size > Pico.romsize || idl_src + idl_size > Pico.romsize ||
-          idl_size > 0x40000 || idl_dst + idl_size > 0x40000 || (idl_src & 3) || (idl_dst & 3)) {
-        elprintf(EL_STATUS|EL_ANOMALY, "32x: invalid initial data ptrs: %06x -> %06x, %06x",
-          idl_src, idl_dst, idl_size);
+      // copy in guest memory space
+      idl_src += 0x2000000;
+      idl_dst += 0x6000000;
+      while (idl_size >= 4) {
+        p32x_sh2_write32(idl_dst, p32x_sh2_read32(idl_src, &msh2), &msh2);
+        idl_src += 4, idl_dst += 4, idl_size -= 4;
       }
-      else
-        memcpy(Pico32xMem->sdram + idl_dst, Pico.rom + idl_src, idl_size);
 
       // VBR
       vbr = CPU_BE2(*(u32 *)(Pico.rom + 0x3e8));
@@ -230,7 +230,6 @@ static void p32x_start_blank(void)
 
     // XXX: no proper handling of 32col mode..
     if ((Pico32x.vdp_regs[0] & P32XV_Mx) != 0 && // 32x not blanking
-        (Pico.video.reg[12] & 1) && // 40col mode
         (!(Pico.video.debug_p & PVD_KILL_32X)))
     {
       int md_bg = Pico.video.reg[7] & 0x3f;
@@ -238,7 +237,7 @@ static void p32x_start_blank(void)
       // we draw full layer (not line-by-line)
       PicoDraw32xLayer(offs, lines, md_bg);
     }
-    else if (Pico32xDrawMode != PDM32X_32X_ONLY)
+    else if (Pico32xDrawMode == PDM32X_BOTH)
       PicoDraw32xLayerMdOnly(offs, lines);
 
     pprof_end(draw);

+ 14 - 11
pico/32x/draw.c

@@ -8,6 +8,14 @@
  */
 #include "../pico_int.h"
 
+// NB: 32X officially doesn't support H32 mode. However, it does work since the
+// cartridge slot carries the EDCLK signal which is always H40 clock and is used
+// as video clock by the 32X. The H32 MD image is overlayed with the 320 px 32X
+// image which has the same on-screen width. How the /YS signal on the cartridge
+// slot (signalling the display of background color) is processed in this case
+// is however unclear and might lead to glitches due to race conditions by the
+// different video clocks for H32 and H40.
+
 // BGR555 to native conversion
 #if defined(USE_BGR555)
 #define PXCONV(t)   ((t)&(mr|mg|mb|mp))
@@ -122,8 +130,6 @@ void FinalizeLine32xRGB555(int sh, int line, struct PicoEState *est)
   FinalizeLine555(sh, line, est);
 
   if ((Pico32x.vdp_regs[0] & P32XV_Mx) == 0 || // 32x blanking
-      // XXX: how is 32col mode hadled by real hardware?
-      !(Pico.video.reg[12] & 1) || // 32col mode
       (Pico.video.debug_p & PVD_KILL_32X))
   {
     return;
@@ -162,6 +168,7 @@ void FinalizeLine32xRGB555(int sh, int line, struct PicoEState *est)
 
 #define PICOSCAN_POST \
   PicoScan32xEnd(l + (lines_sft_offs & 0xff)); \
+  Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement32x; \
 
 #define make_do_loop(name, pre_code, post_code, md_code)        \
 /* Direct Color Mode */                                         \
@@ -260,6 +267,7 @@ void PicoDraw32xLayer(int offs, int lines, int md_bg)
   int which_func;
 
   Pico.est.DrawLineDest = (char *)DrawLineDestBase32x + offs * DrawLineDestIncrement32x;
+  Pico.est.DrawLineDestIncr = DrawLineDestIncrement32x;
   dram = Pico32xMem->dram[Pico32x.vdp_regs[0x0a/2] & P32XV_FS];
 
   if (Pico32xDrawMode == PDM32X_BOTH)
@@ -308,14 +316,6 @@ void PicoDraw32xLayerMdOnly(int offs, int lines)
   int poffs = 0, plen = 320;
   int l, p;
 
-  if (!(Pico.video.reg[12] & 1)) {
-    // 32col mode. for some render modes MD pixel data carries an offset
-    if (!(PicoIn.opt & POPT_DIS_32C_BORDER))
-      pmd += 32;
-    poffs = 32;
-    plen = 256;
-  }
-
   PicoDrawUpdateHighPal();
 
   dst += poffs;
@@ -330,7 +330,7 @@ void PicoDraw32xLayerMdOnly(int offs, int lines)
       dst[p + 2] = pal[*pmd++];
       dst[p + 3] = pal[*pmd++];
     }
-    dst = (void *)((char *)dst + DrawLineDestIncrement32x);
+    dst = Pico.est.DrawLineDest = (char *)dst + DrawLineDestIncrement32x;
     pmd += 328 - plen;
     if (have_scan)
       PicoScan32xEnd(l + offs);
@@ -348,6 +348,9 @@ void PicoDrawSetOutFormat32x(pdso_t which, int use_32x_line_mode)
     PicoDrawSetInternalBuf(NULL, 0);
     PicoDrawSetOutBufMD(Pico.est.Draw2FB, 328);
   }
+  // always need upscaling for H32, before mixing in 32X layer
+  PicoIn.opt |= POPT_EN_SOFTSCALE;
+  PicoIn.opt &= ~POPT_DIS_32C_BORDER;
 
   if (use_32x_line_mode)
     // we'll draw via FinalizeLine32xRGB555 (rare)

+ 21 - 6
pico/32x/draw_arm.S

@@ -20,11 +20,11 @@
 
     PIC_LDR_INIT()
 
-.macro call_scan_prep cond est   @ &Pico.est
+.macro call_scan_prep cond pico   @ &Pico
 .if \cond
     PIC_LDR(r4, r6, PicoScan32xBegin)
     PIC_LDR(r5, r6, PicoScan32xEnd)
-    ldr     r6, [\est, #OFS_EST_DrawLineDest]
+    add     r6, \pico, #OFS_Pico_est
     ldr     r4, [r4]
     ldr     r5, [r5]
     stmfd   sp!, {r4,r5,r6}
@@ -44,9 +44,9 @@
     add     r0, r0, r4
     mov     lr, pc
     ldr     pc, [sp, #(3+0)*4]
-    ldr     r0, [sp, #(3+2)*4] @ &DrawLineDest
+    ldr     r0, [sp, #(3+2)*4] @ &Pico.est
     ldmfd   sp!, {r1-r3}
-    ldr     r0, [r0]
+    ldr     r0, [r0, #OFS_EST_DrawLineDest]
 .endif
 .endm
 
@@ -57,6 +57,11 @@
     add     r0, r0, r4
     mov     lr, pc
     ldr     pc, [sp, #(4+1)*4]
+    ldr     r1, [sp, #(4+2)*4] @ &Pico.est
+    ldr     r0, [r1, #OFS_EST_DrawLineDest]
+    ldr     r2, [r1, #OFS_EST_DrawLineDestIncr]
+    add     r0, r0, r2
+    str     r0, [r1, #OFS_EST_DrawLineDest]
     ldmfd   sp!, {r0-r3}
 .endif
 .endm
@@ -71,6 +76,7 @@
     PIC_LDR(lr, r9, Pico)
     PIC_LDR(r10,r9, Pico32x)
     ldr     r11, [lr, #OFS_Pico_est+OFS_EST_Draw2FB]
+    ldr     r12, [lr, #OFS_Pico_est+OFS_EST_DrawLineDestIncr]
     ldrh    r10,[r10, #0x40] @ Pico32x.vdp_regs[0]
     add     r9, lr, #OFS_Pico_est+OFS_EST_HighPal   @ palmd
 
@@ -88,6 +94,9 @@
 
 0: @ loop_outer:
     call_scan_end \call_scan
+    ldr     r12, [r9, #OFS_EST_DrawLineDestIncr-OFS_EST_HighPal]
+    sub     r0, r0, #320*2
+    add     r0, r0, r12
     add     r4, r4, #1
     cmp     r4, r2, lsr #16
     call_scan_fin_ge \call_scan
@@ -231,7 +240,7 @@
 
     PIC_LDR(lr, r9, Pico)
     PIC_LDR(r10,r9, Pico32xMem)
-    ldr     r9,=OFS_PMEM32x_pal_native
+    ldr     r9, =OFS_PMEM32x_pal_native
     ldr     r10, [r10]
     ldr     r11, [lr, #OFS_Pico_est+OFS_EST_Draw2FB]
     add     r10,r10,r9
@@ -248,6 +257,9 @@
 
 0: @ loop_outer:
     call_scan_end \call_scan
+    ldr     r12, [r9, #OFS_EST_DrawLineDestIncr-OFS_EST_HighPal]
+    sub     r0, r0, #320*2
+    add     r0, r0, r12
     add     r4, r4, #1
     cmp     r4, r2, lsr #16
     call_scan_fin_ge \call_scan
@@ -400,7 +412,7 @@
 
     PIC_LDR(lr, r9, Pico)
     PIC_LDR(r10,r9, Pico32xMem)
-    ldr     r9,=OFS_PMEM32x_pal_native
+    ldr     r9, =OFS_PMEM32x_pal_native
     ldr     r10, [r10]
     ldr     r11, [lr, #OFS_Pico_est+OFS_EST_Draw2FB]
     add     r10,r10,r9
@@ -417,6 +429,9 @@
 
 0: @ loop_outer:
     call_scan_end \call_scan
+    ldr     r12, [r9, #OFS_EST_DrawLineDestIncr-OFS_EST_HighPal]
+    sub     r0, r0, #320*2
+    add     r0, r0, r12
     add     r4, r4, #1
     cmp     r4, r2, lsr #16
     call_scan_fin_ge \call_scan

+ 26 - 25
pico/32x/memory.c

@@ -119,8 +119,9 @@ void NOINLINE p32x_sh2_poll_detect(u32 a, SH2 *sh2, u32 flags, int maxcnt)
   // reading 2 consecutive 16bit values is probably a 32bit access. detect this
   // by checking address (max 2 bytes away) and cycles (max 2 cycles later).
   // no polling if more than 20 cycles have passed since last detect call.
-  if (a - sh2->poll_addr <= 2 && CYCLES_GE(10, cycles_diff)) {
-    if (CYCLES_GT(cycles_diff, 2) && ++sh2->poll_cnt >= maxcnt) {
+  if (a - sh2->poll_addr <= 2 && CYCLES_GE(20, cycles_diff)) {
+    if (!sh2_not_polling(sh2) && CYCLES_GT(cycles_diff, 2) &&
+                ++sh2->poll_cnt >= maxcnt) {
       if (!(sh2->state & flags))
         elprintf_sh2(sh2, EL_32X, "state: %02x->%02x",
           sh2->state, sh2->state | flags);
@@ -144,6 +145,7 @@ void NOINLINE p32x_sh2_poll_detect(u32 a, SH2 *sh2, u32 flags, int maxcnt)
     sh2->poll_addr = a;
   }
   sh2->poll_cycles = cycles_done;
+  sh2_set_polling(sh2);
 }
 
 void NOINLINE p32x_sh2_poll_event(SH2 *sh2, u32 flags, u32 m68k_cycles)
@@ -511,16 +513,17 @@ static void p32x_reg_write8(u32 a, u32 d)
     case 0x2d:
     case 0x2e:
     case 0x2f:
-      if (REG8IN16(r, a) != (u8)d) {
-        unsigned int cycles = SekCyclesDone();
+      { unsigned int cycles = SekCyclesDone();
 
         if (CYCLES_GT(cycles - msh2.m68krcycles_done, 64))
           p32x_sync_sh2s(cycles);
 
-        REG8IN16(r, a) = d;
-        p32x_sh2_poll_event(&sh2s[0], SH2_STATE_CPOLL, cycles);
-        p32x_sh2_poll_event(&sh2s[1], SH2_STATE_CPOLL, cycles);
-        sh2_poll_write(a & ~1, r[a / 2], cycles, NULL);
+        if (REG8IN16(r, a) != (u8)d) {
+          REG8IN16(r, a) = d;
+          p32x_sh2_poll_event(&sh2s[0], SH2_STATE_CPOLL, cycles);
+          p32x_sh2_poll_event(&sh2s[1], SH2_STATE_CPOLL, cycles);
+          sh2_poll_write(a & ~1, r[a / 2], cycles, NULL);
+        }
       }
       return;
     case 0x30:
@@ -608,16 +611,17 @@ static void p32x_reg_write16(u32 a, u32 d)
     case 0x2a/2:
     case 0x2c/2:
     case 0x2e/2:
-      if (r[a / 2] != (u16)d) {
-        unsigned int cycles = SekCyclesDone();
+      { unsigned int cycles = SekCyclesDone();
 
         if (CYCLES_GT(cycles - msh2.m68krcycles_done, 64))
           p32x_sync_sh2s(cycles);
 
-        r[a / 2] = d;
-        p32x_sh2_poll_event(&sh2s[0], SH2_STATE_CPOLL, cycles);
-        p32x_sh2_poll_event(&sh2s[1], SH2_STATE_CPOLL, cycles);
-        sh2_poll_write(a, (u16)d, cycles, NULL);
+        if (r[a / 2] != (u16)d) {
+          r[a / 2] = d;
+          p32x_sh2_poll_event(&sh2s[0], SH2_STATE_CPOLL, cycles);
+          p32x_sh2_poll_event(&sh2s[1], SH2_STATE_CPOLL, cycles);
+          sh2_poll_write(a, (u16)d, cycles, NULL);
+        }
       }
       return;
     case 0x30/2: // PWM control
@@ -1457,7 +1461,7 @@ static u32 REGPARM(2) sh2_read8_cs0(u32 a, SH2 *sh2)
 
   if ((a & 0x3fff0) == 0x4100) {
     d = p32x_vdp_read16(a);
-    p32x_sh2_poll_detect(a, sh2, SH2_STATE_VPOLL, 7);
+    p32x_sh2_poll_detect(a, sh2, SH2_STATE_VPOLL, 9);
     goto out_16to8;
   }
 
@@ -1520,7 +1524,7 @@ static u32 REGPARM(2) sh2_read16_cs0(u32 a, SH2 *sh2)
 
   if ((a & 0x3fff0) == 0x4100) {
     d = p32x_vdp_read16(a);
-    p32x_sh2_poll_detect(a, sh2, SH2_STATE_VPOLL, 7);
+    p32x_sh2_poll_detect(a, sh2, SH2_STATE_VPOLL, 9);
     goto out;
   }
 
@@ -1864,7 +1868,7 @@ static void REGPARM(3) sh2_write32_rom(u32 a, u32 d, SH2 *sh2)
   sh2_write16_rom(a + 2, d, sh2);
 }
 
-typedef u32 (sh2_read_handler)(u32 a, SH2 *sh2);
+typedef u32 REGPARM(2) (sh2_read_handler)(u32 a, SH2 *sh2);
 typedef void REGPARM(3) (sh2_write_handler)(u32 a, u32 d, SH2 *sh2);
 
 #define SH2MAP_ADDR2OFFS_R(a) \
@@ -1995,11 +1999,7 @@ int p32x_sh2_memcpy(u32 dst, u32 src, int count, int size, SH2 *sh2)
     u16 dl, dh = *sp++;
     for (i = 0; i < (len & ~1); i += 2, dst += 2, sp++) {
       dl = dh, dh = *sp;
-#if CPU_IS_LE
       p32x_sh2_write16(dst, (dh >> 8) | (dl << 8), sh2);
-#else
-      p32x_sh2_write16(dst, (dl >> 8) | (dh << 8), sh2);
-#endif
     }
     if (len & 1)
       p32x_sh2_write8(dst, dh, sh2);
@@ -2031,7 +2031,7 @@ int p32x_sh2_memcpy(u32 dst, u32 src, int count, int size, SH2 *sh2)
       dst += 2;
     }
     if (len & 1)
-      p32x_sh2_write8(dst, ((u8 *)sp)[MEM_BE2(0)], sh2);
+      p32x_sh2_write8(dst, *sp >> 8, sh2);
   }
 
   return count;
@@ -2382,9 +2382,10 @@ void PicoMemSetup32x(void)
   msh2_write32_map[0x00/2] = msh2_write32_map[0x20/2] = sh2_write32_cs0;
   // CS1 - ROM
   bank_switch_rom_sh2();
-  msh2_read8_map[0x02/2].mask  = msh2_read8_map[0x22/2].mask  = 0x3fffff; // FIXME
-  msh2_read16_map[0x02/2].mask = msh2_read16_map[0x22/2].mask = 0x3ffffe; // FIXME
-  msh2_read32_map[0x02/2].mask = msh2_read32_map[0x22/2].mask = 0x3ffffc; // FIXME
+  for (rs = 0x8000; rs < Pico.romsize && rs < 0x400000; rs *= 2) ; 
+  msh2_read8_map[0x02/2].mask  = msh2_read8_map[0x22/2].mask  = rs-1;
+  msh2_read16_map[0x02/2].mask = msh2_read16_map[0x22/2].mask = rs-1;
+  msh2_read32_map[0x02/2].mask = msh2_read32_map[0x22/2].mask = rs-1;
   msh2_write16_map[0x02/2] = msh2_write16_map[0x22/2] = sh2_write16_rom;
   msh2_write32_map[0x02/2] = msh2_write32_map[0x22/2] = sh2_write32_rom;
   // CS2 - DRAM 

+ 24 - 1
pico/cart.c

@@ -34,6 +34,7 @@ void (*PicoCDLoadProgressCB)(const char *fname, int percent) = NULL; // handled
 int PicoGameLoaded;
 
 static void PicoCartDetect(const char *carthw_cfg);
+static void PicoCartDetectMS(void);
 
 /* cso struct */
 typedef struct _cso_struct
@@ -242,6 +243,7 @@ zip_failed:
     file->param = cso;
     file->size  = cso->header.total_bytes;
     file->type  = PMT_CSO;
+    strncpy(file->ext, ext, sizeof(file->ext) - 1);
     return file;
 
 cso_failed:
@@ -772,7 +774,7 @@ int PicoCartLoad(pm_file *f,unsigned char **prom,unsigned int *psize,int is_sms)
       ret = pm_read(p,todo,f);
       bytes_read += ret;
       p += ret;
-      PicoCartLoadProgressCB(bytes_read * 100 / size);
+      PicoCartLoadProgressCB(bytes_read * 100LL / size);
     }
     while (ret > 0);
   }
@@ -850,6 +852,8 @@ int PicoCartInsert(unsigned char *rom, unsigned int romsize, const char *carthw_
 
   if (!(PicoIn.AHW & (PAHW_MCD|PAHW_SMS)))
     PicoCartDetect(carthw_cfg);
+  else if (PicoIn.AHW & PAHW_SMS)
+    PicoCartDetectMS();
 
   // setup correct memory map for loaded ROM
   switch (PicoIn.AHW) {
@@ -1133,6 +1137,12 @@ static void parse_carthw(const char *carthw_cfg, int *fill_sram,
         carthw_radica_startup();
       else if (strcmp(p, "piersolar_mapper") == 0)
         carthw_pier_startup();
+      else if (strcmp(p, "sf001_mapper") == 0)
+        carthw_sf001_startup();
+      else if (strcmp(p, "sf002_mapper") == 0)
+        carthw_sf002_startup();
+      else if (strcmp(p, "sf004_mapper") == 0)
+        carthw_sf004_startup();
       else if (strcmp(p, "prot_lk3") == 0)
         carthw_prot_lk3_startup();
       else {
@@ -1314,4 +1324,17 @@ static void PicoCartDetect(const char *carthw_cfg)
     *(u32 *) (Pico.rom + 0x1f0) = CPU_LE4(0x20204520);
 }
 
+static void PicoCartDetectMS(void)
+{
+  memset(&Pico.sv, 0, sizeof(Pico.sv));
+
+  // Always map SRAM, since there's no indicator in ROM if it's needed or not
+  // TODO: this should somehow be coming from a cart database!
+
+  Pico.sv.size  = 0x8000; // Sega mapper, 2 banks of 16 KB each
+  Pico.sv.flags |= SRF_ENABLED;
+  Pico.sv.data = calloc(Pico.sv.size, 1);
+  if (Pico.sv.data == NULL)
+    Pico.sv.flags &= ~SRF_ENABLED;
+}
 // vim:shiftwidth=2:expandtab

+ 18 - 0
pico/carthw.cfg

@@ -16,6 +16,7 @@
 #  realtec_mapper
 #  radica_mapper    - similar to x_in_1_mapper
 #  piersolar_mapper - used in Pier Solar
+#  sf00x_mapper     - versions x=1,2,4 used by superfighter team
 #
 # save storage memory range (inclusive, overrides ROM header):
 #  sram_range = <start, end>
@@ -90,6 +91,23 @@ check_str = 0x150, "PIER"
 check_str = 0x610, "Respect"
 hw = piersolar_mapper
 
+# Beggar Prince, unusual SRAM location
+[Beggar Prince]
+check_str = 0x150, "BEGGAR PRINCE"
+hw = sf001_mapper
+sram_range = 0x400000,0x40ffff
+prop = filled_sram
+
+[Legend of Wukong]
+check_str = 0x150, "LEGEND OF WUKONG"
+hw = sf002_mapper
+
+# Star Odyssey, r/w in SRAM mirror (double SRAM as a kludge)
+[Star Odyssey]
+check_str = 0x150, "STAR ODYSSEY"
+hw = sf004_mapper
+sram_range = 0x200000,0x207fff
+
 # detect *_in_1 based on first game and if it's larger than it should be,
 # as some dumps look like to be incomplete.
 # This will also pick overdumps, but those should still work with the mapper applied.

+ 240 - 1
pico/carthw/carthw.c

@@ -35,7 +35,7 @@ void carthw_ssf2_write8(u32 a, u32 d)
 {
   u32 target, base;
 
-  if ((a & ~0x0e) != 0xa130f1) {
+  if ((a & ~0x0e) != 0xa130f1 || a == 0xa130f1) {
     PicoWrite8_io(a, d);
     return;
   }
@@ -483,6 +483,245 @@ void carthw_pier_startup(void)
   carthw_chunks     = carthw_pier_state;
 }
 
+/* superfighter mappers, see mame: mame/src/devices/bus/megadrive/rom.cpp */
+unsigned int carthw_sf00x_reg;
+
+static carthw_state_chunk carthw_sf00x_state[] =
+{
+	{ CHUNK_CARTHW, sizeof(carthw_sf00x_reg), &carthw_sf00x_reg },
+	{ 0,            0,                         NULL }
+};
+
+// SF-001
+
+// additionally map SRAM at 0x3c0000 for the newer version of sf001
+static u32 carthw_sf001_read8_sram(u32 a)
+{
+  return m68k_read8((a & 0xffff) + Pico.sv.start);
+}
+
+static u32 carthw_sf001_read16_sram(u32 a)
+{
+  return m68k_read16((a & 0xffff) + Pico.sv.start);
+}
+
+static void carthw_sf001_write8_sram(u32 a, u32 d)
+{
+  m68k_write8((a & 0xffff) + Pico.sv.start, d);
+}
+
+static void carthw_sf001_write16_sram(u32 a, u32 d)
+{
+  m68k_write16((a & 0xffff) + Pico.sv.start, d);
+}
+
+static void carthw_sf001_write8(u32 a, u32 d)
+{
+  if ((a & 0xf00) != 0xe00 || (carthw_sf00x_reg & 0x20)) // wrong addr / locked
+    return;
+
+  if (d & 0x80) {
+    // bank 0xe at addr 0x000000
+    cpu68k_map_set(m68k_read8_map,  0x000000, 0x040000-1, Pico.rom+0x380000, 0);
+    cpu68k_map_set(m68k_read16_map, 0x000000, 0x040000-1, Pico.rom+0x380000, 0);
+    // SRAM also at 0x3c0000 for newer mapper version
+    cpu68k_map_set(m68k_read8_map,  0x3c0000, 0x400000-1, carthw_sf001_read8_sram, 1);
+    cpu68k_map_set(m68k_read16_map, 0x3c0000, 0x400000-1, carthw_sf001_read16_sram, 1);
+    cpu68k_map_set(m68k_write8_map, 0x3c0000, 0x400000-1, carthw_sf001_write8_sram, 1);
+    cpu68k_map_set(m68k_write16_map,0x3c0000, 0x400000-1, carthw_sf001_write16_sram, 1);
+  } else {
+    // bank 0x0 at addr 0x000000
+    cpu68k_map_set(m68k_read8_map,  0x000000, 0x040000-1, Pico.rom, 0);
+    cpu68k_map_set(m68k_read16_map, 0x000000, 0x040000-1, Pico.rom, 0);
+    // SRAM off, bank 0xf at addr 0x3c0000
+    cpu68k_map_set(m68k_read8_map,  0x3c0000, 0x400000-1, Pico.rom+0x3c0000, 0);
+    cpu68k_map_set(m68k_read16_map, 0x3c0000, 0x400000-1, Pico.rom+0x3c0000, 0);
+    cpu68k_map_set(m68k_write8_map, 0x3c0000, 0x400000-1, Pico.rom+0x3c0000, 0);
+    cpu68k_map_set(m68k_write16_map,0x3c0000, 0x400000-1, Pico.rom+0x3c0000, 0);
+  }
+  carthw_sf00x_reg = d;
+}
+
+static void carthw_sf001_write16(u32 a, u32 d)
+{
+  carthw_sf001_write8(a + 1, d);
+}
+
+static void carthw_sf001_mem_setup(void)
+{
+  // writing to low cartridge addresses
+  cpu68k_map_set(m68k_write8_map,  0x000000, 0x00ffff, carthw_sf001_write8, 1);
+  cpu68k_map_set(m68k_write16_map, 0x000000, 0x00ffff, carthw_sf001_write16, 1);
+}
+
+static void carthw_sf001_reset(void)
+{
+  carthw_sf00x_reg = 0;
+  carthw_sf001_write8(0x0e01, 0);
+}
+
+static void carthw_sf001_statef(void)
+{
+  int reg = carthw_sf00x_reg;
+  carthw_sf00x_reg = 0;
+  carthw_sf001_write8(0x0e01, reg);
+}
+
+void carthw_sf001_startup(void)
+{
+  PicoCartMemSetup  = carthw_sf001_mem_setup;
+  PicoResetHook     = carthw_sf001_reset;
+  PicoLoadStateHook = carthw_sf001_statef;
+  carthw_chunks     = carthw_sf00x_state;
+}
+
+// SF-002
+
+static void carthw_sf002_write8(u32 a, u32 d)
+{
+  if ((a & 0xf00) != 0xe00)
+    return;
+
+  if (d & 0x80) {
+    // bank 0x00-0x0e on addr 0x20000
+    cpu68k_map_set(m68k_read8_map,  0x200000, 0x3c0000-1, Pico.rom, 0);
+    cpu68k_map_set(m68k_read16_map, 0x200000, 0x3c0000-1, Pico.rom, 0);
+  } else {
+    // bank 0x10-0x1e on addr 0x20000
+    cpu68k_map_set(m68k_read8_map,  0x200000, 0x3c0000-1, Pico.rom+0x200000, 0);
+    cpu68k_map_set(m68k_read16_map, 0x200000, 0x3c0000-1, Pico.rom+0x200000, 0);
+  }
+  carthw_sf00x_reg = d;
+}
+
+static void carthw_sf002_write16(u32 a, u32 d)
+{
+  carthw_sf002_write8(a + 1, d);
+}
+
+static void carthw_sf002_mem_setup(void)
+{
+  // writing to low cartridge addresses
+  cpu68k_map_set(m68k_write8_map,  0x000000, 0x00ffff, carthw_sf002_write8, 1);
+  cpu68k_map_set(m68k_write16_map, 0x000000, 0x00ffff, carthw_sf002_write16, 1);
+}
+
+static void carthw_sf002_reset(void)
+{
+  carthw_sf002_write8(0x0e01, 0);
+}
+
+static void carthw_sf002_statef(void)
+{
+  carthw_sf002_write8(0x0e01, carthw_sf00x_reg);
+}
+
+void carthw_sf002_startup(void)
+{
+  PicoCartMemSetup  = carthw_sf002_mem_setup;
+  PicoResetHook     = carthw_sf002_reset;
+  PicoLoadStateHook = carthw_sf002_statef;
+  carthw_chunks     = carthw_sf00x_state;
+}
+
+// SF-004
+
+// reading from cartridge I/O region returns the current bank index
+static u32 carthw_sf004_read8(u32 a)
+{
+  if ((a & ~0xff) == 0xa13000)
+    return carthw_sf00x_reg & 0xf0; // bank index
+  return PicoRead8_io(a);
+}
+
+static u32 carthw_sf004_read16(u32 a)
+{
+  if ((a & ~0xff) == 0xa13000)
+    return carthw_sf00x_reg & 0xf0;
+  return PicoRead16_io(a);
+}
+
+// writing to low cartridge adresses changes mappings
+static void carthw_sf004_write8(u32 a, u32 d)
+{
+  int idx, i;
+  unsigned bs = 0x40000; // bank size
+
+  // there are 3 byte-sized regs, stored together in carthw_sf00x_reg
+  if (!(carthw_sf00x_reg & 0x8000))
+    return; // locked
+
+  switch (a & 0xf00) {
+  case 0xd00:
+    carthw_sf00x_reg = (carthw_sf00x_reg & ~0xff0000) | ((d & 0xff) << 16);
+    return PicoWrite8_io(0xa130f1, (d & 0x80) ? SRR_MAPPED : 0); // SRAM mapping
+  case 0xe00:
+    carthw_sf00x_reg = (carthw_sf00x_reg & ~0x00ff00) | ((d & 0xff) << 8);
+    break;
+  case 0xf00:
+    carthw_sf00x_reg = (carthw_sf00x_reg & ~0x0000ff) | ((d & 0xff) << 0);
+    break;
+  default:
+    return; // wrong addr
+  }
+
+  // bank mapping changed
+  idx = ((carthw_sf00x_reg>>4) & 0x7); // bank index
+  if ((carthw_sf00x_reg>>8) & 0x40) {
+    // linear bank mapping, starting at idx
+    for (i = 0; i < 8; i++, idx = (idx+1) & 0x7) {
+      cpu68k_map_set(m68k_read8_map,  i*bs, (i+1)*bs-1, Pico.rom + idx*bs, 0);
+      cpu68k_map_set(m68k_read16_map, i*bs, (i+1)*bs-1, Pico.rom + idx*bs, 0);
+    }
+  } else {
+    // single bank mapping
+    for (i = 0; i < 8; i++) {
+      cpu68k_map_set(m68k_read8_map,  i*bs, (i+1)*bs-1, Pico.rom + idx*bs, 0);
+      cpu68k_map_set(m68k_read16_map, i*bs, (i+1)*bs-1, Pico.rom + idx*bs, 0);
+    }
+  }
+}
+
+static void carthw_sf004_write16(u32 a, u32 d)
+{
+  carthw_sf004_write8(a + 1, d);
+}
+
+static void carthw_sf004_mem_setup(void)
+{
+  // writing to low cartridge addresses
+  cpu68k_map_set(m68k_write8_map,  0x000000, 0x00ffff, carthw_sf004_write8, 1);
+  cpu68k_map_set(m68k_write16_map, 0x000000, 0x00ffff, carthw_sf004_write16, 1);
+  // reading from the cartridge I/O region
+  cpu68k_map_set(m68k_read8_map,   0xa10000, 0xa1ffff, carthw_sf004_read8, 1);
+  cpu68k_map_set(m68k_read16_map,  0xa10000, 0xa1ffff, carthw_sf004_read16, 1);
+}
+
+static void carthw_sf004_reset(void)
+{
+  carthw_sf00x_reg = -1;
+  carthw_sf004_write8(0x0d01, 0);
+  carthw_sf004_write8(0x0f01, 0);
+  carthw_sf004_write8(0x0e01, 0x80);
+}
+
+static void carthw_sf004_statef(void)
+{
+  int reg = carthw_sf00x_reg;
+  carthw_sf00x_reg = -1;
+  carthw_sf004_write8(0x0d01, reg >> 16);
+  carthw_sf004_write8(0x0f01, reg >> 0);
+  carthw_sf004_write8(0x0e01, reg >> 8);
+}
+
+void carthw_sf004_startup(void)
+{
+  PicoCartMemSetup  = carthw_sf004_mem_setup;
+  PicoResetHook     = carthw_sf004_reset;
+  PicoLoadStateHook = carthw_sf004_statef;
+  carthw_chunks     = carthw_sf00x_state;
+}
+
 /* Simple unlicensed ROM protection emulation */
 static struct {
   u32 addr;

+ 3 - 0
pico/carthw/carthw.h

@@ -26,6 +26,9 @@ void carthw_Xin1_startup(void);
 void carthw_realtec_startup(void);
 void carthw_radica_startup(void);
 void carthw_pier_startup(void);
+void carthw_sf001_startup(void);
+void carthw_sf002_startup(void);
+void carthw_sf004_startup(void);
 
 void carthw_sprot_startup(void);
 void carthw_sprot_new_location(unsigned int a,

+ 12 - 0
pico/carthw_cfg.c

@@ -42,6 +42,18 @@ static const char builtin_carthw_cfg[] =
   "check_str=0x610,\"Respect\"\n"
   "hw=piersolar_mapper\n"
   "[]\n"
+  "check_str=0x150,\"BEGGAR PRINCE\"\n"
+  "hw=sf001_mapper\n"
+  "sram_range=0x400000,0x40ffff\n"
+  "prop=filled_sram\n"
+  "[]\n"
+  "check_str=0x150,\"LEGEND OF WUKONG\"\n"
+  "hw=sf002_mapper\n"
+  "[]\n"
+  "check_str=0x150,\"STAR ODYSSEY\"\n"
+  "hw=sf004_mapper\n"
+  "sram_range=0x200000,0x207fff\n"
+  "[]\n"
   "check_str=0x120,\"FLICKY\"\n"
   "check_size_gt=0x020000\n"
   "hw=x_in_1_mapper\n"

+ 1 - 1
pico/cd/libchdr

@@ -1 +1 @@
-Subproject commit ff3175ddb040b1a967c665a28868b16bf5aa97dd
+Subproject commit 929a8d6523a7d21ea9e035f43211cd759e072053

+ 2 - 2
pico/cd/memory.c

@@ -1182,10 +1182,10 @@ PICO_INTERNAL void PicoMemSetupCD(void)
 
   // setup FAME fetchmap
   {
-#ifdef __clang__
+#if defined __clang__ || defined HW_WUP
     volatile // prevent strange relocs from clang
 #endif
-    unsigned long ptr_ram = (uptr)PicoMem.ram;
+    uptr ptr_ram = (uptr)PicoMem.ram;
     int i;
 
     // M68k

+ 3 - 2
pico/cd/pcm.c

@@ -92,7 +92,7 @@ void pcd_pcm_sync(unsigned int to)
     mul_l = (int)ch->regs[0] * (ch->regs[1] & 0xf); 
     mul_r = (int)ch->regs[0] * (ch->regs[1] >>  4);
 
-    for (s = 0; s < steps; s++, addr = (addr + inc) & 0x07FFFFFF)
+    for (s = 0; s < steps; s++)
     {
       smp = Pico_mcd->pcm_ram[addr >> PCM_STEP_SHIFT];
 
@@ -104,7 +104,8 @@ void pcd_pcm_sync(unsigned int to)
         addr <<= PCM_STEP_SHIFT;
         if (smp == 0xff)
           break;
-      }
+      } else
+        addr = (addr + inc) & 0x07FFFFFF;
 
       if (smp & 0x80)
         smp = -(smp & 0x7f);

+ 1 - 1
pico/debug.c

@@ -212,7 +212,7 @@ void PDebugShowPalette(unsigned short *screen, int stride)
 
   Pico.m.dirtyPal = 1;
   if (PicoIn.AHW & PAHW_SMS)
-    PicoDoHighPal555M4();
+    PicoDoHighPal555SMS();
   else
     PicoDoHighPal555(1, 0, est);
   Pico.m.dirtyPal = 1;

+ 101 - 62
pico/draw.c

@@ -44,6 +44,8 @@
  */
 
 #include "pico_int.h"
+#include <platform/common/upscale.h>
+
 #define FORCE	// layer forcing via debug register?
 
 int (*PicoScanBegin)(unsigned int num) = NULL;
@@ -130,10 +132,14 @@ void blockcpy_or(void *dst, void *src, size_t n, int pat);
 void blockcpy_or(void *dst, void *src, size_t n, int pat)
 {
   unsigned char *pd = dst, *ps = src;
-  for (; n; n--)
-    *pd++ = (unsigned char) (*ps++ | pat);
+  if (dst > src) {
+    for (pd += n, ps += n; n; n--)
+      *--pd = (unsigned char) (*--ps | pat);
+  } else
+    for (; n; n--)
+      *pd++ = (unsigned char) (*ps++ | pat);
 }
-#define blockcpy memcpy
+#define blockcpy memmove
 #endif
 
 #define TileNormMaker_(pix_func,ret)                         \
@@ -1407,7 +1413,7 @@ static NOINLINE void PrepareSprites(int max_lines)
 
   if (!(Pico.video.reg[12]&1))
     max_sprites = 64, max_line_sprites = 16, max_width = 264;
-  if (PicoIn.opt & POPT_DIS_SPRITE_LIM)
+  if (*est->PicoOpt & POPT_DIS_SPRITE_LIM)
     max_line_sprites = MAX_LINE_SPRITES;
 
   sh = Pico.video.reg[0xC]&8; // shadow/hilight?
@@ -1640,23 +1646,34 @@ void FinalizeLine555(int sh, int line, struct PicoEState *est)
 
   PicoDrawUpdateHighPal();
 
-  if (Pico.video.reg[12]&1) {
-    len = 320;
+  if ((PicoIn.AHW & PAHW_SMS) && (Pico.m.hardware & 0x3) == 0x3)
+                                        len = 160;
+  else if (Pico.video.reg[12]&1)        len = 320;
+  else                                  len = 256;
+
+  if ((*est->PicoOpt & POPT_EN_SOFTSCALE) && len < 320) {
+    if (len == 256) {
+      switch (PicoIn.filter) {
+      case 3: h_upscale_bl4_4_5(pd, 320, ps, 256, len, f_pal); break;
+      case 2: h_upscale_bl2_4_5(pd, 320, ps, 256, len, f_pal); break;
+      case 1: h_upscale_snn_4_5(pd, 320, ps, 256, len, f_pal); break;
+      default: h_upscale_nn_4_5(pd, 320, ps, 256, len, f_pal); break;
+      }
+      if (est->rendstatus & PDRAW_32X_SCALE) { // 32X needs scaled CLUT data
+        unsigned char *psc = ps - 256, *pdc = psc;
+        rh_upscale_nn_4_5(pdc, 320, psc, 256, 256, f_nop);
+      }
+    } else if (len == 160)
+      switch (PicoIn.filter) {
+      case 3:
+      case 2: h_upscale_bl2_1_2(pd, 320, ps, 160, len, f_pal); break;
+      default: h_upscale_nn_1_2(pd, 320, ps, 160, len, f_pal); break;
+      }
   } else {
-    if (!(PicoIn.opt&POPT_DIS_32C_BORDER)) pd+=32;
-    len = 256;
-  }
-
-  {
+    if (!(*est->PicoOpt & POPT_DIS_32C_BORDER) && len < 320)
+      pd += (320-len) / 2;
 #if 1
-    int i;
-
-    for (i = len; i > 0; i-=4) {
-      *pd++ = pal[*ps++];
-      *pd++ = pal[*ps++];
-      *pd++ = pal[*ps++];
-      *pd++ = pal[*ps++];
-    }
+    h_copy(pd, 320, ps, 320, len, f_pal);
 #else
     extern void amips_clut(unsigned short *dst, unsigned char *src, unsigned short *pal, int count);
     extern void amips_clut_6bit(unsigned short *dst, unsigned char *src, unsigned short *pal, int count);
@@ -1668,19 +1685,20 @@ void FinalizeLine555(int sh, int line, struct PicoEState *est)
 }
 #endif
 
-static void FinalizeLine8bit(int sh, int line, struct PicoEState *est)
+void FinalizeLine8bit(int sh, int line, struct PicoEState *est)
 {
   unsigned char *pd = est->DrawLineDest;
+  unsigned char *ps = est->HighCol+8;
   int len;
   static int dirty_line;
 
+  // a hack for mid-frame palette changes
   if (Pico.m.dirtyPal == 1)
   {
-    // a hack for mid-frame palette changes
-    if (!(est->rendstatus & PDRAW_SONIC_MODE) | (line - dirty_line > 4)) {
-      // store a maximum of 2 additional palettes in SonicPal
-      if (est->SonicPalCount < 2)
-        est->SonicPalCount ++;
+    // store a maximum of 2 additional palettes in SonicPal
+    if (est->SonicPalCount < 2 &&
+        (!(est->rendstatus & PDRAW_SONIC_MODE) || (line - dirty_line > 4))) {
+      est->SonicPalCount ++;
       dirty_line = line;
       est->rendstatus |= PDRAW_SONIC_MODE;
     }
@@ -1688,22 +1706,33 @@ static void FinalizeLine8bit(int sh, int line, struct PicoEState *est)
     Pico.m.dirtyPal = 2;
   }
 
-  if (Pico.video.reg[12]&1) {
-    len = 320;
-  } else {
-    if (!(PicoIn.opt & POPT_DIS_32C_BORDER))
-      pd += 32;
-    len = 256;
-  }
+  if ((PicoIn.AHW & PAHW_SMS) && (Pico.m.hardware & 0x3) == 0x3)
+                                        len = 160;
+  else if (Pico.video.reg[12]&1)        len = 320;
+  else                                  len = 256;
+
+  if (DrawLineDestIncrement == 0)
+    pd = est->HighCol+8;
+
+  if ((PicoIn.opt & POPT_EN_SOFTSCALE) && len < 320) {
+    unsigned char pal = 0;
 
-  if (DrawLineDestIncrement == 0) {
     if (!sh && (est->rendstatus & PDRAW_SONIC_MODE))
-      blockcpy_or(pd+8, est->HighCol+8, len, est->SonicPalCount*0x40);
-  } else if (!sh && (est->rendstatus & PDRAW_SONIC_MODE)) {
-    // select active backup palette
-    blockcpy_or(pd, est->HighCol+8, len, est->SonicPalCount*0x40);
+      pal = est->SonicPalCount*0x40;
+    // Smoothing can't be used with CLUT, hence it's always Nearest Neighbour.
+    if (len == 256)
+      // use reverse version since src and dest ptr may be the same.
+      rh_upscale_nn_4_5(pd, 320, ps, 256, len, f_or);
+    else
+      rh_upscale_nn_1_2(pd, 320, ps, 256, len, f_or);
   } else {
-    blockcpy(pd, est->HighCol+8, len);
+    if (!(*est->PicoOpt & POPT_DIS_32C_BORDER) && len < 320)
+      pd += (320-len) / 2;
+    if (!sh && (est->rendstatus & PDRAW_SONIC_MODE))
+      // select active backup palette
+      blockcpy_or(pd, ps, len, est->SonicPalCount*0x40);
+    else if (pd != ps)
+      blockcpy(pd, ps, len);
   }
 }
 
@@ -1828,8 +1857,7 @@ static int DrawDisplay(int sh)
 // MUST be called every frame
 PICO_INTERNAL void PicoFrameStart(void)
 {
-  int offs = 8, lines = 224;
-  int dirty = ((Pico.est.rendstatus & PDRAW_SONIC_MODE) || Pico.m.dirtyPal);
+  int loffs = 8, lines = 224, coffs = 0, columns = 320;
   int sprep = Pico.est.rendstatus & (PDRAW_SPRITES_MOVED|PDRAW_DIRTY_SPRITES);
   int skipped = Pico.est.rendstatus & PDRAW_SKIP_FRAME;
 
@@ -1837,43 +1865,58 @@ PICO_INTERNAL void PicoFrameStart(void)
   Pico.est.rendstatus = 0;
   if ((Pico.video.reg[12] & 6) == 6)
     Pico.est.rendstatus |= PDRAW_INTERLACE; // interlace mode
-  if (!(Pico.video.reg[12] & 1))
+  if (!(Pico.video.reg[12] & 1)) {
     Pico.est.rendstatus |= PDRAW_32_COLS;
+    if (!(PicoIn.opt & POPT_EN_SOFTSCALE)) {
+      columns = 256;
+      coffs = 32;
+    }
+  }
   if (Pico.video.reg[1] & 8) {
     Pico.est.rendstatus |= PDRAW_30_ROWS;
-    offs = 0;
     lines = 240;
+    loffs = 0;
   }
+  if (PicoIn.opt & POPT_DIS_32C_BORDER)
+    coffs = 0;
 
   if (Pico.est.rendstatus != rendstatus_old || lines != rendlines) {
     rendlines = lines;
     // mode_change() might reset rendstatus_old by calling SetColorFormat
-    emu_video_mode_change((lines == 240) ? 0 : 8,
-      lines, (Pico.video.reg[12] & 1) ? 0 : 1);
+    emu_video_mode_change(loffs, lines, coffs, columns);
     rendstatus_old = Pico.est.rendstatus;
   }
   if (PicoIn.skipFrame) // preserve this until something is rendered at last
     Pico.est.rendstatus |= PDRAW_SKIP_FRAME;
   if (sprep | skipped)
     Pico.est.rendstatus |= PDRAW_PARSE_SPRITES;
+  if (PicoIn.AHW & PAHW_32X)
+    Pico.est.rendstatus |= PDRAW_32X_SCALE;
 
-  Pico.est.HighCol = HighColBase + offs * HighColIncrement;
-  Pico.est.DrawLineDest = (char *)DrawLineDestBase + offs * DrawLineDestIncrement;
+  Pico.est.HighCol = HighColBase + loffs * HighColIncrement;
+  Pico.est.DrawLineDest = (char *)DrawLineDestBase + loffs * DrawLineDestIncrement;
   Pico.est.DrawScanline = 0;
   skip_next_line = 0;
 
   if (FinalizeLine == FinalizeLine8bit) {
     // make a backup of the current palette in case Sonic mode is detected later
-    Pico.est.SonicPalCount = 0;
-    Pico.m.dirtyPal = (dirty ? 2 : 0); // mark as dirty but already copied
+    Pico.m.dirtyPal = (Pico.m.dirtyPal || Pico.est.SonicPalCount ? 2 : 0);
     blockcpy(Pico.est.SonicPal, PicoMem.cram, 0x40*2);
   }
+  Pico.est.SonicPalCount = 0;
 }
 
 static void DrawBlankedLine(int line, int offs, int sh, int bgc)
 {
-  if (PicoScanBegin != NULL)
-    PicoScanBegin(line + offs);
+  int skip = skip_next_line;
+
+  if (PicoScanBegin != NULL && skip == 0)
+    skip = PicoScanBegin(line + offs);
+
+  if (skip) {
+    skip_next_line = skip - 1;
+    return;
+  }
 
   BackFill(bgc, sh, &Pico.est);
 
@@ -1881,7 +1924,7 @@ static void DrawBlankedLine(int line, int offs, int sh, int bgc)
     FinalizeLine(sh, line, &Pico.est);
 
   if (PicoScanEnd != NULL)
-    PicoScanEnd(line + offs);
+    skip_next_line = PicoScanEnd(line + offs);
 
   Pico.est.HighCol += HighColIncrement;
   Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement;
@@ -1889,15 +1932,10 @@ static void DrawBlankedLine(int line, int offs, int sh, int bgc)
 
 static void PicoLine(int line, int offs, int sh, int bgc)
 {
-  int skip = 0;
-
-  if (skip_next_line > 0) {
-    skip_next_line--;
-    return;
-  }
+  int skip = skip_next_line;
 
   Pico.est.DrawScanline = line;
-  if (PicoScanBegin != NULL)
+  if (PicoScanBegin != NULL && skip == 0)
     skip = PicoScanBegin(line + offs);
 
   if (skip) {
@@ -1967,7 +2005,7 @@ void PicoDrawUpdateHighPal(void)
       sh = 0; // no s/h support
 
     if (PicoIn.AHW & PAHW_SMS)
-      PicoDoHighPal555M4();
+      PicoDoHighPal555SMS();
     else if (FinalizeLine == FinalizeLine8bit)
       PicoDoHighPal555_8bit(sh, 0, est);
     else
@@ -1978,6 +2016,8 @@ void PicoDrawUpdateHighPal(void)
       blockcpy(est->HighPal+0x40, est->HighPal, 0x40*2);
       blockcpy(est->HighPal+0x80, est->HighPal, 0x80*2);
     }
+    Pico.est.HighPal[0xe0] = 0x0000; // black and white, reserved for OSD
+    Pico.est.HighPal[0xf0] = 0xffff;
   }
 }
 
@@ -1990,7 +2030,6 @@ void PicoDrawSetOutFormat(pdso_t which, int use_32x_line_mode)
   {
     case PDF_8BIT:
       FinalizeLine = FinalizeLine8bit;
-      PicoDrawSetInternalBuf(Pico.est.Draw2FB, 328);
       break;
 
     case PDF_RGB555:
@@ -2006,7 +2045,7 @@ void PicoDrawSetOutFormat(pdso_t which, int use_32x_line_mode)
   }
   if (PicoIn.AHW & PAHW_32X)
     PicoDrawSetOutFormat32x(which, use_32x_line_mode);
-  PicoDrawSetOutputMode4(which);
+  PicoDrawSetOutputSMS(which);
   rendstatus_old = -1;
   Pico.m.dirtyPal = 1;
 }
@@ -2017,7 +2056,7 @@ void PicoDrawSetOutBufMD(void *dest, int increment)
     // kludge for no-copy mode, using ALT_RENDERER layout
     PicoDrawSetInternalBuf(dest, increment);
   } else if (FinalizeLine == NULL) {
-    PicoDrawSetInternalBuf(dest, increment); // needed for Mode4
+    PicoDrawSetInternalBuf(dest, increment); // needed for SMS
     PicoDraw2SetOutBuf(dest, increment);
   } else if (dest != NULL) {
     DrawLineDestBase = dest;

+ 436 - 42
pico/draw_arm.S

@@ -21,6 +21,7 @@
 .equ PDRAW_DIRTY_SPRITES, (1<<4)
 .equ PDRAW_PLANE_HI_PRIO, (1<<6)
 .equ PDRAW_SHHI_DONE,     (1<<7)
+.equ PDRAW_32X_SCALE,     (1<<12)
 
 @ helpers
 .macro add_c24 d s c
@@ -1633,36 +1634,51 @@ PicoDoHighPal555_end:
 .global FinalizeLine555
 
 FinalizeLine555:
-    stmfd   sp!, {r4-r10,lr}
-    mov     r10,r2               @ est
-    ldr     r8, [r10, #OFS_EST_Pico]
+    stmfd   sp!, {r4-r11,lr}
+    mov     r11,r2               @ est
+    ldr     r8, [r11, #OFS_EST_Pico]
 
     bl      PicoDrawUpdateHighPal
 
-    add     r3, r10, #OFS_EST_HighPal
+    add     r3, r11, #OFS_EST_HighPal
 
     mov     lr, #0xff
+    mov     lr, lr, lsl #1
 
-    ldr     r1, [r10, #OFS_EST_HighCol]
-    ldr     r0, [r10, #OFS_EST_DrawLineDest]
+    ldr     r5, [r11, #OFS_EST_PicoOpt]
+    ldr     r1, [r11, #OFS_EST_HighCol]
+    ldr     r0, [r11, #OFS_EST_DrawLineDest]
+    ldr     r4, [r5]
+    ldr     r7, [r5, #OFS_PicoIn_AHW-OFS_PicoIn_opt]
+    ldrb    r12,[r8, #OFS_Pico_video_reg+12]
+    ldr     r2, [r8, #OFS_Pico_m_hardware]
     add     r1, r1, #8
 
-    ldrb    r12, [r8, #OFS_Pico_video_reg+12]
-    mov     lr, lr, lsl #1
+    tst     r7, #0x10
+    beq     .fl_no20colRGB555
+    and     r7, r2, #0x3
+    cmp     r7, #0x3		@ Game Gear, LCD?
+    bne     .fl_no20colRGB555
 
-    tst     r12, #1
-    movne   r2, #320/8           @ len
+    mov     r2, #160/8		@ len = 160
+    tst     r4, #0x4000		@ EN_SOFTSCALE?
+    bne     .fl_20scale_RGB555	@ scale 160->320
+    beq     .fl_checkborder
+
+.fl_no20colRGB555:
+    tst     r12, #1		@ h32?
+    movne   r2, #320/8          @ len = 320
     bne     .fl_no32colRGB555
-    ldr     r4, [r10, #OFS_EST_PicoOpt]
-    mov     r2, #256/8
-    ldr     r4, [r4]
-    tst     r4, #0x4000
-    bne     .fl_32scale_RGB555
-    tst     r4, #0x0100
-    addeq   r0, r0, #32*2
+    moveq   r2, #256/8		@ len = 256
+    tst     r4, #0x4000		@ EN_SOFTSCALE?
+    bne     .fl_32scale_RGB555	@ scale 256->320
 
-.fl_no32colRGB555:
+.fl_checkborder:
+    tst     r4, #0x0100		@ DIS_32C_BORDER?
+    rsbeq   r4, r2, #320/8	@ pd += (320-len)/2
+    addeq   r0, r0, r4, lsl #3
 
+.fl_no32colRGB555:
 #ifdef UNALIGNED_DRAWLINEDEST
     @ this is basically for Gizmondo, which has unaligned odd lines in the framebuffer
     tst     r0, #2
@@ -1701,19 +1717,174 @@ FinalizeLine555:
     stmia   r0!, {r4,r5,r8,r12}
     bne     .fl_loopRGB555
 
-    ldmfd   sp!, {r4-r10,pc}
+    ldmfd   sp!, {r4-r11,pc}
 
 
 .fl_32scale_RGB555:
-    mov     r9, #0x3900 @ f800 07e0 001f | e000 0780 001c | 3800 01e0 0007
-    orr     r9, r9, #0x00e7
+    ldr     r5, [r5, #OFS_PicoIn_filter-OFS_PicoIn_opt]
+
+    mov     r9, #0xf700 @ f800 07e0 001f | e000 0780 001c | 3800 01e0 0007
+    orr     r9, r9, #0x00de
 
 #ifdef UNALIGNED_DRAWLINEDEST
     tst     r0, #2
     bne     .fl_32scale_RGB555u
 #endif
 
-.fl_loop32scale_RGB555:
+    and     r5, r5, #0x3
+    add     pc, pc, r5, lsl #2
+    nop
+    b       .fl_32scale_nn
+    b       .fl_32scale_snn
+    b       .fl_32scale_bl2
+    b       .fl_32scale_bl4
+
+.fl_32scale_nn:
+    ldr     r12, [r1], #4
+    ldr     r7,  [r1], #4
+
+    and     r4, lr, r12, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r12, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6, lr, r12, lsr #15
+    ldrh    r6, [r3, r6]
+    and     r10,lr, r12, lsr #23
+    ldrh    r10,[r3, r10]
+
+    orr     r4, r4, r5, lsl #16
+    orr     r5, r6, r6, lsl #16
+
+    and     r6, lr, r7, lsl #1
+    ldrh    r6, [r3, r6]
+    and     r8, lr, r7, lsr #7
+    ldrh    r8, [r3, r8]
+    and     r12,lr, r7, lsr #15
+    ldrh    r12,[r3, r12]
+    and     r7, lr, r7, lsr #23
+    ldrh    r7, [r3, r7]
+
+    orr     r6, r10,r6, lsl  #16
+    orr     r8, r8,r12, lsl #16
+
+    subs    r2, r2, #1
+
+    orr     r10,r12,r7, lsl #16
+
+    stmia   r0!, {r4,r5,r6,r8,r10}
+    bne     .fl_32scale_nn
+
+    b       .fl_32scale_8bit
+
+.fl_32scale_snn:
+    ldr     r12, [r1], #4
+    ldr     r7,  [r1], #4
+
+    and     r4, lr, r12, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r12, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6, lr, r12, lsr #15
+    ldrh    r6, [r3, r6]
+    and     r10,lr, r12, lsr #23
+    ldrh    r10,[r3, r10]
+
+    and     r4, r4, r9
+    and     r5, r5, r9
+    orr     r4, r4, r5, lsl #16
+    and     r6, r6, r9
+    add     r5, r5, r6
+    mov     r5, r5, lsr #1
+    orr     r5, r5, r6, lsl #16
+
+    and     r6, lr, r7, lsl #1
+    ldrh    r6, [r3, r6]
+    and     r8, lr, r7, lsr #7
+    ldrh    r8, [r3, r8]
+    and     r12,lr, r7, lsr #15
+    ldrh    r12,[r3, r12]
+    and     r7, lr, r7, lsr #23
+    ldrh    r7, [r3, r7]
+
+    and     r6, r6, r9
+    and     r10,r10,r9
+    orr     r6, r10,r6, lsl  #16
+    and     r12,r12,r9
+    and     r7, r7, r9
+    orr     r10,r12,r7, lsl #16
+
+    and     r8, r8, r9
+    add     r12,r12,r8
+    mov     r12,r12,lsr #1
+    orr     r8, r8,r12, lsl #16
+
+    subs    r2, r2, #1
+
+    stmia   r0!, {r4,r5,r6,r8,r10}
+    bne     .fl_32scale_snn
+
+    b       .fl_32scale_8bit
+
+.fl_32scale_bl2:
+    ldr     r12, [r1], #4
+    ldr     r7,  [r1], #4
+
+    and     r4, lr, r12, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r12, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6, lr, r12, lsr #15
+    ldrh    r6, [r3, r6]
+
+    and     r4, r4, r9
+    and     r5, r5, r9
+    add     r10,r4, r5
+    mov     r10,r10,lsr #1
+    orr     r4, r4, r10,lsl #16		@ px0 | (px0+px1)/2
+
+    and     r6, r6, r9
+    add     r5, r5, r6
+    mov     r5, r5, lsr #1
+    orr     r5, r5, r6, lsl #16		@ (px1+px2)/2 | px2
+
+    and     r10,lr, r12, lsr #23
+    ldrh    r10,[r3, r10]
+    and     r8, lr, r7, lsl #1
+    ldrh    r8, [r3, r8]
+
+    and     r10,r10,r9
+    and     r8, r8, r9
+    orr     r6, r10,r8, lsl  #16	@ px3 | px4
+
+    and     r12,lr, r7, lsr #15
+    ldrh    r12,[r3, r12]
+    and     r10, lr, r7, lsr #23
+    ldrh    r10, [r3, r10]
+    and     r7, lr, r7, lsr #7
+    ldrh    r7, [r3, r7]
+
+    and     r12,r12,r9
+    and     r10,r10,r9
+    orr     r10,r12,r10, lsl #16	@ px6 | px7
+
+    and     r7, r7, r9
+    add     r12,r12,r7
+    add     r8, r8, r7
+    mov     r8, r8, lsr #1
+    mov     r12,r12,lsr #1
+    orr     r8, r8,r12, lsl #16		@ (px4+px5)/2 | (px5+px6)/2
+
+    subs    r2, r2, #1
+
+    stmia   r0!, {r4,r5,r6,r8,r10}
+    bne     .fl_32scale_bl2
+
+    b       .fl_32scale_8bit
+
+.fl_32scale_bl4:
+    // TODO this should reflect the bl4 C algorithm, but it doesn't, it's bln.
+    and     r9, r9, r9, lsl #1        @ nuke 2 LSBs to avoid spilling for n/4
+.fl_32loop_bl4:
     ldr     r12, [r1], #4
     ldr     r7,  [r1], #4
 
@@ -1721,16 +1892,21 @@ FinalizeLine555:
     ldrh    r4, [r3, r4]
     and     r5, lr, r12,lsr #7
     ldrh    r5, [r3, r5]
-    and     r4, r4, r9, lsl #2
+
+    @ r4 = 1/4px0+3/4px1 : px0
+    and     r4, r4, r9
     orr     r4, r4, r4, lsl #14       @ r4[31:16] = 1/4 pix_s 0
-    and     r5, r5, r9, lsl #2
+    and     r5, r5, r9
     sub     r6, r5, r5, lsr #2        @ r6 = 3/4 pix_s 1
     add     r4, r4, r6, lsl #16       @ pix_d 0, 1
+
     and     r6, lr, r12,lsr #15
     ldrh    r6, [r3, r6]
     and     r12,lr, r12,lsr #23
     ldrh    r12,[r3, r12]
-    and     r6, r6, r9, lsl #2
+
+    @ r5 = 3/4px2+1/4px3 : (px1+px2)/2
+    and     r6, r6, r9
     add     r5, r5, r6
     mov     r5, r5, lsr #1
     sub     r6, r6, r6, lsr #2        @ r6 = 3/4 pix_s 2
@@ -1738,34 +1914,218 @@ FinalizeLine555:
 
     and     r6, lr, r7, lsl #1
     ldrh    r6, [r3, r6]
-    and     r12,r12,r9, lsl #2
+    and     r12,r12,r9
     add     r5, r5, r12,lsl #14       @ pix_d 2, 3
-    and     r6, r6, r9, lsl #2
+
+    @ r6 = px4 : px3
+    and     r6, r6, r9
     orr     r6, r12,r6, lsl #16       @ pix_d 4, 5
 
+    @ r8 = (px5+px6)/2 : 1/4px4+3/4px5
     and     r12,lr, r7, lsr #7
     ldrh    r12,[r3, r12]
     and     r10,lr, r7, lsr #15
     ldrh    r10,[r3, r10]
-    and     r12,r12,r9, lsl #2
+    and     r12,r12,r9
     sub     r8, r12,r12,lsr #2        @ r8 = 3/4 pix_s 1
     add     r8, r8, r6, lsr #18
+
     and     r7, lr, r7, lsr #23
     ldrh    r7, [r3, r7]
-    and     r10,r10,r9, lsl #2
+    and     r10,r10,r9
     orr     r8, r8, r10,lsl #15
     add     r8, r8, r12,lsl #15       @ pix_d 6, 7
+
+    @ r10 = px7 : 3/4px6+1/4px7
     sub     r10,r10,r10,lsr #2        @ r10= 3/4 pix_s 2
-    and     r7, r7, r9, lsl #2
+    and     r7, r7, r9
     add     r10,r10,r7, lsr #2        @ += 1/4 pix_s 3
     orr     r10,r10,r7, lsl #16       @ pix_d 8, 9
 
     subs    r2, r2, #1
 
     stmia   r0!, {r4,r5,r6,r8,r10}
-    bne     .fl_loop32scale_RGB555
+    bne     .fl_32loop_bl4
+
+.fl_32scale_8bit:
+    ldr     r4, [r11, #OFS_EST_rendstatus]
+    add     r0, r1, #320-256
+    mov     r2, #256/8
+    tst     r4, #PDRAW_32X_SCALE
+    ldmeqfd sp!, {r4-r11,pc}
+    mov     lr, #0xff
+
+.fl_32scale_8bit_nn:
+    ldr     r7,  [r1, #-4]!
+    ldr     r12, [r1, #-4]!
+
+    and     r4, lr, r12, lsl #0
+    and     r5, lr, r12, lsr #8
+    and     r6, lr, r12, lsr #16
+    and     r10,lr, r12, lsr #24
+    
+    orr     r4, r4, r5, lsl #8
+    orr     r5, r6, r6, lsl #8
+
+    and     r6, lr, r7, lsl #0
+    and     r8, lr, r7, lsr #8
+    and     r12,lr, r7, lsr #16
+    and     r7, lr, r7, lsr #24
+
+    orr     r6, r10,r6, lsl  #8
+    orr     r8, r8,r12, lsl #8
+
+    subs    r2, r2, #1
+
+    orr     r10,r12,r7, lsl #8
+
+    strh    r10, [r0, #-2]!
+    strh    r8, [r0, #-2]!
+    strh    r6, [r0, #-2]!
+    strh    r5, [r0, #-2]!
+    strh    r4, [r0, #-2]!
+
+    bne     .fl_32scale_8bit_nn
+
+    ldmfd   sp!, {r4-r11,pc}
+
+
+.fl_20scale_RGB555:
+    ldr     r5, [r5, #OFS_PicoIn_filter-OFS_PicoIn_opt]
+
+    mov     r9, #0xf700 @ f800 07e0 001f | e000 0780 001c | 3800 01e0 0007
+    orr     r9, r9, #0x00de
+
+#ifdef UNALIGNED_DRAWLINEDEST
+    tst     r0, #2
+    bne     .fl_20scale_RGB555u
+#endif
+
+    and     r5, r5, #0x2
+    add     pc, pc, r5, lsl #1
+    nop
+    b       .fl_20scale_nn
+    b       .fl_20scale_bl2
+
+.fl_20scale_nn:
+    ldr     r12, [r1], #4
+    ldr     r7,  [r1], #4
+
+    and     r4, lr, r12, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r12, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6, lr, r12, lsr #15
+    ldrh    r6, [r3, r6]
+    and     r8 ,lr, r12, lsr #23
+    ldrh    r8 ,[r3, r8 ]
+
+    orr     r4, r4, r4, lsl #16
+    orr     r5, r5, r5, lsl #16
+    orr     r6, r6, r6, lsl #16
+    orr     r8, r8, r8, lsl #16
+    stmia   r0!, {r4,r5,r6,r8}
+
+    and     r4, lr, r7, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r7, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6 ,lr, r7, lsr #15
+    ldrh    r6 ,[r3, r6]
+    and     r8, lr, r7, lsr #23
+    ldrh    r8, [r3, r8]
+
+    orr     r4, r4, r4, lsl #16
+    orr     r5, r5, r5, lsl #16
+    orr     r6, r6, r6, lsl #16
+    orr     r8, r8, r8, lsl #16
+    stmia   r0!, {r4,r5,r6,r8}
+
+    subs    r2, r2, #1
+    bne     .fl_20scale_nn
+
+    ldmfd   sp!, {r4-r11,pc}
+
+
+.fl_20scale_bl2:
+    ldr     r8,  [r1]
+    and     r8, lr, r8, lsl #1
+    ldrh    r8, [r3, r8]
+    and     r8, r8, r9
+    lsl     r8, r8, #16
+
+.fl_20loop_bl2:
+    ldr     r12, [r1], #4
+    ldr     r7,  [r1], #4
+
+    and     r4, lr, r12, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r12, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6, lr, r12, lsr #15
+    ldrh    r6, [r3, r6]
+
+    and     r4, r4, r9
+    add     r10,r4, r8, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r4, r10,r4, lsl #16		@ (px-1+px0)/2 | px0
+
+    and     r8 ,lr, r12, lsr #23
+    ldrh    r8 ,[r3, r8]
+
+    and     r5, r5, r9
+    add     r10,r5, r4, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r5, r10,r5, lsl #16		@ (px0 +px1)/2 | px1
+
+    and     r6, r6, r9
+    add     r10,r6, r5, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r6, r10,r6, lsl #16		@ (px1 +px2)/2 | px2
+
+    and     r8, r8, r9
+    add     r10,r8, r6, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r8, r10,r8, lsl #16		@ (px2 +px3)/2 | px3
+
+    stmia   r0!, {r4,r5,r6,r8}
+
+    and     r4, lr, r7, lsl #1
+    ldrh    r4, [r3, r4]
+    and     r5, lr, r7, lsr #7
+    ldrh    r5, [r3, r5]
+    and     r6, lr, r7, lsr #15
+    ldrh    r6, [r3, r6]
+
+    and     r4, r4, r9
+    add     r10,r4, r8, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r4, r10,r4, lsl #16		@ (px-1+px0)/2 | px0
+
+    and     r8 ,lr, r7, lsr #23
+    ldrh    r8 ,[r3, r8]
+
+    and     r5, r5, r9
+    add     r10,r5, r4, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r5, r10,r5, lsl #16		@ (px0 +px1)/2 | px1
+
+    and     r6, r6, r9
+    add     r10,r6, r5, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r6, r10,r6, lsl #16		@ (px1 +px2)/2 | px2
+
+    and     r8, r8, r9
+    add     r10,r8, r6, lsr #16
+    mov     r10,r10,lsr #1
+    orr     r8, r10,r8, lsl #16		@ (px2 +px3)/2 | px3
+
+    subs    r2, r2, #1
+    stmia   r0!, {r4,r5,r6,r8}
+    bne     .fl_20loop_bl2
+
+    ldmfd   sp!, {r4-r11,pc}
 
-    ldmfd   sp!, {r4-r10,pc}
 
 #ifdef UNALIGNED_DRAWLINEDEST
     @ unaligned versions of loops
@@ -1810,7 +2170,7 @@ FinalizeLine555:
 
     strh    r8, [r0], #2
 
-    ldmfd   sp!, {r4-r10,pc}
+    ldmfd   sp!, {r4-r11,pc}
 
 
 .fl_32scale_RGB555u:
@@ -1826,10 +2186,10 @@ FinalizeLine555:
     ldrh    r6, [r3, r6]
     and     r5, lr, r12,lsr #7
     ldrh    r5, [r3, r5]
-    and     r6, r6, r9, lsl #2
+    and     r6, r6, r9
     orr     r4, r4, r6, lsl #16       @ r4 = pix_d -1, 0
 
-    and     r5, r5, r9, lsl #2
+    and     r5, r5, r9
     sub     r8, r5, r5, lsr #2        @ r8 = 3/4 pix_s 1
     add     r6, r8, r6, lsr #2        @ r6 = (1/4 pix_s 0) + (3/4 pix_s 1)
     orr     r5, r6, r5, lsl #15
@@ -1838,20 +2198,20 @@ FinalizeLine555:
     ldrh    r6, [r3, r6]
     and     r12,lr, r12,lsr #23
     ldrh    r12,[r3, r12]
-    and     r6, r6, r9, lsl #2
+    and     r6, r6, r9
     add     r5, r5, r6, lsl #15       @ r5 = pix_d 1, 2
 
     and     r8, lr, r7, lsl #1
     ldrh    r8, [r3, r8]
     and     r10,lr, r7, lsr #7
     ldrh    r10,[r3, r10]
-    and     r12,r12,r9, lsl #2
+    and     r12,r12,r9
     sub     r6, r6, r6, lsr #2        @ r6 = 3/4 pix_s 2
     add     r6, r6, r12,lsr #2
     orr     r6, r6, r12,lsl #16       @ r6 = pix_d 3, 4
 
-    and     r8, r8, r9, lsl #2
-    and     r10,r10,r9, lsl #2
+    and     r8, r8, r9
+    and     r10,r10,r9
     sub     r12,r10,r10,lsr #2        @ r12 = 3/4 pix_s 5
     orr     r8, r8, r8, lsl #14
     add     r8, r8, r12,lsl #16       @ r8 = pix_d 5, 6
@@ -1859,12 +2219,12 @@ FinalizeLine555:
     ldrh    r12,[r3, r12]
     and     r7, lr, r7, lsr #23
     ldrh    r7, [r3, r7]
-    and     r12,r12,r9, lsl #2
+    and     r12,r12,r9
     add     r10,r10,r12
     mov     r10,r10,    lsr #1
     sub     r12,r12,r12,lsr #2        @ r12 = 3/4 pix_s 6
     orr     r10,r10,r12,lsl #16
-    and     r7, r7, r9, lsl #2
+    and     r7, r7, r9
     add     r10,r10,r7, lsl #14       @ r10 = pix_d 7, 8
 
     subs    r2, r2, #1
@@ -1875,7 +2235,7 @@ FinalizeLine555:
 
     strh    r4, [r0], #2
 
-    ldmfd   sp!, {r4-r10,pc}
+    ldmfd   sp!, {r4-r11,pc}
 
 #endif /* UNALIGNED_DRAWLINEDEST */
 
@@ -1887,6 +2247,9 @@ FinalizeLine555:
 
 blockcpy:
     stmfd   sp!, {r4,r5}
+    cmp     r0, r1
+    bhs     blockcpyhi
+
     mov     r2, r2, lsr #4
 blockcpy_loop:
     ldmia   r1!, {r3-r5,r12}
@@ -1896,6 +2259,18 @@ blockcpy_loop:
     ldmfd   sp!, {r4,r5}
     bx      lr
 
+blockcpyhi:
+    add     r0, r0, r2
+    add     r1, r1, r2
+    mov     r2, r2, lsr #4
+blockcpyhi_loop:
+    ldmdb   r1!, {r3-r5,r12}
+    subs    r2, r2, #1
+    stmdb   r0!, {r3-r5,r12}
+    bne     blockcpyhi_loop
+    ldmfd   sp!, {r4,r5}
+    bx      lr
+
 
 .global blockcpy_or @ void *dst, void *src, size_t n, int pat
 
@@ -1903,6 +2278,9 @@ blockcpy_or:
     stmfd   sp!, {r4-r6}
     orr     r3, r3, r3, lsl #8
     orr     r3, r3, r3, lsl #16
+    cmp     r0, r1
+    bhs     blockcpyhi_or
+
     mov     r2, r2, lsr #4
 blockcpy_loop_or:
     ldmia   r1!, {r4-r6,r12}
@@ -1916,4 +2294,20 @@ blockcpy_loop_or:
     ldmfd   sp!, {r4-r6}
     bx      lr
 
+blockcpyhi_or:
+    add     r0, r0, r2
+    add     r1, r1, r2
+    mov     r2, r2, lsr #4
+blockcpyhi_loop_or:
+    ldmdb   r1!, {r4-r6,r12}
+    subs    r2, r2, #1
+    orr     r4, r4, r3
+    orr     r5, r5, r3
+    orr     r6, r6, r3
+    orr     r12,r12,r3
+    stmdb   r0!, {r4-r6,r12}
+    bne     blockcpyhi_loop_or
+    ldmfd   sp!, {r4-r6}
+    bx      lr
+
 @ vim:filetype=armasm

+ 2 - 1
pico/m68kif_cyclone.s

@@ -27,7 +27,8 @@ cyclone_checkpc:
     and     r3, r0, #0xff000000
     bic     r0, r0, #1
     bics    r2, r0, #0xff000000
-    beq     crashed
+    @ ouf, some Codemasters titles actually start at address 0
+    @ beq     crashed
 
     ldr     r1, [r7, #0x6c]  @ read16 map
     mov     r2, r2, lsr #M68K_MEM_SHIFT

+ 20 - 6
pico/media.c

@@ -35,8 +35,8 @@ static int detect_media(const char *fname)
 {
   static const short sms_offsets[] = { 0x7ff0, 0x3ff0, 0x1ff0 };
   static const char *sms_exts[] = { "sms", "gg", "sg" };
-  static const char *md_exts[] = { "gen", "bin", "smd" };
-  char buff0[32], buff[32];
+  static const char *md_exts[] = { "gen", "smd" };
+  char buff0[512], buff[32];
   unsigned short *d16;
   pm_file *pmf;
   char ext[5];
@@ -56,7 +56,7 @@ static int detect_media(const char *fname)
   if (pmf == NULL)
     return PM_BAD_DETECT;
 
-  if (pm_read(buff0, 32, pmf) != 32) {
+  if (pm_read(buff0, 512, pmf) != 512) {
     pm_close(pmf);
     return PM_BAD_DETECT;
   }
@@ -111,6 +111,11 @@ extension_check:
     lprintf("bad MD reset vector, assuming SMS\n");
     goto looks_like_sms;
   }
+  d16 = (unsigned short *)(buff0 + 0x1a0);
+  if ((((d16[0] << 16) | d16[1]) & 0xffffff) != 0) {
+    lprintf("bad MD rom start, assuming SMS\n");
+    goto looks_like_sms;
+  }
 
 looks_like_md:
   pm_close(pmf);
@@ -243,7 +248,6 @@ enum media_type_e PicoLoadMedia(const char *filename,
     }
   }
   else if (media_type == PM_MARK3) {
-    lprintf("detected SMS ROM\n");
     PicoIn.AHW = PAHW_SMS;
   }
 
@@ -255,7 +259,6 @@ enum media_type_e PicoLoadMedia(const char *filename,
   }
 
   ret = PicoCartLoad(rom, &rom_data, &rom_size, (PicoIn.AHW & PAHW_SMS) ? 1 : 0);
-  pm_close(rom);
   if (ret != 0) {
     if      (ret == 2) lprintf("Out of memory\n");
     else if (ret == 3) lprintf("Read failed\n");
@@ -292,9 +295,18 @@ enum media_type_e PicoLoadMedia(const char *filename,
     goto out;
   }
   rom_data = NULL; // now belongs to PicoCart
-  Pico.m.ncart_in = 0;
+
+  // simple test for GG. Do this here since m.hardware is nulled in Insert
+  if (PicoIn.AHW & PAHW_SMS) {
+    if (!strcmp(rom->ext,"gg") && !PicoIn.hwSelect) {
+      Pico.m.hardware |= 0x1;
+      lprintf("detected GG ROM\n");
+    } else
+      lprintf("detected SMS ROM\n");
+  }
 
   // insert CD if it was detected
+  Pico.m.ncart_in = 0;
   if (cd_img_type != CT_UNKNOWN) {
     ret = cdd_load(filename, cd_img_type);
     if (ret != 0) {
@@ -309,6 +321,8 @@ enum media_type_e PicoLoadMedia(const char *filename,
     PicoSetInputDevice(0, PICO_INPUT_PAD_6BTN);
 
 out:
+  if (rom)
+    pm_close(rom);
   if (rom_data)
     free(rom_data);
   return media_type;

+ 96 - 25
pico/memory.c

@@ -20,7 +20,7 @@ uptr m68k_read16_map [0x1000000 >> M68K_MEM_SHIFT];
 uptr m68k_write8_map [0x1000000 >> M68K_MEM_SHIFT];
 uptr m68k_write16_map[0x1000000 >> M68K_MEM_SHIFT];
 
-static void xmap_set(uptr *map, int shift, int start_addr, int end_addr,
+static void xmap_set(uptr *map, int shift, u32 start_addr, u32 end_addr,
     const void *func_or_mh, int is_func)
 {
 #ifdef __clang__
@@ -53,13 +53,17 @@ static void xmap_set(uptr *map, int shift, int start_addr, int end_addr,
   }
 }
 
-void z80_map_set(uptr *map, int start_addr, int end_addr,
+void z80_map_set(uptr *map, u16 start_addr, u16 end_addr,
     const void *func_or_mh, int is_func)
 {
   xmap_set(map, Z80_MEM_SHIFT, start_addr, end_addr, func_or_mh, is_func);
+#ifdef _USE_CZ80
+  if (!is_func)
+    Cz80_Set_Fetch(&CZ80, start_addr, end_addr, (FPTR)func_or_mh);
+#endif
 }
 
-void cpu68k_map_set(uptr *map, int start_addr, int end_addr,
+void cpu68k_map_set(uptr *map, u32 start_addr, u32 end_addr,
     const void *func_or_mh, int is_func)
 {
   xmap_set(map, M68K_MEM_SHIFT, start_addr, end_addr, func_or_mh, is_func);
@@ -77,7 +81,7 @@ void cpu68k_map_set(uptr *map, int start_addr, int end_addr,
 }
 
 // more specialized/optimized function (does same as above)
-void cpu68k_map_all_ram(int start_addr, int end_addr, void *ptr, int is_sub)
+void cpu68k_map_all_ram(u32 start_addr, u32 end_addr, void *ptr, int is_sub)
 {
   uptr *r8map, *r16map, *w8map, *w16map;
   uptr addr = (uptr)ptr;
@@ -116,13 +120,13 @@ void cpu68k_map_all_ram(int start_addr, int end_addr, void *ptr, int is_sub)
 static u32 m68k_unmapped_read8(u32 a)
 {
   elprintf(EL_UIO, "m68k unmapped r8  [%06x] @%06x", a, SekPc);
-  return 0; // assume pulldown, as if MegaCD2 was attached
+  return (PicoIn.AHW & PAHW_MCD) ? 0x00 : 0xff; // pulldown if MegaCD2 attached
 }
 
 static u32 m68k_unmapped_read16(u32 a)
 {
   elprintf(EL_UIO, "m68k unmapped r16 [%06x] @%06x", a, SekPc);
-  return 0;
+  return (PicoIn.AHW & PAHW_MCD) ? 0x00 : 0xffff;
 }
 
 static void m68k_unmapped_write8(u32 a, u32 d)
@@ -135,7 +139,7 @@ static void m68k_unmapped_write16(u32 a, u32 d)
   elprintf(EL_UIO, "m68k unmapped w16 [%06x] %04x @%06x", a, d & 0xffff, SekPc);
 }
 
-void m68k_map_unmap(int start_addr, int end_addr)
+void m68k_map_unmap(u32 start_addr, u32 end_addr)
 {
 #ifdef __clang__
   // workaround bug (segfault) in 
@@ -236,9 +240,9 @@ static u32 read_pad_6btn(int i, u32 out_bits)
   }
   else if(phase == 3) {
     if (out_bits & 0x40)
-      return (pad & 0x30) | ((pad >> 8) & 0xf);  // ?1CB MXYZ
+      value = (pad & 0x30) | ((pad >> 8) & 0xf); // ?1CB MXYZ
     else
-      return ((pad & 0xc0) >> 2) | 0x0f;         // ?0SA 1111
+      value = ((pad & 0xc0) >> 2) | 0x0f;        // ?0SA 1111
     goto out;
   }
 
@@ -252,6 +256,65 @@ out:
   return value;
 }
 
+static u32 read_pad_team(int i, u32 out_bits)
+{
+  u32 pad;
+  int phase = Pico.m.padTHPhase[i];
+  u32 value;
+
+  if (phase == 0) {
+    value = 0x03;
+    goto out;
+  }
+  if (phase == 1) {
+    value = 0x0f;
+    goto out;
+  }
+
+  pad = ~PicoIn.padInt[0]; // Get inverse of pad MXYZ SACB RLDU
+  if (phase == 8) {
+    value = pad & 0x0f;                          // ?x?x RLDU
+    goto out;
+  }
+  else if(phase == 9) {
+    value = (pad & 0xf0) >>  4;                  // ?x?x SACB
+    goto out;
+  }
+
+  pad = ~PicoIn.padInt[1]; // Get inverse of pad MXYZ SACB RLDU
+  if (phase == 12) {
+    value = pad & 0x0f;                          // ?x?x RLDU
+    goto out;
+  }
+  else if(phase == 13) {
+    value = (pad & 0xf0) >>  4;                  // ?x?x SACB
+    goto out;
+  }
+
+  if (phase >= 8 && pad < 16) {
+    value = 0x0f;
+    goto out;
+  }
+
+  value = 0;
+
+out:
+  value |= (out_bits & 0x40) | ((out_bits & 0x20)>>1);
+  return value;
+}
+
+static u32 read_pad_4way(int i, u32 out_bits)
+{
+  u32 pad = (PicoMem.ioports[2] & 0x70) >> 4;
+  u32 value = 0;
+
+  if (i == 0 && !(pad & 1))
+    value = read_pad_3btn(pad >> 1, out_bits);
+
+  value |= (out_bits & 0x40);
+  return value;
+}
+
 static u32 read_nothing(int i, u32 out_bits)
 {
   return 0xff;
@@ -277,7 +340,7 @@ static NOINLINE u32 port_read(int i)
   // disables output before doing TH-low read, so don't emulate it for TH.
   // Decap Attack reportedly doesn't work on Nomad but works on must
   // other MD revisions (different pull-up strength?).
-  if (PicoIn.AHW & PAHW_32X) // don't do it on 32X, it breaks WWF Raw
+  if (PicoIn.AHW & (PAHW_32X|PAHW_MCD)) // don't do it on 32X, it breaks WWF Raw
     out |= 0x7f & ~ctrl_reg;
   else
     out |= 0x3f & ~ctrl_reg;
@@ -303,6 +366,14 @@ void PicoSetInputDevice(int port, enum input_device device)
     func = read_pad_6btn;
     break;
 
+  case PICO_INPUT_PAD_TEAM:
+    func = read_pad_team;
+    break;
+
+  case PICO_INPUT_PAD_4WAY:
+    func = read_pad_4way;
+    break;
+
   default:
     func = read_nothing;
     break;
@@ -333,7 +404,17 @@ NOINLINE void io_ports_write(u32 a, u32 d)
   if (1 <= a && a <= 2)
   {
     Pico.m.padDelay[a - 1] = 0;
-    if (!(PicoMem.ioports[a] & 0x40) && (d & 0x40))
+    if (port_readers[a - 1] == read_pad_team) {
+      if (d & 0x40)
+        Pico.m.padTHPhase[a - 1] = 0;
+      else if ((d^PicoMem.ioports[a]) & 0x60)
+        Pico.m.padTHPhase[a - 1]++;
+    } else if (port_readers[a - 1] == read_pad_4way) {
+      if (a == 2 && ((PicoMem.ioports[a] ^ d) & 0x70))
+        Pico.m.padTHPhase[0] = 0;
+      if (a == 1 && !(PicoMem.ioports[a] & 0x40) && (d & 0x40))
+        Pico.m.padTHPhase[0]++;
+    } else if (!(PicoMem.ioports[a] & 0x40) && (d & 0x40))
       Pico.m.padTHPhase[a - 1]++;
   }
 
@@ -394,24 +475,15 @@ void NOINLINE ctl_write_z80reset(u32 d)
   }
 }
 
-static int get_scanline(int is_from_z80);
-
 static void psg_write_68k(u32 d)
 {
-  // look for volume write and update if needed
-  if ((d & 0x90) == 0x90)
-    PsndDoPSG(Pico.m.scanline);
-
+  PsndDoPSG(z80_cycles_from_68k());
   SN76496Write(d);
 }
 
 static void psg_write_z80(u32 d)
 {
-  if ((d & 0x90) == 0x90) {
-    int scanline = get_scanline(1);
-    PsndDoPSG(scanline);
-  }
-
+  PsndDoPSG(z80_cyclesDone());
   SN76496Write(d);
 }
 
@@ -772,12 +844,13 @@ PICO_INTERNAL void PicoMemSetup(void)
   // align to bank size. We know ROM loader allocated enough for this
   mask = (1 << M68K_MEM_SHIFT) - 1;
   rs = (Pico.romsize + mask) & ~mask;
+  if (rs > 0x400000) rs = 0x400000; // max cartridge area
   cpu68k_map_set(m68k_read8_map,  0x000000, rs - 1, Pico.rom, 0);
   cpu68k_map_set(m68k_read16_map, 0x000000, rs - 1, Pico.rom, 0);
 
   // Common case of on-cart (save) RAM, usually at 0x200000-...
   if ((Pico.sv.flags & SRF_ENABLED) && Pico.sv.data != NULL) {
-    sstart = Pico.sv.start;
+    sstart = Pico.sv.start & ~mask;
     rs = Pico.sv.end - sstart;
     rs = (rs + mask) & ~mask;
     if (sstart + rs >= 0x1000000)
@@ -1298,8 +1371,6 @@ static void z80_mem_setup(void)
   drZ80.z80_out = z80_md_out;
 #endif
 #ifdef _USE_CZ80
-  Cz80_Set_Fetch(&CZ80, 0x0000, 0x1fff, (FPTR)PicoMem.zram); // main RAM
-  Cz80_Set_Fetch(&CZ80, 0x2000, 0x3fff, (FPTR)PicoMem.zram); // mirror
   Cz80_Set_INPort(&CZ80, z80_md_in);
   Cz80_Set_OUTPort(&CZ80, z80_md_out);
 #endif

+ 5 - 5
pico/memory.h

@@ -38,18 +38,18 @@ extern void s68k_write16(u32 a, u16 d);
 extern void s68k_write32(u32 a, u32 d);
 
 // z80
-#define Z80_MEM_SHIFT 13
+#define Z80_MEM_SHIFT 10 // must be <=10 to allow 1KB pages for SMS Sega mapper
 extern uptr z80_read_map [0x10000 >> Z80_MEM_SHIFT];
 extern uptr z80_write_map[0x10000 >> Z80_MEM_SHIFT];
 typedef unsigned char (z80_read_f)(unsigned short a);
 typedef void (z80_write_f)(unsigned int a, unsigned char data);
 
-void z80_map_set(uptr *map, int start_addr, int end_addr,
+void z80_map_set(uptr *map, u16 start_addr, u16 end_addr,
     const void *func_or_mh, int is_func);
-void cpu68k_map_set(uptr *map, int start_addr, int end_addr,
+void cpu68k_map_set(uptr *map, u32 start_addr, u32 end_addr,
     const void *func_or_mh, int is_func);
-void cpu68k_map_all_ram(int start_addr, int end_addr, void *ptr, int is_sub);
-void m68k_map_unmap(int start_addr, int end_addr);
+void cpu68k_map_all_ram(u32 start_addr, u32 end_addr, void *ptr, int is_sub);
+void m68k_map_unmap(u32 start_addr, u32 end_addr);
 
 #define MAP_FLAG ((uptr)1 << (sizeof(uptr) * 8 - 1))
 #define map_flag_set(x) ((x) & MAP_FLAG)

+ 602 - 201
pico/mode4.c

@@ -1,247 +1,297 @@
 /*
- * mode4/SMS renderer
+ * SMS renderer
  * (C) notaz, 2009-2010
+ * (C) kub, 2021
+ *
+ * currently supports VDP mode 4 (SMS and GG) and mode 2+0 (TMS)
  *
  * This work is licensed under the terms of MAME license.
  * See COPYING file in the top-level directory.
  */
 /*
  * TODO:
- * - TMS9918 modes?
- * - gg mode?
- * - column scroll (reg 0 bit7)
- * - 224/240 line modes
- * - doubled sprites
+ * - other TMS9918 modes?
  */
 #include "pico_int.h"
+#include <platform/common/upscale.h>
 
-static void (*FinalizeLineM4)(int line);
+static void (*FinalizeLineSMS)(int line);
 static int skip_next_line;
 static int screen_offset, line_offset;
+static u8 mode;
+
+static unsigned int sprites_addr[32]; // bitmap address
+static unsigned char sprites_c[32]; // TMS sprites color
+static int sprites_x[32]; // x position
+static int sprites; // count
+static unsigned char sprites_map[2+256/8+2]; // collision detection map
+
+unsigned int sprites_status;
 
-static void TileBGM4(int sx, int pal)
+/* sprite collision detection */
+static int CollisionDetect(u8 *mb, u16 sx, unsigned int pack, int zoomed)
 {
-  u32 *pd = (u32 *)(Pico.est.HighCol + sx);
-  pd[0] = pd[1] = pal ? 0x10101010 : 0;
+  static u8 morton[16] = { 0x00,0x03,0x0c,0x0f,0x30,0x33,0x3c,0x3f,
+                           0xc0,0xc3,0xcc,0xcf,0xf0,0xf3,0xfc,0xff };
+  u8 *mp = mb + (sx>>3);
+  unsigned col, m;
+
+  // check sprite map for collision and update map with current sprite
+  if (!zoomed) { // 8 sprite pixels
+    m = mp[0] | (mp[1]<<8);
+    col = m & (pack<<(sx&7)); // collision if current sprite overlaps sprite map
+    m |= pack<<(sx&7);
+    mp[0] = m, mp[1] = m>>8;
+  } else { // 16 sprite pixels in zoom mode
+    pack = morton[pack&0x0f] | (morton[(pack>>4)&0x0f] << 8);
+    m = mp[0] | (mp[1]<<8) | (mp[2]<<16);
+    col = m & (pack<<(sx&7));
+    m |= pack<<(sx&7);
+    mp[0] = m, mp[1] = m>>8, mp[2] = m>>16;
+  }
+
+  // invisible overscan area, not tested for collision
+  mb[0] = mb[33] = mb[34] = 0;
+  return col;
 }
 
-#define PLANAR_PIXELL(x,p) \
-  t = pack & (0x80808080 >> p); \
-  t = ((t >> (7-p)) | (t >> (14-p)) | (t >> (21-p)) | (t >> (28-p))) & 0x0f; \
-  pd[x] = pal|t;
+/* Mode 4 */
+/*========*/
 
-static void TileNormM4Low(int sx, unsigned int pack, int pal)
+static void TileBGM4(u16 sx, int pal)
 {
-  unsigned char *pd = Pico.est.HighCol + sx;
-  unsigned int t;
-
-  PLANAR_PIXELL(0, 0)
-  PLANAR_PIXELL(1, 1)
-  PLANAR_PIXELL(2, 2)
-  PLANAR_PIXELL(3, 3)
-  PLANAR_PIXELL(4, 4)
-  PLANAR_PIXELL(5, 5)
-  PLANAR_PIXELL(6, 6)
-  PLANAR_PIXELL(7, 7)
+  if (sx & 3) {
+    u8 *pd = (u8 *)(Pico.est.HighCol + sx);
+    pd[0] = pd[1] = pd[2] = pd[3] = pal;
+    pd[4] = pd[5] = pd[6] = pd[7] = pal;
+  } else {
+    u32 *pd = (u32 *)(Pico.est.HighCol + sx);
+    pd[0] = pd[1] = pal * 0x01010101;
+  }
 }
 
-static void TileFlipM4Low(int sx, unsigned int pack, int pal)
+// 8 pixels are arranged in 4 bitplane bytes in a 32 bit word. To pull the
+// 4 bitplanes together multiply with each bit distance (multiples of 1<<7)
+#define PLANAR_PIXELBG(x,p) \
+  t = (pack>>(7-p)) & 0x01010101; \
+  t = (t*0x10204080) >> 28; \
+  pd[x] = pal|t;
+
+static void TileNormBGM4(u16 sx, unsigned int pack, int pal)
 {
-  unsigned char *pd = Pico.est.HighCol + sx;
-  unsigned int t;
+  u8 *pd = Pico.est.HighCol + sx;
+  u32 t;
+
+  PLANAR_PIXELBG(0, 0)
+  PLANAR_PIXELBG(1, 1)
+  PLANAR_PIXELBG(2, 2)
+  PLANAR_PIXELBG(3, 3)
+  PLANAR_PIXELBG(4, 4)
+  PLANAR_PIXELBG(5, 5)
+  PLANAR_PIXELBG(6, 6)
+  PLANAR_PIXELBG(7, 7)
+}
 
-  PLANAR_PIXELL(0, 7)
-  PLANAR_PIXELL(1, 6)
-  PLANAR_PIXELL(2, 5)
-  PLANAR_PIXELL(3, 4)
-  PLANAR_PIXELL(4, 3)
-  PLANAR_PIXELL(5, 2)
-  PLANAR_PIXELL(6, 1)
-  PLANAR_PIXELL(7, 0)
+static void TileFlipBGM4(u16 sx, unsigned int pack, int pal)
+{
+  u8 *pd = Pico.est.HighCol + sx;
+  u32 t;
+
+  PLANAR_PIXELBG(0, 7)
+  PLANAR_PIXELBG(1, 6)
+  PLANAR_PIXELBG(2, 5)
+  PLANAR_PIXELBG(3, 4)
+  PLANAR_PIXELBG(4, 3)
+  PLANAR_PIXELBG(5, 2)
+  PLANAR_PIXELBG(6, 1)
+  PLANAR_PIXELBG(7, 0)
 }
 
-#define PLANAR_PIXEL(x,p) \
-  t = pack & (0x80808080 >> p); \
-  if (t) { \
-    t = ((t >> (7-p)) | (t >> (14-p)) | (t >> (21-p)) | (t >> (28-p))) & 0x0f; \
+// non-transparent sprite pixels apply if no higher prio pixel is already there
+#define PLANAR_PIXELSP(x,p) \
+  t = (pack>>(7-p)) & 0x01010101; \
+  if (t && (pd[x] & 0x2f) <= 0x20) { \
+    t = (t*0x10204080) >> 28; \
     pd[x] = pal|t; \
   }
 
-static void TileNormM4(int sx, unsigned int pack, int pal)
+static void TileNormSprM4(u16 sx, unsigned int pack, int pal)
 {
-  unsigned char *pd = Pico.est.HighCol + sx;
-  unsigned int t;
-
-  PLANAR_PIXEL(0, 0)
-  PLANAR_PIXEL(1, 1)
-  PLANAR_PIXEL(2, 2)
-  PLANAR_PIXEL(3, 3)
-  PLANAR_PIXEL(4, 4)
-  PLANAR_PIXEL(5, 5)
-  PLANAR_PIXEL(6, 6)
-  PLANAR_PIXEL(7, 7)
+  u8 *pd = Pico.est.HighCol + sx;
+  u32 t;
+
+  PLANAR_PIXELSP(0, 0)
+  PLANAR_PIXELSP(1, 1)
+  PLANAR_PIXELSP(2, 2)
+  PLANAR_PIXELSP(3, 3)
+  PLANAR_PIXELSP(4, 4)
+  PLANAR_PIXELSP(5, 5)
+  PLANAR_PIXELSP(6, 6)
+  PLANAR_PIXELSP(7, 7)
 }
 
-static void TileFlipM4(int sx, unsigned int pack, int pal)
+static void TileDoubleSprM4(int sx, unsigned int pack, int pal)
 {
-  unsigned char *pd = Pico.est.HighCol + sx;
-  unsigned int t;
-
-  PLANAR_PIXEL(0, 7)
-  PLANAR_PIXEL(1, 6)
-  PLANAR_PIXEL(2, 5)
-  PLANAR_PIXEL(3, 4)
-  PLANAR_PIXEL(4, 3)
-  PLANAR_PIXEL(5, 2)
-  PLANAR_PIXEL(6, 1)
-  PLANAR_PIXEL(7, 0)
+  u8 *pd = Pico.est.HighCol + sx;
+  u32 t;
+
+  PLANAR_PIXELSP(0, 0)
+  PLANAR_PIXELSP(1, 0)
+  PLANAR_PIXELSP(2, 1)
+  PLANAR_PIXELSP(3, 1)
+  PLANAR_PIXELSP(4, 2)
+  PLANAR_PIXELSP(5, 2)
+  PLANAR_PIXELSP(6, 3)
+  PLANAR_PIXELSP(7, 3)
+  PLANAR_PIXELSP(8, 4)
+  PLANAR_PIXELSP(9, 4)
+  PLANAR_PIXELSP(10, 5)
+  PLANAR_PIXELSP(11, 5)
+  PLANAR_PIXELSP(12, 6)
+  PLANAR_PIXELSP(13, 6)
+  PLANAR_PIXELSP(14, 7)
+  PLANAR_PIXELSP(15, 7)
 }
 
-static void draw_sprites(int scanline)
+static void ParseSpritesM4(int scanline)
 {
   struct PicoVideo *pv = &Pico.video;
-  unsigned int sprites_addr[8];
-  unsigned int sprites_x[8];
-  unsigned int pack;
-  unsigned char *sat;
+  u8 *sat;
   int xoff = 8; // relative to HighCol, which is (screen - 8)
   int sprite_base, addr_mask;
-  int i, s, h;
+  int zoomed = pv->reg[1] & 0x1; // zoomed sprites, e.g. Earthworm Jim
+  unsigned int pack;
+  int i, s, h, m;
 
   if (pv->reg[0] & 8)
     xoff = 0;
   xoff += line_offset;
+  if ((Pico.m.hardware & 0x3) == 0x3)
+    xoff -= 48; // GG LCD, adjust to center 160 px
 
-  sat = (unsigned char *)PicoMem.vram + ((pv->reg[5] & 0x7e) << 7);
+  sat = (u8 *)PicoMem.vram + ((pv->reg[5] & 0x7e) << 7);
   if (pv->reg[1] & 2) {
     addr_mask = 0xfe; h = 16;
   } else {
     addr_mask = 0xff; h = 8;
   }
+  if (zoomed) h *= 2;
   sprite_base = (pv->reg[6] & 4) << (13-2-1);
 
+  m = 0;
+  memset(sprites_map, 0, sizeof(sprites_map));
   for (i = s = 0; i < 64; i++)
   {
     int y;
-    y = sat[MEM_LE2(i)] + 1;
-    if (y == 0xd1)
+    y = sat[MEM_LE2(i)];
+    if (y == 0xd0 && !((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)))
       break;
+    if (y >= 0xe0)
+      y -= 256;
+    y &= ~zoomed; // zoomed sprites apparently only on even lines, see GG Tarzan
     if (y + h <= scanline || scanline < y)
       continue; // not on this line
     if (s >= 8) {
-      pv->status |= SR_SOVR;
-      break;
+      if (scanline >= 0) sprites_status |= SR_SOVR;
+      if (!(PicoIn.opt & POPT_DIS_SPRITE_LIM) || s >= 32)
+        break;
     }
 
-    sprites_x[s] = xoff + sat[MEM_LE2(0x80 + i*2)];
-    sprites_addr[s] = sprite_base + ((sat[MEM_LE2(0x80 + i*2 + 1)] & addr_mask) << (5-1)) +
-      ((scanline - y) << (2-1));
-    s++;
+    if (xoff + sat[MEM_LE2(0x80 + i*2)] >= 0) {
+      sprites_x[s] = xoff + sat[MEM_LE2(0x80 + i*2)];
+      sprites_addr[s] = sprite_base + ((sat[MEM_LE2(0x80 + i*2 + 1)] & addr_mask) << (5-1)) +
+        ((scanline - y) >> zoomed << (2-1));
+      if (Pico.video.reg[1] & 0x40) {
+        // collision detection. Do it here since off-screen lines aren't drawn
+        pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s]));
+        // make sprite pixel map by merging the 4 bitplanes
+        pack = ((pack | (pack>>16)) | ((pack | (pack>>16))>>8)) & 0xff;
+        if (!m) m = CollisionDetect(sprites_map, sprites_x[s], pack, zoomed);
+      }
+      s++;
+    }
   }
+  if (m)
+    sprites_status |= SR_C;
+  sprites = s;
+}
 
-  // really half-assed but better than nothing
-  if (s > 1)
-    pv->status |= SR_C;
+static void DrawSpritesM4(void)
+{
+  struct PicoVideo *pv = &Pico.video;
+  unsigned int pack;
+  int zoomed = pv->reg[1] & 0x1; // zoomed sprites, e.g. Earthworm Jim
+  int s = sprites;
 
   // now draw all sprites backwards
   for (--s; s >= 0; s--) {
     pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s]));
-    TileNormM4(sprites_x[s], pack, 0x10);
+    if (zoomed) TileDoubleSprM4(sprites_x[s], pack, 0x10);
+    else        TileNormSprM4(sprites_x[s], pack, 0x10);
   }
 }
 
-
-// tilex_ty_prio merged to reduce register pressure
-static void draw_strip_low(const unsigned short *nametab, int dx, int cells, int tilex_ty_prio)
+// cells_dx, tilex_ty merged to reduce register pressure
+static void DrawStripM4(const u16 *nametab, int cells_dx, int tilex_ty)
 {
   int oldcode = -1;
   int addr = 0, pal = 0;
 
   // Draw tiles across screen:
-  for (; cells > 0; dx += 8, tilex_ty_prio++, cells--)
-  {
-    unsigned int pack;
-    unsigned code;
-
-    code = nametab[tilex_ty_prio & 0x1f];
-
-    if (code != oldcode) {
-      oldcode = code;
-      // Get tile address/2:
-      addr = (code & 0x1ff) << 4;
-      addr += tilex_ty_prio >> 16;
-      if (code & 0x0400)
-        addr ^= 0xe; // Y-flip
-
-      pal = (code>>7) & 0x10;
-    }
-
-    pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); /* Get 4 bitplanes / 8 pixels */
-    if (pack == 0)          TileBGM4(dx, pal);
-    else if (code & 0x0200) TileFlipM4Low(dx, pack, pal);
-    else                    TileNormM4Low(dx, pack, pal);
-  }
-}
-// tilex_ty_prio merged to reduce register pressure
-static void draw_strip_high(const unsigned short *nametab, int dx, int cells, int tilex_ty_prio)
-{
-  int oldcode = -1, blank = -1; // The tile we know is blank
-  int addr = 0, pal = 0;
-
-  // Draw tiles across screen:
-  for (; cells > 0; dx += 8, tilex_ty_prio++, cells--)
+  for (; cells_dx > 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000)
   {
     unsigned int pack;
     unsigned code;
 
-    code = nametab[tilex_ty_prio & 0x1f];
-    if (code == blank)
-      continue;
-    if ((code ^ tilex_ty_prio) & 0x1000) // priority differs?
-      continue;
+    code = nametab[tilex_ty & 0x1f];
 
     if (code != oldcode) {
       oldcode = code;
       // Get tile address/2:
       addr = (code & 0x1ff) << 4;
-      addr += tilex_ty_prio >> 16;
+      addr += tilex_ty >> 16;
       if (code & 0x0400)
         addr ^= 0xe; // Y-flip
 
-      pal = (code>>7) & 0x10;
+      pal = (code>>7) & 0x30;  // prio | palette select
     }
 
-    pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); /* Get 4 bitplanes / 8 pixels */
-    if (pack == 0) {
-      blank = code;
-      continue;
-    }
-    if (code & 0x0200) TileFlipM4(dx, pack, pal);
-    else               TileNormM4(dx, pack, pal);
+    pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); // Get 4 bitplanes / 8 pixels
+    if (pack == 0)          TileBGM4(cells_dx, pal);
+    else if (code & 0x0200) TileFlipBGM4(cells_dx, pack, pal);
+    else                    TileNormBGM4(cells_dx, pack, pal);
   }
 }
 
 static void DrawDisplayM4(int scanline)
 {
   struct PicoVideo *pv = &Pico.video;
-  unsigned short *nametab;
+  u16 *nametab, *nametab2;
   int line, tilex, dx, ty, cells;
   int cellskip = 0; // XXX
   int maxcells = 32;
 
   // Find the line in the name table
   line = pv->reg[9] + scanline; // vscroll + scanline
-  if (line >= 224)
-    line -= 224;
 
   // Find name table:
   nametab = PicoMem.vram;
-  nametab += (pv->reg[2] & 0x0e) << (10-1);
-  nametab += (line>>3) << (6-1);
+  if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)) {
+    // 224/240 line mode
+    line &= 0xff;
+    nametab += ((pv->reg[2] & 0x0c) << (10-1)) + (0x700 >> 1);
+  } else {
+    while (line >= 224) line -= 224;
+    nametab += (pv->reg[2] & 0x0e) << (10-1);
+    // old SMS only, masks line:7 with reg[2]:0 for address calculation
+    //if ((pv->reg[2] & 0x01) == 0) line &= 0x7f;
+  }
+  nametab2 = nametab + ((scanline>>3) << (6-1));
+  nametab  = nametab + ((line>>3)     << (6-1));
 
   dx = pv->reg[8]; // hscroll
   if (scanline < 16 && (pv->reg[0] & 0x40))
-    dx = 0; // hscroll disabled for top 2 rows
+    dx = 0; // hscroll disabled for top 2 rows (e.g. Fantasy Zone II)
 
   tilex = ((-dx >> 3) + cellskip) & 0x1f;
   ty = (line & 7) << 1; // Y-Offset into tile
@@ -253,134 +303,485 @@ static void DrawDisplayM4(int scanline)
   dx += cellskip << 3;
   dx += line_offset;
 
-  // low priority tiles
+  // tiles
+  if (!(pv->debug_p & PVD_KILL_B)) {
+    if ((Pico.m.hardware & 0x3) == 0x3) {
+      // on GG render only the center 160 px
+      DrawStripM4(nametab , dx | ((cells-12)<< 16),(tilex+6) | (ty  << 16));
+    } else if (pv->reg[0] & 0x80) {
+      // vscroll disabled for rightmost 8 columns (e.g. Gauntlet)
+      int dx2 = dx + (cells-8)*8, tilex2 = tilex + (cells-8), ty2 = scanline&7;
+      DrawStripM4(nametab,  dx | ((cells-8) << 16), tilex  | (ty  << 16));
+      DrawStripM4(nametab2, dx2 |       (8  << 16), tilex2 | (ty2 << 17));
+    } else
+      DrawStripM4(nametab , dx | ( cells    << 16), tilex  | (ty  << 16));
+  }
+
+  // sprites
+  if (!(pv->debug_p & PVD_KILL_S_LO))
+    DrawSpritesM4();
+
+  if ((pv->reg[0] & 0x20) && (Pico.m.hardware & 0x3) != 0x3) {
+    // first column masked with background, caculate offset to start of line
+    dx = (dx&~0x1f) / 4;
+    ty = ((pv->reg[7]&0x0f)|0x10) * 0x01010101;
+    ((u32 *)Pico.est.HighCol)[dx+2] = ((u32 *)Pico.est.HighCol)[dx+3] = ty;
+  }
+}
+
+
+/* TMS Modes */
+/*===========*/
+
+/* Background, Graphics modes */
+
+#define TMS_PIXELBG(x,p) \
+  t = (pack>>(7-p)) & 0x01; \
+  t = (pal >> (t << 2)) & 0x0f; \
+  pd[x] = t;
+
+static void TileNormBgGr(u16 sx, unsigned int pack, int pal)
+{
+  u8 *pd = Pico.est.HighCol + sx;
+  unsigned int t;
+
+  TMS_PIXELBG(0, 0)
+  TMS_PIXELBG(1, 1)
+  TMS_PIXELBG(2, 2)
+  TMS_PIXELBG(3, 3)
+  TMS_PIXELBG(4, 4)
+  TMS_PIXELBG(5, 5)
+  TMS_PIXELBG(6, 6)
+  TMS_PIXELBG(7, 7)
+}
+
+/* Sprites */
+
+#define TMS_PIXELSP(x,p) \
+  t = (pack>>(7-p)) & 0x01; \
+  if (t) \
+    pd[x] = pal;
+
+static void TileNormSprTMS(u16 sx, unsigned int pack, int pal)
+{
+  u8 *pd = Pico.est.HighCol + sx;
+  unsigned int t;
+
+  TMS_PIXELSP(0, 0)
+  TMS_PIXELSP(1, 1)
+  TMS_PIXELSP(2, 2)
+  TMS_PIXELSP(3, 3)
+  TMS_PIXELSP(4, 4)
+  TMS_PIXELSP(5, 5)
+  TMS_PIXELSP(6, 6)
+  TMS_PIXELSP(7, 7)
+}
+
+static void TileDoubleSprTMS(u16 sx, unsigned int pack, int pal)
+{
+  u8 *pd = Pico.est.HighCol + sx;
+  unsigned int t;
+
+  TMS_PIXELSP(0, 0)
+  TMS_PIXELSP(1, 0)
+  TMS_PIXELSP(2, 1)
+  TMS_PIXELSP(3, 1)
+  TMS_PIXELSP(4, 2)
+  TMS_PIXELSP(5, 2)
+  TMS_PIXELSP(6, 3)
+  TMS_PIXELSP(7, 3)
+  TMS_PIXELSP(8, 4)
+  TMS_PIXELSP(9, 4)
+  TMS_PIXELSP(10, 5)
+  TMS_PIXELSP(11, 5)
+  TMS_PIXELSP(12, 6)
+  TMS_PIXELSP(13, 6)
+  TMS_PIXELSP(14, 7)
+  TMS_PIXELSP(15, 7)
+}
+
+static void ParseSpritesTMS(int scanline)
+{
+  struct PicoVideo *pv = &Pico.video;
+  unsigned int pack;
+  u8 *sat;
+  int xoff = 8; // relative to HighCol, which is (screen - 8)
+  int sprite_base, addr_mask;
+  int zoomed = pv->reg[1] & 0x1; // zoomed sprites
+  int i, s, h, m;
+
+  xoff += line_offset;
+
+  sat = (u8 *)PicoMem.vramb + ((pv->reg[5] & 0x7e) << 7);
+  if (pv->reg[1] & 2) {
+    addr_mask = 0xfc; h = 16;
+  } else {
+    addr_mask = 0xff; h = 8;
+  }
+  if (zoomed) h *= 2;
+  sprite_base = (pv->reg[6] & 0x7) << 11;
+
+  m = 0;
+  memset(sprites_map, 0, sizeof(sprites_map));
+  /* find sprites on this scanline */
+  for (i = s = 0; i < 32; i++)
+  {
+    int x, y;
+    y = sat[MEM_LE2(4*i)];
+    if (y == 0xd0)
+      break;
+    if (y >= 0xe0)
+      y -= 256;
+    y &= ~zoomed;
+    if (y + h <= scanline || scanline < y)
+      continue; // not on this line
+    if (s >= 4) {
+      if (scanline >= 0) sprites_status |= SR_SOVR | i;
+      if (!(PicoIn.opt & POPT_DIS_SPRITE_LIM) || s >= 32)
+        break;
+    }
+    x = sat[MEM_LE2(4*i+1)] + xoff;
+    if (sat[MEM_LE2(4*i+3)] & 0x80)
+      x -= 32;
+
+    sprites_c[s] = sat[MEM_LE2(4*i+3)] & 0x0f;
+    sprites_x[s] = x;
+    sprites_addr[s] = sprite_base + ((sat[MEM_LE2(4*i + 2)] & addr_mask) << 3) +
+      ((scanline - y) >> zoomed);
+    if (Pico.video.reg[1] & 0x40) {
+      // collision detection. Do it here since off-screen lines aren't drawn
+      if (sprites_c[s] && x > 0) {
+        pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])];
+        if (!m) m = CollisionDetect(sprites_map, x, pack, zoomed);
+      }
+      x += (zoomed ? 16:8);
+      if (sprites_c[s] && (pv->reg[1] & 0x2) && x > 0 && x < 8+256) {
+        pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)];
+        if (!m) m = CollisionDetect(sprites_map, x, pack, zoomed);
+      }
+    }
+    s++;
+  }
+  if (m)
+    sprites_status |= SR_C;
+  sprites = s;
+}
+
+/* Draw sprites into a scanline, max 4 */
+static void DrawSpritesTMS(void)
+{
+  struct PicoVideo *pv = &Pico.video;
+  unsigned int pack;
+  int zoomed = pv->reg[1] & 0x1; // zoomed sprites
+  int s = sprites;
+
+  // now draw all sprites backwards
+  for (--s; s >= 0; s--) {
+    int x, c, w = (zoomed ? 16: 8);
+    x = sprites_x[s];
+    c = sprites_c[s];
+    // c may be 0 (transparent): sprite invisible
+    if (c && x > 0) {
+      pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])];
+      if (zoomed) TileDoubleSprTMS(x, pack, c);
+      else        TileNormSprTMS(x, pack, c);
+    }
+    if (c && (pv->reg[1] & 0x2) && (x+=w) > 0 && x < 8+256) {
+      pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)];
+      if (zoomed) TileDoubleSprTMS(x, pack, c);
+      else        TileNormSprTMS(x, pack, c);
+    }
+  }
+}
+
+/* Mode 2 */
+/*========*/
+
+/* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */
+static void DrawStripM2(const u8 *nametab, const u8 *coltab, const u8 *pattab, int cells_dx, int tilex_ty)
+{
+  // Draw tiles across screen:
+  for (; cells_dx > 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000)
+  {
+    unsigned int pack, pal;
+    unsigned code;
+
+    code = nametab[tilex_ty & 0x1f] << 3;
+    pal  = coltab[code];
+    pack = pattab[code];
+    TileNormBgGr(cells_dx, pack, pal);
+  }
+}
+
+/* Draw a scanline */
+static void DrawDisplayM2(int scanline)
+{
+  struct PicoVideo *pv = &Pico.video;
+  u8 *nametab, *coltab, *pattab;
+  int tilex, dx, cells;
+  int cellskip = 0; // XXX
+  int maxcells = 32;
+
+  // name, color, pattern table:
+  nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00);
+  coltab  = PicoMem.vramb + ((pv->reg[3]<< 6) & 0x2000);
+  pattab  = PicoMem.vramb + ((pv->reg[4]<<11) & 0x2000);
+
+  nametab += ((scanline>>3) << 5);
+  coltab  += ((scanline>>6) <<11) + (scanline & 0x7);
+  pattab  += ((scanline>>6) <<11) + (scanline & 0x7);
+
+  tilex = cellskip & 0x1f;
+  cells = maxcells - cellskip;
+  dx = (cellskip << 3) + line_offset + 8;
+
+  // tiles
   if (!(pv->debug_p & PVD_KILL_B))
-    draw_strip_low(nametab, dx, cells, tilex | 0x0000 | (ty << 16));
+    DrawStripM2(nametab, coltab, pattab, dx | (cells << 16), tilex | (scanline << 16));
 
   // sprites
   if (!(pv->debug_p & PVD_KILL_S_LO))
-    draw_sprites(scanline);
+    DrawSpritesTMS();
+}
 
-  // high priority tiles (use virtual layer switch just for fun)
-  if (!(pv->debug_p & PVD_KILL_A))
-    draw_strip_high(nametab, dx, cells, tilex | 0x1000 | (ty << 16));
+/* Mode 0 */
+/*========*/
 
-  if (pv->reg[0] & 0x20) {
-    // first column masked, caculate offset to start of line
-    dx = (dx&~0x1f) / 4;
-    ((u32 *)Pico.est.HighCol)[dx+2] = ((u32 *)Pico.est.HighCol)[dx+3] = 0xe0e0e0e0;
+/* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */
+static void DrawStripM0(const u8 *nametab, const u8 *coltab, const u8 *pattab, int cells_dx, int tilex_ty)
+{
+  // Draw tiles across screen:
+  for (; cells_dx > 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000)
+  {
+    unsigned int pack, pal;
+    unsigned code;
+
+    code = nametab[tilex_ty & 0x1f];
+    pal  = coltab[code >> 3];
+    pack = pattab[code << 3];
+    TileNormBgGr(cells_dx, pack, pal);
   }
 }
 
-void PicoFrameStartMode4(void)
+/* Draw a scanline */
+static void DrawDisplayM0(int scanline)
 {
-  int lines = 192;
+  struct PicoVideo *pv = &Pico.video;
+  u8 *nametab, *coltab, *pattab;
+  int tilex, dx, cells;
+  int cellskip = 0; // XXX
+  int maxcells = 32;
+
+  // name, color, pattern table:
+  nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00);
+  coltab  = PicoMem.vramb + ((pv->reg[3]<< 6) & 0x3fc0);
+  pattab  = PicoMem.vramb + ((pv->reg[4]<<11) & 0x3800);
+
+  nametab += (scanline>>3) << 5;
+  pattab  += (scanline & 0x7);
+
+  tilex = cellskip & 0x1f;
+  cells = maxcells - cellskip;
+  dx = (cellskip << 3) + line_offset + 8;
+
+  // tiles
+  if (!(pv->debug_p & PVD_KILL_B))
+    DrawStripM0(nametab, coltab, pattab, dx | (cells << 16), tilex | (scanline << 16));
+
+  // sprites
+  if (!(pv->debug_p & PVD_KILL_S_LO))
+    DrawSpritesTMS();
+}
+
+
+/* Common/global */
+/*===============*/
+
+static void FinalizeLineRGB555SMS(int line);
+static void FinalizeLine8bitSMS(int line);
+
+void PicoFrameStartSMS(void)
+{
+  int lines = 192, columns = 256, loffs, coffs;
   skip_next_line = 0;
-  screen_offset = 24;
+  loffs = screen_offset = 24; // 192 lines is really 224 with top/bottom bars
   Pico.est.rendstatus = PDRAW_32_COLS;
 
-  if ((Pico.video.reg[0] & 6) == 6 && (Pico.video.reg[1] & 0x18)) {
-    if (Pico.video.reg[1] & 0x08) {
-      screen_offset = 0;
+  // if mode changes make palette dirty since some modes switch to a fixed one
+  if (mode != ((Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18))) {
+    mode = (Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18);
+    Pico.m.dirtyPal = 1;
+  }
+
+  // Copy LCD enable flag for easier handling
+  Pico.m.hardware &= ~0x2;
+  if (PicoIn.opt & POPT_EN_GG_LCD)
+    Pico.m.hardware |= 0x2;
+
+  if ((Pico.m.hardware & 0x3) == 0x3) {
+    // GG LCD always has 160x144 regardless of settings
+    screen_offset = 24; // nonetheless the vdp timing has 224 lines
+    loffs = 48;
+    lines = 144;
+    columns = 160;
+  } else switch (mode) {
+  // SMS2 only 224/240 line modes, e.g. Micro Machines
+  case 0x06|0x08:
+      loffs = screen_offset = 0;
       lines = 240;
-    }
-    else {
-      screen_offset = 8;
+      break;
+  case 0x06|0x10:
+      loffs = screen_offset = 8;
       lines = 224;
-    }
+      break;
   }
+  if (PicoIn.opt & POPT_EN_SOFTSCALE) {
+    coffs = 0;
+    columns = 320;
+  } else
+    coffs = PicoIn.opt & POPT_DIS_32C_BORDER ? 0:(320-columns)/2;
+  line_offset = (FinalizeLineSMS == NULL ? coffs : 0);
+
+  if (FinalizeLineSMS == FinalizeLineRGB555SMS)
+    line_offset = 0 /* done in FinalizeLine */;
 
   if (Pico.est.rendstatus != rendstatus_old || lines != rendlines) {
-    emu_video_mode_change(screen_offset, lines, 1);
+    emu_video_mode_change(loffs, lines, coffs, columns);
     rendstatus_old = Pico.est.rendstatus;
     rendlines = lines;
+    sprites = 0;
   }
 
   Pico.est.HighCol = HighColBase + screen_offset * HighColIncrement;
   Pico.est.DrawLineDest = (char *)DrawLineDestBase + screen_offset * DrawLineDestIncrement;
+
+  if (FinalizeLineSMS == FinalizeLine8bitSMS) {
+    Pico.m.dirtyPal = (Pico.m.dirtyPal || Pico.est.SonicPalCount ? 2 : 0);
+    memcpy(Pico.est.SonicPal, PicoMem.cram, 0x40*2);
+  }
+  Pico.est.SonicPalCount = 0;
+}
+
+void PicoParseSATSMS(int line)
+{
+  if (Pico.video.reg[0] & 0x04) ParseSpritesM4(line);
+  else                          ParseSpritesTMS(line);
 }
 
-void PicoLineMode4(int line)
+void PicoLineSMS(int line)
 {
-  if (skip_next_line > 0) {
-    skip_next_line--;
+  int skip = skip_next_line;
+  unsigned bgcolor;
+
+  // GG LCD, render only visible part of screen
+  if ((Pico.m.hardware & 0x3) == 0x3 && (line < 24 || line >= 24+144))
+    goto norender;
+
+  if (PicoScanBegin != NULL && skip == 0)
+    skip = PicoScanBegin(line + screen_offset);
+
+  if (skip) {
+    skip_next_line = skip - 1;
     return;
   }
 
-  if (PicoScanBegin != NULL)
-    skip_next_line = PicoScanBegin(line + screen_offset);
-
   // Draw screen:
-  BackFill(Pico.video.reg[7] & 0x0f, 0, &Pico.est);
-  if (Pico.video.reg[1] & 0x40)
-    DrawDisplayM4(line);
+  bgcolor = (Pico.video.reg[7] & 0x0f) | ((Pico.video.reg[0] & 0x04) << 2);
+  BackFill(bgcolor, 0, &Pico.est); // bgcolor is from 2nd palette in mode 4
+  if (Pico.video.reg[1] & 0x40) {
+    if      (Pico.video.reg[0] & 0x04) DrawDisplayM4(line);
+    else if (Pico.video.reg[0] & 0x02) DrawDisplayM2(line);
+    else                               DrawDisplayM0(line);
+  }
 
-  if (FinalizeLineM4 != NULL)
-    FinalizeLineM4(line);
+  if (FinalizeLineSMS != NULL)
+    FinalizeLineSMS(line);
 
   if (PicoScanEnd != NULL)
     skip_next_line = PicoScanEnd(line + screen_offset);
 
+norender:
   Pico.est.HighCol += HighColIncrement;
   Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement;
 }
 
-void PicoDoHighPal555M4(void)
+/* Fixed palette for TMS9918 modes */
+static u16 tmspal[32] = {
+  // SMS palette
+  0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
+  0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
+  // TMS palette
+  0x0000, 0x0000, 0x04c2, 0x07d5, 0x0e55, 0x0f77, 0x045d, 0x0fe4,
+  0x055f, 0x077f, 0x05cd, 0x08ce, 0x03b2, 0x0b5c, 0x0ccc, 0x0fff,
+};
+
+void PicoDoHighPal555SMS(void)
 {
-  unsigned int *spal=(void *)PicoMem.cram;
-  unsigned int *dpal=(void *)Pico.est.HighPal;
+  u32 *spal = (void *)Pico.est.SonicPal;
+  u32 *dpal = (void *)Pico.est.HighPal;
+  unsigned int cnt = Pico.est.SonicPalCount+1;
   unsigned int t;
-  int i;
-
-  Pico.m.dirtyPal = 0;
-
-  /* cram is always stored as shorts, even though real hardware probably uses bytes */
-  for (i = 0x20/2; i > 0; i--, spal++, dpal++) {
-    t = *spal;
+  int i, j;
+ 
+  if (FinalizeLineSMS != FinalizeLine8bitSMS || Pico.m.dirtyPal == 2)
+    Pico.m.dirtyPal = 0;
+
+  // use hardware palette for 16bit accurate mode
+  if (FinalizeLineSMS == FinalizeLineRGB555SMS)
+    spal = (void *)PicoMem.cram;
+
+  /* SMS 6 bit cram data was already converted to MD/GG format by vdp write,
+   * hence GG/SMS/TMS can all be handled the same here */
+  for (j = cnt; j > 0; j--) {
+    if (!(Pico.video.reg[0] & 0x4))
+      spal = (u32 *)tmspal; // fixed palette in TMS modes
+    for (i = 0x20/2; i > 0; i--, spal++, dpal++) { 
+      t = *spal;
 #if defined(USE_BGR555)
-    t = ((t & 0x00030003)<< 3) | ((t & 0x000c000c)<<6) | ((t & 0x00300030)<<9);
-    t |= (t >> 2) | ((t >> 4) & 0x04210421);
+      t = ((t & 0x000f000f)<<1) | ((t & 0x00f000f0)<<2) | ((t & 0x0f000f00)<<3);
+      t |= (t >> 4) & 0x04210421;
 #elif defined(USE_BGR565)
-    t = ((t & 0x00030003)<< 3) | ((t & 0x000c000c)<<7) | ((t & 0x00300030)<<10);
-    t |= (t >> 2) | ((t >> 4) & 0x08610861);
+      t = ((t & 0x000f000f)<<1) | ((t & 0x00f000f0)<<3) | ((t & 0x0f000f00)<<4);
+      t |= (t >> 4) & 0x08610861;
 #else
-    t = ((t & 0x00030003)<<14) | ((t & 0x000c000c)<<7) | ((t & 0x00300030)>>1);
-    t |= (t >> 2) | ((t >> 4) & 0x08610861);
+      t = ((t & 0x000f000f)<<12)| ((t & 0x00f000f0)<<3) | ((t & 0x0f000f00)>>7);
+      t |= (t >> 4) & 0x08610861;
 #endif
-    *dpal = t;
+      *dpal = t;
+    }
+    memcpy(dpal, dpal-0x20/2, 0x20*2); // for prio bit
+    spal += 0x20/2, dpal += 0x20/2;
   }
   Pico.est.HighPal[0xe0] = 0;
 }
 
-static void FinalizeLineRGB555M4(int line)
+static void FinalizeLineRGB555SMS(int line)
 {
   if (Pico.m.dirtyPal)
-    PicoDoHighPal555M4();
+    PicoDoHighPal555SMS();
 
   // standard FinalizeLine can finish it for us,
   // with features like scaling and such
   FinalizeLine555(0, line, &Pico.est);
 }
 
-static void FinalizeLine8bitM4(int line)
+static void FinalizeLine8bitSMS(int line)
 {
-  unsigned char *pd = Pico.est.DrawLineDest;
-
-  if (DrawLineDestIncrement)
-    memcpy(pd + line_offset, Pico.est.HighCol + line_offset + 8, 256);
+  FinalizeLine8bit(0, line, &Pico.est);
 }
 
-void PicoDrawSetOutputMode4(pdso_t which)
+void PicoDrawSetOutputSMS(pdso_t which)
 {
-  line_offset = PicoIn.opt & POPT_DIS_32C_BORDER ? 0 : 32;
   switch (which)
   {
-    case PDF_8BIT:   FinalizeLineM4 = FinalizeLine8bitM4; break;
-    case PDF_RGB555: FinalizeLineM4 = FinalizeLineRGB555M4;
-                     line_offset = 0 /* done in FinalizeLine */; break;
-    default:         FinalizeLineM4 = NULL;
+    case PDF_8BIT:   FinalizeLineSMS = FinalizeLine8bitSMS; break;
+    case PDF_RGB555: FinalizeLineSMS = FinalizeLineRGB555SMS; break;
+    // there's no fast renderer yet, just treat it like PDF_8BIT
+    default:         FinalizeLineSMS = FinalizeLine8bitSMS;
                      PicoDrawSetInternalBuf(Pico.est.Draw2FB, 328); break;
   }
+  rendstatus_old = -1;
+  mode = -1;
 }
 
 // vim:shiftwidth=2:ts=2:expandtab

+ 15 - 3
pico/pico.h

@@ -41,7 +41,7 @@ extern void *plat_mem_get_for_drc(size_t size);
 extern int   plat_mem_set_exec(void *ptr, size_t size);
 
 // this one should handle display mode changes
-extern void emu_video_mode_change(int start_line, int line_count, int is_32cols);
+extern void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count);
 
 // this must switch to 16bpp mode
 extern void emu_32x_startup(void);
@@ -64,7 +64,7 @@ extern void *p32x_bios_g, *p32x_bios_m, *p32x_bios_s;
 #define POPT_EN_MCD_PCM     (1<<10)
 #define POPT_EN_MCD_CDDA    (1<<11)
 #define POPT_EN_MCD_GFX     (1<<12) // 00 x000
-// unused                   (1<<13)
+#define POPT_EN_GG_LCD      (1<<13)
 #define POPT_EN_SOFTSCALE   (1<<14)
 #define POPT_EN_MCD_RAMCART (1<<15)
 #define POPT_DIS_VDP_FIFO   (1<<16) // 0x 0000
@@ -75,6 +75,7 @@ extern void *p32x_bios_g, *p32x_bios_m, *p32x_bios_s;
 #define POPT_EN_PWM         (1<<21)
 #define POPT_PWM_IRQ_OPT    (1<<22)
 #define POPT_DIS_FM_SSGEG   (1<<23)
+#define POPT_EN_FM_DAC      (1<<24) //x00 0000
 
 #define PAHW_MCD  (1<<0)
 #define PAHW_32X  (1<<1)
@@ -82,11 +83,15 @@ extern void *p32x_bios_g, *p32x_bios_m, *p32x_bios_s;
 #define PAHW_PICO (1<<3)
 #define PAHW_SMS  (1<<4)
 
+#define PHWS_AUTO 0
+#define PHWS_GG   1
+#define PHWS_SMS  2
+
 #define PQUIRK_FORCE_6BTN   (1<<0)
 
 // the emulator is configured and some status is reported
 // through this global state (not saved in savestates)
-typedef struct
+typedef struct PicoInterface
 {
 	unsigned int opt; // POPT_* bitfield
 
@@ -97,10 +102,14 @@ typedef struct
 	unsigned short skipFrame;      // skip rendering frame, but still do sound (if enabled) and emulation stuff
 	unsigned short regionOverride; // override the region detection 0: auto, 1: Japan NTSC, 2: Japan PAL, 4: US, 8: Europe
 	unsigned short autoRgnOrder;   // packed priority list of regions, for example 0x148 means this detection order: EUR, USA, JAP
+	unsigned int hwSelect;         // hardware preselected via option menu
+	unsigned int mapper;           // mapper selection for SMS, 0 = auto
 
 	unsigned short quirks;         // game-specific quirks: PQUIRK_*
 	unsigned short overclockM68k;  // overclock the emulated 68k, in %
 
+	unsigned short filter;         // softscale filter type
+
 	int sndRate;                   // rate in Hz
 	int sndFilterAlpha;            // Low pass sound filter alpha (Q16)
 	short *sndOut;                 // PCM output buffer
@@ -214,6 +223,7 @@ void PicoDoHighPal555(int sh, int line, struct PicoEState *est);
 #define PDRAW_BORDER_32     (1<<9) // center H32 in buffer (32 px border)
 #define PDRAW_SKIP_FRAME   (1<<10) // frame is skipped
 #define PDRAW_30_ROWS      (1<<11) // 30 rows mode (240 lines)
+#define PDRAW_32X_SCALE    (1<<12) // scale CLUT layer for 32X
 extern int rendstatus_old;
 extern int rendlines;
 
@@ -298,6 +308,8 @@ enum input_device {
   PICO_INPUT_NOTHING,
   PICO_INPUT_PAD_3BTN,
   PICO_INPUT_PAD_6BTN,
+  PICO_INPUT_PAD_TEAM,
+  PICO_INPUT_PAD_4WAY,
 };
 void PicoSetInputDevice(int port, enum input_device device);
 

+ 33 - 10
pico/pico_int.h

@@ -180,7 +180,7 @@ extern struct DrZ80 drZ80;
 #define z80_run_nr(cycles) Cz80_Exec(&CZ80, cycles)
 #define z80_int()          Cz80_Set_IRQ(&CZ80, 0, HOLD_LINE)
 #define z80_int_assert(a)  Cz80_Set_IRQ(&CZ80, 0, (a) ? ASSERT_LINE : CLEAR_LINE)
-#define z80_nmi()          Cz80_Set_IRQ(&CZ80, IRQ_LINE_NMI, 0)
+#define z80_nmi()          Cz80_Set_IRQ(&CZ80, IRQ_LINE_NMI, ASSERT_LINE)
 
 #define z80_cyclesLeft     (CZ80.ICount - CZ80.ExtraCycles)
 #define z80_subCLeft(c)    CZ80.ICount -= c
@@ -199,7 +199,7 @@ extern struct DrZ80 drZ80;
 #define Z80_STATE_SIZE 0x60
 
 #define z80_resetCycles() \
-  Pico.t.z80c_cnt = Pico.t.z80c_aim = Pico.t.z80_scanline = 0
+  Pico.t.z80c_cnt -= Pico.t.z80c_aim, Pico.t.z80c_aim = Pico.t.z80_scanline = 0
 
 #define z80_cyclesDone() \
   (Pico.t.z80c_aim - z80_cyclesLeft)
@@ -224,6 +224,8 @@ extern SH2 sh2s[2];
 # define sh2_cycles_left(sh2) (sh2)->icount
 # define sh2_burn_cycles(sh2, n) (sh2)->icount -= n
 # define sh2_pc(sh2) (sh2)->ppc
+# define sh2_not_polling(sh2) (sh2)->no_polling
+# define sh2_set_polling(sh2) (sh2)->no_polling = 0
 #else
 # define sh2_end_run(sh2, after_) do { \
   int left_ = ((signed int)(sh2)->sr >> 12) - (after_); \
@@ -235,6 +237,8 @@ extern SH2 sh2s[2];
 # define sh2_cycles_left(sh2) ((signed int)(sh2)->sr >> 12)
 # define sh2_burn_cycles(sh2, n) (sh2)->sr -= ((n) << 12)
 # define sh2_pc(sh2) (sh2)->pc
+# define sh2_not_polling(sh2) ((sh2)->sr & SH2_NO_POLLING)
+# define sh2_set_polling(sh2) ((sh2)->sr &= ~SH2_NO_POLLING)
 #endif
 
 #define sh2_cycles_done(sh2) (unsigned)((int)(sh2)->cycles_timeslice - sh2_cycles_left(sh2))
@@ -338,12 +342,27 @@ struct PicoMisc
   unsigned int  frame_count;   // 1c for movies and idle det
 };
 
+#define PMS_MAP_AUTO	0
+#define PMS_MAP_SEGA	1
+#define PMS_MAP_CODEM	2
+#define PMS_MAP_KOREA	3
+#define PMS_MAP_MSX	4
+#define PMS_MAP_N32K	5
+#define PMS_MAP_N16K	6
+#define PMS_MAP_JANGGUN	7
+#define PMS_MAP_NEMESIS	8
+
 struct PicoMS
 {
   unsigned char carthw[0x10];
   unsigned char io_ctl;
   unsigned char nmi_state;
-  unsigned char pad[0x4e];
+  unsigned char mapper;
+  unsigned char fm_ctl;
+  unsigned char vdp_buffer;
+  unsigned char vdp_hlatch;
+  unsigned char io_gg[0x08];
+  unsigned char pad[0x42];
 };
 
 // emu state and data for the asm code
@@ -352,6 +371,7 @@ struct PicoEState
   int DrawScanline;
   int rendstatus;
   void *DrawLineDest;          // draw destination
+  int DrawLineDestIncr;
   unsigned char *HighCol;
   s32 *HighPreSpr;
   struct Pico *Pico;
@@ -417,6 +437,7 @@ struct PicoTiming
 
   unsigned int z80c_cnt;                // z80 cycles done (this frame)
   unsigned int z80c_aim;
+  unsigned int z80c_line_start;
   int z80_scanline;
 
   int timer_a_next_oflow, timer_a_step; // in z80 cycles
@@ -664,6 +685,7 @@ PICO_INTERNAL void PicoFrameStart(void);
 void PicoDrawSync(int to, int blank_last_line);
 void BackFill(int reg7, int sh, struct PicoEState *est);
 void FinalizeLine555(int sh, int line, struct PicoEState *est);
+void FinalizeLine8bit(int sh, int line, struct PicoEState *est);
 void PicoDrawSetOutBufMD(void *dest, int increment);
 extern int (*PicoScanBegin)(unsigned int num);
 extern int (*PicoScanEnd)(unsigned int num);
@@ -681,10 +703,11 @@ void PicoDraw2Init(void);
 PICO_INTERNAL void PicoFrameFull();
 
 // mode4.c
-void PicoFrameStartMode4(void);
-void PicoLineMode4(int line);
-void PicoDoHighPal555M4(void);
-void PicoDrawSetOutputMode4(pdso_t which);
+void PicoFrameStartSMS(void);
+void PicoParseSATSMS(int line);
+void PicoLineSMS(int line);
+void PicoDoHighPal555SMS(void);
+void PicoDrawSetOutputSMS(pdso_t which);
 
 // memory.c
 PICO_INTERNAL void PicoMemSetup(void);
@@ -909,9 +932,9 @@ PICO_INTERNAL void PsndExit(void);
 PICO_INTERNAL void PsndReset(void);
 PICO_INTERNAL void PsndStartFrame(void);
 PICO_INTERNAL void PsndDoDAC(int cycle_to);
-PICO_INTERNAL void PsndDoPSG(int line_to);
-PICO_INTERNAL void PsndDoYM2413(int line_to);
-PICO_INTERNAL void PsndDoFM(int line_to);
+PICO_INTERNAL void PsndDoPSG(int cyc_to);
+PICO_INTERNAL void PsndDoYM2413(int cyc_to);
+PICO_INTERNAL void PsndDoFM(int cyc_to);
 PICO_INTERNAL void PsndClear(void);
 PICO_INTERNAL void PsndGetSamples(int y);
 PICO_INTERNAL void PsndGetSamplesMS(int y);

+ 1 - 1
pico/pico_port.h

@@ -38,7 +38,7 @@
 // some well known non-standard macros for detection.
 #if defined __BYTE_ORDER__
 #define	CPU_IS_LE	__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
-#elif defined _BYTE_ORDER
+#elif defined __BYTE_ORDER
 #define	CPU_IS_LE	__BYTE_ORDER == __LITTLE_ENDIAN
 #elif defined __BIG_ENDIAN__ || defined _M_PPC // Windows on PPC was big endian
 #define	CPU_IS_LE	0

+ 458 - 74
pico/sms.c

@@ -1,16 +1,16 @@
 /*
  * SMS emulation
  * (C) notaz, 2009-2010
+ * (C) kub, 2021
  *
  * This work is licensed under the terms of MAME license.
  * See COPYING file in the top-level directory.
  */
 /*
  * TODO:
- * - start in a state as if BIOS ran
- * - RAM support in mapper
- * - region support
- * - H counter
+ * - start in a state as if BIOS ran (partly done for VDP registers, RAM)
+ * - region support (currently only very limited PAL and Mark-III support)
+ * - mapper for EEPROM support
  */
 #include "pico_int.h"
 #include "memory.h"
@@ -20,15 +20,15 @@
 extern void YM2413_regWrite(unsigned reg);
 extern void YM2413_dataWrite(unsigned data);
 
-
-static unsigned short ymflag = 0xffff;
+extern unsigned sprites_status; // TODO put in some hdr file!
 
 static unsigned char vdp_data_read(void)
 {
   struct PicoVideo *pv = &Pico.video;
   unsigned char d;
 
-  d = PicoMem.vramb[MEM_LE2(pv->addr)];
+  d = Pico.ms.vdp_buffer;
+  Pico.ms.vdp_buffer = PicoMem.vramb[MEM_LE2(pv->addr)];
   pv->addr = (pv->addr + 1) & 0x3fff;
   pv->pending = 0;
   return d;
@@ -44,6 +44,9 @@ static unsigned char vdp_ctl_read(void)
   pv->pending = pv->pending_ints = 0;
   pv->status = 0;
 
+  if (pv->reg[0] & 0x04)
+    d |= 0x1f; // unused bits in mode 4 read as 1
+
   elprintf(EL_SR, "VDP sr: %02x", d);
   return d;
 }
@@ -53,13 +56,26 @@ static void vdp_data_write(unsigned char d)
   struct PicoVideo *pv = &Pico.video;
 
   if (pv->type == 3) {
-    if (PicoMem.cram[pv->addr & 0x1f] != d) Pico.m.dirtyPal = 1;
-    PicoMem.cram[pv->addr & 0x1f] = d;
+    // cram. 32 on SMS, but 64 on MD. Fill 2nd half of cram for prio bit mirror
+    if (Pico.m.hardware & 0x1) { // GG, same layout as MD
+      unsigned a = pv->addr & 0x3f;
+      if (a & 0x1) { // write complete color on high byte write
+        u16 c = ((d&0x0f) << 8) | Pico.ms.vdp_buffer;
+        if (PicoMem.cram[a >> 1] != c) Pico.m.dirtyPal = 1;
+        PicoMem.cram[a >> 1] = PicoMem.cram[(a >> 1)+0x20] = c;
+      }
+    } else { // SMS, convert to MD layout (00BbGgRr to 0000BbBbGgGgRrRr)
+      unsigned a = pv->addr & 0x1f;
+      u16 c = ((d&0x30)<<6) + ((d&0x0c)<<4) + ((d&0x03)<<2);
+      if (PicoMem.cram[a] != (c | (c>>2))) Pico.m.dirtyPal = 1;
+      PicoMem.cram[a] = PicoMem.cram[a+0x20] = c | (c>>2);
+    }
   } else {
     PicoMem.vramb[MEM_LE2(pv->addr)] = d;
   }
   pv->addr = (pv->addr + 1) & 0x3fff;
 
+  Pico.ms.vdp_buffer = d;
   pv->pending = 0;
 }
 
@@ -87,14 +103,18 @@ static void vdp_ctl_write(u8 d)
   struct PicoVideo *pv = &Pico.video;
 
   if (pv->pending) {
-    if ((d >> 6) == 2) {
+    pv->type = d >> 6;
+    if (pv->type == 2) {
       elprintf(EL_IO, "  VDP r%02x=%02x", d & 0x0f, pv->addr & 0xff);
       if (pv->reg[d & 0x0f] != (u8)pv->addr)
         vdp_reg_write(pv, d & 0x0f, pv->addr);
     }
-    pv->type = d >> 6;
     pv->addr &= 0x00ff;
     pv->addr |= (d & 0x3f) << 8;
+    if (pv->type == 0) {
+      Pico.ms.vdp_buffer = PicoMem.vramb[MEM_LE2(pv->addr)];
+      pv->addr = (pv->addr + 1) & 0x3fff;
+    }
   } else {
     pv->addr &= 0x3f00;
     pv->addr |= d;
@@ -102,14 +122,25 @@ static void vdp_ctl_write(u8 d)
   pv->pending ^= 1;
 }
 
+static u8 vdp_hcounter(int cycles)
+{
+  // 171 slots per scanline of 228 clocks, counted 0xf4-0x93, 0xe9-0xf3
+  // this matches h counter tables in SMSVDPTest:
+  //  hc =   (cycles+2) *   171    /228      -1 + 0xf4;
+  int hc = (((cycles+2) * ((171<<8)/228))>>8)-1 + 0xf4; // Q8 to avoid dividing
+  if (hc > 0x193) hc += 0xe9-0x93-1;
+  return hc;
+}
+
 static unsigned char z80_sms_in(unsigned short a)
 {
-  unsigned char d = 0;
+  unsigned char d = 0xff;
 
+  a &= 0xff;
   elprintf(EL_IO, "z80 port %04x read", a);
-  if((a&0xff)>= 0xf0){
+  if(a >= 0xf0){
     if (PicoIn.opt & POPT_EN_YM2413){
-      switch((a&0xff))
+      switch(a)
       {
       case 0xf0:
         // FM reg port
@@ -119,19 +150,24 @@ static unsigned char z80_sms_in(unsigned short a)
         break;
       case 0xf2:
         // bit 0 = 1 active FM Pac
-        d = ymflag;
-        //printf("read FM Check = %02x\n", d);
+        d = 0xf8 | Pico.ms.fm_ctl;
         break;
       }
     }
   }
   else{
-    a &= 0xc1;
-    switch (a)
+    switch (a & 0xc1)
     {
       case 0x00:
       case 0x01:
-        d = 0xff;
+        if ((Pico.m.hardware & 0x1) && a < 0x8) { // GG I/O area
+          switch (a) {
+          case 0: d = 0xff & ~(PicoIn.pad[0] & 0x80);               break;
+          case 1: d = Pico.ms.io_gg[1] | (Pico.ms.io_gg[2] & 0x7f); break;
+          case 5: d = Pico.ms.io_gg[5] & 0xf8;                      break;
+          default: d = Pico.ms.io_gg[a];                            break;
+          }
+        }
         break;
 
       case 0x40: /* V counter */
@@ -140,7 +176,7 @@ static unsigned char z80_sms_in(unsigned short a)
         break;
 
       case 0x41: /* H counter */
-        d = Pico.m.rotate++;
+        d = Pico.ms.vdp_hlatch;
         elprintf(EL_HVCNT, "H counter read: %02x", d);
         break;
 
@@ -159,6 +195,8 @@ static unsigned char z80_sms_in(unsigned short a)
       case 0xc1: /* I/O port B and miscellaneous */
         d = (Pico.ms.io_ctl & 0x80) | ((Pico.ms.io_ctl << 1) & 0x40) | 0x30;
         d |= ~(PicoIn.pad[1] >> 2) & 0x0f;
+        if (Pico.ms.io_ctl & 0x08) d |= 0x80; // TH as input is unconnected
+        if (Pico.ms.io_ctl & 0x02) d |= 0x40;
         break;
     }
   }
@@ -170,40 +208,47 @@ static void z80_sms_out(unsigned short a, unsigned char d)
 {
   elprintf(EL_IO, "z80 port %04x write %02x", a, d);
 
-  if((a&0xff)>= 0xf0){
+  a &= 0xff;
+  if(a >= 0xf0){
     if (PicoIn.opt & POPT_EN_YM2413){
-      switch((a&0xff))
+      switch(a)
       {
         case 0xf0:
           // FM reg port
           YM2413_regWrite(d);
-          //printf("write FM register = %02x\n", d);
           break;
         case 0xf1:
           // FM data port
           YM2413_dataWrite(d);
-          //printf("write FM data = %02x\n", d);
           break;
         case 0xf2:
           // bit 0 = 1 active FM Pac
-          ymflag = d;
-          //printf("write FM Check = %02x\n", d);
+          Pico.ms.fm_ctl = d & 0x1;
           break;
       }
     }
   }
   else{
-    a &= 0xc1;
-    switch (a)
+    switch (a & 0xc1)
     {
+      case 0x00:
+        if ((Pico.m.hardware & 0x1) && a < 0x8)   // GG I/O area
+          Pico.ms.io_gg[a] = d;
+        break;
       case 0x01:
-        Pico.ms.io_ctl = d;
+        if ((Pico.m.hardware & 0x1) && a < 0x8) { // GG I/O area
+          Pico.ms.io_gg[a] = d;
+        } else {
+          // pad. latch hcounter if one of the TH lines is switched to 1
+          if ((Pico.ms.io_ctl ^ d) & d & 0xa0)
+            Pico.ms.vdp_hlatch = vdp_hcounter(z80_cyclesDone() - Pico.t.z80c_line_start);
+          Pico.ms.io_ctl = d;
+        }
         break;
 
       case 0x40:
       case 0x41:
-        if ((d & 0x90) == 0x90)
-          PsndDoPSG(Pico.m.scanline);
+        PsndDoPSG(z80_cyclesDone());
         SN76496Write(d);
         break;
 
@@ -218,52 +263,301 @@ static void z80_sms_out(unsigned short a, unsigned char d)
   }
 }
 
+static void z80_exec(int aim)
+{
+  Pico.t.z80c_aim = aim;
+  Pico.t.z80c_cnt += z80_run(Pico.t.z80c_aim - Pico.t.z80c_cnt);
+}
+
+
+// ROM/SRAM bank mapping, see https://www.smspower.org/Development/Mappers
+
 static int bank_mask;
 
-static void write_bank(unsigned short a, unsigned char d)
+static void xwrite(unsigned int a, unsigned char d);
+
+
+// Sega mapper. Maps 3 banks 16KB each, with SRAM support
+static void write_sram_sega(unsigned short a, unsigned char d)
+{
+  // SRAM is mapped in 2 16KB banks, selected by bit 2 in control reg
+  a &= 0x3fff;
+  a += ((Pico.ms.carthw[0x0c] & 0x04) >> 2) * 0x4000;
+
+  Pico.sv.changed |= (Pico.sv.data[a] != d);
+  Pico.sv.data[a] = d;
+}
+
+static void write_bank_sega(unsigned short a, unsigned char d)
 {
-  elprintf(EL_Z80BNK, "bank %04x %02x @ %04x", a, d, z80_pc());
+  if (a < 0xfff8) return;
+  // avoid mapper detection for RAM fill with 0
+  if (Pico.ms.mapper != PMS_MAP_SEGA && (Pico.ms.mapper || d == 0)) return;
+
+  elprintf(EL_Z80BNK, "bank sega %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.mapper = PMS_MAP_SEGA;
+  Pico.ms.carthw[a & 0x0f] = d;
+
   switch (a & 0x0f)
   {
-    case 0x0c:
-      elprintf(EL_STATUS|EL_ANOMALY, "%02x written to control reg!", d);
-      break;
     case 0x0d:
-      if (d != 0)
-        elprintf(EL_STATUS|EL_ANOMALY, "bank0 changed to %d!", d);
+      d &= bank_mask;
+      z80_map_set(z80_read_map, 0x0400, 0x3fff, Pico.rom+0x400 + (d << 14), 0);
       break;
     case 0x0e:
       d &= bank_mask;
       z80_map_set(z80_read_map, 0x4000, 0x7fff, Pico.rom + (d << 14), 0);
-#ifdef _USE_CZ80
-      Cz80_Set_Fetch(&CZ80, 0x4000, 0x7fff, (FPTR)Pico.rom + (d << 14));
-#endif
       break;
+
+    case 0x0c:
+      if (d & ~0x8c)
+        elprintf(EL_STATUS|EL_ANOMALY, "%02x written to control reg!", d);
+      /*FALLTHROUGH*/
     case 0x0f:
-      d &= bank_mask;
-      z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.rom + (d << 14), 0);
-#ifdef _USE_CZ80
-      Cz80_Set_Fetch(&CZ80, 0x8000, 0xbfff, (FPTR)Pico.rom + (d << 14));
-#endif
+      if (Pico.ms.carthw[0xc] & 0x08) {
+        d = (Pico.ms.carthw[0xc] & 0x04) >> 2;
+        z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.sv.data + d*0x4000, 0);
+        z80_map_set(z80_write_map, 0x8000, 0xbfff, write_sram_sega, 1);
+      } else {
+        d = Pico.ms.carthw[0xf] & bank_mask;
+        z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.rom + (d << 14), 0);
+        z80_map_set(z80_write_map, 0x8000, 0xbfff, xwrite, 1);
+      }
       break;
   }
-  Pico.ms.carthw[a & 0x0f] = d;
 }
 
+// Codemasters mapper. Similar to Sega, but different addresses, TODO: SRAM
+static void write_bank_codem(unsigned short a, unsigned char d)
+{
+  if (a >= 0xc000 || (a & 0x3fff)) return; // address is 0x0000, 0x4000, 0x8000?
+  // don't detect linear mapping to avoid confusing with MSX
+  if (Pico.ms.mapper != PMS_MAP_CODEM && (Pico.ms.mapper || (a>>14) == d)) return;
+  elprintf(EL_Z80BNK, "bank codem %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.mapper = PMS_MAP_CODEM;
+  Pico.ms.carthw[a>>14] = d;
+
+  d &= bank_mask;
+  z80_map_set(z80_read_map, a, a+0x3fff, Pico.rom + (d << 14), 0);
+}
+
+// MSX mapper. 4 selectable 8KB banks at the top
+static void write_bank_msx(unsigned short a, unsigned char d)
+{
+  if (a > 0x0003) return;
+  // don't detect linear mapping to avoid confusing with Codemasters
+  if (Pico.ms.mapper != PMS_MAP_MSX && (Pico.ms.mapper || (a|d) == 0)) return;
+  elprintf(EL_Z80BNK, "bank msx %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.mapper = PMS_MAP_MSX;
+  Pico.ms.carthw[a] = d;
+
+  a = (a^2)*0x2000 + 0x4000;
+  d &= 2*bank_mask + 1;
+  z80_map_set(z80_read_map, a, a+0x1fff, Pico.rom + (d << 13), 0);
+}
+
+// Korea mapping, 1 selectable 16KB bank at the top
+static void write_bank_korea(unsigned short a, unsigned char d)
+{
+  if (a != 0xa000) return;
+  if (Pico.ms.mapper != PMS_MAP_KOREA && (Pico.ms.mapper)) return;
+  elprintf(EL_Z80BNK, "bank korea %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.mapper = PMS_MAP_KOREA;
+  Pico.ms.carthw[0xf] = d;
+
+  d &= bank_mask;
+  z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.rom + (d << 14), 0);
+}
+
+// Korean n-in-1 mapping. 1 selectable 32KB bank at the bottom
+static void write_bank_n32k(unsigned short a, unsigned char d)
+{
+  if (a != 0xffff) return;
+  // code must be in RAM since all visible ROM space is swapped
+  if (Pico.ms.mapper != PMS_MAP_N32K && (Pico.ms.mapper || z80_pc() < 0xc000)) return;
+  elprintf(EL_Z80BNK, "bank 32k %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.mapper = PMS_MAP_N32K;
+  Pico.ms.carthw[0xf] = d;
+
+  d &= bank_mask >> 1;
+  z80_map_set(z80_read_map, 0,   0x7fff, Pico.rom + (d << 15), 0);
+}
+
+// Korean 4-in-1. 2 selectable 16KB banks, top bank is shifted by bottom one
+static void write_bank_n16k(unsigned short a, unsigned char d)
+{
+  if (a != 0x3ffe && a != 0x7fff && a != 0xbfff) return;
+  // code must be in RAM since all visible ROM space is swapped
+  if (Pico.ms.mapper != PMS_MAP_N16K && (Pico.ms.mapper || z80_pc() < 0xc000)) return;
+  elprintf(EL_Z80BNK, "bank 16k %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.mapper = PMS_MAP_N16K;
+  Pico.ms.carthw[a>>14] = d;
+
+  d &= bank_mask;
+  a = a & 0xc000;
+  // the top bank shifts with the bottom bank.
+  if (a == 0x8000) d += Pico.ms.carthw[0] & 0x30;
+  z80_map_set(z80_read_map, a, a+0x3fff, Pico.rom + (d << 14), 0);
+}
+
+// MSX-Nemesis mapper. 4 selectable 8KB banks at the top
+static void write_bank_msxn(unsigned short a, unsigned char d)
+{
+  if (a > 0x0003) return;
+  // never autodetected, selectable only via config
+  if (Pico.ms.mapper != PMS_MAP_NEMESIS) return;
+  elprintf(EL_Z80BNK, "bank nems %04x %02x @ %04x", a, d, z80_pc());
+  Pico.ms.carthw[a] = d;
+
+  a = (a^2)*0x2000 + 0x4000;
+  d &= 2*bank_mask + 1;
+  z80_map_set(z80_read_map, a, a+0x1fff, Pico.rom + (d << 13), 0);
+}
+
+// Korean Janggun mapper. 4 selectable 8KB banks at the top, hardware byte flip
+static unsigned char read_flipped_jang(unsigned a)
+{
+  static unsigned char flipper[16] = // reversed nibble bit order
+      { 0x0,0x8,0x4,0xc,0x2,0xa,0x6,0xe,0x1,0x9,0x5,0xd,0x3,0xb,0x7,0xf };
+  unsigned char c;
+
+  // return value at address a in reversed bit order
+  c = Pico.rom[(Pico.ms.carthw[a>>13] << 13) + (a & 0x1fff)];
+  return (flipper[c&0xf]<<4) | flipper[c>>4];
+}
+
+static void write_bank_jang(unsigned short a, unsigned char d)
+{
+  // address is 0xfffe, 0xffff, 0x4000, 0x6000, 0x8000, 0xa000
+  if ((a|1) != 0xffff && (!((a^(a<<1)) & 0x8000) || (a & 0x1fff))) return;
+  // never autodetected, selectable only via config
+  if (Pico.ms.mapper != PMS_MAP_JANGGUN) return;
+  elprintf(EL_Z80BNK, "bank jang %04x %02x @ %04x", a, d, z80_pc());
+
+  if ((a|1) == 0xffff) {
+    int x = a & 1, f = d & 0x40;
+    Pico.ms.carthw[x] = d;
+    d &= bank_mask;
+    Pico.ms.carthw[2*x + 2] = 2*d, Pico.ms.carthw[2*x + 3] = 2*d+1;
+    a = (x+1) * 0x4000;
+    if (!f)
+      z80_map_set(z80_read_map, a, a+0x3fff, Pico.rom + (d << 14), 0);
+    else
+      z80_map_set(z80_read_map, a, a+0x3fff, read_flipped_jang, 1);
+  } else {
+    d &= 2*bank_mask + 1;
+    Pico.ms.carthw[a>>13] = d;
+    if (!(Pico.ms.carthw[(a>>15)&1] & 0x40))
+      z80_map_set(z80_read_map, a, a+0x1fff, Pico.rom + (d << 13), 0);
+    else
+      z80_map_set(z80_read_map, a, a+0x1fff, read_flipped_jang, 1);
+  }
+}
+
+// TODO auto-selecting is not really reliable.
+// Before adding more mappers this should be revised.
 static void xwrite(unsigned int a, unsigned char d)
 {
   elprintf(EL_IO, "z80 write [%04x] %02x", a, d);
   if (a >= 0xc000)
     PicoMem.zram[a & 0x1fff] = d;
-  if (a >= 0xfff8)
-    write_bank(a, d);
+
+  switch (Pico.ms.mapper) { // via config, or auto detected
+  case PMS_MAP_SEGA:    write_bank_sega(a, d);  break;
+  case PMS_MAP_CODEM:	write_bank_codem(a, d); break;
+  case PMS_MAP_MSX:	write_bank_msx(a, d);   break;
+  case PMS_MAP_KOREA:	write_bank_korea(a, d); break;
+  case PMS_MAP_N32K:	write_bank_n32k(a, d);  break;
+  case PMS_MAP_N16K:	write_bank_n16k(a, d);  break;
+  case PMS_MAP_JANGGUN: write_bank_jang(a, d);  break;
+  case PMS_MAP_NEMESIS: write_bank_msxn(a, d);  break;
+
+  case PMS_MAP_AUTO:
+        // NB the sequence of mappers is crucial for the auto detection
+        write_bank_n32k(a, d);
+        write_bank_sega(a, d);
+        write_bank_msx(a, d);
+        write_bank_codem(a, d);
+        write_bank_korea(a, d);
+        write_bank_n16k(a, d);
+        break;
+  }
 }
 
+// TMR product codes and hardware type for know 50Hz-only games */
+static u32 region_pal[] = { // cf. GX+, core/cart_hw/sms_cartc.c
+  0x40207067 /* Addams Family */, 0x40207020 /* Back.Future 3 */,
+  0x40207058 /* Battlemaniacs */, 0x40007105 /* Cal.Games 2 */,
+  0x40207065 /* Dracula */      , 0x40007109 /* Home Alone */,
+  0x40009024 /* Pwr.Strike 2 */ , 0x40207047 /* Predator 2 EU */,
+  0x40002519 /* Quest.Yak */    , 0x40207064 /* Robocop 3 */,
+  0x40205014 /* Sens.Soccer */  , 0x40002573 /* Sonic Blast */,
+  0x40007080 /* S.Harrier EU */ , 0x40007038 /* Taito Chase */,
+};
+
 void PicoResetMS(void)
 {
+  unsigned tmr;
+  int id, hw, i;
+
+  // set preselected hw/mapper from config
+  if (PicoIn.hwSelect) {
+    switch (PicoIn.hwSelect) {
+    case PHWS_GG:  Pico.m.hardware |=  0x1; break;
+    default:       Pico.m.hardware &= ~0x1; break;
+    }
+  }
+  if (PicoIn.mapper)
+    Pico.ms.mapper = PicoIn.mapper;
+  Pico.m.hardware |= 0x4; // default region Japan if no TMR header
+
+  // check if the ROM header contains more system information
+  for (tmr = 0x2000; tmr < 0xbfff && tmr <= Pico.romsize; tmr *= 2) {
+    if (!memcmp(Pico.rom + tmr-16, "TMR SEGA", 8)) {
+      hw = Pico.rom[tmr-1] >> 4;
+      if (!PicoIn.hwSelect) {
+        Pico.m.hardware &= ~0x1;
+        if (hw >= 0x5 && hw < 0x8)
+          Pico.m.hardware |= 0x1; // GG cartridge detected
+      }
+      if (!PicoIn.regionOverride) {
+        Pico.m.hardware &= ~0x4;
+        if (hw == 0x5 || hw == 0x3)
+          Pico.m.hardware |= 0x4; // region Japan
+      }
+      id = CPU_LE4(*(u32 *)&Pico.rom[tmr-4]) & 0xf0f0ffff;
+      for (i = 0; i < sizeof(region_pal)/sizeof(*region_pal); i++)
+        if (id == region_pal[i] && !PicoIn.regionOverride) {
+          Pico.m.pal = 1; // requires 50Hz timing
+          break;
+        }
+      break;
+    }
+  }
+
   z80_reset();
   PsndReset(); // pal must be known here
-  ymflag = 0xffff;
+  Pico.ms.fm_ctl = 0xff;
+  Pico.m.dirtyPal = 1;
+
+  // reset memory mapping
+  PicoMemSetupMS();
+
+  // BIOS, VDP intialisation
+  Pico.video.reg[0] = 0x36;
+  Pico.video.reg[1] = 0xa0;
+  Pico.video.reg[2] = 0xff;
+  Pico.video.reg[3] = 0xff;
+  Pico.video.reg[4] = 0xff;
+  Pico.video.reg[5] = 0xff;
+  Pico.video.reg[6] = 0xfb;
+  Pico.video.reg[7] = 0x00;
+  Pico.video.reg[8] = 0x00;
+  Pico.video.reg[9] = 0x00;
+  Pico.video.reg[10] = 0xff;
+
+  // BIOS, clear zram (unitialized on Mark-III, cf src/mame/drivers/sms.cpp)
+  memset(PicoMem.zram, (Pico.m.hardware&5) == 4 ? 0xf0:0, sizeof(PicoMem.zram));
 }
 
 void PicoPowerMS(void)
@@ -285,9 +579,8 @@ void PicoPowerMS(void)
   tmp = 1 << s;
   bank_mask = (tmp - 1) >> 14;
 
-  Pico.ms.carthw[0x0e] = 1;
-  Pico.ms.carthw[0x0f] = 2;
-
+  PicoMem.ioports[0] = 0xc3; // hack to jump @0 at end of RAM to wrap around
+  Pico.ms.mapper = PicoIn.mapper;
   PicoReset();
 }
 
@@ -300,24 +593,78 @@ void PicoMemSetupMS(void)
   z80_map_set(z80_write_map, 0x0000, 0xbfff, xwrite, 1);
   z80_map_set(z80_write_map, 0xc000, 0xdfff, PicoMem.zram, 0);
   z80_map_set(z80_write_map, 0xe000, 0xffff, xwrite, 1);
- 
+
+  // Nemesis mapper maps last 8KB rom bank #15 to adress 0
+  if (Pico.ms.mapper == PMS_MAP_NEMESIS && Pico.romsize > 0x1e000)
+    z80_map_set(z80_read_map, 0x0000, 0x1fff, Pico.rom + 0x1e000, 0);
 #ifdef _USE_DRZ80
   drZ80.z80_in = z80_sms_in;
   drZ80.z80_out = z80_sms_out;
 #endif
 #ifdef _USE_CZ80
-  Cz80_Set_Fetch(&CZ80, 0x0000, 0xbfff, (FPTR)Pico.rom);
-  Cz80_Set_Fetch(&CZ80, 0xc000, 0xdfff, (FPTR)PicoMem.zram);
-  Cz80_Set_Fetch(&CZ80, 0xe000, 0xffff, (FPTR)PicoMem.zram);
   Cz80_Set_INPort(&CZ80, z80_sms_in);
   Cz80_Set_OUTPort(&CZ80, z80_sms_out);
 #endif
+
+  // memory mapper setup, linear mapping of 1st 48KB
+  u8 mapper = Pico.ms.mapper;
+  if (mapper == PMS_MAP_MSX || mapper == PMS_MAP_NEMESIS) {
+    xwrite(0x0000, 4);
+    xwrite(0x0001, 5);
+    xwrite(0x0002, 2);
+    xwrite(0x0003, 3);
+  } else if (mapper == PMS_MAP_N32K) {
+    xwrite(0xffff, 0);
+  } else if (mapper == PMS_MAP_N16K) {
+    xwrite(0x3ffe, 0);
+    xwrite(0x7fff, 1);
+    xwrite(0xbfff, 2);
+  } else if (mapper == PMS_MAP_JANGGUN) {
+    xwrite(0xfffe, 1);
+    xwrite(0xffff, 2);
+  } else if (mapper == PMS_MAP_CODEM) {
+    xwrite(0x0000, 0);
+    xwrite(0x4000, 1);
+    xwrite(0x8000, 2);
+  } else {
+    xwrite(0xfffc, 0);
+    xwrite(0xfffd, 0);
+    xwrite(0xfffe, 1);
+    xwrite(0xffff, 2);
+  }
+  Pico.ms.mapper = mapper;
 }
 
 void PicoStateLoadedMS(void)
 {
-  write_bank(0xfffe, Pico.ms.carthw[0x0e]);
-  write_bank(0xffff, Pico.ms.carthw[0x0f]);
+  u8 mapper = Pico.ms.mapper;
+  if (Pico.ms.mapper == PMS_MAP_MSX || Pico.ms.mapper == PMS_MAP_NEMESIS) {
+    xwrite(0x0000, Pico.ms.carthw[0]);
+    xwrite(0x0001, Pico.ms.carthw[1]);
+    xwrite(0x0002, Pico.ms.carthw[2]);
+    xwrite(0x0003, Pico.ms.carthw[3]);
+  } else if (Pico.ms.mapper == PMS_MAP_N32K) {
+    xwrite(0xffff, Pico.ms.carthw[0x0f]);
+  } else if (Pico.ms.mapper == PMS_MAP_N16K) {
+    xwrite(0x3ffe, Pico.ms.carthw[0]);
+    xwrite(0x7fff, Pico.ms.carthw[1]);
+    xwrite(0xbfff, Pico.ms.carthw[2]);
+  } else if (Pico.ms.mapper == PMS_MAP_JANGGUN) {
+    xwrite(0x4000, Pico.ms.carthw[2]);
+    xwrite(0x6000, Pico.ms.carthw[3]);
+    xwrite(0x8000, Pico.ms.carthw[4]);
+    xwrite(0xa000, Pico.ms.carthw[5]);
+  } else if (Pico.ms.mapper == PMS_MAP_CODEM) {
+    xwrite(0x0000, Pico.ms.carthw[0]);
+    xwrite(0x4000, Pico.ms.carthw[1]);
+    xwrite(0x8000, Pico.ms.carthw[2]);
+  } else {
+    xwrite(0xfffc, Pico.ms.carthw[0x0c]);
+    xwrite(0xfffd, Pico.ms.carthw[0x0d]);
+    xwrite(0xfffe, Pico.ms.carthw[0x0e]);
+    xwrite(0xffff, Pico.ms.carthw[0x0f]);
+  }
+  Pico.ms.mapper = mapper;
 }
 
 void PicoFrameMS(void)
@@ -325,40 +672,71 @@ void PicoFrameMS(void)
   struct PicoVideo *pv = &Pico.video;
   int is_pal = Pico.m.pal;
   int lines = is_pal ? 313 : 262;
-  int cycles_line = is_pal ? 58020 : 58293; /* (226.6 : 227.7) * 256 */
-  int cycles_done = 0, cycles_aim = 0;
+  int cycles_line = 228;
   int skip = PicoIn.skipFrame;
   int lines_vis = 192;
   int hint; // Hint counter
   int nmi;
   int y;
 
+  z80_resetCycles();
   PsndStartFrame();
 
+  // for SMS the pause button generates an NMI, for GG ths is not the case
   nmi = (PicoIn.pad[0] >> 7) & 1;
-  if (!Pico.ms.nmi_state && nmi)
+  if (!(Pico.m.hardware & 0x1) && !Pico.ms.nmi_state && nmi)
     z80_nmi();
   Pico.ms.nmi_state = nmi;
 
-  PicoFrameStartMode4();
+  if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18))
+    lines_vis = (pv->reg[1] & 0x08) ? 240 : 224;
+  PicoFrameStartSMS();
   hint = pv->reg[0x0a];
 
+  // SMS: xscroll:f3 sprovr,vint,   vcount:fc, hint:fd
+  // GG:  xscroll:f5 sprovr,vint:fd vcount:fe, hint:ff
   for (y = 0; y < lines; y++)
   {
-    pv->v_counter = Pico.m.scanline = y;
-    if (y > 218)
-      pv->v_counter = y - 6;
+    Pico.t.z80c_line_start = Pico.t.z80c_aim;
+
+    // advance the line counter. It is set back at some point in the VBLANK so
+    // that the line count in the active area (-32..lines+1) is contiguous.
+    pv->v_counter = Pico.m.scanline = (u8)y;
+    switch (is_pal ? -lines_vis : lines_vis) {
+    case  192: if (y > 218) pv->v_counter = y - (lines-256); break;
+    case  224: if (y > 234) pv->v_counter = y - (lines-256); break;
+    case -192: if (y > 242) pv->v_counter = y - (lines-256); break;
+    case -224: if (y > 258) pv->v_counter = y - (lines-256); break;
+    case -240: if (y > 266) pv->v_counter = y - (lines-256); break;
+    }
+
+    // Parse sprites for the next line
+    if (y < lines_vis)
+      PicoParseSATSMS(y-1);
+    else if (y > lines-32)
+      PicoParseSATSMS(y-1-lines);
 
+    // render next line
     if (y < lines_vis && !skip)
-      PicoLineMode4(y);
+      PicoLineSMS(y);
 
+    // take over status bits from previously rendered line TODO: cycle exact?
+    pv->status |= sprites_status;
+    sprites_status = 0;
+
+    // Interrupt handling. Simulate interrupt flagged and immediately reset in
+    // same insn by flagging the irq, execute for 1 insn, then checking if the
+    // irq is still pending. (GG Chicago, SMS Back to the Future III)
+    pv->pending_ints &= ~2; // lost if not caught in the same line
     if (y <= lines_vis)
     {
       if (--hint < 0)
       {
         hint = pv->reg[0x0a];
         pv->pending_ints |= 2;
-        if (pv->reg[0] & 0x10) {
+        z80_exec(Pico.t.z80c_cnt + 1);
+
+        if ((pv->reg[0] & 0x10) && (pv->pending_ints & 2)) {
           elprintf(EL_INTS, "hint");
           z80_int_assert(1);
         }
@@ -366,14 +744,15 @@ void PicoFrameMS(void)
     }
     else if (y == lines_vis + 1) {
       pv->pending_ints |= 1;
-      if (pv->reg[1] & 0x20) {
+      z80_exec(Pico.t.z80c_cnt + 1);
+
+      if ((pv->reg[1] & 0x20) && (pv->pending_ints & 1)) {
         elprintf(EL_INTS, "vint");
         z80_int_assert(1);
       }
     }
 
-    cycles_aim += cycles_line;
-    cycles_done += z80_run((cycles_aim - cycles_done) >> 8) << 8;
+    z80_exec(Pico.t.z80c_line_start + cycles_line);
   }
 
   if (PicoIn.sndOut)
@@ -382,13 +761,18 @@ void PicoFrameMS(void)
 
 void PicoFrameDrawOnlyMS(void)
 {
+  struct PicoVideo *pv = &Pico.video;
   int lines_vis = 192;
   int y;
 
-  PicoFrameStartMode4();
+  if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18))
+    lines_vis = (pv->reg[1] & 0x08) ? 240 : 224;
+  PicoFrameStartSMS();
 
-  for (y = 0; y < lines_vis; y++)
-    PicoLineMode4(y);
+  for (y = 0; y < lines_vis; y++) {
+    PicoParseSATSMS(y-1);
+    PicoLineSMS(y);
+  }
 }
 
 // vim:ts=2:sw=2:expandtab

+ 1 - 1
pico/sound/emu2413

@@ -1 +1 @@
-Subproject commit 9f25348936e691252afcfc992069c5a4810bf3f2
+Subproject commit 4062e93e9316ee5c488167ade5a83753e5f07ae3

+ 1 - 1
pico/sound/mix.c

@@ -14,7 +14,7 @@
 
 /* limitter */
 #define Limit16(val) \
-	val -= val >> 2; /* reduce level to avoid clipping */	\
+	val -= val >> 3; /* reduce level to avoid clipping */	\
 	if ((short)val != val) val = (val < 0 ? MINOUT : MAXOUT)
 
 int mix_32_to_16l_level;

+ 36 - 50
pico/sound/sn76496.c

@@ -22,7 +22,7 @@
 
 #include "sn76496.h"
 
-#define MAX_OUTPUT 0x47ff // was 0x7fff
+#define MAX_OUTPUT 0x4800 // was 0x7fff
 
 #define STEP 0x10000
 
@@ -31,22 +31,15 @@
 /* bit0 = output */
 
 /* noise feedback for white noise mode (verified on real SN76489 by John Kortink) */
-#define FB_WNOISE 0x14002	/* (16bits) bit16 = bit0(out) ^ bit2 ^ bit15 */
+#define FB_WNOISE_T 0x3000	/* (15bits) bit15 = bit1 ^ bit2, TI */
+#define FB_WNOISE_S 0x9000	/* (16bits) bit16 = bit0 ^ bit3, Sega PSG */
 
 /* noise feedback for periodic noise mode */
-//#define FB_PNOISE 0x10000 /* 16bit rorate */
-#define FB_PNOISE 0x08000   /* JH 981127 - fixes Do Run Run */
+#define FB_PNOISE_T 0x4000	/* 15bit rotate for TI */
+#define FB_PNOISE_S 0x8000	/* 16bit rotate for Sega PSG */
 
-/*
-0x08000 is definitely wrong. The Master System conversion of Marble Madness
-uses periodic noise as a baseline. With a 15-bit rotate, the bassline is
-out of tune.
-The 16-bit rotate has been confirmed against a real PAL Sega Master System 2.
-Hope that helps the System E stuff, more news on the PSG as and when!
-*/
-
-/* noise generator start preset (for periodic noise) */
-#define NG_PRESET 0x0f35
+#define FB_WNOISE FB_WNOISE_S	/* Sega */
+#define FB_PNOISE FB_PNOISE_S
 
 
 struct SN76496
@@ -58,7 +51,7 @@ struct SN76496
 	int Register[8];	/* registers */
 	int LastRegister;	/* last register written */
 	int Volume[4];		/* volume of voice 0-2 and noise */
-	unsigned int RNG;		/* noise generator      */
+	unsigned int RNG;	/* noise generator      */
 	int NoiseFB;		/* noise feedback mask */
 	int Period[4];
 	int Count[4];
@@ -97,6 +90,7 @@ void SN76496Write(int data)
 		case 4:	/* tone 2 : frequency */
 			R->Period[c] = R->UpdateStep * data;
 			if (R->Period[c] == 0) R->Period[c] = R->UpdateStep;
+			if (R->Count[c] > R->Period[c]) R->Count[c] = R->Period[c];
 			if (r == 4)
 			{
 				/* update noise shift frequency */
@@ -115,10 +109,10 @@ void SN76496Write(int data)
 			R->NoiseFB = (n & 4) ? FB_WNOISE : FB_PNOISE;
 			n &= 3;
 			/* N/512,N/1024,N/2048,Tone #3 output */
-			R->Period[3] = (n == 3) ? 2 * R->Period[2] : (R->UpdateStep << (5 + n));
+			R->Period[3] = 2 * (n == 3 ? R->Period[2] : R->UpdateStep << (4 + n));
 
 			/* reset noise shifter */
-			R->RNG = NG_PRESET;
+			R->RNG = FB_PNOISE;
 			R->Output[3] = R->RNG & 1;
 			break;
 	}
@@ -138,18 +132,6 @@ void SN76496Update(short *buffer, int length, int stereo)
 	int i;
 	struct SN76496 *R = &ono_sn;
 
-	/* If the volume is 0, increase the counter */
-	for (i = 0;i < 4;i++)
-	{
-		if (R->Volume[i] == 0)
-		{
-			/* note that I do count += length, NOT count = length + 1. You might think */
-			/* it's the same since the volume is 0, but doing the latter could cause */
-			/* interferencies when the program is rapidly modulating the volume. */
-			if (R->Count[i] <= length*STEP) R->Count[i] += length*STEP;
-		}
-	}
-
 	while (length > 0)
 	{
 		int vol[4];
@@ -173,13 +155,17 @@ void SN76496Update(short *buffer, int length, int stereo)
 			/* If we exit the loop in the middle, Output[i] has to be inverted */
 			/* and vol[i] incremented only if the exit status of the square */
 			/* wave is 1. */
-			left = 0;
-			while (R->Count[i] <= 0)
+			if (R->Count[i] < -2*R->Period[i] || R->Volume[i] == 0) {
+				/* Cut off anything above the Nyquist frequency. */
+				/* It will only create aliasing anyway. This is actually an */
+				/* ideal lowpass filter with Nyquist corner frequency. */
+				vol[i] += STEP/2; // mean value
+				R->Count[i] = R->Output[i] = 0;
+			}
+			while (R->Count[i] < 0)
 			{
-				if (R->Count[i] + R->Period[i]*4 < R->Period[i])
-					left+= 4, R->Count[i] += R->Period[i]*4;
-				else	left++,   R->Count[i] += R->Period[i];
-				if (R->Count[i] > 0)
+				R->Count[i] += R->Period[i];
+				if (R->Count[i] >= 0)
 				{
 					R->Output[i] ^= 1;
 					if (R->Output[i]) vol[i] += R->Period[i];
@@ -189,12 +175,10 @@ void SN76496Update(short *buffer, int length, int stereo)
 				vol[i] += R->Period[i];
 			}
 			if (R->Output[i]) vol[i] -= R->Count[i];
-			/* Cut of anything above the sample freqency. It will only create */
-			/* aliasing and hearable distortions anyway. */
-			if (left > 1) vol[i] = STEP/2;
 		}
 
 		left = STEP;
+		if (R->Output[3]) vol[3] += R->Count[3];
 		do
 		{
 			int nextevent;
@@ -202,27 +186,29 @@ void SN76496Update(short *buffer, int length, int stereo)
 			if (R->Count[3] < left) nextevent = R->Count[3];
 			else nextevent = left;
 
-			if (R->Output[3]) vol[3] += R->Count[3];
 			R->Count[3] -= nextevent;
 			if (R->Count[3] <= 0)
 			{
-				if (R->RNG & 1) R->RNG ^= R->NoiseFB;
-				R->RNG >>= 1;
 				R->Output[3] = R->RNG & 1;
+				R->RNG >>= 1;
+				if (R->Output[3])
+				{
+					R->RNG ^= R->NoiseFB;
+					vol[3] += R->Period[3];
+				}
 				R->Count[3] += R->Period[3];
-				if (R->Output[3]) vol[3] += R->Period[3];
 			}
-			if (R->Output[3]) vol[3] -= R->Count[3];
 
 			left -= nextevent;
-		} while (left > 0);
+		} while (left > 0 && R->Volume[3]);
+		if (R->Output[3]) vol[3] -= R->Count[3];
 
 		out = vol[0] * R->Volume[0] + vol[1] * R->Volume[1] +
 				vol[2] * R->Volume[2] + vol[3] * R->Volume[3];
 
 		if (out > MAX_OUTPUT * STEP) out = MAX_OUTPUT * STEP;
 
-		if ((out /= STEP)) // will be optimized to shift; max 0x47ff = 18431
+		if ((out /= STEP)) // will be optimized to shift; max 0x4800 = 18432
 			*buffer += out;
 		if(stereo) buffer+=2; // only left for stereo, to be mixed to right later
 		else buffer++;
@@ -254,7 +240,7 @@ static void SN76496_set_gain(struct SN76496 *R,int gain)
 	gain &= 0xff;
 
 	/* increase max output basing on gain (0.2 dB per step) */
-	out = MAX_OUTPUT / 3;
+	out = MAX_OUTPUT / 4.0;
 	while (gain-- > 0)
 		out *= 1.023292992;	/* = (10 ^ (0.2/20)) */
 
@@ -262,7 +248,7 @@ static void SN76496_set_gain(struct SN76496 *R,int gain)
 	for (i = 0;i < 15;i++)
 	{
 		/* limit volume to avoid clipping */
-		if (out > MAX_OUTPUT / 3) R->VolTable[i] = MAX_OUTPUT / 3;
+		if (out > MAX_OUTPUT / 4) R->VolTable[i] = MAX_OUTPUT / 4;
 		else R->VolTable[i] = out;
 
 		out /= 1.258925412;	/* = 10 ^ (2/20) = 2dB */
@@ -294,10 +280,10 @@ int SN76496_init(int clock,int sample_rate)
 
 	for (i = 0;i < 4;i++)
 	{
-		R->Output[i] = 0;
-		R->Period[i] = R->Count[i] = R->UpdateStep;
+		R->Volume[i] = R->Output[i] = R->Count[i] = 0;
+		R->Period[i] = R->UpdateStep;
 	}
-	R->RNG = NG_PRESET;
+	R->RNG = FB_PNOISE;
 	R->Output[3] = R->RNG & 1;
 
 	// added

+ 29 - 28
pico/sound/sound.c

@@ -17,7 +17,8 @@
 void (*PsndMix_32_to_16l)(short *dest, int *src, int count) = mix_32_to_16l_stereo;
 
 // master int buffer to mix to
-static s32 PsndBuffer[2*(44100+100)/50];
+// +1 for a fill triggered by an instruction overhanging into the next scanline
+static s32 PsndBuffer[2*(44100+100)/50+2];
 
 // cdda output buffer
 s16 cdda_out_buffer[2*1152];
@@ -66,7 +67,9 @@ void PsndRerate(int preserve_state)
     ym2612_pack_state();
     memcpy(state, YM2612GetRegs(), 0x204);
   }
-  YM2612Init(Pico.m.pal ? OSC_PAL/7 : OSC_NTSC/7, PicoIn.sndRate, !(PicoIn.opt&POPT_DIS_FM_SSGEG));
+  YM2612Init(Pico.m.pal ? OSC_PAL/7 : OSC_NTSC/7, PicoIn.sndRate,
+        ((PicoIn.opt&POPT_DIS_FM_SSGEG) ? 0 : ST_SSG) |
+        ((PicoIn.opt&POPT_EN_FM_DAC)    ? ST_DAC : 0));
   if (preserve_state) {
     // feed it back it's own registers, just like after loading state
     memcpy(YM2612GetRegs(), state, 0x204);
@@ -161,21 +164,21 @@ PICO_INTERNAL void PsndDoDAC(int cyc_to)
   Pico.snd.dac_val = dout;
 }
 
-PICO_INTERNAL void PsndDoPSG(int line_to)
+PICO_INTERNAL void PsndDoPSG(int cyc_to)
 {
   int pos, len;
   int stereo = 0;
 
-  // Q16, number of samples since last call
-  len = ((line_to+1) * Pico.snd.smpl_mult) - Pico.snd.psg_pos;
-  if (len <= 0)
-    return;
+  // number of samples to fill in buffer (Q20)
+  len = (cyc_to * Pico.snd.clkl_mult) - Pico.snd.psg_pos;
 
   // update position and calculate buffer offset and length
-  pos = (Pico.snd.psg_pos+0x8000) >> 16;
+  pos = (Pico.snd.psg_pos+0x80000) >> 20;
   Pico.snd.psg_pos += len;
-  len = ((Pico.snd.psg_pos+0x8000) >> 16) - pos;
+  len = ((Pico.snd.psg_pos+0x80000) >> 20) - pos;
 
+  if (len <= 0)
+    return;
   if (!PicoIn.sndOut || !(PicoIn.opt & POPT_EN_PSG))
     return;
 
@@ -187,22 +190,22 @@ PICO_INTERNAL void PsndDoPSG(int line_to)
 }
 
 #if 0
-PICO_INTERNAL void PsndDoYM2413(int line_to)
+PICO_INTERNAL void PsndDoYM2413(int cyc_to)
 {
   int pos, len;
   int stereo = 0;
   short *buf;
 
-  // Q16, number of samples since last call
-  len = ((line_to+1) * Pico.snd.smpl_mult) - Pico.snd.ym2413_pos;
-  if (len <= 0)
-    return;
+  // number of samples to fill in buffer (Q20)
+  len = (cyc_to * Pico.snd.clkl_mult) - Pico.snd.ym2413_pos;
 
   // update position and calculate buffer offset and length
-  pos = (Pico.snd.ym2413_pos+0x8000) >> 16;
+  pos = (Pico.snd.ym2413_pos+0x80000) >> 20;
   Pico.snd.ym2413_pos += len;
-  len = ((Pico.snd.ym2413_pos+0x8000) >> 16) - pos;
+  len = ((Pico.snd.ym2413_pos+0x80000) >> 20) - pos;
 
+  if (len <= 0)
+    return;
   if (!PicoIn.sndOut || !(PicoIn.opt & POPT_EN_YM2413))
     return;
 
@@ -233,17 +236,15 @@ PICO_INTERNAL void PsndDoFM(int cyc_to)
   int pos, len;
   int stereo = 0;
 
-  // Q16, number of samples since last call
+  // Q20, number of samples since last call
   len = (cyc_to * Pico.snd.clkl_mult) - Pico.snd.fm_pos;
 
-  // don't do this too often (about once every scanline)
-  if (len >> 16 <= PicoIn.sndRate >> 10)
-    return;
-
   // update position and calculate buffer offset and length
   pos = (Pico.snd.fm_pos+0x80000) >> 20;
   Pico.snd.fm_pos += len;
   len = ((Pico.snd.fm_pos+0x80000) >> 20) - pos;
+  if (len <= 0)
+    return;
 
   // fill buffer
   if (PicoIn.opt & POPT_EN_STEREO) {
@@ -326,7 +327,7 @@ static int PsndRender(int offset, int length)
   int stereo = (PicoIn.opt & 8) >> 3;
   int fmlen = ((Pico.snd.fm_pos+0x80000) >> 20);
   int daclen = ((Pico.snd.dac_pos+0x80000) >> 20);
-  int psglen = ((Pico.snd.psg_pos+0x8000) >> 16);
+  int psglen = ((Pico.snd.psg_pos+0x80000) >> 20);
 
   buf32 = PsndBuffer+(offset<<stereo);
 
@@ -337,7 +338,7 @@ static int PsndRender(int offset, int length)
     return length;
   }
 
-  // Fill up DAC output in case of missing samples (Q16 rounding errors)
+  // Fill up DAC output in case of missing samples (Q rounding errors)
   if (length-daclen > 0) {
     short *dacbuf = PicoIn.sndOut + (daclen << stereo);
     Pico.snd.dac_pos += (length-daclen) << 20;
@@ -353,7 +354,7 @@ static int PsndRender(int offset, int length)
   // Add in parts of the PSG output not yet done
   if (length-psglen > 0) {
     short *psgbuf = PicoIn.sndOut + (psglen << stereo);
-    Pico.snd.psg_pos += (length-psglen) << 16;
+    Pico.snd.psg_pos += (length-psglen) << 20;
     if (PicoIn.opt & POPT_EN_PSG)
       SN76496Update(psgbuf, length-psglen, stereo);
   }
@@ -410,22 +411,22 @@ PICO_INTERNAL void PsndGetSamples(int y)
 static int PsndRenderMS(int offset, int length)
 {
   int stereo = (PicoIn.opt & 8) >> 3;
-  int psglen = ((Pico.snd.psg_pos+0x8000) >> 16);
-  int ym2413len = ((Pico.snd.ym2413_pos+0x8000) >> 16);
+  int psglen = ((Pico.snd.psg_pos+0x80000) >> 20);
+  int ym2413len = ((Pico.snd.ym2413_pos+0x80000) >> 20);
 
   pprof_start(sound);
 
   // Add in parts of the PSG output not yet done
   if (length-psglen > 0) {
     short *psgbuf = PicoIn.sndOut + (psglen << stereo);
-    Pico.snd.psg_pos += (length-psglen) << 16;
+    Pico.snd.psg_pos += (length-psglen) << 20;
     if (PicoIn.opt & POPT_EN_PSG)
       SN76496Update(psgbuf, length-psglen, stereo);
   }
 
   if (length-ym2413len > 0) {
     short *ym2413buf = PicoIn.sndOut + (ym2413len << stereo);
-    Pico.snd.ym2413_pos += (length-ym2413len) << 16;
+    Pico.snd.ym2413_pos += (length-ym2413len) << 20;
     int len = (length-ym2413len);
     if (PicoIn.opt & POPT_EN_YM2413){
       while (len-- > 0) {

+ 20 - 4
pico/sound/ym2612.c

@@ -918,6 +918,15 @@ typedef struct
 
 
 #if !defined(_ASM_YM2612_C) || defined(EXTERNAL_YM2612)
+#include <limits.h>
+static int clip(int n) 
+{
+    unsigned b = 14, s = n < 0;
+    int m = s + INT_MAX;
+    if (s + (n>>(b-1))) n = m >> (8*sizeof(int)-b);
+    return n;
+}
+
 static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
 {
 	int scounter;					/* sample counter */
@@ -1226,6 +1235,11 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
 
 		/* mix sample to output buffer */
 		if (smp) {
+			smp = clip(smp); /* saturate to 14 bit */
+			if (ct->algo & 0x80) {
+				smp &= ~0x1f; /* drop bits (DAC has 9 bits) */
+				smp -= (smp < 0 ? 7:0) << 5; /* discontinuity */
+			}
 			if (ct->pack & 1) { /* stereo */
 				if (ct->pack & 0x20) /* L */ /* TODO: check correctness */
 					buffer[scounter*2] += smp;
@@ -1256,7 +1270,7 @@ static void chan_render_prep(void)
 	crct.lfo_inc = ym2612.OPN.lfo_inc;
 }
 
-static void chan_render_finish(void)
+static void chan_render_finish(int *buffer, unsigned short length, int active_chans)
 {
 	ym2612.OPN.eg_cnt = crct.eg_cnt;
 	ym2612.OPN.eg_timer = crct.eg_timer;
@@ -1321,6 +1335,8 @@ static int chan_render(int *buffer, int length, int c, UINT32 flags) // flags: s
 
 	crct.op1_out = crct.CH->op1_out;
 	crct.algo = crct.CH->ALGO & 7;
+	if (ym2612.OPN.ST.flags & ST_DAC)
+		crct.algo |= 0x80;
 
 	if(crct.CH->pms && (ym2612.OPN.ST.mode & 0xC0) && c == 2) {
 		/* 3 slot mode */
@@ -1835,21 +1851,21 @@ int YM2612UpdateOne_(int *buffer, int length, int stereo, int is_buf_empty)
 	BIT_IF(flags, 1, (ym2612.ssg_mask & 0xf00000) && (ym2612.OPN.ST.flags & 1));
 	if (ym2612.slot_mask & 0xf00000) active_chs |= chan_render(buffer, length, 5, flags|((pan&0xc00)>>6)|(!!ym2612.dacen<<2)) << 5;
 #undef	BIT_IF
-	chan_render_finish();
+	chan_render_finish(buffer, length, active_chs);
 
 	return active_chs; // 1 if buffer updated
 }
 
 
 /* initialize YM2612 emulator */
-void YM2612Init_(int clock, int rate, int ssg)
+void YM2612Init_(int clock, int rate, int flags)
 {
 	memset(&ym2612, 0, sizeof(ym2612));
 	init_tables();
 
 	ym2612.OPN.ST.clock = clock;
 	ym2612.OPN.ST.rate = rate;
-	ym2612.OPN.ST.flags = (ssg ? 1:0);
+	ym2612.OPN.ST.flags = flags;
 
 	OPNSetPres( 6*24 );
 

+ 7 - 4
pico/sound/ym2612.h

@@ -108,6 +108,9 @@ typedef struct
 	INT32	dt_tab[8][32];/* DeTune table       */
 } FM_ST;
 
+#define ST_SSG		1
+#define ST_DAC		2
+
 /***********************************************************/
 /* OPN unit                                                */
 /***********************************************************/
@@ -162,7 +165,7 @@ typedef struct
 extern YM2612 ym2612;
 #endif
 
-void YM2612Init_(int baseclock, int rate, int ssg);
+void YM2612Init_(int baseclock, int rate, int flags);
 void YM2612ResetChip_(void);
 int  YM2612UpdateOne_(int *buffer, int length, int stereo, int is_buf_empty);
 
@@ -184,9 +187,9 @@ int  YM2612PicoStateLoad2(int *tat, int *tbt);
 #else
 /* GP2X specific */
 #include <platform/gp2x/940ctl.h>
-#define YM2612Init(baseclock,rate,ssg) do { \
-	if (PicoIn.opt&POPT_EXT_FM) YM2612Init_940(baseclock, rate, ssg); \
-	else               YM2612Init_(baseclock, rate, ssg); \
+#define YM2612Init(baseclock,rate,flags) do { \
+	if (PicoIn.opt&POPT_EXT_FM) YM2612Init_940(baseclock, rate, flags); \
+	else               YM2612Init_(baseclock, rate, flags); \
 } while (0)
 #define YM2612ResetChip() do { \
 	if (PicoIn.opt&POPT_EXT_FM) YM2612ResetChip_940(); \

+ 22 - 12
pico/sound/ym2612_arm.S

@@ -166,7 +166,7 @@
     addpl   r0, r0, r2, asr #4
     cmp     r0, #0               @ if (volume <= MIN_ATT_INDEX)
     bgt     10f
-    ldr     r2, [r5,#0x1c]
+    ldr     r2, [r5,#0x1c]       @ sl
     mov     r0, #0
     cmp     r2, #0
     movne   r3, #EG_DEC
@@ -175,8 +175,7 @@
     b       10f
 
 1:  @ EG_REL
-    mov     r2, #0x200
-    cmp     r0, r2               @ if ( volume >= 0x200 )
+    cmp     r0, #0x200           @ if ( volume >= 0x200 )
     movge   r0, #1024
     subge   r0, #1
     movge   r3, #EG_OFF
@@ -236,7 +235,7 @@
     eorne   r0, r0, #0x400                @ ssgn ^= 4
     strneh  r0, [r5,#0x30]
     moveq   r0, #0
-    streq   r0, [r5,#0x0c]                @ phase = 0
+    streq   r0, [lr,#0x10]                @ phase = 0
 
     cmp     r2, #EG_ATT                   @ if ( state != EG_ATT )
     beq     9f
@@ -250,11 +249,11 @@
     cmp     r0, #32+62                    @ if ( ar+ksr >= 32+62 )
     movge   r3, #0
     strgeh  r3, [r5,#0x1a]                @ volume = MIN_ATT
-    bge     9f
+    bge     8f
 
     cmp     r3, #0
     movgt   r2, #EG_ATT
-    strb    r2, [r5,#0x17]                @ state
+8:  strb    r2, [r5,#0x17]                @ state
 9:
 .endm
 
@@ -277,8 +276,6 @@
     rsbge   r0, r0, #0x200                @ volume = (0x200-volume) & MAX_ATT
     lslge   r0, r0, #22
     lsrge   r0, r0, #22
-    ldrh    r0, [r5,#0x1a]                @ volume
-    ldrh    r3, [r5,#0x18]                @ tl
 
     add     r0, r0, r3                    @ volume += tl
     strh    r0, [r5,#0x34]                @ vol_out
@@ -680,7 +677,7 @@
 
 
 @ lr=context, r12=pack (stereo, ssg_enabled, disabled, lfo_enabled | pan_r, pan_l, ams[2] | AMmasks[4] | FB[4] | lfo_ampm[16])
-@ r0-r2=scratch, r3=sin_tab/scratch, r4=(length<<8)|unused[3],ssg_update,was_update,algo[3], r5=tl_tab/slot,
+@ r0-r2=scratch, r3=sin_tab/scratch, r4=(length<<8)|dac,unused[2],ssg_update,was_update,algo[3], r5=tl_tab/slot,
 @ r6-r7=vol_out[4], r8=eg_timer, r9=eg_timer_add[31:16], r10=op1_out, r11=buffer
 .global chan_render_loop @ chan_rend_context *ct, int *buffer, int length
 
@@ -691,8 +688,8 @@ chan_render_loop:
     ldr     r12, [lr, #0x4c]
     ldr     r0,  [lr, #0x50]
     mov     r11, r1
-    and     r0,  r0, #7
-    orr     r4,  r4, r0          @ (length<<8)|algo
+    and     r0,  r0, #0x87
+    orr     r4,  r4, r0          @ (length<<8)|dac,unused[4],algo[3]
     ldr     r8, [lr, #0x44]      @ eg_timer
     ldr     r9, [lr, #0x48]      @ eg_timer_add
     ldr     r10, [lr, #0x54]     @ op1_out
@@ -731,18 +728,24 @@ crl_loop:
 
 ssg_loop:
     mov     r6, #4
+    bic     r4, r4, #0x10           @ ssg_update
 ssg_upd_loop:
+    @ use lr as a pointer to the slot phases stored in the context
     update_ssg_eg
 #if 0
     subs    r6, r6, #1
+    addne   lr, lr, #4
     addne   r5, r5, #SLOT_STRUCT_SIZE
 #else
+    add     lr, lr, #4*2
     add     r5, r5, #SLOT_STRUCT_SIZE*2
     update_ssg_eg
     subs    r6, r6, #2
+    subne   lr, lr, #4
     subne   r5, r5, #SLOT_STRUCT_SIZE
 #endif
     bne     ssg_upd_loop
+    sub     lr, lr, #4*3
     sub     r5, r5, #SLOT_STRUCT_SIZE*3
 
     subs    r7, r7, #1<<EG_SH
@@ -753,7 +756,7 @@ ssg_done:
     @ -- EG --
     add     r8, r8, r9
     cmp     r8, #EG_TIMER_OVERFLOW
-    bcc     volout_upd
+    blo     volout_upd
     ldr     r1, [lr, #0x3c]     @ eg_cnt
 eg_loop:
     sub     r8, r8, #EG_TIMER_OVERFLOW
@@ -921,6 +924,13 @@ crl_algo_done:
     tst     r0, r0
     beq     ctl_sample_skip
     orr     r4, r4, #8              @ have_output
+    lsr     r1, r0, #31             @ clip (saturate) sample to 14 bit
+    cmn     r1, r0, asr #13
+    subne   r0, r1, #0x80000001
+    asrne   r0, r0, #18
+    tst     r4, r1, lsl #7          @ (sample < 0) && dac?
+    bicne   r0, r0, #0x1f
+    subne   r0, r0, #7<<5
     tst     r12, #1
     beq     ctl_sample_mono
 

+ 37 - 27
pico/videoport.c

@@ -158,7 +158,7 @@ int (*PicoDmaHook)(u32 source, int len, unsigned short **base, u32 *mask) = NULL
  *			full	total==4
  *			wait	total>4
  * Conditions:
- * fifo_slot is always behind slot2cyc[cycles]. Advancing it beyond cycles
+ * fifo_slot is normally behind slot2cyc[cycles]. Advancing it beyond cycles
  * implies blocking the 68k up to that slot.
  *
  * A FIFO write goes to the end of the FIFO queue, but DMA running in background
@@ -179,11 +179,11 @@ int (*PicoDmaHook)(u32 source, int len, unsigned short **base, u32 *mask) = NULL
 // NB code assumes fifo_* arrays have size 2^n
 static struct VdpFIFO { // XXX this must go into save file!
   // last transferred FIFO data, ...x = index  XXX currently only CPU
-  unsigned short fifo_data[4], fifo_dx;
+  u16 fifo_data[4], fifo_dx;
 
   // queued FIFO transfers, ...x = index, ...l = queue length
   // each entry has 2 values: [n]>>3 = #writes, [n]&7 = flags (FQ_*)
-  unsigned int fifo_queue[8], fifo_qx, fifo_ql;
+  u32 fifo_queue[8], fifo_qx, fifo_ql;
   int fifo_total;             // total# of pending FIFO entries (w/o BGDMA)
 
   unsigned short fifo_slot;   // last executed slot in current scanline
@@ -212,33 +212,30 @@ static __inline int AdvanceFIFOEntry(struct VdpFIFO *vf, struct PicoVideo *pv, i
     l = cnt;
   if (!(vf->fifo_queue[vf->fifo_qx] & FQ_BGDMA))
     vf->fifo_total -= ((cnt & b) + l) >> b;
-  if (vf->fifo_total < 0) vf->fifo_total = 0;
   cnt -= l;
+  pv->fifo_cnt = cnt;
 
   // if entry has been processed...
   if (cnt == 0) {
     // remove entry from FIFO
-    if (vf->fifo_ql) {
-      vf->fifo_queue[vf->fifo_qx] = 0;
-      vf->fifo_qx = (vf->fifo_qx+1) & 7, vf->fifo_ql --;
-    }
+    vf->fifo_queue[vf->fifo_qx] = 0;
+    vf->fifo_qx = (vf->fifo_qx+1) & 7, vf->fifo_ql --;
     // start processing for next entry if there is one
     if (vf->fifo_ql) {
       b = vf->fifo_queue[vf->fifo_qx] & FQ_BYTE;
-      cnt = (vf->fifo_queue[vf->fifo_qx] >> 3) << b;
+      pv->fifo_cnt = (vf->fifo_queue[vf->fifo_qx] >> 3) << b;
     } else { // FIFO empty
       pv->status &= ~PVS_FIFORUN;
       vf->fifo_total = 0;
     }
   }
 
-  pv->fifo_cnt = cnt;
   return l;
 }
 
 static __inline void SetFIFOState(struct VdpFIFO *vf, struct PicoVideo *pv)
 {
-  unsigned int st = pv->status, cmd = pv->command;
+  u32 st = pv->status, cmd = pv->command;
   // release CPU and terminate DMA if FIFO isn't blocking the 68k anymore
   if (vf->fifo_total <= 4) {
     st &= ~PVS_CPUWR;
@@ -287,28 +284,29 @@ static int PicoVideoFIFODrain(int level, int cycles, int bgdma)
   struct VdpFIFO *vf = &VdpFIFO;
   struct PicoVideo *pv = &Pico.video;
   unsigned ocyc = cycles;
+  int bd = vf->fifo_queue[vf->fifo_qx] & bgdma;
   int burn = 0;
 
   // process FIFO entries until low level is reached
-  while (vf->fifo_slot <= vf->fifo_maxslot && cycles < 488 &&
-         ((vf->fifo_total > level) | (vf->fifo_queue[vf->fifo_qx] & bgdma))) {
+  while (vf->fifo_slot < vf->fifo_maxslot &&
+         vf->fifo_ql && ((vf->fifo_total > level) | bd)) {
     int b = vf->fifo_queue[vf->fifo_qx] & FQ_BYTE;
-    int cnt = bgdma ? pv->fifo_cnt : ((vf->fifo_total-level)<<b) - (pv->fifo_cnt&b);
+    int cnt = bd ? pv->fifo_cnt : ((vf->fifo_total-level)<<b) - (pv->fifo_cnt&b);
     int slot = (pv->fifo_cnt<cnt ? pv->fifo_cnt:cnt) + vf->fifo_slot;
 
     if (slot > vf->fifo_maxslot) {
       // target slot in later scanline, advance to eol
       slot = vf->fifo_maxslot;
-      cycles = 488;
-    } else {
-      // advance FIFO to target slot and CPU to cycles at that slot
-      cycles = Sl2Cyc(vf, slot);
     }
     if (slot > vf->fifo_slot) {
-      AdvanceFIFOEntry(vf, pv, slot - vf->fifo_slot);
-      vf->fifo_slot = slot;
+      // advance FIFO to target slot and CPU to cycles at that slot
+      vf->fifo_slot += AdvanceFIFOEntry(vf, pv, slot - vf->fifo_slot);
+      cycles = Sl2Cyc(vf, vf->fifo_slot);
+      bd = vf->fifo_queue[vf->fifo_qx] & bgdma;
     }
   }
+  if (vf->fifo_ql && ((vf->fifo_total > level) | bd))
+    cycles = 488; // not completed in this scanline
   if (cycles > ocyc)
     burn = cycles - ocyc;
 
@@ -363,11 +361,11 @@ int PicoVideoFIFOWrite(int count, int flags, unsigned sr_mask,unsigned sr_flags)
       // XXX if interrupting a DMA fill, fill data changes
       if (x == vf->fifo_qx) { // overtaking to queue head?
         int f = vf->fifo_queue[x] & 7;
-        vf->fifo_queue[(x+1) & 7] = (pv->fifo_cnt >> (f & FQ_BYTE) << 3) | f;
+        vf->fifo_queue[x] = (pv->fifo_cnt >> (f & FQ_BYTE) << 3) | f;
         pv->status &= ~PVS_FIFORUN;
-      } else
-        // push background DMA back
-        vf->fifo_queue[(x+1) & 7] = vf->fifo_queue[x];
+      }
+      // push background DMA back
+      vf->fifo_queue[(x+1) & 7] = vf->fifo_queue[x];
       x = (x-1) & 7;
     }
 
@@ -911,6 +909,7 @@ PICO_INTERNAL_ASM void PicoVideoWrite(u32 a,unsigned short d)
         if (num == 12 && ((pvid->reg[12]^d)&0x01))
           PicoVideoFIFOMode(pvid->reg[1]&0x40, d & 1);
         DrawSync(SekCyclesDone() - Pico.t.m68c_line_start <= 488-390);
+        d &= 0xff;
         pvid->reg[num]=(unsigned char)d;
         switch (num)
         {
@@ -1146,14 +1145,25 @@ void PicoVideoLoad(void)
 {
   struct VdpFIFO *vf = &VdpFIFO;
   struct PicoVideo *pv = &Pico.video;
+  int b = pv->type == 1;
 
   // convert former dma_xfers (why was this in PicoMisc anyway?)
   if (Pico.m.dma_xfers) {
-    pv->status |= SR_DMA|PVS_FIFORUN;
-    pv->fifo_cnt = Pico.m.dma_xfers * (pv->type == 1 ? 2 : 1);
-    vf->fifo_total = Pico.m.dma_xfers;
+    pv->status |= SR_DMA;
+    pv->fifo_cnt = Pico.m.dma_xfers << b;
     Pico.m.dma_xfers = 0;
   }
+  // make an entry in the FIFO if there are outstanding transfers
+  vf->fifo_ql = vf->fifo_total = 0;
+  if (pv->fifo_cnt) {
+    pv->status |= PVS_FIFORUN|PVS_CPUWR;
+    if (!(pv->status & PVS_DMABG))
+      vf->fifo_total = (pv->fifo_cnt + b) >> b;
+    if ((pv->status & SR_DMA) && !(pv->status & PVS_DMAFILL))
+      b |= (pv->status & PVS_DMABG) ? FQ_BGDMA : FQ_FGDMA;
+    vf->fifo_queue[vf->fifo_qx] = (vf->fifo_total << 3) | b;
+    vf->fifo_ql = 1;
+  }
   PicoVideoCacheSAT();
 }
 // vim:shiftwidth=2:ts=2:expandtab

+ 9 - 0
picodrive.sh

@@ -0,0 +1,9 @@
+#!/bin/sh
+# Compare md5 of the PicoDrive binary in /usr/games with the OPK one
+if [ `md5sum /usr/games/PicoDrive | cut -d' ' -f1` != `md5sum PicoDrive | cut -d' ' -f1` ]; then
+	rw
+	cp -f PicoDrive /usr/games
+	sed -i 's|list.extensions.*|list.extensions = zip,ZIP,sms,SMS,bin,BIN,gg,GG|' /usr/games/collections/Sega\ Master\ System/settings.conf
+	ro
+fi
+exec /usr/games/launchers/megadrive_launch.sh "$1"

+ 1 - 1
platform/common/arm_utils.h

@@ -1,5 +1,5 @@
 
-void bgr444_to_rgb32(void *to, void *from);
+void bgr444_to_rgb32(void *to, void *from, unsigned entries);
 void bgr444_to_rgb32_sh(void *to, void *from);
 
 void vidcpy_m2(void *dest, void *src, int m32col, int with_32c_border);

+ 29 - 31
platform/common/arm_utils.s

@@ -9,10 +9,10 @@
 .text
 .align 4
 
-@ Convert 0000bbb0 ggg0rrr0 0000bbb0 ggg0rrr0
-@ to      00000000 rrr00000 ggg00000 bbb00000 ...
+@ Convert 0000bbbb ggggrrrr 0000bbbb ggggrrrr
+@ to      00000000 rrrr0000 gggg0000 bbbb0000 ...
 
-@ lr =  0x00e000e0, out: r3=lower_pix, r2=higher_pix; trashes rin
+@ lr =  0x00f000f0, out: r3=lower_pix, r2=higher_pix; trashes rin
 .macro convRGB32_2 rin sh=0
     and     r2,  lr, \rin, lsr #4 @ blue
     and     r3,  \rin, lr
@@ -25,13 +25,13 @@
     mov     r3,  r3,  ror #17             @ shadow mode
 .elseif \sh == 2
     adds    r3,  r3,  #0x40000000         @ green
-    orrcs   r3,  r3,  #0xe0000000
+    orrcs   r3,  r3,  lr, lsl #24
     mov     r3,  r3,  ror #8
     adds    r3,  r3,  #0x40000000
-    orrcs   r3,  r3,  #0xe0000000
+    orrcs   r3,  r3,  lr, lsl #24
     mov     r3,  r3,  ror #16
     adds    r3,  r3,  #0x40000000
-    orrcs   r3,  r3,  #0xe0000000
+    orrcs   r3,  r3,  lr, lsl #24
     mov     r3,  r3,  ror #24
 .else
     mov     r3,  r3,  ror #16             @ r3=low
@@ -47,41 +47,38 @@
 .elseif \sh == 2
     mov     r2,  r2,  ror #8
     adds    r2,  r2,  #0x40000000         @ blue
-    orrcs   r2,  r2,  #0xe0000000
+    orrcs   r2,  r2,  lr, lsl #24
     mov     r2,  r2,  ror #8
     adds    r2,  r2,  #0x40000000
-    orrcs   r2,  r2,  #0xe0000000
+    orrcs   r2,  r2,  lr, lsl #24
     mov     r2,  r2,  ror #8
     adds    r2,  r2,  #0x40000000
-    orrcs   r2,  r2,  #0xe0000000
+    orrcs   r2,  r2,  lr, lsl #24
     mov     r2,  r2,  ror #8
 .endif
 
     orr     r2,  r2,   r2,  lsr #3
-.if \sh == 1
-    str     r2, [r0, #0x40*2*4]
-.endif
     str     r2, [r0], #4
 .endm
 
 
-.global bgr444_to_rgb32 @ void *to, void *from
+.global bgr444_to_rgb32 @ void *to, void *from, unsigned entries
 
 bgr444_to_rgb32:
     stmfd   sp!, {r4-r7,lr}
 
-    mov     r12, #0x40>>3 @ repeats
-    mov     lr, #0x00e00000
-    orr     lr, lr, #0x00e0
+    mov     r12, r2, lsr #3 @ repeats
+    mov     lr, #0x00f00000
+    orr     lr, lr, #0x00f0
 
 .loopRGB32:
-    subs    r12, r12, #1
-
     ldmia    r1!, {r4-r7}
     convRGB32_2 r4
     convRGB32_2 r5
     convRGB32_2 r6
     convRGB32_2 r7
+
+    subs    r12, r12, #1
     bgt     .loopRGB32
 
     ldmfd   sp!, {r4-r7,pc}
@@ -94,28 +91,29 @@ bgr444_to_rgb32_sh:
 
     mov     r12, #0x40>>3 @ repeats
     add     r0, r0, #0x40*4
-    mov     lr, #0x00e00000
-    orr     lr, lr, #0x00e0
+    mov     lr, #0x00f00000
+    orr     lr, lr, #0x00f0
 
 .loopRGB32sh:
-    subs    r12, r12, #1
-
     ldmia    r1!, {r4-r7}
-    convRGB32_2 r4, 1
-    convRGB32_2 r5, 1
-    convRGB32_2 r6, 1
-    convRGB32_2 r7, 1
+    convRGB32_2 r4, 2
+    convRGB32_2 r5, 2
+    convRGB32_2 r6, 2
+    convRGB32_2 r7, 2
+
+    subs    r12, r12, #1
     bgt     .loopRGB32sh
 
     mov     r12, #0x40>>3 @ repeats
     sub     r1, r1, #0x40*2
+    and     lr, lr, lr, lsl #1  @ kill LSB for correct shadow colors
 
 .loopRGB32hi:
-     ldmia    r1!, {r4-r7}
-    convRGB32_2 r4, 2
-    convRGB32_2 r5, 2
-    convRGB32_2 r6, 2
-    convRGB32_2 r7, 2
+    ldmia    r1!, {r4-r7}
+    convRGB32_2 r4, 1
+    convRGB32_2 r5, 1
+    convRGB32_2 r6, 1
+    convRGB32_2 r7, 1
 
     subs    r12, r12, #1
     bgt     .loopRGB32hi

+ 0 - 12
platform/common/config_file.c

@@ -338,18 +338,6 @@ static int custom_read(menu_entry *me, const char *var, const char *val)
 			return 1;
 
 		/* PSP */
-		case MA_OPT3_SCALE:
-			if (strcasecmp(var, "Scale factor") != 0) return 0;
-			currentConfig.scale = atof(val);
-			return 1;
-		case MA_OPT3_HSCALE32:
-			if (strcasecmp(var, "Hor. scale (for low res. games)") != 0) return 0;
-			currentConfig.hscale32 = atof(val);
-			return 1;
-		case MA_OPT3_HSCALE40:
-			if (strcasecmp(var, "Hor. scale (for hi res. games)") != 0) return 0;
-			currentConfig.hscale40 = atof(val);
-			return 1;
 		case MA_OPT3_VSYNC:
 			// XXX: use enum
 			if (strcasecmp(var, "Wait for vsync") != 0) return 0;

+ 1 - 1
platform/common/dr_libs

@@ -1 +1 @@
-Subproject commit b20f7512a69b732ab4082c05e01fc29d42333fae
+Subproject commit 49de65c5204cb055afc1a14b11753e17d52a1d1b

+ 1930 - 1922
platform/common/emu.c

@@ -1,1922 +1,1930 @@
-/*
- * PicoDrive
- * (C) notaz, 2007-2010
- *
- * This work is licensed under the terms of MAME license.
- * See COPYING file in the top-level directory.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#ifdef __GP2X__
-#include <unistd.h>
-#endif
-
-#include "../libpicofe/posix.h"
-#include "../libpicofe/input.h"
-#include "../libpicofe/fonts.h"
-#include "../libpicofe/sndout.h"
-#include "../libpicofe/lprintf.h"
-#include "../libpicofe/plat.h"
-#include "emu.h"
-#include "configfile_fk.h"
-#include "input_pico.h"
-#include "menu_pico.h"
-#include "config_file.h"
-
-#include <pico/pico_int.h>
-#include <pico/patch.h>
-
-#ifdef USE_LIBRETRO_VFS
-#include "file_stream_transforms.h"
-#endif
-
-#if defined(__GNUC__) && __GNUC__ >= 7
-#pragma GCC diagnostic ignored "-Wformat-truncation"
-#endif
-
-#ifndef _WIN32
-#define PATH_SEP      "/"
-#define PATH_SEP_C    '/'
-#else
-#define PATH_SEP      "\\"
-#define PATH_SEP_C    '\\'
-#endif
-
-#define STATUS_MSG_TIMEOUT 2000
-
-void *g_screen_ptr;
-
-int g_screen_width  = 320;
-int g_screen_height = 240;
-int g_screen_ppitch = 320; // pitch in pixels
-
-const char *PicoConfigFile = "config2.cfg";
-currentConfig_t currentConfig, defaultConfig;
-int state_slot = 0;
-int config_slot = 0, config_slot_current = 0;
-int pico_pen_x = 320/2, pico_pen_y = 240/2;
-int pico_inp_mode;
-int flip_after_sync;
-int engineState = PGS_Menu;
-int show_fps_bypass = 0;
-int need_screen_cleared = 0;
-
-static short __attribute__((aligned(4))) sndBuffer[2*44100/50];
-
-/* tmp buff to reduce stack usage for plats with small stack */
-static char static_buff[1024];
-const char *rom_fname_reload;
-char rom_fname_loaded[1024];
-int reset_timing = 0;
-static unsigned int notice_msg_time;	/* when started showing */
-static char noticeMsg[40];
-
-unsigned char *movie_data = NULL;
-static int movie_size = 0;
-
-
-/* don't use tolower() for easy old glibc binary compatibility */
-static void strlwr_(char *string)
-{
-	char *p;
-	for (p = string; *p; p++)
-		if ('A' <= *p && *p <= 'Z')
-			*p += 'a' - 'A';
-}
-
-static int try_rfn_cut(char *fname)
-{
-	FILE *tmp;
-	char *p;
-
-	p = fname + strlen(fname) - 1;
-	for (; p > fname; p--)
-		if (*p == '.') break;
-	*p = 0;
-
-	if((tmp = fopen(fname, "rb"))) {
-		fclose(tmp);
-		return 1;
-	}
-	return 0;
-}
-
-static void get_ext(const char *file, char *ext)
-{
-	const char *p;
-
-	p = file + strlen(file) - 4;
-	if (p < file) p = file;
-	strncpy(ext, p, 4);
-	ext[4] = 0;
-	strlwr_(ext);
-}
-
-static void fname_ext(char *dst, int dstlen, const char *prefix, const char *ext, const char *fname)
-{
-	int prefix_len = 0;
-	const char *p;
-
-	*dst = 0;
-	if (prefix) {
-		/*int len = plat_get_root_dir(dst, dstlen);
-		strcpy(dst + len, prefix);
-		prefix_len = len + strlen(prefix);*/
-
-		/* Saves are in ROM folder */
-		prefix_len = strlen(mRomPath)+1;
-		sprintf(dst, "%s/", mRomPath);
-	}
-
-	p = fname + strlen(fname) - 1;
-	for (; p >= fname && *p != PATH_SEP_C; p--)
-		;
-	p++;
-	strncpy(dst + prefix_len, p, dstlen - prefix_len - 1);
-
-	dst[dstlen - 8] = 0;
-	if ((p = strrchr(dst, '.')) != NULL)
-		dst[p-dst] = 0;
-	if (ext)
-		strcat(dst, ext);
-}
-
-static void romfname_ext(char *dst, int dstlen, const char *prefix, const char *ext)
-{
-	fname_ext(dst, dstlen, prefix, ext, rom_fname_loaded);
-}
-
-void emu_status_msg(const char *format, ...)
-{
-	va_list vl;
-	int ret;
-
-	va_start(vl, format);
-	ret = vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);
-	va_end(vl);
-
-	/* be sure old text gets overwritten */
-	for (; ret < 28; ret++)
-		noticeMsg[ret] = ' ';
-	noticeMsg[ret] = 0;
-
-	notice_msg_time = plat_get_ticks_ms();
-}
-
-static const char * const biosfiles_us[] = {
-	"us_scd2_9306", "SegaCDBIOS9303", "us_scd1_9210", "bios_CD_U"
-};
-static const char * const biosfiles_eu[] = {
-	"eu_mcd2_9306", "eu_mcd2_9303", "eu_mcd1_9210", "bios_CD_E"
-};
-static const char * const biosfiles_jp[] = {
-	"jp_mcd2_921222", "jp_mcd1_9112", "jp_mcd1_9111", "bios_CD_J"
-};
-
-static const char *find_bios(int *region, const char *cd_fname)
-{
-	int i, count;
-	const char * const *files;
-	FILE *f = NULL;
-	int ret;
-
-	// we need to have config loaded at this point
-	ret = emu_read_config(cd_fname, 0);
-	if (!ret) emu_read_config(NULL, 0);
-
-	if (PicoIn.regionOverride) {
-		*region = PicoIn.regionOverride;
-		lprintf("override region to %s\n", *region != 4 ?
-			(*region == 8 ? "EU" : "JAP") : "USA");
-	}
-
-	if (*region == 4) { // US
-		files = biosfiles_us;
-		count = sizeof(biosfiles_us) / sizeof(char *);
-	} else if (*region == 8) { // EU
-		files = biosfiles_eu;
-		count = sizeof(biosfiles_eu) / sizeof(char *);
-	} else if (*region == 1 || *region == 2) {
-		files = biosfiles_jp;
-		count = sizeof(biosfiles_jp) / sizeof(char *);
-	} else {
-		return 0;
-	}
-
-	/** Check Bios files in home directory */
-	for (i = 0; i < count; i++)
-	{
-		emu_make_path(static_buff, files[i], sizeof(static_buff) - 4);
-		//printf("bios name static_buff: %s\n", static_buff);
-		strcat(static_buff, ".bin");
-		f = fopen(static_buff, "rb");
-		if (f) break;
-
-		static_buff[strlen(static_buff) - 4] = 0;
-		strcat(static_buff, ".zip");
-		f = fopen(static_buff, "rb");
-		if (f) break;
-	}
-
-	/** Check Bios files in <rom path>/bios */
-	if(!f){
-		for (i = 0; i < count; i++)
-		{
-			sprintf(static_buff, "%s/bios/%s", mRomPath, files[i]);
-			printf("static_buff: %s\n", static_buff);
-			strcat(static_buff, ".bin");
-			f = fopen(static_buff, "rb");
-			if (f) break;
-
-			static_buff[strlen(static_buff) - 4] = 0;
-			strcat(static_buff, ".zip");
-			f = fopen(static_buff, "rb");
-			if (f) break;
-		}
-	}
-
-	/** Check Bios files in rom path */
-	if(!f){
-		for (i = 0; i < count; i++)
-		{
-			sprintf(static_buff, "%s/%s", mRomPath, files[i]);
-			printf("static_buff: %s\n", static_buff);
-			strcat(static_buff, ".bin");
-			f = fopen(static_buff, "rb");
-			if (f) break;
-
-			static_buff[strlen(static_buff) - 4] = 0;
-			strcat(static_buff, ".zip");
-			f = fopen(static_buff, "rb");
-			if (f) break;
-		}
-	}
-
-	if (f) {
-		lprintf("using bios: %s\n", static_buff);
-		fclose(f);
-		return static_buff;
-	} else {
-		char* country;
-		char** bios_names;
-		if(*region == 4){ //USA
-			country = "USA";
-			bios_names = biosfiles_us;
-		}
-		else if(*region == 8){ //EU
-			country = "EU";
-			bios_names = biosfiles_eu;
-		}
-		else{ //JAP
-			country = "JAP";
-			bios_names = biosfiles_jp;
-		}
-
-		sprintf(static_buff, "no %s BIOS files found, read docs", country);
-		menu_update_msg(static_buff);
-
-		/** Set notif for BIOS */
-	    char shell_cmd[400];
-	    sprintf(shell_cmd, "%s 0 \"    %s BIOS FILE MISSING^^Connect your FunKey S to ^your computer and copy the^BIOS file in the folder:^    Sega Genesis/bios/^^The file can be called:^ - %s.bin^ - %s.bin^ - %s.bin^ - %s.bin^^For more instructions:^www.funkey-project.com^^Press any button to exit...\"",
-	            SHELL_CMD_NOTIF_SET, country, bios_names[0], bios_names[1], bios_names[2], bios_names[3]);
-	    FILE *fp = popen(shell_cmd, "r");
-	    if (fp == NULL) {
-	        printf("In %s, Failed to run command %s\n", __func__, shell_cmd);
-	    } else {
-	        pclose(fp);
-	    }
-
-	    /** Wait for key press */
-	    SDL_Event event;
-	    while(event.type != SDL_KEYUP && event.type != SDL_QUIT){
-	        while (SDL_PollEvent(&event))
-			SDL_Delay(60);
-	    }
-
-	    /** Clear notif for BIOS */
-	    fp = popen(SHELL_CMD_NOTIF_CLEAR, "r");
-	    if (fp == NULL) {
-	        printf("In %s, Failed to run command %s\n", __func__, SHELL_CMD_NOTIF_CLEAR);
-	    } else {
-	        pclose(fp);
-	    }
-
-	    /** Force clean exit */
-        //engineState = PGS_Quit;
-		emu_finish();
-		plat_finish();
-		plat_target_finish();
-		exit(0);
-		return NULL;
-	}
-}
-
-/* check if the name begins with BIOS name */
-/*
-static int emu_isBios(const char *name)
-{
-	int i;
-	for (i = 0; i < sizeof(biosfiles_us)/sizeof(biosfiles_us[0]); i++)
-		if (strstr(name, biosfiles_us[i]) != NULL) return 1;
-	for (i = 0; i < sizeof(biosfiles_eu)/sizeof(biosfiles_eu[0]); i++)
-		if (strstr(name, biosfiles_eu[i]) != NULL) return 1;
-	for (i = 0; i < sizeof(biosfiles_jp)/sizeof(biosfiles_jp[0]); i++)
-		if (strstr(name, biosfiles_jp[i]) != NULL) return 1;
-	return 0;
-}
-*/
-
-static int extract_text(char *dest, const unsigned char *src, int len, int swab)
-{
-	char *p = dest;
-	int i;
-
-	if (swab) swab = 1;
-
-	for (i = len - 1; i >= 0; i--)
-	{
-		if (src[i^swab] != ' ') break;
-	}
-	len = i + 1;
-
-	for (i = 0; i < len; i++)
-	{
-		unsigned char s = src[i^swab];
-		if (s >= 0x20 && s < 0x7f && s != '#' && s != '|' &&
-			s != '[' && s != ']' && s != '\\')
-		{
-			*p++ = s;
-		}
-		else
-		{
-			sprintf(p, "\\%02x", s);
-			p += 3;
-		}
-	}
-
-	return p - dest;
-}
-
-static char *emu_make_rom_id(const char *fname)
-{
-	static char id_string[3+0xe*3+0x3*3+0x30*3+3];
-	int pos, swab = 1;
-
-	if (PicoIn.AHW & PAHW_MCD) {
-		strcpy(id_string, "CD|");
-		swab = 0;
-	}
-	else if (PicoIn.AHW & PAHW_SMS)
-		strcpy(id_string, "MS|");
-	else	strcpy(id_string, "MD|");
-	pos = 3;
-
-	if (!(PicoIn.AHW & PAHW_SMS)) {
-		pos += extract_text(id_string + pos, media_id_header + 0x80, 0x0e, swab); // serial
-		id_string[pos] = '|'; pos++;
-		pos += extract_text(id_string + pos, media_id_header + 0xf0, 0x03, swab); // region
-		id_string[pos] = '|'; pos++;
-		pos += extract_text(id_string + pos, media_id_header + 0x50, 0x30, swab); // overseas name
-		id_string[pos] = 0;
-		if (pos > 5)
-			return id_string;
-		pos = 3;
-	}
-
-	// can't find name in ROM, use filename
-	fname_ext(id_string + 3, sizeof(id_string) - 3, NULL, NULL, fname);
-
-	return id_string;
-}
-
-// buffer must be at least 150 byte long
-void emu_get_game_name(char *str150)
-{
-	int ret, swab = (PicoIn.AHW & PAHW_MCD) ? 0 : 1;
-	char *s, *d;
-
-	ret = extract_text(str150, media_id_header + 0x50, 0x30, swab); // overseas name
-
-	for (s = d = str150 + 1; s < str150+ret; s++)
-	{
-		if (*s == 0) break;
-		if (*s != ' ' || d[-1] != ' ')
-			*d++ = *s;
-	}
-	*d = 0;
-}
-
-static void system_announce(void)
-{
-	const char *sys_name, *tv_standard, *extra = "";
-	int fps;
-
-	if (PicoIn.AHW & PAHW_SMS) {
-		sys_name = "Master System";
-#ifdef NO_SMS
-		extra = " [no support]";
-#endif
-	} else if (PicoIn.AHW & PAHW_PICO) {
-		sys_name = "Pico";
-	} else if ((PicoIn.AHW & (PAHW_32X|PAHW_MCD)) == (PAHW_32X|PAHW_MCD)) {
-		sys_name = "32X + Mega CD";
-		if ((Pico.m.hardware & 0xc0) == 0x80)
-			sys_name = "32X + Sega CD";
-	} else if (PicoIn.AHW & PAHW_MCD) {
-		sys_name = "Mega CD";
-		if ((Pico.m.hardware & 0xc0) == 0x80)
-			sys_name = "Sega CD";
-	} else if (PicoIn.AHW & PAHW_32X) {
-		sys_name = "32X";
-	} else {
-		sys_name = "MegaDrive";
-		if ((Pico.m.hardware & 0xc0) == 0x80)
-			sys_name = "Genesis";
-	}
-	tv_standard = Pico.m.pal ? "PAL" : "NTSC";
-	fps = Pico.m.pal ? 50 : 60;
-
-	//emu_status_msg("%s %s / %dFPS%s", tv_standard, sys_name, fps, extra);
-	printf("\nSystem Announce: %s, %s / %dFPS%s\n", sys_name, tv_standard, fps, extra);
-	printf("PicoIn.AHW = %d, Pico.m.hardware=%d\n", PicoIn.AHW, Pico.m.hardware);
-}
-
-static void do_region_override(const char *media_fname)
-{
-	// we only need to override region if config tells us so
-	int ret = emu_read_config(media_fname, 0);
-	if (!ret) emu_read_config(NULL, 0);
-}
-
-int emu_reload_rom(const char *rom_fname_in)
-{
-	// use setting before rom config is loaded
-	int autoload = g_autostateld_opt;
-	char *rom_fname = NULL;
-	char ext[5];
-	enum media_type_e media_type;
-	int menu_romload_started = 0;
-	char carthw_path[512];
-	int retval = 0;
-
-	lprintf("emu_ReloadRom(%s)\n", rom_fname_in);
-
-	rom_fname = strdup(rom_fname_in);
-	if (rom_fname == NULL)
-		return 0;
-
-	get_ext(rom_fname, ext);
-
-	// early cleanup
-	PicoPatchUnload();
-	if (movie_data) {
-		free(movie_data);
-		movie_data = 0;
-	}
-
-	if (!strcmp(ext, ".gmv"))
-	{
-		// check for both gmv and rom
-		int dummy;
-		FILE *movie_file = fopen(rom_fname, "rb");
-		if (!movie_file) {
-			menu_update_msg("Failed to open movie.");
-			goto out;
-		}
-		fseek(movie_file, 0, SEEK_END);
-		movie_size = ftell(movie_file);
-		fseek(movie_file, 0, SEEK_SET);
-		if (movie_size < 64+3) {
-			menu_update_msg("Invalid GMV file.");
-			fclose(movie_file);
-			goto out;
-		}
-		movie_data = malloc(movie_size);
-		if (movie_data == NULL) {
-			menu_update_msg("low memory.");
-			fclose(movie_file);
-			goto out;
-		}
-		dummy = fread(movie_data, 1, movie_size, movie_file);
-		fclose(movie_file);
-		if (strncmp((char *)movie_data, "Gens Movie TEST", 15) != 0) {
-			menu_update_msg("Invalid GMV file.");
-			goto out;
-		}
-		dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);
-		if (!dummy) {
-			menu_update_msg("Could't find a ROM for movie.");
-			goto out;
-		}
-		get_ext(rom_fname, ext);
-		lprintf("gmv loaded for %s\n", rom_fname);
-	}
-	else if (!strcmp(ext, ".pat"))
-	{
-		int dummy;
-		PicoPatchLoad(rom_fname);
-		dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);
-		if (!dummy) {
-			menu_update_msg("Could't find a ROM to patch.");
-			goto out;
-		}
-		get_ext(rom_fname, ext);
-	}
-
-	menu_romload_prepare(rom_fname); // also CD load
-	menu_romload_started = 1;
-
-	emu_make_path(carthw_path, "carthw.cfg", sizeof(carthw_path));
-
-	media_type = PicoLoadMedia(rom_fname, carthw_path,
-			find_bios, do_region_override);
-
-	switch (media_type) {
-	case PM_BAD_DETECT:
-		menu_update_msg("Not a ROM/CD img selected.");
-		goto out;
-	case PM_BAD_CD:
-		menu_update_msg("Invalid CD image");
-		goto out;
-	case PM_BAD_CD_NO_BIOS:
-		// find_bios() prints a message
-		goto out;
-	case PM_ERROR:
-		menu_update_msg("Load error");
-		goto out;
-	default:
-		break;
-	}
-
-	// make quirks visible in UI
-	if (PicoIn.quirks & PQUIRK_FORCE_6BTN)
-		currentConfig.input_dev0 = PICO_INPUT_PAD_6BTN;
-
-	menu_romload_end();
-	menu_romload_started = 0;
-
-	if (PicoPatches) {
-		PicoPatchPrepare();
-		PicoPatchApply();
-	}
-
-	// additional movie stuff
-	if (movie_data)
-	{
-		enum input_device indev = (movie_data[0x14] == '6') ?
-			PICO_INPUT_PAD_6BTN : PICO_INPUT_PAD_3BTN;
-		PicoSetInputDevice(0, indev);
-		PicoSetInputDevice(1, indev);
-
-		PicoIn.opt |= POPT_DIS_VDP_FIFO; // no VDP fifo timing
-		if (movie_data[0xF] >= 'A') {
-			if (movie_data[0x16] & 0x80) {
-				PicoIn.regionOverride = 8;
-			} else {
-				PicoIn.regionOverride = 4;
-			}
-			PicoReset();
-			// TODO: bits 6 & 5
-		}
-		movie_data[0x18+30] = 0;
-		emu_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);
-	}
-	else
-	{
-		system_announce();
-		PicoIn.opt &= ~POPT_DIS_VDP_FIFO;
-	}
-
-	/* Set input map */
-	if (PicoIn.AHW & PAHW_SMS) {
-		printf("plat set sms input\n");
-		plat_set_sms_input();
-	}
-	else{
-		plat_set_genesis_input();
-	}
-
-	strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);
-	rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;
-
-	// load SRAM for this ROM
-	if (currentConfig.EmuOpt & EOPT_EN_SRAM)
-		emu_save_load_game(1, 1);
-
-	// state autoload?
-	if (autoload) {
-		int time, newest = 0, newest_slot = -1;
-		int slot;
-
-		for (slot = 0; slot < 10; slot++) {
-			if (emu_check_save_file(slot, &time)) {
-				if (time > newest) {
-					newest = time;
-					newest_slot = slot;
-				}
-			}
-		}
-
-		if (newest_slot >= 0) {
-			lprintf("autoload slot %d\n", newest_slot);
-			state_slot = newest_slot;
-			emu_save_load_game(1, 0);
-		}
-		else {
-			lprintf("no save to autoload.\n");
-		}
-	}
-
-	retval = 1;
-out:
-	if (menu_romload_started)
-		menu_romload_end();
-	free(rom_fname);
-	return retval;
-}
-
-int emu_swap_cd(const char *fname)
-{
-	enum cd_track_type cd_type;
-	int ret = -1;
-
-	cd_type = PicoCdCheck(fname, NULL);
-	if (cd_type != CT_UNKNOWN)
-		ret = cdd_load(fname, cd_type);
-	if (ret != 0) {
-		menu_update_msg("Load failed, invalid CD image?");
-		return 0;
-	}
-
-	strncpy(rom_fname_loaded, fname, sizeof(rom_fname_loaded)-1);
-	rom_fname_loaded[sizeof(rom_fname_loaded) - 1] = 0;
-
-	return 1;
-}
-
-// <base dir><end>
-void emu_make_path(char *buff, const char *end, int size)
-{
-	int pos, end_len;
-
-	end_len = strlen(end);
-	pos = plat_get_root_dir(buff, size);
-	strncpy(buff + pos, end, size - pos);
-	buff[size - 1] = 0;
-	if (pos + end_len > size - 1)
-		lprintf("Warning: path truncated: %s\n", buff);
-}
-
-static void make_config_cfg(char *cfg_buff_512)
-{
-	emu_make_path(cfg_buff_512, PicoConfigFile, 512-6);
-	if (config_slot != 0)
-	{
-		char *p = strrchr(cfg_buff_512, '.');
-		if (p == NULL)
-			p = cfg_buff_512 + strlen(cfg_buff_512);
-		sprintf(p, ".%i.cfg", config_slot);
-	}
-	cfg_buff_512[511] = 0;
-}
-
-void emu_prep_defconfig(void)
-{
-	memset(&defaultConfig, 0, sizeof(defaultConfig));
-	defaultConfig.EmuOpt    = EOPT_EN_SRAM | EOPT_EN_SOUND | EOPT_16BPP |
-				  EOPT_GZIP_SAVES | 0x10/*?*/;
-	defaultConfig.s_PicoOpt = POPT_EN_YM2413|POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80 |
-				  POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_MCD_GFX |
-				  POPT_EN_DRC|POPT_ACC_SPRITES |
-				  POPT_EN_32X|POPT_EN_PWM;
-	defaultConfig.s_PsndRate = 44100;
-	defaultConfig.s_PicoRegion = 0; // auto
-	defaultConfig.s_PicoAutoRgnOrder = 0x184; // US, EU, JP
-	defaultConfig.s_PicoCDBuffers = 0;
-	defaultConfig.s_PicoSndFilterAlpha = 0x10000 * 60 / 100;
-	defaultConfig.confirm_save = EOPT_CONFIRM_SAVE;
-	defaultConfig.Frameskip = -1; // auto
-	defaultConfig.input_dev0 = PICO_INPUT_PAD_3BTN;
-	defaultConfig.input_dev1 = PICO_INPUT_PAD_3BTN;
-	defaultConfig.volume = 50;
-	//defaultConfig.volume = 99;
-	defaultConfig.gamma = 100;
-	defaultConfig.scaling = 0;
-	defaultConfig.turbo_rate = 15;
-	defaultConfig.msh2_khz = PICO_MSH2_HZ / 1000;
-	defaultConfig.ssh2_khz = PICO_SSH2_HZ / 1000;
-	defaultConfig.max_skip = 4;
-
-	// platform specific overrides
-	pemu_prep_defconfig();
-}
-
-void emu_set_defconfig(void)
-{
-	memcpy(&currentConfig, &defaultConfig, sizeof(currentConfig));
-	PicoIn.opt = currentConfig.s_PicoOpt;
-	PicoIn.sndRate = currentConfig.s_PsndRate;
-	PicoIn.regionOverride = currentConfig.s_PicoRegion;
-	PicoIn.autoRgnOrder = currentConfig.s_PicoAutoRgnOrder;
-	PicoIn.sndFilterAlpha = currentConfig.s_PicoSndFilterAlpha;
-}
-
-int emu_read_config(const char *rom_fname, int no_defaults)
-{
-	char cfg[512];
-	int ret;
-
-	if (!no_defaults)
-		emu_set_defconfig();
-
-	if (rom_fname == NULL)
-	{
-		// global config
-		make_config_cfg(cfg);
-		ret = config_readsect(cfg, NULL);
-	}
-	else
-	{
-		char ext[16];
-		int vol;
-
-		if (config_slot != 0)
-			snprintf(ext, sizeof(ext), ".%i.cfg", config_slot);
-		else
-			strcpy(ext, ".cfg");
-
-		fname_ext(cfg, sizeof(cfg), "cfg"PATH_SEP, ext, rom_fname); // gamepath/gamename.cfg
-
-		// read user's config
-		vol = currentConfig.volume;
-		ret = config_readsect(cfg, NULL);
-		currentConfig.volume = vol; // make vol global (bah)
-
-		if (ret != 0)
-		{
-			// read global config, and apply game_def.cfg on top
-			make_config_cfg(cfg); // $HOME/.picodrive/config2.cfg
-			config_readsect(cfg, NULL);
-
-			emu_make_path(cfg, "game_def.cfg", sizeof(cfg)); // $HOME/.picodrive/game_def.cfg
-			ret = config_readsect(cfg, emu_make_rom_id(rom_fname));
-		}
-	}
-
-	pemu_validate_config();
-	PicoIn.overclockM68k = currentConfig.overclock_68k;
-
-	// some sanity checks
-	if (currentConfig.volume < 0 || currentConfig.volume > 99)
-		currentConfig.volume = 50;
-
-	if (ret == 0)
-		config_slot_current = config_slot;
-
-	return (ret == 0);
-}
-
-
-int emu_write_config(int is_game)
-{
-	char cfg[512];
-	int ret, write_lrom = 0;
-
-	if (!is_game)
-	{
-		make_config_cfg(cfg);
-		write_lrom = 1;
-	} else {
-		char ext[16];
-
-		if (config_slot != 0)
-			snprintf(ext, sizeof(ext), ".%i.cfg", config_slot);
-		else
-			strcpy(ext, ".cfg");
-
-		romfname_ext(cfg, sizeof(cfg), "cfg"PATH_SEP, ext);
-	}
-
-	lprintf("emu_write_config: %s ", cfg);
-	ret = config_write(cfg);
-	if (write_lrom) config_writelrom(cfg);
-#ifdef __GP2X__
-	sync();
-#endif
-	lprintf((ret == 0) ? "(ok)\n" : "(failed)\n");
-
-	if (ret == 0) config_slot_current = config_slot;
-	return ret == 0;
-}
-
-
-/* always using built-in font */
-
-#define mk_text_out(name, type, val, topleft, step_x, step_y) \
-void name(int x, int y, const char *text)				\
-{									\
-	int i, l, len = strlen(text);					\
-	type *screen = (type *)(topleft) + x * step_x + y * step_y;	\
-									\
-	for (i = 0; i < len; i++, screen += 8 * step_x)			\
-	{								\
-		for (l = 0; l < 8; l++)					\
-		{							\
-			unsigned char fd = fontdata8x8[text[i] * 8 + l];\
-			type *s = screen + l * step_y;			\
-			if (fd&0x80) s[step_x * 0] = val;		\
-			if (fd&0x40) s[step_x * 1] = val;		\
-			if (fd&0x20) s[step_x * 2] = val;		\
-			if (fd&0x10) s[step_x * 3] = val;		\
-			if (fd&0x08) s[step_x * 4] = val;		\
-			if (fd&0x04) s[step_x * 5] = val;		\
-			if (fd&0x02) s[step_x * 6] = val;		\
-			if (fd&0x01) s[step_x * 7] = val;		\
-		}							\
-	}								\
-}
-
-mk_text_out(emu_text_out8,      unsigned char,    0xf0, g_screen_ptr, 1, g_screen_ppitch)
-mk_text_out(emu_text_out16,     unsigned short, 0xffff, g_screen_ptr, 1, g_screen_ppitch)
-mk_text_out(emu_text_out8_rot,  unsigned char,    0xf0,
-	(char *)g_screen_ptr  + (g_screen_ppitch - 1) * g_screen_height, -g_screen_height, 1)
-mk_text_out(emu_text_out16_rot, unsigned short, 0xffff,
-	(short *)g_screen_ptr + (g_screen_ppitch - 1) * g_screen_height, -g_screen_height, 1)
-
-#undef mk_text_out
-
-void emu_osd_text16(int x, int y, const char *text)
-{
-	int len = strlen(text) * 8;
-	int i, h;
-
-	len++;
-	if (x + len > g_screen_width)
-		len = g_screen_width - x;
-
-	for (h = 0; h < 8; h++) {
-		unsigned short *p;
-		p = (unsigned short *)g_screen_ptr
-			+ x + g_screen_ppitch * (y + h);
-		for (i = len; i > 0; i--, p++)
-			*p = (*p >> 2) & 0x39e7;
-	}
-	emu_text_out16(x, y, text);
-}
-
-static void update_movie(void)
-{
-	int offs = Pico.m.frame_count*3 + 0x40;
-	if (offs+3 > movie_size) {
-		free(movie_data);
-		movie_data = 0;
-		emu_status_msg("END OF MOVIE.");
-		lprintf("END OF MOVIE.\n");
-	} else {
-		// MXYZ SACB RLDU
-		PicoIn.pad[0] = ~movie_data[offs]   & 0x8f; // ! SCBA RLDU
-		if(!(movie_data[offs]   & 0x10)) PicoIn.pad[0] |= 0x40; // C
-		if(!(movie_data[offs]   & 0x20)) PicoIn.pad[0] |= 0x10; // A
-		if(!(movie_data[offs]   & 0x40)) PicoIn.pad[0] |= 0x20; // B
-		PicoIn.pad[1] = ~movie_data[offs+1] & 0x8f; // ! SCBA RLDU
-		if(!(movie_data[offs+1] & 0x10)) PicoIn.pad[1] |= 0x40; // C
-		if(!(movie_data[offs+1] & 0x20)) PicoIn.pad[1] |= 0x10; // A
-		if(!(movie_data[offs+1] & 0x40)) PicoIn.pad[1] |= 0x20; // B
-		PicoIn.pad[0] |= (~movie_data[offs+2] & 0x0A) << 8; // ! MZYX
-		if(!(movie_data[offs+2] & 0x01)) PicoIn.pad[0] |= 0x0400; // X
-		if(!(movie_data[offs+2] & 0x04)) PicoIn.pad[0] |= 0x0100; // Z
-		PicoIn.pad[1] |= (~movie_data[offs+2] & 0xA0) << 4; // ! MZYX
-		if(!(movie_data[offs+2] & 0x10)) PicoIn.pad[1] |= 0x0400; // X
-		if(!(movie_data[offs+2] & 0x40)) PicoIn.pad[1] |= 0x0100; // Z
-	}
-}
-
-static int try_ropen_file(const char *fname, int *time)
-{
-	struct stat st;
-	FILE *f;
-
-	f = fopen(fname, "rb");
-	if (f) {
-		if (time != NULL) {
-			*time = 0;
-			if (fstat(fileno(f), &st) == 0)
-				*time = (int)st.st_mtime;
-		}
-		fclose(f);
-		return 1;
-	}
-	return 0;
-}
-
-char *emu_get_save_fname(int load, int is_sram, int slot, int *time)
-{
-	char *saveFname = static_buff;
-	char ext[16];
-
-	if (is_sram)
-	{
-		strcpy(ext, (PicoIn.AHW & PAHW_MCD) ? ".brm" : ".srm");
-		romfname_ext(saveFname, sizeof(static_buff),
-			(PicoIn.AHW & PAHW_MCD) ? "brm"PATH_SEP : "srm"PATH_SEP, ext);
-		if (!load)
-			return saveFname;
-
-		if (try_ropen_file(saveFname, time))
-			return saveFname;
-
-		romfname_ext(saveFname, sizeof(static_buff), NULL, ext);
-		if (try_ropen_file(saveFname, time))
-			return saveFname;
-	}
-	else
-	{
-		const char *ext_main = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds.gz" : ".mds";
-		const char *ext_othr = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds" : ".mds.gz";
-		ext[0] = 0;
-		if (slot > 0 && slot < 10)
-			sprintf(ext, ".%i", slot);
-		strcat(ext, ext_main);
-
-		if (!load) {
-			romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);
-			return saveFname;
-		}
-		else {
-			romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);
-			if (try_ropen_file(saveFname, time))
-				return saveFname;
-
-			romfname_ext(saveFname, sizeof(static_buff), NULL, ext);
-			if (try_ropen_file(saveFname, time))
-				return saveFname;
-
-			// try the other ext
-			ext[0] = 0;
-			if (slot > 0 && slot < 10)
-				sprintf(ext, ".%i", slot);
-			strcat(ext, ext_othr);
-
-			romfname_ext(saveFname, sizeof(static_buff), "mds"PATH_SEP, ext);
-			if (try_ropen_file(saveFname, time))
-				return saveFname;
-		}
-	}
-
-	return NULL;
-}
-
-int emu_check_save_file(int slot, int *time)
-{
-	return emu_get_save_fname(1, 0, slot, time) ? 1 : 0;
-}
-
-int emu_save_load_game_from_file(int load, char *saveFname){
-	int ret = PicoState(saveFname, !load);
-	if (!ret) {
-		//emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");
-	} else {
-		//emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");
-		ret = -1;
-	}
-
-	return ret;
-}
-
-
-int emu_save_load_game(int load, int sram)
-{
-	int ret = 0;
-	char *saveFname;
-
-	// make save filename
-	saveFname = emu_get_save_fname(load, sram, state_slot, NULL);
-	if (saveFname == NULL) {
-		/*if (!sram)
-			emu_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");*/
-		return -1;
-	}
-
-	lprintf("saveLoad (%i, %i): %s\n", load, sram, saveFname);
-
-	if (sram)
-	{
-		FILE *sramFile;
-		int sram_size;
-		unsigned char *sram_data;
-		int truncate = 1;
-		if (PicoIn.AHW & PAHW_MCD)
-		{
-			if (PicoIn.opt & POPT_EN_MCD_RAMCART) {
-				sram_size = 0x12000;
-				sram_data = Pico.sv.data;
-				if (sram_data)
-					memcpy(sram_data, Pico_mcd->bram, 0x2000);
-			} else {
-				sram_size = 0x2000;
-				sram_data = Pico_mcd->bram;
-				truncate  = 0; // the .brm may contain RAM cart data after normal brm
-			}
-		} else {
-			sram_size = Pico.sv.size;
-			sram_data = Pico.sv.data;
-		}
-		if (sram_data == NULL)
-			return 0; // cart saves forcefully disabled for this game
-
-		if (load)
-		{
-			sramFile = fopen(saveFname, "rb");
-			if (!sramFile)
-				return -1;
-			ret = fread(sram_data, 1, sram_size, sramFile);
-			ret = ret > 0 ? 0 : -1;
-			fclose(sramFile);
-			if ((PicoIn.AHW & PAHW_MCD) && (PicoIn.opt&POPT_EN_MCD_RAMCART))
-				memcpy(Pico_mcd->bram, sram_data, 0x2000);
-		} else {
-			// sram save needs some special processing
-			// see if we have anything to save
-			for (; sram_size > 0; sram_size--)
-				if (sram_data[sram_size-1]) break;
-
-			if (sram_size) {
-				sramFile = fopen(saveFname, truncate ? "wb" : "r+b");
-				if (!sramFile) sramFile = fopen(saveFname, "wb"); // retry
-				if (!sramFile) return -1;
-				ret = fwrite(sram_data, 1, sram_size, sramFile);
-				ret = (ret != sram_size) ? -1 : 0;
-				fclose(sramFile);
-#ifdef __GP2X__
-				sync();
-#endif
-			}
-		}
-		return ret;
-	}
-	else
-	{
-		ret = PicoState(saveFname, !load);
-		if (!ret) {
-#ifdef __GP2X__
-			if (!load) sync();
-#endif
-			//emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");
-		} else {
-			//emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");
-			ret = -1;
-		}
-
-		return ret;
-	}
-}
-
-void emu_set_fastforward(int set_on)
-{
-	static void *set_PsndOut = NULL;
-	static int set_Frameskip, set_EmuOpt, is_on = 0;
-
-	if (set_on && !is_on) {
-		set_PsndOut = PicoIn.sndOut;
-		set_Frameskip = currentConfig.Frameskip;
-		set_EmuOpt = currentConfig.EmuOpt;
-		PicoIn.sndOut = NULL;
-		currentConfig.Frameskip = 8;
-		currentConfig.EmuOpt &= ~4;
-		currentConfig.EmuOpt |= EOPT_NO_FRMLIMIT;
-		is_on = 1;
-		emu_status_msg("FAST FORWARD");
-	}
-	else if (!set_on && is_on) {
-		PicoIn.sndOut = set_PsndOut;
-		currentConfig.Frameskip = set_Frameskip;
-		currentConfig.EmuOpt = set_EmuOpt;
-		PsndRerate(1);
-		is_on = 0;
-		// mainly to unbreak pcm
-		if (PicoIn.AHW & PAHW_MCD)
-			pcd_state_loaded();
-	}
-}
-
-static void emu_tray_open(void)
-{
-	engineState = PGS_TrayMenu;
-}
-
-static void emu_tray_close(void)
-{
-	emu_status_msg("CD tray closed.");
-}
-
-void emu_32x_startup(void)
-{
-	plat_video_toggle_renderer(0, 0); // HACK
-	system_announce();
-}
-
-void emu_reset_game(void)
-{
-	PicoReset();
-	reset_timing = 1;
-}
-
-void run_events_pico(unsigned int events)
-{
-	int lim_x;
-
-	if (events & PEV_PICO_SWINP) {
-		pico_inp_mode++;
-		if (pico_inp_mode > 2)
-			pico_inp_mode = 0;
-		switch (pico_inp_mode) {
-			case 2: emu_status_msg("Input: Pen on Pad"); break;
-			case 1: emu_status_msg("Input: Pen on Storyware"); break;
-			case 0: emu_status_msg("Input: Joystick");
-				PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;
-				break;
-		}
-	}
-	if (events & PEV_PICO_PPREV) {
-		PicoPicohw.page--;
-		if (PicoPicohw.page < 0)
-			PicoPicohw.page = 0;
-		emu_status_msg("Page %i", PicoPicohw.page);
-	}
-	if (events & PEV_PICO_PNEXT) {
-		PicoPicohw.page++;
-		if (PicoPicohw.page > 6)
-			PicoPicohw.page = 6;
-		emu_status_msg("Page %i", PicoPicohw.page);
-	}
-
-	if (pico_inp_mode == 0)
-		return;
-
-	/* handle other input modes */
-	if (PicoIn.pad[0] & 1) pico_pen_y--;
-	if (PicoIn.pad[0] & 2) pico_pen_y++;
-	if (PicoIn.pad[0] & 4) pico_pen_x--;
-	if (PicoIn.pad[0] & 8) pico_pen_x++;
-	PicoIn.pad[0] &= ~0x0f; // release UDLR
-
-	lim_x = (Pico.video.reg[12]&1) ? 319 : 255;
-	if (pico_pen_y < 8)
-		pico_pen_y = 8;
-	if (pico_pen_y > 224 - PICO_PEN_ADJUST_Y)
-		pico_pen_y = 224 - PICO_PEN_ADJUST_Y;
-	if (pico_pen_x < 0)
-		pico_pen_x = 0;
-	if (pico_pen_x > lim_x - PICO_PEN_ADJUST_X)
-		pico_pen_x = lim_x - PICO_PEN_ADJUST_X;
-
-	PicoPicohw.pen_pos[0] = pico_pen_x;
-	if (!(Pico.video.reg[12] & 1))
-		PicoPicohw.pen_pos[0] += pico_pen_x / 4;
-	PicoPicohw.pen_pos[0] += 0x3c;
-	PicoPicohw.pen_pos[1] = pico_inp_mode == 1 ? (0x2f8 + pico_pen_y) : (0x1fc + pico_pen_y);
-}
-
-static void do_turbo(unsigned short *pad, int acts)
-{
-	static int turbo_pad = 0;
-	static unsigned char turbo_cnt[3] = { 0, 0, 0 };
-	int inc = currentConfig.turbo_rate * 2;
-
-	if (acts & 0x1000) {
-		turbo_cnt[0] += inc;
-		if (turbo_cnt[0] >= 60)
-			turbo_pad ^= 0x10, turbo_cnt[0] = 0;
-	}
-	if (acts & 0x2000) {
-		turbo_cnt[1] += inc;
-		if (turbo_cnt[1] >= 60)
-			turbo_pad ^= 0x20, turbo_cnt[1] = 0;
-	}
-	if (acts & 0x4000) {
-		turbo_cnt[2] += inc;
-		if (turbo_cnt[2] >= 60)
-			turbo_pad ^= 0x40, turbo_cnt[2] = 0;
-	}
-	*pad |= turbo_pad & (acts >> 8);
-}
-
-static void run_events_ui(unsigned int which)
-{
-	char shell_cmd[100];
-	FILE *fp;
-	//emu_action_old = emu_action;
-	//printf("New event: %d\n", which);
-
-	if (which & (PEV_STATE_LOAD|PEV_STATE_SAVE))
-	{
-		int do_it = 1;
-		if ( emu_check_save_file(state_slot, NULL) &&
-			(((which & PEV_STATE_LOAD) && (currentConfig.confirm_save & EOPT_CONFIRM_LOAD)) ||
-			 ((which & PEV_STATE_SAVE) && (currentConfig.confirm_save & EOPT_CONFIRM_SAVE))) )
-		{
-			const char *nm;
-			char tmp[64];
-			int keys, len;
-
-			strcpy(tmp, (which & PEV_STATE_LOAD) ? "LOAD STATE? " : "OVERWRITE SAVE? ");
-			len = strlen(tmp);
-			nm = in_get_key_name(-1, -PBTN_MOK);
-			snprintf(tmp + len, sizeof(tmp) - len, "(%s=yes, ", nm);
-			len = strlen(tmp);
-			nm = in_get_key_name(-1, -PBTN_MBACK);
-			snprintf(tmp + len, sizeof(tmp) - len, "%s=no)", nm);
-
-			plat_status_msg_busy_first(tmp);
-
-			in_set_config_int(0, IN_CFG_BLOCKING, 1);
-			while (in_menu_wait_any(NULL, 50) & (PBTN_MOK | PBTN_MBACK))
-				;
-			while ( !((keys = in_menu_wait_any(NULL, 50)) & (PBTN_MOK | PBTN_MBACK)))
-				;
-			if (keys & PBTN_MBACK)
-				do_it = 0;
-			while (in_menu_wait_any(NULL, 50) & (PBTN_MOK | PBTN_MBACK))
-				;
-			in_set_config_int(0, IN_CFG_BLOCKING, 0);
-			plat_status_msg_clear();
-		}
-		if (do_it) {
-			plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING STATE" : "SAVING STATE");
-			PicoStateProgressCB = plat_status_msg_busy_next;
-			emu_save_load_game((which & PEV_STATE_LOAD) ? 1 : 0, 0);
-			PicoStateProgressCB = NULL;
-		}
-	}
-	if (which & PEV_SWITCH_RND)
-	{
-		plat_video_toggle_renderer(1, 0);
-	}
-	if (which & PEV_VOL_DOWN)
-	{
-		printf("PEV_VOL_DOWN\r\n");
-		/// ----- Compute new value -----
-		volume_percentage = (volume_percentage < STEP_CHANGE_VOLUME)?
-			0:(volume_percentage-STEP_CHANGE_VOLUME);
-		/// ----- HUD msg ------
-		char txt[100];
-		sprintf(txt, "VOLUME %d%%", volume_percentage);
-		plat_status_msg_busy_first(txt);
-		/// ----- Shell cmd ----
-		sprintf(shell_cmd, "%s %d", SHELL_CMD_VOLUME_SET, volume_percentage);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-	}
-	if (which & PEV_VOL_UP)
-	{
-		printf("PEV_VOL_UP\r\n");
-		/// ----- Compute new value -----
-		volume_percentage = (volume_percentage > 100 - STEP_CHANGE_VOLUME)?
-			100:(volume_percentage+STEP_CHANGE_VOLUME);
-		/// ----- HUD msg ------
-		char txt[100];
-		sprintf(txt, "VOLUME %d%%", volume_percentage);
-		plat_status_msg_busy_first(txt);
-		/// ----- Shell cmd ----
-		sprintf(shell_cmd, "%s %d", SHELL_CMD_VOLUME_SET, volume_percentage);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-	}
-	if (which & PEV_BRIGHT_UP)
-	{
-		printf("PEV_BRIGHT_UP\r\n");
-		/// ----- Compute new value -----
-		brightness_percentage = (brightness_percentage > 100 - STEP_CHANGE_BRIGHTNESS)?
-			100:(brightness_percentage+STEP_CHANGE_BRIGHTNESS);
-		/// ----- HUD msg ------
-		char txt[100];
-		sprintf(txt, "BRIGHTNESS %d%%", brightness_percentage);
-		plat_status_msg_busy_first(txt);
-		/// ----- Shell cmd ----
-		sprintf(shell_cmd, "%s %d", SHELL_CMD_BRIGHTNESS_SET, brightness_percentage);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-	}
-	if (which & PEV_BRIGHT_DOWN)
-	{
-		printf("PEV_BRIGHT_DOWN\r\n");
-		/// ----- Compute new value -----
-		brightness_percentage = (brightness_percentage < STEP_CHANGE_BRIGHTNESS)?
-			0:(brightness_percentage-STEP_CHANGE_BRIGHTNESS);
-		/// ----- HUD msg ------
-		char txt[100];
-		sprintf(txt, "BRIGHTNESS %d%%", brightness_percentage);
-		plat_status_msg_busy_first(txt);
-		/// ----- Shell cmd ----
-		sprintf(shell_cmd, "%s %d", SHELL_CMD_BRIGHTNESS_SET, brightness_percentage);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-	}
-	if (which & PEV_AR_FACT_UP)
-	{
-		printf("PEV_AR_FACT_UP\r\n");
-		/// ----- Compute new value -----
-		if(aspect_ratio == ASPECT_RATIOS_TYPE_MANUAL){
-			aspect_ratio_factor_percent = (aspect_ratio_factor_percent+aspect_ratio_factor_step<100)?
-				aspect_ratio_factor_percent+aspect_ratio_factor_step:100;
-			need_screen_cleared = 1;
-		}
-		else{
-			aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
-		}
-		aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
-		/// ----- HUD msg ------
-		/*char txt[100];
-		sprintf(txt, "    DISPLAY MODE: ZOOMED - %d%%", aspect_ratio_factor_percent);
-		plat_status_msg_busy_first(txt);*/
-		sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: ZOOMED %d%%%%\"",
-			SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_factor_percent);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-
-        // Save config file
-        configfile_save(cfg_file_rom);
-	}
-	if (which & PEV_AR_FACT_DOWN)
-	{
-		printf("PEV_AR_FACT_DOWN\r\n");
-		/// ----- Compute new value -----
-		if(aspect_ratio == ASPECT_RATIOS_TYPE_MANUAL){
-			aspect_ratio_factor_percent = (aspect_ratio_factor_percent>aspect_ratio_factor_step)?
-				aspect_ratio_factor_percent-aspect_ratio_factor_step:0;
-			need_screen_cleared = 1;
-		}
-		else{
-			aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
-		}
-		aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
-		/// ----- HUD msg ------
-		/*char txt[100];
-		sprintf(txt, "    DISPLAY MODE: ZOOMED - %d%%", aspect_ratio_factor_percent);
-		plat_status_msg_busy_first(txt);*/
-		sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: ZOOMED %d%%%%\"",
-			SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_factor_percent);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-
-        // Save config file
-        configfile_save(cfg_file_rom);
-
-	}
-	if (which & PEV_DISPMODE)
-	{
-		printf("PEV_DISPMODE\r\n");
-		/// ----- Compute new value -----
-		aspect_ratio = (aspect_ratio+1)%NB_ASPECT_RATIOS_TYPES;
-		/// ----- HUD msg ------
-		//char txt[100];
-		if(aspect_ratio == ASPECT_RATIOS_TYPE_MANUAL){
-			//sprintf(txt, "    DISPLAY MODE: ZOOMED - %d%%", aspect_ratio_factor_percent);
-			sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: ZOOMED %d%%%%\"",
-				SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_factor_percent);
-		}
-		else{
-			//sprintf(txt, "DISPLAY MODE: %s", aspect_ratio_name[aspect_ratio]);
-			sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: %s\"",
-				SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_name[aspect_ratio]);
-		}
-		//plat_status_msg_busy_first(txt);
-		fp = popen(shell_cmd, "r");
-		if (fp == NULL) {
-			printf("Failed to run command %s\n", shell_cmd);
-		} else {
-	        	pclose(fp);
-		}
-
-        // Save config file
-        configfile_save(cfg_file_rom);
-	}
-	if (which & (PEV_SSLOT_PREV|PEV_SSLOT_NEXT))
-	{
-		if (which & PEV_SSLOT_PREV) {
-			state_slot -= 1;
-			if (state_slot < 0)
-				state_slot = 9;
-		} else {
-			state_slot += 1;
-			if (state_slot > 9)
-				state_slot = 0;
-		}
-
-		emu_status_msg("SAVE SLOT %i [%s]", state_slot,
-			emu_check_save_file(state_slot, NULL) ? "USED" : "FREE");
-	}
-	if (which & PEV_RESET)
-		emu_reset_game();
-	if (which & PEV_MENU)
-		engineState = PGS_Menu;
-}
-
-void emu_update_input(void)
-{
-	static int prev_events = 0;
-	int actions[IN_BINDTYPE_COUNT] = { 0, };
-	int pl_actions[2];
-	int events;
-
-	in_update(actions);
-
-	pl_actions[0] = actions[IN_BINDTYPE_PLAYER12];
-	pl_actions[1] = actions[IN_BINDTYPE_PLAYER12] >> 16;
-
-	PicoIn.pad[0] = pl_actions[0] & 0xfff;
-	PicoIn.pad[1] = pl_actions[1] & 0xfff;
-
-	if (pl_actions[0] & 0x7000)
-		do_turbo(&PicoIn.pad[0], pl_actions[0]);
-	if (pl_actions[1] & 0x7000)
-		do_turbo(&PicoIn.pad[1], pl_actions[1]);
-
-	events = actions[IN_BINDTYPE_EMU] & PEV_MASK;
-
-	// volume is treated in special way and triggered every frame
-	if (events & (PEV_VOL_DOWN|PEV_VOL_UP))
-		plat_update_volume(1, events & PEV_VOL_UP);
-
-	if ((events ^ prev_events) & PEV_FF) {
-		emu_set_fastforward(events & PEV_FF);
-		plat_update_volume(0, 0);
-		reset_timing = 1;
-	}
-
-	events &= ~prev_events;
-
-	/* SMS */
-	if (PicoIn.AHW == PAHW_PICO)
-		run_events_pico(events);
-
-	if (events)
-		run_events_ui(events);
-
-	if (movie_data)
-		update_movie();
-
-	prev_events = actions[IN_BINDTYPE_EMU] & PEV_MASK;
-}
-
-static void mkdir_path(char *path_with_reserve, int pos, const char *name)
-{
-	strcpy(path_with_reserve + pos, name);
-	if (plat_is_dir(path_with_reserve))
-		return;
-	if (mkdir(path_with_reserve, 0777) < 0)
-		lprintf("failed to create: %s\n", path_with_reserve);
-}
-
-void emu_cmn_forced_frame(int no_scale, int do_emu, void *buf)
-{
-	int po_old = PicoIn.opt;
-	int y;
-
-	for (y = 0; y < g_screen_height; y++)
-		memset32((short *)g_screen_ptr + g_screen_ppitch * y, 0,
-			 g_screen_width * 2 / 4);
-
-	PicoIn.opt &= ~POPT_ALT_RENDERER;
-	PicoIn.opt |= POPT_ACC_SPRITES;
-	if (!no_scale && currentConfig.scaling)
-		PicoIn.opt |= POPT_EN_SOFTSCALE;
-
-	PicoDrawSetOutFormat(PDF_RGB555, 1);
-	PicoDrawSetOutBuf(buf, g_screen_ppitch * 2);
-	Pico.m.dirtyPal = 1;
-	Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;
-	if (do_emu)
-		PicoFrame();
-	else
-		PicoFrameDrawOnly();
-
-	PicoIn.opt = po_old;
-}
-
-int emu_is_segaCD(){
-	return (PicoIn.AHW & PAHW_MCD);
-}
-
-/* Quick save and turn off the console */
-void quick_save_and_poweroff()
-{
-    FILE *fp;
-
-    printf("Save Instant Play file\n");
-
-    /* Send command to cancel any previously scheduled powerdown */
-    fp = popen(SHELL_CMD_POWERDOWN_HANDLE, "r");
-    if (fp == NULL)
-    {
-        /* Countdown is still ticking, so better do nothing
-	   than start writing and get interrupted!
-	*/
-        printf("Failed to cancel scheduled shutdown\n");
-	exit(0);
-    }
-    pclose(fp);
-
-    /* Save  */
-    emu_save_load_game_from_file(0, quick_save_file);
-
-    /* Perform Instant Play save and shutdown */
-    execlp(SHELL_CMD_INSTANT_PLAY, SHELL_CMD_INSTANT_PLAY,
-	"save", prog_name, "-loadStateFile", quick_save_file, mRomName, NULL);
-
-    /* Should not be reached */
-    printf("Failed to perform Instant Play save and shutdown\n");
-
-    /* Exit Emulator */
-    exit(0);
-}
-
-
-void emu_init(void)
-{
-	char path[512];
-	int pos;
-
-#if 0
-	// FIXME: handle through menu, etc
-	FILE *f;
-	f = fopen("32X_M_BIOS.BIN", "rb");
-	p32x_bios_m = malloc(2048);
-	fread(p32x_bios_m, 1, 2048, f);
-	fclose(f);
-	f = fopen("32X_S_BIOS.BIN", "rb");
-	p32x_bios_s = malloc(1024);
-	fread(p32x_bios_s, 1, 1024, f);
-	fclose(f);
-#endif
-
-	/* make dirs for saves */
-	pos = plat_get_root_dir(path, sizeof(path) - 4);
-	mkdir_path(path, pos, "mds");
-	mkdir_path(path, pos, "srm");
-	mkdir_path(path, pos, "brm");
-	mkdir_path(path, pos, "cfg");
-
-	pprof_init();
-
-	make_config_cfg(path);
-	config_readlrom(path);
-
-	PicoInit();
-	PicoIn.osdMessage = plat_status_msg_busy_next;
-	PicoIn.mcdTrayOpen = emu_tray_open;
-	PicoIn.mcdTrayClose = emu_tray_close;
-
-	sndout_init();
-}
-
-void emu_finish(void)
-{
-	// save SRAM
-	if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && Pico.sv.changed) {
-		emu_save_load_game(0, 1);
-		Pico.sv.changed = 0;
-	}
-
-	if (!(currentConfig.EmuOpt & EOPT_NO_AUTOSVCFG)) {
-		char cfg[512];
-		make_config_cfg(cfg);
-		config_writelrom(cfg);
-#ifdef __GP2X__
-		sync();
-#endif
-	}
-
-	pprof_finish();
-
-	PicoExit();
-	sndout_exit();
-}
-
-static void snd_write_nonblocking(int len)
-{
-	sndout_write_nb(PicoIn.sndOut, len);
-}
-
-void emu_sound_start(void)
-{
-	PicoIn.sndOut = NULL;
-
-	if (currentConfig.EmuOpt & EOPT_EN_SOUND)
-	{
-		int is_stereo = (PicoIn.opt & POPT_EN_STEREO) ? 1 : 0;
-		/// Hard Bypass Stereo to mono
-		//is_stereo = 0;
-
-		PsndRerate(Pico.m.frame_count ? 1 : 0);
-
-		printf("starting audio: %i len: %i stereo: %i, pal: %i\n",
-			PicoIn.sndRate, Pico.snd.len, is_stereo, Pico.m.pal);
-		sndout_start(PicoIn.sndRate, is_stereo);
-		PicoIn.writeSound = snd_write_nonblocking;
-		plat_update_volume(0, 0);
-		memset(sndBuffer, 0, sizeof(sndBuffer));
-		PicoIn.sndOut = sndBuffer;
-	}
-}
-
-void emu_sound_stop(void)
-{
-	sndout_stop();
-}
-
-void emu_sound_wait(void)
-{
-	sndout_wait();
-}
-
-static void emu_loop_prep(void)
-{
-	static int pal_old = -1;
-	static int filter_old = -1;
-
-	if (currentConfig.CPUclock != plat_target_cpu_clock_get())
-		plat_target_cpu_clock_set(currentConfig.CPUclock);
-
-	if (Pico.m.pal != pal_old) {
-		plat_target_lcdrate_set(Pico.m.pal);
-		pal_old = Pico.m.pal;
-	}
-
-	if (currentConfig.filter != filter_old) {
-		plat_target_hwfilter_set(currentConfig.filter);
-		filter_old = currentConfig.filter;
-	}
-
-	plat_target_gamma_set(currentConfig.gamma, 0);
-
-	pemu_loop_prep();
-}
-
-/* our tick here is 1 us right now */
-#define ms_to_ticks(x) (unsigned int)(x * 1000)
-#define get_ticks() plat_get_ticks_us()
-
-void emu_loop(void)
-{
-	int frames_done, frames_shown;	/* actual frames for fps counter */
-	int frame_nb = 0;
-	int target_frametime_x3;
-	unsigned int timestamp_x3 = 0;
-	unsigned int timestamp_aim_x3 = 0;
-	unsigned int timestamp_fps_x3 = 0;
-	char *notice_msg = NULL;
-	char fpsbuff[24];
-	int fskip_cnt = 0;
-
-	fpsbuff[0] = 0;
-
-	PicoLoopPrepare();
-
-	plat_video_loop_prepare();
-	emu_loop_prep();
-	pemu_sound_start();
-
-	/* number of ticks per frame */
-	if (Pico.m.pal)
-		target_frametime_x3 = 3 * ms_to_ticks(1000) / 50;
-	else
-		target_frametime_x3 = 3 * ms_to_ticks(1000) / 60;
-
-	reset_timing = 1;
-	frames_done = frames_shown = 0;
-
-	/* loop with resync every 1 sec. */
-	while (engineState == PGS_Running)
-	{
-		int skip = 0;
-		int diff;
-
-		pprof_start(main);
-
-		if (reset_timing) {
-			reset_timing = 0;
-			plat_video_wait_vsync();
-			timestamp_aim_x3 = get_ticks() * 3;
-			timestamp_fps_x3 = timestamp_aim_x3;
-			fskip_cnt = 0;
-		}
-		else if (currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) {
-			timestamp_aim_x3 = get_ticks() * 3;
-		}
-
-		timestamp_x3 = get_ticks() * 3;
-
-		// show notice_msg message?
-		if (notice_msg_time != 0)
-		{
-			static int noticeMsgSum;
-			if (timestamp_x3 - ms_to_ticks(notice_msg_time) * 3
-			     > ms_to_ticks(STATUS_MSG_TIMEOUT) * 3)
-			{
-				notice_msg_time = 0;
-				notice_msg = NULL;
-				plat_status_msg_clear();
-			}
-			else {
-				int sum = noticeMsg[0] + noticeMsg[1] + noticeMsg[2];
-				if (sum != noticeMsgSum) {
-					plat_status_msg_clear();
-					noticeMsgSum = sum;
-				}
-				notice_msg = noticeMsg;
-			}
-		}
-
-		// second changed?
-		if (timestamp_x3 - timestamp_fps_x3 >= ms_to_ticks(1000) * 3)
-		{
-
-#define FUNKEY_RESYNCHRONIZE_AUDIO_SECS	(5*60)
-#ifdef FUNKEY_RESYNCHRONIZE_AUDIO_SECS
-			static unsigned int last_resync = 0, cur_sec=0;
-			if(cur_sec++ - last_resync >= FUNKEY_RESYNCHRONIZE_AUDIO_SECS){
-				last_resync = cur_sec;
-
-				/** Resync here (same as quitting ans relaunching loop for now) */
-				printf("Resync sound now\n");
-				emu_sound_stop();
-				pemu_sound_start();
-			}
-#endif //FUNKEY_RESYNCHRONIZE_AUDIO_SECS
-
-#ifdef BENCHMARK
-			static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];
-			if (++bench == 10) {
-				bench = 0;
-				bench_fps_s = bench_fps;
-				bf[bfp++ & 3] = bench_fps;
-				bench_fps = 0;
-			}
-			bench_fps += frames_shown;
-			sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);
-			printf("%s\n", fpsbuff);
-#else
-			if (currentConfig.EmuOpt & EOPT_SHOW_FPS || show_fps_bypass){
-				printf("%02i/%02i  \n", frames_shown, frames_done);
-				snprintf(fpsbuff, 8, "%02i/%02i  ", frames_shown, frames_done);
-			}
-#endif
-			frames_shown = frames_done = 0;
-			timestamp_fps_x3 += ms_to_ticks(1000) * 3;
-		}
-#ifdef PFRAMES
-		sprintf(fpsbuff, "%i", Pico.m.frame_count);
-#endif
-
-		diff = timestamp_aim_x3 - timestamp_x3;
-
-		if (currentConfig.Frameskip >= 0) // frameskip enabled (or 0)
-		{
-			if (fskip_cnt < currentConfig.Frameskip) {
-				fskip_cnt++;
-				skip = 1;
-			}
-			else {
-				fskip_cnt = 0;
-			}
-		}
-		else if (diff < -target_frametime_x3)
-		{
-			/* no time left for this frame - skip */
-			/* limit auto frameskip to max_skip */
-			if (fskip_cnt < currentConfig.max_skip) {
-				fskip_cnt++;
-				skip = 1;
-			}
-			else {
-				fskip_cnt = 0;
-			}
-		} else
-			fskip_cnt = 0;
-
-		// don't go in debt too much
-		while (diff < -target_frametime_x3 * 3) {
-			timestamp_aim_x3 += target_frametime_x3;
-			diff = timestamp_aim_x3 - timestamp_x3;
-		}
-
-        /* Quick save and poweroff */
-        if(mQuickSaveAndPoweroff){
-			quick_save_and_poweroff();
-			mQuickSaveAndPoweroff = 0;
-        }
-
-		emu_update_input();
-		if (skip) {
-			int do_audio = diff > -target_frametime_x3 * 2;
-			PicoIn.skipFrame = do_audio ? 1 : 2;
-			PicoFrame();
-			PicoIn.skipFrame = 0;
-		}
-		else {
-			PicoFrame();
-			pemu_finalize_frame(fpsbuff, notice_msg);
-			frames_shown++;
-		}
-		frames_done++;
-		timestamp_aim_x3 += target_frametime_x3;
-
-		if (!skip && !flip_after_sync)
-			plat_video_flip();
-
-		/* frame limiter */
-		if (!skip && !reset_timing
-		    && !(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT)))
-		{
-			unsigned int timestamp = get_ticks();
-			diff = timestamp_aim_x3 - timestamp * 3;
-
-			// sleep or vsync if we are still too fast
-			if (diff > target_frametime_x3 && (currentConfig.EmuOpt & EOPT_VSYNC)) {
-				// we are too fast
-				plat_video_wait_vsync();
-				timestamp = get_ticks();
-				diff = timestamp * 3 - timestamp_aim_x3;
-			}
-			if (diff > target_frametime_x3) {
-				// still too fast
-				plat_wait_till_us(timestamp + (diff - target_frametime_x3) / 3);
-			}
-		}
-
-		if (!skip && flip_after_sync)
-			plat_video_flip();
-
-
-		/* FOR SEGA CD */
-		/* Leave some time for the bios to load before starting a quick load at boot */
-		/* (Sega CD doesn't quick load at boot otherwise) */
-		/* Should have a much cleaner implementation thant this fix */
-		if((PicoIn.AHW & PAHW_MCD) && frame_nb >= 120){
-
-			/* Load slot */
-			if(load_state_slot != -1){
-				printf("LOADING FROM SLOT (SEGA CD) %d...\n", load_state_slot+1);
-				char fname[1024];
-				emu_save_load_game(1, 0);
-				printf("LOADED FROM SLOT (SEGA CD) %d\n", load_state_slot+1);
-				load_state_slot = -1;
-			}
-			if(need_quick_load != -1){
-				load_state_file = quick_save_file;
-				need_quick_load = -1;
-			}
-			/* Load file */
-			if(load_state_file != NULL){
-				printf("LOADING FROM FILE (SEGA CD) %s...\n", load_state_file);
-				emu_save_load_game_from_file(1, load_state_file);
-				printf("LOADED FROM SLOT (SEGA CD) %s\n", load_state_file);
-				load_state_file = NULL;
-			}
-		}
-
-		frame_nb++;
-
-		pprof_end(main);
-	}
-
-	emu_set_fastforward(0);
-
-	// save SRAM
-	if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && Pico.sv.changed) {
-		plat_status_msg_busy_first("Writing SRAM/BRAM...");
-		emu_save_load_game(0, 1);
-		Pico.sv.changed = 0;
-	}
-
-	pemu_loop_end();
-	emu_sound_stop();
-}
+/*
+ * PicoDrive
+ * (C) notaz, 2007-2010
+ *
+ * This work is licensed under the terms of MAME license.
+ * See COPYING file in the top-level directory.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#ifdef __GP2X__
+#include <unistd.h>
+#endif
+
+#include "../libpicofe/posix.h"
+#include "../libpicofe/input.h"
+#include "../libpicofe/fonts.h"
+#include "../libpicofe/sndout.h"
+#include "../libpicofe/lprintf.h"
+#include "../libpicofe/plat.h"
+#include "emu.h"
+#include "configfile_fk.h"
+#include "input_pico.h"
+#include "menu_pico.h"
+#include "config_file.h"
+
+#include <pico/pico_int.h>
+#include <pico/patch.h>
+
+#ifdef USE_LIBRETRO_VFS
+#include "file_stream_transforms.h"
+#endif
+
+#if defined(__GNUC__) && __GNUC__ >= 7
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+#endif
+
+#ifndef _WIN32
+#define PATH_SEP      "/"
+#define PATH_SEP_C    '/'
+#else
+#define PATH_SEP      "\\"
+#define PATH_SEP_C    '\\'
+#endif
+
+#define STATUS_MSG_TIMEOUT 2000
+
+void *g_screen_ptr;
+
+int g_screen_width  = 320;
+int g_screen_height = 240;
+int g_screen_ppitch = 320; // pitch in pixels
+
+const char *PicoConfigFile = "config2.cfg";
+currentConfig_t currentConfig, defaultConfig;
+int state_slot = 0;
+int config_slot = 0, config_slot_current = 0;
+int pico_pen_x = 320/2, pico_pen_y = 240/2;
+int pico_inp_mode;
+int flip_after_sync;
+int engineState = PGS_Menu;
+int show_fps_bypass = 0;
+int need_screen_cleared = 0;
+
+static short __attribute__((aligned(4))) sndBuffer[2*44100/50];
+
+/* tmp buff to reduce stack usage for plats with small stack */
+static char static_buff[1024];
+const char *rom_fname_reload;
+char rom_fname_loaded[1024];
+int reset_timing = 0;
+static unsigned int notice_msg_time;	/* when started showing */
+static char noticeMsg[40];
+
+unsigned char *movie_data = NULL;
+static int movie_size = 0;
+
+
+/* don't use tolower() for easy old glibc binary compatibility */
+static void strlwr_(char *string)
+{
+	char *p;
+	for (p = string; *p; p++)
+		if ('A' <= *p && *p <= 'Z')
+			*p += 'a' - 'A';
+}
+
+static int try_rfn_cut(char *fname)
+{
+	FILE *tmp;
+	char *p;
+
+	p = fname + strlen(fname) - 1;
+	for (; p > fname; p--)
+		if (*p == '.') break;
+	*p = 0;
+
+	if((tmp = fopen(fname, "rb"))) {
+		fclose(tmp);
+		return 1;
+	}
+	return 0;
+}
+
+static void get_ext(const char *file, char *ext)
+{
+	const char *p;
+
+	p = file + strlen(file) - 4;
+	if (p < file) p = file;
+	strncpy(ext, p, 4);
+	ext[4] = 0;
+	strlwr_(ext);
+}
+
+static void fname_ext(char *dst, int dstlen, const char *prefix, const char *ext, const char *fname)
+{
+	int prefix_len = 0;
+	const char *p;
+
+	*dst = 0;
+	if (prefix) {
+		/*int len = plat_get_root_dir(dst, dstlen);
+		strcpy(dst + len, prefix);
+		prefix_len = len + strlen(prefix);*/
+
+		/* Saves are in ROM folder */
+		prefix_len = strlen(mRomPath)+1;
+		sprintf(dst, "%s/", mRomPath);
+	}
+
+	p = fname + strlen(fname) - 1;
+	for (; p >= fname && *p != PATH_SEP_C; p--)
+		;
+	p++;
+	strncpy(dst + prefix_len, p, dstlen - prefix_len - 1);
+
+	dst[dstlen - 8] = 0;
+	if ((p = strrchr(dst, '.')) != NULL)
+		dst[p-dst] = 0;
+	if (ext)
+		strcat(dst, ext);
+}
+
+static void romfname_ext(char *dst, int dstlen, const char *prefix, const char *ext)
+{
+	fname_ext(dst, dstlen, prefix, ext, rom_fname_loaded);
+}
+
+void emu_status_msg(const char *format, ...)
+{
+	va_list vl;
+	int ret;
+
+	va_start(vl, format);
+	ret = vsnprintf(noticeMsg, sizeof(noticeMsg), format, vl);
+	va_end(vl);
+
+	/* be sure old text gets overwritten */
+	for (; ret < 28; ret++)
+		noticeMsg[ret] = ' ';
+	noticeMsg[ret] = 0;
+
+	notice_msg_time = plat_get_ticks_ms();
+}
+
+static const char * const biosfiles_us[] = {
+	"us_scd2_9306", "SegaCDBIOS9303", "us_scd1_9210", "bios_CD_U"
+};
+static const char * const biosfiles_eu[] = {
+	"eu_mcd2_9306", "eu_mcd2_9303", "eu_mcd1_9210", "bios_CD_E"
+};
+static const char * const biosfiles_jp[] = {
+	"jp_mcd2_921222", "jp_mcd1_9112", "jp_mcd1_9111", "bios_CD_J"
+};
+
+static const char *find_bios(int *region, const char *cd_fname)
+{
+	int i, count;
+	const char * const *files;
+	FILE *f = NULL;
+	int ret;
+
+	// we need to have config loaded at this point
+	ret = emu_read_config(cd_fname, 0);
+	if (!ret) emu_read_config(NULL, 0);
+
+	if (PicoIn.regionOverride) {
+		*region = PicoIn.regionOverride;
+		lprintf("override region to %s\n", *region != 4 ?
+			(*region == 8 ? "EU" : "JAP") : "USA");
+	}
+
+	if (*region == 4) { // US
+		files = biosfiles_us;
+		count = sizeof(biosfiles_us) / sizeof(char *);
+	} else if (*region == 8) { // EU
+		files = biosfiles_eu;
+		count = sizeof(biosfiles_eu) / sizeof(char *);
+	} else if (*region == 1 || *region == 2) {
+		files = biosfiles_jp;
+		count = sizeof(biosfiles_jp) / sizeof(char *);
+	} else {
+		return 0;
+	}
+
+	/** Check Bios files in home directory */
+	for (i = 0; i < count; i++)
+	{
+		emu_make_path(static_buff, files[i], sizeof(static_buff) - 4);
+		//printf("bios name static_buff: %s\n", static_buff);
+		strcat(static_buff, ".bin");
+		f = fopen(static_buff, "rb");
+		if (f) break;
+
+		static_buff[strlen(static_buff) - 4] = 0;
+		strcat(static_buff, ".zip");
+		f = fopen(static_buff, "rb");
+		if (f) break;
+
+		strcpy(static_buff, files[i]);
+		strcat(static_buff, ".bin");
+		f = fopen(static_buff, "rb");
+		if (f) break;
+
+		static_buff[strlen(static_buff) - 4] = 0;
+		strcat(static_buff, ".zip");
+		f = fopen(static_buff, "rb");
+		if (f) break;
+	}
+
+	/** Check Bios files in <rom path>/bios */
+	if(!f){
+		for (i = 0; i < count; i++)
+		{
+			sprintf(static_buff, "%s/bios/%s", mRomPath, files[i]);
+			printf("static_buff: %s\n", static_buff);
+			strcat(static_buff, ".bin");
+			f = fopen(static_buff, "rb");
+			if (f) break;
+
+			static_buff[strlen(static_buff) - 4] = 0;
+			strcat(static_buff, ".zip");
+			f = fopen(static_buff, "rb");
+			if (f) break;
+		}
+	}
+
+	/** Check Bios files in rom path */
+	if(!f){
+		for (i = 0; i < count; i++)
+		{
+			sprintf(static_buff, "%s/%s", mRomPath, files[i]);
+			printf("static_buff: %s\n", static_buff);
+			strcat(static_buff, ".bin");
+			f = fopen(static_buff, "rb");
+			if (f) break;
+
+			static_buff[strlen(static_buff) - 4] = 0;
+			strcat(static_buff, ".zip");
+			f = fopen(static_buff, "rb");
+			if (f) break;
+		}
+	}
+
+	if (f) {
+		lprintf("using bios: %s\n", static_buff);
+		fclose(f);
+		return static_buff;
+	} else {
+		char* country;
+		char** bios_names;
+		if(*region == 4){ //USA
+			country = "USA";
+			bios_names = biosfiles_us;
+		}
+		else if(*region == 8){ //EU
+			country = "EU";
+			bios_names = biosfiles_eu;
+		}
+		else{ //JAP
+			country = "JAP";
+			bios_names = biosfiles_jp;
+		}
+
+		sprintf(static_buff, "no %s BIOS files found, read docs", country);
+		menu_update_msg(static_buff);
+
+		/** Set notif for BIOS */
+	    char shell_cmd[400];
+	    sprintf(shell_cmd, "%s 0 \"    %s BIOS FILE MISSING^^Connect your FunKey S to ^your computer and copy the^BIOS file in the folder:^    Sega Genesis/bios/^^The file can be called:^ - %s.bin^ - %s.bin^ - %s.bin^ - %s.bin^^For more instructions:^www.funkey-project.com^^Press any button to exit...\"",
+	            SHELL_CMD_NOTIF_SET, country, bios_names[0], bios_names[1], bios_names[2], bios_names[3]);
+	    FILE *fp = popen(shell_cmd, "r");
+	    if (fp == NULL) {
+	        printf("In %s, Failed to run command %s\n", __func__, shell_cmd);
+	    } else {
+	        pclose(fp);
+	    }
+
+	    /** Wait for key press */
+	    SDL_Event event;
+	    while(event.type != SDL_KEYUP && event.type != SDL_QUIT){
+	        while (SDL_PollEvent(&event))
+			SDL_Delay(60);
+	    }
+
+	    /** Clear notif for BIOS */
+	    fp = popen(SHELL_CMD_NOTIF_CLEAR, "r");
+	    if (fp == NULL) {
+	        printf("In %s, Failed to run command %s\n", __func__, SHELL_CMD_NOTIF_CLEAR);
+	    } else {
+	        pclose(fp);
+	    }
+
+	    /** Force clean exit */
+        //engineState = PGS_Quit;
+		emu_finish();
+		plat_finish();
+		plat_target_finish();
+		exit(0);
+		return NULL;
+	}
+}
+
+/* check if the name begins with BIOS name */
+/*
+static int emu_isBios(const char *name)
+{
+	int i;
+	for (i = 0; i < sizeof(biosfiles_us)/sizeof(biosfiles_us[0]); i++)
+		if (strstr(name, biosfiles_us[i]) != NULL) return 1;
+	for (i = 0; i < sizeof(biosfiles_eu)/sizeof(biosfiles_eu[0]); i++)
+		if (strstr(name, biosfiles_eu[i]) != NULL) return 1;
+	for (i = 0; i < sizeof(biosfiles_jp)/sizeof(biosfiles_jp[0]); i++)
+		if (strstr(name, biosfiles_jp[i]) != NULL) return 1;
+	return 0;
+}
+*/
+
+static int extract_text(char *dest, const unsigned char *src, int len, int swab)
+{
+	char *p = dest;
+	int i;
+
+	if (swab) swab = 1;
+
+	for (i = len - 1; i >= 0; i--)
+	{
+		if (src[i^swab] != ' ') break;
+	}
+	len = i + 1;
+
+	for (i = 0; i < len; i++)
+	{
+		unsigned char s = src[i^swab];
+		if (s >= 0x20 && s < 0x7f && s != '#' && s != '|' &&
+			s != '[' && s != ']' && s != '\\')
+		{
+			*p++ = s;
+		}
+		else
+		{
+			sprintf(p, "\\%02x", s);
+			p += 3;
+		}
+	}
+
+	return p - dest;
+}
+
+static char *emu_make_rom_id(const char *fname)
+{
+	static char id_string[3+0xe*3+0x3*3+0x30*3+3];
+	int pos, swab = 1;
+
+	if (PicoIn.AHW & PAHW_MCD) {
+		strcpy(id_string, "CD|");
+		swab = 0;
+	}
+	else if (PicoIn.AHW & PAHW_SMS)
+		strcpy(id_string, "MS|");
+	else	strcpy(id_string, "MD|");
+	pos = 3;
+
+	if (!(PicoIn.AHW & PAHW_SMS)) {
+		pos += extract_text(id_string + pos, media_id_header + 0x80, 0x0e, swab); // serial
+		id_string[pos] = '|'; pos++;
+		pos += extract_text(id_string + pos, media_id_header + 0xf0, 0x03, swab); // region
+		id_string[pos] = '|'; pos++;
+		pos += extract_text(id_string + pos, media_id_header + 0x50, 0x30, swab); // overseas name
+		id_string[pos] = 0;
+		if (pos > 5)
+			return id_string;
+		pos = 3;
+	}
+
+	// can't find name in ROM, use filename
+	fname_ext(id_string + 3, sizeof(id_string) - 3, NULL, NULL, fname);
+
+	return id_string;
+}
+
+// buffer must be at least 150 byte long
+void emu_get_game_name(char *str150)
+{
+	int ret, swab = (PicoIn.AHW & PAHW_MCD) ? 0 : 1;
+	char *s, *d;
+
+	ret = extract_text(str150, media_id_header + 0x50, 0x30, swab); // overseas name
+
+	for (s = d = str150 + 1; s < str150+ret; s++)
+	{
+		if (*s == 0) break;
+		if (*s != ' ' || d[-1] != ' ')
+			*d++ = *s;
+	}
+	*d = 0;
+}
+
+static void system_announce(void)
+{
+	const char *sys_name, *tv_standard, *extra = "";
+	int fps;
+
+	if (PicoIn.AHW & PAHW_SMS) {
+		sys_name = "Master System";
+		if (Pico.m.hardware & 0x1)
+			sys_name = "Game Gear";
+		else if (Pico.m.hardware & 0x4)
+			sys_name = "Mark III";
+#ifdef NO_SMS
+		extra = " [no support]";
+#endif
+	} else if (PicoIn.AHW & PAHW_PICO) {
+		sys_name = "Pico";
+	} else if ((PicoIn.AHW & (PAHW_32X|PAHW_MCD)) == (PAHW_32X|PAHW_MCD)) {
+		sys_name = "32X + Mega CD";
+		if ((Pico.m.hardware & 0xc0) == 0x80)
+			sys_name = "32X + Sega CD";
+	} else if (PicoIn.AHW & PAHW_MCD) {
+		sys_name = "Mega CD";
+		if ((Pico.m.hardware & 0xc0) == 0x80)
+			sys_name = "Sega CD";
+	} else if (PicoIn.AHW & PAHW_32X) {
+		sys_name = "32X";
+	} else {
+		sys_name = "MegaDrive";
+		if ((Pico.m.hardware & 0xc0) == 0x80)
+			sys_name = "Genesis";
+	}
+	tv_standard = Pico.m.pal ? "PAL" : "NTSC";
+	fps = Pico.m.pal ? 50 : 60;
+
+	//emu_status_msg("%s %s / %dFPS%s", tv_standard, sys_name, fps, extra);
+	printf("\nSystem Announce: %s, %s / %dFPS%s\n", sys_name, tv_standard, fps, extra);
+	printf("PicoIn.AHW = %d, Pico.m.hardware=%d\n", PicoIn.AHW, Pico.m.hardware);
+}
+
+static void do_region_override(const char *media_fname)
+{
+	// we only need to override region if config tells us so
+	int ret = emu_read_config(media_fname, 0);
+	if (!ret) emu_read_config(NULL, 0);
+}
+
+int emu_reload_rom(const char *rom_fname_in)
+{
+	// use setting before rom config is loaded
+	int autoload = g_autostateld_opt;
+	char *rom_fname = NULL;
+	char ext[5];
+	enum media_type_e media_type;
+	int menu_romload_started = 0;
+	char carthw_path[512];
+	int retval = 0;
+
+	lprintf("emu_ReloadRom(%s)\n", rom_fname_in);
+
+	rom_fname = strdup(rom_fname_in);
+	if (rom_fname == NULL)
+		return 0;
+
+	get_ext(rom_fname, ext);
+
+	// early cleanup
+	PicoPatchUnload();
+	if (movie_data) {
+		free(movie_data);
+		movie_data = 0;
+	}
+
+	if (!strcmp(ext, ".gmv"))
+	{
+		// check for both gmv and rom
+		int dummy;
+		FILE *movie_file = fopen(rom_fname, "rb");
+		if (!movie_file) {
+			menu_update_msg("Failed to open movie.");
+			goto out;
+		}
+		fseek(movie_file, 0, SEEK_END);
+		movie_size = ftell(movie_file);
+		fseek(movie_file, 0, SEEK_SET);
+		if (movie_size < 64+3) {
+			menu_update_msg("Invalid GMV file.");
+			fclose(movie_file);
+			goto out;
+		}
+		movie_data = malloc(movie_size);
+		if (movie_data == NULL) {
+			menu_update_msg("low memory.");
+			fclose(movie_file);
+			goto out;
+		}
+		dummy = fread(movie_data, 1, movie_size, movie_file);
+		fclose(movie_file);
+		if (strncmp((char *)movie_data, "Gens Movie TEST", 15) != 0) {
+			menu_update_msg("Invalid GMV file.");
+			goto out;
+		}
+		dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);
+		if (!dummy) {
+			menu_update_msg("Could't find a ROM for movie.");
+			goto out;
+		}
+		get_ext(rom_fname, ext);
+		lprintf("gmv loaded for %s\n", rom_fname);
+	}
+	else if (!strcmp(ext, ".pat"))
+	{
+		int dummy;
+		PicoPatchLoad(rom_fname);
+		dummy = try_rfn_cut(rom_fname) || try_rfn_cut(rom_fname);
+		if (!dummy) {
+			menu_update_msg("Could't find a ROM to patch.");
+			goto out;
+		}
+		get_ext(rom_fname, ext);
+	}
+
+	menu_romload_prepare(rom_fname); // also CD load
+	menu_romload_started = 1;
+
+	emu_make_path(carthw_path, "carthw.cfg", sizeof(carthw_path));
+
+	media_type = PicoLoadMedia(rom_fname, carthw_path,
+			find_bios, do_region_override);
+
+	switch (media_type) {
+	case PM_BAD_DETECT:
+		menu_update_msg("Not a ROM/CD img selected.");
+		goto out;
+	case PM_BAD_CD:
+		menu_update_msg("Invalid CD image");
+		goto out;
+	case PM_BAD_CD_NO_BIOS:
+		// find_bios() prints a message
+		goto out;
+	case PM_ERROR:
+		menu_update_msg("Load error");
+		goto out;
+	default:
+		break;
+	}
+
+	// make quirks visible in UI
+	if (PicoIn.quirks & PQUIRK_FORCE_6BTN)
+		currentConfig.input_dev0 = PICO_INPUT_PAD_6BTN;
+
+	menu_romload_end();
+	menu_romload_started = 0;
+
+	if (PicoPatches) {
+		PicoPatchPrepare();
+		PicoPatchApply();
+	}
+
+	// additional movie stuff
+	if (movie_data)
+	{
+		enum input_device indev = (movie_data[0x14] == '6') ?
+			PICO_INPUT_PAD_6BTN : PICO_INPUT_PAD_3BTN;
+		PicoSetInputDevice(0, indev);
+		PicoSetInputDevice(1, indev);
+
+		PicoIn.opt |= POPT_DIS_VDP_FIFO; // no VDP fifo timing
+		if (movie_data[0xF] >= 'A') {
+			if (movie_data[0x16] & 0x80) {
+				PicoIn.regionOverride = 8;
+			} else {
+				PicoIn.regionOverride = 4;
+			}
+			PicoReset();
+			// TODO: bits 6 & 5
+		}
+		movie_data[0x18+30] = 0;
+		emu_status_msg("MOVIE: %s", (char *) &movie_data[0x18]);
+	}
+	else
+	{
+		PicoSetInputDevice(0, currentConfig.input_dev0);
+		PicoSetInputDevice(1, currentConfig.input_dev1);
+
+		system_announce();
+		PicoIn.opt &= ~POPT_DIS_VDP_FIFO;
+	}
+
+	/* Set input map */
+	if (PicoIn.AHW & PAHW_SMS) {
+		printf("plat set sms input\n");
+		plat_set_sms_input();
+	}
+	else{
+		plat_set_genesis_input();
+	}
+
+	strncpy(rom_fname_loaded, rom_fname, sizeof(rom_fname_loaded)-1);
+	rom_fname_loaded[sizeof(rom_fname_loaded)-1] = 0;
+
+	// load SRAM for this ROM
+	if (currentConfig.EmuOpt & EOPT_EN_SRAM)
+		emu_save_load_game(1, 1);
+
+	// state autoload?
+	if (autoload) {
+		int time, newest = 0, newest_slot = -1;
+		int slot;
+
+		for (slot = 0; slot < 10; slot++) {
+			if (emu_check_save_file(slot, &time)) {
+				if (time > newest) {
+					newest = time;
+					newest_slot = slot;
+				}
+			}
+		}
+
+		if (newest_slot >= 0) {
+			lprintf("autoload slot %d\n", newest_slot);
+			state_slot = newest_slot;
+			emu_save_load_game(1, 0);
+		}
+		else {
+			lprintf("no save to autoload.\n");
+		}
+	}
+
+	retval = 1;
+out:
+	if (menu_romload_started)
+		menu_romload_end();
+	free(rom_fname);
+	return retval;
+}
+
+int emu_swap_cd(const char *fname)
+{
+	enum cd_track_type cd_type;
+	int ret = -1;
+
+	cd_type = PicoCdCheck(fname, NULL);
+	if (cd_type != CT_UNKNOWN)
+		ret = cdd_load(fname, cd_type);
+	if (ret != 0) {
+		menu_update_msg("Load failed, invalid CD image?");
+		return 0;
+	}
+
+	strncpy(rom_fname_loaded, fname, sizeof(rom_fname_loaded)-1);
+	rom_fname_loaded[sizeof(rom_fname_loaded) - 1] = 0;
+
+	return 1;
+}
+
+// <base dir><end>
+void emu_make_path(char *buff, const char *end, int size)
+{
+	int pos, end_len;
+
+	end_len = strlen(end);
+	pos = plat_get_root_dir(buff, size);
+	strncpy(buff + pos, end, size - pos);
+	buff[size - 1] = 0;
+	if (pos + end_len > size - 1)
+		lprintf("Warning: path truncated: %s\n", buff);
+}
+
+static void make_config_cfg(char *cfg_buff_512)
+{
+	emu_make_path(cfg_buff_512, PicoConfigFile, 512-6);
+	if (config_slot != 0)
+	{
+		char *p = strrchr(cfg_buff_512, '.');
+		if (p == NULL)
+			p = cfg_buff_512 + strlen(cfg_buff_512);
+		sprintf(p, ".%i.cfg", config_slot);
+	}
+	cfg_buff_512[511] = 0;
+}
+
+void emu_prep_defconfig(void)
+{
+	memset(&defaultConfig, 0, sizeof(defaultConfig));
+	defaultConfig.EmuOpt    = EOPT_EN_SRAM | EOPT_EN_SOUND | EOPT_16BPP |
+				  EOPT_EN_CD_LEDS | EOPT_GZIP_SAVES | 0x10/*?*/;
+	defaultConfig.s_PicoOpt = POPT_EN_YM2413|POPT_EN_GG_LCD |
+				  POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80 |
+				  POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_MCD_GFX |
+				  POPT_EN_DRC|POPT_ACC_SPRITES |
+				  POPT_EN_32X|POPT_EN_PWM;
+	defaultConfig.s_PsndRate = 44100;
+	defaultConfig.s_PicoRegion = 0; // auto
+	defaultConfig.s_PicoAutoRgnOrder = 0x184; // US, EU, JP
+	defaultConfig.s_hwSelect = PHWS_AUTO;
+	defaultConfig.s_PicoCDBuffers = 0;
+	defaultConfig.s_PicoSndFilterAlpha = 0x10000 * 60 / 100;
+	defaultConfig.confirm_save = EOPT_CONFIRM_SAVE;
+	defaultConfig.Frameskip = -1; // auto
+	defaultConfig.input_dev0 = PICO_INPUT_PAD_3BTN;
+	defaultConfig.input_dev1 = PICO_INPUT_PAD_3BTN;
+	defaultConfig.volume = 50;
+	//defaultConfig.volume = 99;
+	defaultConfig.gamma = 100;
+	defaultConfig.scaling = 0;
+	defaultConfig.turbo_rate = 15;
+	defaultConfig.msh2_khz = PICO_MSH2_HZ / 1000;
+	defaultConfig.ssh2_khz = PICO_SSH2_HZ / 1000;
+	defaultConfig.max_skip = 4;
+
+	// platform specific overrides
+	pemu_prep_defconfig();
+}
+
+void emu_set_defconfig(void)
+{
+	memcpy(&currentConfig, &defaultConfig, sizeof(currentConfig));
+	PicoIn.opt = currentConfig.s_PicoOpt;
+	PicoIn.sndRate = currentConfig.s_PsndRate;
+	PicoIn.regionOverride = currentConfig.s_PicoRegion;
+	PicoIn.autoRgnOrder = currentConfig.s_PicoAutoRgnOrder;
+	PicoIn.hwSelect = currentConfig.s_hwSelect;
+	PicoIn.sndFilterAlpha = currentConfig.s_PicoSndFilterAlpha;
+}
+
+int emu_read_config(const char *rom_fname, int no_defaults)
+{
+	char cfg[512];
+	int ret;
+
+	if (!no_defaults)
+		emu_set_defconfig();
+
+	if (rom_fname == NULL)
+	{
+		// global config
+		make_config_cfg(cfg);
+		ret = config_readsect(cfg, NULL);
+	}
+	else
+	{
+		char ext[16];
+		int vol;
+
+		if (config_slot != 0)
+			snprintf(ext, sizeof(ext), ".%i.cfg", config_slot);
+		else
+			strcpy(ext, ".cfg");
+
+		fname_ext(cfg, sizeof(cfg), "cfg"PATH_SEP, ext, rom_fname); // gamepath/gamename.cfg
+
+		// read user's config
+		vol = currentConfig.volume;
+		ret = config_readsect(cfg, NULL);
+		currentConfig.volume = vol; // make vol global (bah)
+
+		if (ret != 0)
+		{
+			// read global config, and apply game_def.cfg on top
+			make_config_cfg(cfg); // $HOME/.picodrive/config2.cfg
+			config_readsect(cfg, NULL);
+
+			emu_make_path(cfg, "game_def.cfg", sizeof(cfg)); // $HOME/.picodrive/game_def.cfg
+			ret = config_readsect(cfg, emu_make_rom_id(rom_fname));
+		}
+	}
+
+	pemu_validate_config();
+	PicoIn.overclockM68k = currentConfig.overclock_68k;
+
+	// some sanity checks
+	if (currentConfig.volume < 0 || currentConfig.volume > 99)
+		currentConfig.volume = 50;
+
+	if (ret == 0)
+		config_slot_current = config_slot;
+
+	return (ret == 0);
+}
+
+
+int emu_write_config(int is_game)
+{
+	char cfg[512];
+	int ret, write_lrom = 0;
+
+	if (!is_game)
+	{
+		make_config_cfg(cfg);
+		write_lrom = 1;
+	} else {
+		char ext[16];
+
+		if (config_slot != 0)
+			snprintf(ext, sizeof(ext), ".%i.cfg", config_slot);
+		else
+			strcpy(ext, ".cfg");
+
+		romfname_ext(cfg, sizeof(cfg), "cfg"PATH_SEP, ext);
+	}
+
+	lprintf("emu_write_config: %s ", cfg);
+	ret = config_write(cfg);
+	if (write_lrom) config_writelrom(cfg);
+#ifdef __GP2X__
+	sync();
+#endif
+	lprintf((ret == 0) ? "(ok)\n" : "(failed)\n");
+
+	if (ret == 0) config_slot_current = config_slot;
+	return ret == 0;
+}
+
+
+/* always using built-in font */
+
+#define mk_text_out(name, type, val, topleft, step_x, step_y) \
+void name(int x, int y, const char *text)				\
+{									\
+	int i, l, len = strlen(text);					\
+	type *screen = (type *)(topleft) + x * step_x + y * step_y;	\
+									\
+	for (i = 0; i < len; i++, screen += 8 * step_x)			\
+	{								\
+		for (l = 0; l < 8; l++)					\
+		{							\
+			unsigned char fd = fontdata8x8[text[i] * 8 + l];\
+			type *s = screen + l * step_y;			\
+			if (fd&0x80) s[step_x * 0] = val;		\
+			if (fd&0x40) s[step_x * 1] = val;		\
+			if (fd&0x20) s[step_x * 2] = val;		\
+			if (fd&0x10) s[step_x * 3] = val;		\
+			if (fd&0x08) s[step_x * 4] = val;		\
+			if (fd&0x04) s[step_x * 5] = val;		\
+			if (fd&0x02) s[step_x * 6] = val;		\
+			if (fd&0x01) s[step_x * 7] = val;		\
+		}							\
+	}								\
+}
+
+mk_text_out(emu_text_out8,      unsigned char,    0xf0, g_screen_ptr, 1, g_screen_ppitch)
+mk_text_out(emu_text_out16,     unsigned short, 0xffff, g_screen_ptr, 1, g_screen_ppitch)
+mk_text_out(emu_text_out8_rot,  unsigned char,    0xf0,
+	(char *)g_screen_ptr  + (g_screen_ppitch - 1) * g_screen_height, -g_screen_height, 1)
+mk_text_out(emu_text_out16_rot, unsigned short, 0xffff,
+	(short *)g_screen_ptr + (g_screen_ppitch - 1) * g_screen_height, -g_screen_height, 1)
+
+#undef mk_text_out
+
+void emu_osd_text16(int x, int y, const char *text)
+{
+	int len = strlen(text) * 8;
+	int i, h;
+
+	len++;
+	if (x + len > g_screen_width)
+		len = g_screen_width - x;
+
+	for (h = 0; h < 8; h++) {
+		unsigned short *p;
+		p = (unsigned short *)g_screen_ptr
+			+ x + g_screen_ppitch * (y + h);
+		for (i = len; i > 0; i--, p++)
+			*p = (*p >> 2) & 0x39e7;
+	}
+	emu_text_out16(x, y, text);
+}
+
+static void update_movie(void)
+{
+	int offs = Pico.m.frame_count*3 + 0x40;
+	if (offs+3 > movie_size) {
+		free(movie_data);
+		movie_data = 0;
+		emu_status_msg("END OF MOVIE.");
+		lprintf("END OF MOVIE.\n");
+	} else {
+		// MXYZ SACB RLDU
+		PicoIn.pad[0] = ~movie_data[offs]   & 0x8f; // ! SCBA RLDU
+		if(!(movie_data[offs]   & 0x10)) PicoIn.pad[0] |= 0x40; // C
+		if(!(movie_data[offs]   & 0x20)) PicoIn.pad[0] |= 0x10; // A
+		if(!(movie_data[offs]   & 0x40)) PicoIn.pad[0] |= 0x20; // B
+		PicoIn.pad[1] = ~movie_data[offs+1] & 0x8f; // ! SCBA RLDU
+		if(!(movie_data[offs+1] & 0x10)) PicoIn.pad[1] |= 0x40; // C
+		if(!(movie_data[offs+1] & 0x20)) PicoIn.pad[1] |= 0x10; // A
+		if(!(movie_data[offs+1] & 0x40)) PicoIn.pad[1] |= 0x20; // B
+		PicoIn.pad[0] |= (~movie_data[offs+2] & 0x0A) << 8; // ! MZYX
+		if(!(movie_data[offs+2] & 0x01)) PicoIn.pad[0] |= 0x0400; // X
+		if(!(movie_data[offs+2] & 0x04)) PicoIn.pad[0] |= 0x0100; // Z
+		PicoIn.pad[1] |= (~movie_data[offs+2] & 0xA0) << 4; // ! MZYX
+		if(!(movie_data[offs+2] & 0x10)) PicoIn.pad[1] |= 0x0400; // X
+		if(!(movie_data[offs+2] & 0x40)) PicoIn.pad[1] |= 0x0100; // Z
+	}
+}
+
+static int try_ropen_file(const char *fname, int *time)
+{
+	struct stat st;
+	FILE *f;
+
+	f = fopen(fname, "rb");
+	if (f) {
+		if (time != NULL) {
+			*time = 0;
+			if (fstat(fileno(f), &st) == 0)
+				*time = (int)st.st_mtime;
+		}
+		fclose(f);
+		return 1;
+	}
+	return 0;
+}
+
+char *emu_get_save_fname(int load, int is_sram, int slot, int *time)
+{
+	char *saveFname = static_buff;
+	char ext[16];
+
+	if (is_sram)
+	{
+		strcpy(ext, (PicoIn.AHW & PAHW_MCD) ? ".brm" : ".srm");
+		romfname_ext(saveFname, sizeof(static_buff),
+			(PicoIn.AHW & PAHW_MCD) ? "brm"PATH_SEP : "srm"PATH_SEP, ext);
+		if (!load)
+			return saveFname;
+
+		if (try_ropen_file(saveFname, time))
+			return saveFname;
+
+		romfname_ext(saveFname, sizeof(static_buff), NULL, ext);
+		if (try_ropen_file(saveFname, time))
+			return saveFname;
+	}
+	else
+	{
+		const char *ext_main = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds.gz" : ".mds";
+		const char *ext_othr = (currentConfig.EmuOpt & EOPT_GZIP_SAVES) ? ".mds" : ".mds.gz";
+		ext[0] = 0;
+		if (slot > 0 && slot < 10)
+			sprintf(ext, ".%i", slot);
+		strcat(ext, ext_main);
+
+		if (!load) {
+			romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);
+			return saveFname;
+		}
+		else {
+			romfname_ext(saveFname, sizeof(static_buff), "mds" PATH_SEP, ext);
+			if (try_ropen_file(saveFname, time))
+				return saveFname;
+
+			romfname_ext(saveFname, sizeof(static_buff), NULL, ext);
+			if (try_ropen_file(saveFname, time))
+				return saveFname;
+
+			// try the other ext
+			ext[0] = 0;
+			if (slot > 0 && slot < 10)
+				sprintf(ext, ".%i", slot);
+			strcat(ext, ext_othr);
+
+			romfname_ext(saveFname, sizeof(static_buff), "mds"PATH_SEP, ext);
+			if (try_ropen_file(saveFname, time))
+				return saveFname;
+		}
+	}
+
+	return NULL;
+}
+
+int emu_check_save_file(int slot, int *time)
+{
+	return emu_get_save_fname(1, 0, slot, time) ? 1 : 0;
+}
+
+int emu_save_load_game_from_file(int load, char *saveFname){
+	int ret = PicoState(saveFname, !load);
+	if (!ret) {
+		//emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");
+	} else {
+		//emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");
+		ret = -1;
+	}
+
+	return ret;
+}
+
+
+int emu_save_load_game(int load, int sram)
+{
+	int ret = 0;
+	char *saveFname;
+
+	// make save filename
+	saveFname = emu_get_save_fname(load, sram, state_slot, NULL);
+	if (saveFname == NULL) {
+		/*if (!sram)
+			emu_status_msg(load ? "LOAD FAILED (missing file)" : "SAVE FAILED");*/
+		return -1;
+	}
+
+	lprintf("saveLoad (%i, %i): %s\n", load, sram, saveFname);
+
+	if (sram)
+	{
+		FILE *sramFile;
+		int sram_size;
+		unsigned char *sram_data;
+		int truncate = 1;
+		if (PicoIn.AHW & PAHW_MCD)
+		{
+			if (PicoIn.opt & POPT_EN_MCD_RAMCART) {
+				sram_size = 0x12000;
+				sram_data = Pico.sv.data;
+				if (sram_data)
+					memcpy(sram_data, Pico_mcd->bram, 0x2000);
+			} else {
+				sram_size = 0x2000;
+				sram_data = Pico_mcd->bram;
+				truncate  = 0; // the .brm may contain RAM cart data after normal brm
+			}
+		} else {
+			sram_size = Pico.sv.size;
+			sram_data = Pico.sv.data;
+		}
+		if (sram_data == NULL)
+			return 0; // cart saves forcefully disabled for this game
+
+		if (load)
+		{
+			sramFile = fopen(saveFname, "rb");
+			if (!sramFile)
+				return -1;
+			ret = fread(sram_data, 1, sram_size, sramFile);
+			ret = ret > 0 ? 0 : -1;
+			fclose(sramFile);
+			if ((PicoIn.AHW & PAHW_MCD) && (PicoIn.opt&POPT_EN_MCD_RAMCART))
+				memcpy(Pico_mcd->bram, sram_data, 0x2000);
+		} else {
+			// sram save needs some special processing
+			// see if we have anything to save
+			for (; sram_size > 0; sram_size--)
+				if (sram_data[sram_size-1]) break;
+
+			if (sram_size) {
+				sramFile = fopen(saveFname, truncate ? "wb" : "r+b");
+				if (!sramFile) sramFile = fopen(saveFname, "wb"); // retry
+				if (!sramFile) return -1;
+				ret = fwrite(sram_data, 1, sram_size, sramFile);
+				ret = (ret != sram_size) ? -1 : 0;
+				fclose(sramFile);
+#ifdef __GP2X__
+				sync();
+#endif
+			}
+		}
+		return ret;
+	}
+	else
+	{
+		ret = PicoState(saveFname, !load);
+		if (!ret) {
+#ifdef __GP2X__
+			if (!load) sync();
+#endif
+			//emu_status_msg(load ? "STATE LOADED" : "STATE SAVED");
+		} else {
+			//emu_status_msg(load ? "LOAD FAILED" : "SAVE FAILED");
+			ret = -1;
+		}
+
+		return ret;
+	}
+}
+
+void emu_set_fastforward(int set_on)
+{
+	static void *set_PsndOut = NULL;
+	static int set_Frameskip, set_EmuOpt, is_on = 0;
+
+	if (set_on && !is_on) {
+		set_PsndOut = PicoIn.sndOut;
+		set_Frameskip = currentConfig.Frameskip;
+		set_EmuOpt = currentConfig.EmuOpt;
+		PicoIn.sndOut = NULL;
+		currentConfig.Frameskip = 8;
+		currentConfig.EmuOpt &= ~4;
+		currentConfig.EmuOpt |= EOPT_NO_FRMLIMIT;
+		is_on = 1;
+		emu_status_msg("FAST FORWARD");
+	}
+	else if (!set_on && is_on) {
+		PicoIn.sndOut = set_PsndOut;
+		currentConfig.Frameskip = set_Frameskip;
+		currentConfig.EmuOpt = set_EmuOpt;
+		PsndRerate(1);
+		is_on = 0;
+		// mainly to unbreak pcm
+		if (PicoIn.AHW & PAHW_MCD)
+			pcd_state_loaded();
+	}
+}
+
+static void emu_tray_open(void)
+{
+	engineState = PGS_TrayMenu;
+}
+
+static void emu_tray_close(void)
+{
+	emu_status_msg("CD tray closed.");
+}
+
+void emu_32x_startup(void)
+{
+	plat_video_toggle_renderer(0, 0); // HACK
+	system_announce();
+}
+
+void emu_reset_game(void)
+{
+	PicoReset();
+	reset_timing = 1;
+}
+
+void run_events_pico(unsigned int events)
+{
+	int lim_x;
+
+	if (events & PEV_PICO_SWINP) {
+		pico_inp_mode++;
+		if (pico_inp_mode > 2)
+			pico_inp_mode = 0;
+		switch (pico_inp_mode) {
+			case 2: emu_status_msg("Input: Pen on Pad"); break;
+			case 1: emu_status_msg("Input: Pen on Storyware"); break;
+			case 0: emu_status_msg("Input: Joystick");
+				PicoPicohw.pen_pos[0] = PicoPicohw.pen_pos[1] = 0x8000;
+				break;
+		}
+	}
+	if (events & PEV_PICO_PPREV) {
+		PicoPicohw.page--;
+		if (PicoPicohw.page < 0)
+			PicoPicohw.page = 0;
+		emu_status_msg("Page %i", PicoPicohw.page);
+	}
+	if (events & PEV_PICO_PNEXT) {
+		PicoPicohw.page++;
+		if (PicoPicohw.page > 6)
+			PicoPicohw.page = 6;
+		emu_status_msg("Page %i", PicoPicohw.page);
+	}
+
+	if (pico_inp_mode == 0)
+		return;
+
+	/* handle other input modes */
+	if (PicoIn.pad[0] & 1) pico_pen_y--;
+	if (PicoIn.pad[0] & 2) pico_pen_y++;
+	if (PicoIn.pad[0] & 4) pico_pen_x--;
+	if (PicoIn.pad[0] & 8) pico_pen_x++;
+	PicoIn.pad[0] &= ~0x0f; // release UDLR
+
+	lim_x = (Pico.video.reg[12]&1) ? 319 : 255;
+	if (pico_pen_y < 8)
+		pico_pen_y = 8;
+	if (pico_pen_y > 224 - PICO_PEN_ADJUST_Y)
+		pico_pen_y = 224 - PICO_PEN_ADJUST_Y;
+	if (pico_pen_x < 0)
+		pico_pen_x = 0;
+	if (pico_pen_x > lim_x - PICO_PEN_ADJUST_X)
+		pico_pen_x = lim_x - PICO_PEN_ADJUST_X;
+
+	PicoPicohw.pen_pos[0] = pico_pen_x;
+	if (!(Pico.video.reg[12] & 1))
+		PicoPicohw.pen_pos[0] += pico_pen_x / 4;
+	PicoPicohw.pen_pos[0] += 0x3c;
+	PicoPicohw.pen_pos[1] = pico_inp_mode == 1 ? (0x2f8 + pico_pen_y) : (0x1fc + pico_pen_y);
+}
+
+static void do_turbo(unsigned short *pad, int acts)
+{
+	static int turbo_pad = 0;
+	static unsigned char turbo_cnt[3] = { 0, 0, 0 };
+	int inc = currentConfig.turbo_rate * 2;
+
+	if (acts & 0x1000) {
+		turbo_cnt[0] += inc;
+		if (turbo_cnt[0] >= 60)
+			turbo_pad ^= 0x10, turbo_cnt[0] = 0;
+	}
+	if (acts & 0x2000) {
+		turbo_cnt[1] += inc;
+		if (turbo_cnt[1] >= 60)
+			turbo_pad ^= 0x20, turbo_cnt[1] = 0;
+	}
+	if (acts & 0x4000) {
+		turbo_cnt[2] += inc;
+		if (turbo_cnt[2] >= 60)
+			turbo_pad ^= 0x40, turbo_cnt[2] = 0;
+	}
+	*pad |= turbo_pad & (acts >> 8);
+}
+
+static void run_events_ui(unsigned int which)
+{
+	char shell_cmd[100];
+	FILE *fp;
+	//emu_action_old = emu_action;
+	//printf("New event: %d\n", which);
+
+	if (which & (PEV_STATE_LOAD|PEV_STATE_SAVE))
+	{
+		int do_it = 1;
+		if ( emu_check_save_file(state_slot, NULL) &&
+			(((which & PEV_STATE_LOAD) && (currentConfig.confirm_save & EOPT_CONFIRM_LOAD)) ||
+			 ((which & PEV_STATE_SAVE) && (currentConfig.confirm_save & EOPT_CONFIRM_SAVE))) )
+		{
+			const char *nm;
+			char tmp[64];
+			int keys, len;
+
+			strcpy(tmp, (which & PEV_STATE_LOAD) ? "LOAD STATE? " : "OVERWRITE SAVE? ");
+			len = strlen(tmp);
+			nm = in_get_key_name(-1, -PBTN_MOK);
+			snprintf(tmp + len, sizeof(tmp) - len, "(%s=yes, ", nm);
+			len = strlen(tmp);
+			nm = in_get_key_name(-1, -PBTN_MBACK);
+			snprintf(tmp + len, sizeof(tmp) - len, "%s=no)", nm);
+
+			plat_status_msg_busy_first(tmp);
+
+			in_set_config_int(0, IN_CFG_BLOCKING, 1);
+			while (in_menu_wait_any(NULL, 50) & (PBTN_MOK | PBTN_MBACK))
+				;
+			while ( !((keys = in_menu_wait_any(NULL, 50)) & (PBTN_MOK | PBTN_MBACK)))
+				;
+			if (keys & PBTN_MBACK)
+				do_it = 0;
+			while (in_menu_wait_any(NULL, 50) & (PBTN_MOK | PBTN_MBACK))
+				;
+			in_set_config_int(0, IN_CFG_BLOCKING, 0);
+			plat_status_msg_clear();
+		}
+		if (do_it) {
+			plat_status_msg_busy_first((which & PEV_STATE_LOAD) ? "LOADING STATE" : "SAVING STATE");
+			PicoStateProgressCB = plat_status_msg_busy_next;
+			emu_save_load_game((which & PEV_STATE_LOAD) ? 1 : 0, 0);
+			PicoStateProgressCB = NULL;
+		}
+	}
+	if (which & PEV_SWITCH_RND)
+	{
+		plat_video_toggle_renderer(1, 0);
+	}
+	if (which & PEV_VOL_DOWN)
+	{
+		printf("PEV_VOL_DOWN\r\n");
+		/// ----- Compute new value -----
+		volume_percentage = (volume_percentage < STEP_CHANGE_VOLUME)?
+			0:(volume_percentage-STEP_CHANGE_VOLUME);
+		/// ----- HUD msg ------
+		char txt[100];
+		sprintf(txt, "VOLUME %d%%", volume_percentage);
+		plat_status_msg_busy_first(txt);
+		/// ----- Shell cmd ----
+		sprintf(shell_cmd, "%s %d", SHELL_CMD_VOLUME_SET, volume_percentage);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+	}
+	if (which & PEV_VOL_UP)
+	{
+		printf("PEV_VOL_UP\r\n");
+		/// ----- Compute new value -----
+		volume_percentage = (volume_percentage > 100 - STEP_CHANGE_VOLUME)?
+			100:(volume_percentage+STEP_CHANGE_VOLUME);
+		/// ----- HUD msg ------
+		char txt[100];
+		sprintf(txt, "VOLUME %d%%", volume_percentage);
+		plat_status_msg_busy_first(txt);
+		/// ----- Shell cmd ----
+		sprintf(shell_cmd, "%s %d", SHELL_CMD_VOLUME_SET, volume_percentage);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+	}
+	if (which & PEV_BRIGHT_UP)
+	{
+		printf("PEV_BRIGHT_UP\r\n");
+		/// ----- Compute new value -----
+		brightness_percentage = (brightness_percentage > 100 - STEP_CHANGE_BRIGHTNESS)?
+			100:(brightness_percentage+STEP_CHANGE_BRIGHTNESS);
+		/// ----- HUD msg ------
+		char txt[100];
+		sprintf(txt, "BRIGHTNESS %d%%", brightness_percentage);
+		plat_status_msg_busy_first(txt);
+		/// ----- Shell cmd ----
+		sprintf(shell_cmd, "%s %d", SHELL_CMD_BRIGHTNESS_SET, brightness_percentage);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+	}
+	if (which & PEV_BRIGHT_DOWN)
+	{
+		printf("PEV_BRIGHT_DOWN\r\n");
+		/// ----- Compute new value -----
+		brightness_percentage = (brightness_percentage < STEP_CHANGE_BRIGHTNESS)?
+			0:(brightness_percentage-STEP_CHANGE_BRIGHTNESS);
+		/// ----- HUD msg ------
+		char txt[100];
+		sprintf(txt, "BRIGHTNESS %d%%", brightness_percentage);
+		plat_status_msg_busy_first(txt);
+		/// ----- Shell cmd ----
+		sprintf(shell_cmd, "%s %d", SHELL_CMD_BRIGHTNESS_SET, brightness_percentage);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+	}
+	if (which & PEV_AR_FACT_UP)
+	{
+		printf("PEV_AR_FACT_UP\r\n");
+		/// ----- Compute new value -----
+		if(aspect_ratio == ASPECT_RATIOS_TYPE_MANUAL){
+			aspect_ratio_factor_percent = (aspect_ratio_factor_percent+aspect_ratio_factor_step<100)?
+				aspect_ratio_factor_percent+aspect_ratio_factor_step:100;
+			need_screen_cleared = 1;
+		}
+		else{
+			aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
+		}
+		aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
+		/// ----- HUD msg ------
+		/*char txt[100];
+		sprintf(txt, "    DISPLAY MODE: ZOOMED - %d%%", aspect_ratio_factor_percent);
+		plat_status_msg_busy_first(txt);*/
+		sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: ZOOMED %d%%%%\"",
+			SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_factor_percent);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+
+        // Save config file
+        configfile_save(cfg_file_rom);
+	}
+	if (which & PEV_AR_FACT_DOWN)
+	{
+		printf("PEV_AR_FACT_DOWN\r\n");
+		/// ----- Compute new value -----
+		if(aspect_ratio == ASPECT_RATIOS_TYPE_MANUAL){
+			aspect_ratio_factor_percent = (aspect_ratio_factor_percent>aspect_ratio_factor_step)?
+				aspect_ratio_factor_percent-aspect_ratio_factor_step:0;
+			need_screen_cleared = 1;
+		}
+		else{
+			aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
+		}
+		aspect_ratio = ASPECT_RATIOS_TYPE_MANUAL;
+		/// ----- HUD msg ------
+		/*char txt[100];
+		sprintf(txt, "    DISPLAY MODE: ZOOMED - %d%%", aspect_ratio_factor_percent);
+		plat_status_msg_busy_first(txt);*/
+		sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: ZOOMED %d%%%%\"",
+			SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_factor_percent);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+
+        // Save config file
+        configfile_save(cfg_file_rom);
+
+	}
+	if (which & PEV_DISPMODE)
+	{
+		printf("PEV_DISPMODE\r\n");
+		/// ----- Compute new value -----
+		aspect_ratio = (aspect_ratio+1)%NB_ASPECT_RATIOS_TYPES;
+		/// ----- HUD msg ------
+		//char txt[100];
+		if(aspect_ratio == ASPECT_RATIOS_TYPE_MANUAL){
+			//sprintf(txt, "    DISPLAY MODE: ZOOMED - %d%%", aspect_ratio_factor_percent);
+			sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: ZOOMED %d%%%%\"",
+				SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_factor_percent);
+		}
+		else{
+			//sprintf(txt, "DISPLAY MODE: %s", aspect_ratio_name[aspect_ratio]);
+			sprintf(shell_cmd, "%s %d \"    DISPLAY MODE: %s\"",
+				SHELL_CMD_NOTIF_SET, NOTIF_SECONDS_DISP, aspect_ratio_name[aspect_ratio]);
+		}
+		//plat_status_msg_busy_first(txt);
+		fp = popen(shell_cmd, "r");
+		if (fp == NULL) {
+			printf("Failed to run command %s\n", shell_cmd);
+		} else {
+	        	pclose(fp);
+		}
+
+        // Save config file
+        configfile_save(cfg_file_rom);
+	}
+	if (which & (PEV_SSLOT_PREV|PEV_SSLOT_NEXT))
+	{
+		if (which & PEV_SSLOT_PREV) {
+			state_slot -= 1;
+			if (state_slot < 0)
+				state_slot = 9;
+		} else {
+			state_slot += 1;
+			if (state_slot > 9)
+				state_slot = 0;
+		}
+
+		emu_status_msg("SAVE SLOT %i [%s]", state_slot,
+			emu_check_save_file(state_slot, NULL) ? "USED" : "FREE");
+	}
+	if (which & PEV_RESET)
+		emu_reset_game();
+	if (which & PEV_MENU)
+		engineState = PGS_Menu;
+}
+
+void emu_update_input(void)
+{
+	static int prev_events = 0;
+	int actions[IN_BINDTYPE_COUNT] = { 0, };
+	int pl_actions[2];
+	int events;
+
+	in_update(actions);
+
+	pl_actions[0] = actions[IN_BINDTYPE_PLAYER12];
+	pl_actions[1] = actions[IN_BINDTYPE_PLAYER12] >> 16;
+
+	PicoIn.pad[0] = pl_actions[0] & 0xfff;
+	PicoIn.pad[1] = pl_actions[1] & 0xfff;
+
+	if (pl_actions[0] & 0x7000)
+		do_turbo(&PicoIn.pad[0], pl_actions[0]);
+	if (pl_actions[1] & 0x7000)
+		do_turbo(&PicoIn.pad[1], pl_actions[1]);
+
+	events = actions[IN_BINDTYPE_EMU] & PEV_MASK;
+
+	// volume is treated in special way and triggered every frame
+	if (events & (PEV_VOL_DOWN|PEV_VOL_UP))
+		plat_update_volume(1, events & PEV_VOL_UP);
+
+	if ((events ^ prev_events) & PEV_FF) {
+		emu_set_fastforward(events & PEV_FF);
+		plat_update_volume(0, 0);
+		reset_timing = 1;
+	}
+
+	events &= ~prev_events;
+
+	/* SMS */
+	if (PicoIn.AHW == PAHW_PICO)
+		run_events_pico(events);
+
+	if (events)
+		run_events_ui(events);
+
+	if (movie_data)
+		update_movie();
+
+	prev_events = actions[IN_BINDTYPE_EMU] & PEV_MASK;
+}
+
+static void mkdir_path(char *path_with_reserve, int pos, const char *name)
+{
+	strcpy(path_with_reserve + pos, name);
+	if (plat_is_dir(path_with_reserve))
+		return;
+	if (mkdir(path_with_reserve, 0777) < 0)
+		lprintf("failed to create: %s\n", path_with_reserve);
+}
+
+void emu_cmn_forced_frame(int no_scale, int do_emu, void *buf)
+{
+	int po_old = PicoIn.opt;
+	int y;
+
+	for (y = 0; y < g_screen_height; y++)
+		memset32((short *)g_screen_ptr + g_screen_ppitch * y, 0,
+			 g_screen_width * 2 / 4);
+
+	PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);
+	PicoIn.opt |= POPT_ACC_SPRITES;
+	if (!no_scale && currentConfig.scaling)
+		PicoIn.opt |= POPT_EN_SOFTSCALE;
+
+	PicoDrawSetOutFormat(PDF_RGB555, 1);
+	PicoDrawSetOutBuf(buf, g_screen_ppitch * 2);
+	Pico.m.dirtyPal = 1;
+	Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;
+	if (do_emu)
+		PicoFrame();
+	else
+		PicoFrameDrawOnly();
+
+	PicoIn.opt = po_old;
+}
+
+int emu_is_segaCD(){
+	return (PicoIn.AHW & PAHW_MCD);
+}
+
+/* Quick save and turn off the console */
+void quick_save_and_poweroff()
+{
+    FILE *fp;
+
+    printf("Save Instant Play file\n");
+
+    /* Send command to cancel any previously scheduled powerdown */
+    fp = popen(SHELL_CMD_POWERDOWN_HANDLE, "r");
+    if (fp == NULL)
+    {
+        /* Countdown is still ticking, so better do nothing
+	   than start writing and get interrupted!
+	*/
+        printf("Failed to cancel scheduled shutdown\n");
+	exit(0);
+    }
+    pclose(fp);
+
+    /* Save  */
+    emu_save_load_game_from_file(0, quick_save_file);
+
+    /* Perform Instant Play save and shutdown */
+    execlp(SHELL_CMD_INSTANT_PLAY, SHELL_CMD_INSTANT_PLAY,
+	"save", prog_name, "-loadStateFile", quick_save_file, mRomName, NULL);
+
+    /* Should not be reached */
+    printf("Failed to perform Instant Play save and shutdown\n");
+
+    /* Exit Emulator */
+    exit(0);
+}
+
+
+void emu_init(void)
+{
+	char path[512];
+	int pos;
+
+#if 0
+	// FIXME: handle through menu, etc
+	FILE *f;
+	f = fopen("32X_M_BIOS.BIN", "rb");
+	p32x_bios_m = malloc(2048);
+	fread(p32x_bios_m, 1, 2048, f);
+	fclose(f);
+	f = fopen("32X_S_BIOS.BIN", "rb");
+	p32x_bios_s = malloc(1024);
+	fread(p32x_bios_s, 1, 1024, f);
+	fclose(f);
+#endif
+
+	/* make dirs for saves */
+	pos = plat_get_root_dir(path, sizeof(path) - 4);
+	mkdir_path(path, pos, "mds");
+	mkdir_path(path, pos, "srm");
+	mkdir_path(path, pos, "brm");
+	mkdir_path(path, pos, "cfg");
+
+	pprof_init();
+
+	make_config_cfg(path);
+	config_readlrom(path);
+
+	PicoInit();
+	PicoIn.osdMessage = plat_status_msg_busy_next;
+	PicoIn.mcdTrayOpen = emu_tray_open;
+	PicoIn.mcdTrayClose = emu_tray_close;
+
+	sndout_init();
+}
+
+void emu_finish(void)
+{
+	// save SRAM
+	if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && Pico.sv.changed) {
+		emu_save_load_game(0, 1);
+		Pico.sv.changed = 0;
+	}
+
+	if (!(currentConfig.EmuOpt & EOPT_NO_AUTOSVCFG)) {
+		char cfg[512];
+		make_config_cfg(cfg);
+		config_writelrom(cfg);
+#ifdef __GP2X__
+		sync();
+#endif
+	}
+
+	pprof_finish();
+
+	PicoExit();
+	sndout_exit();
+}
+
+static void snd_write_nonblocking(int len)
+{
+	sndout_write_nb(PicoIn.sndOut, len);
+}
+
+void emu_sound_start(void)
+{
+	PicoIn.sndOut = NULL;
+
+	if (currentConfig.EmuOpt & EOPT_EN_SOUND)
+	{
+		int is_stereo = (PicoIn.opt & POPT_EN_STEREO) ? 1 : 0;
+		/// Hard Bypass Stereo to mono
+		//is_stereo = 0;
+
+		PsndRerate(Pico.m.frame_count ? 1 : 0);
+
+		printf("starting audio: %i len: %i stereo: %i, pal: %i\n",
+			PicoIn.sndRate, Pico.snd.len, is_stereo, Pico.m.pal);
+		sndout_start(PicoIn.sndRate, is_stereo);
+		PicoIn.writeSound = snd_write_nonblocking;
+		plat_update_volume(0, 0);
+		memset(sndBuffer, 0, sizeof(sndBuffer));
+		PicoIn.sndOut = sndBuffer;
+	}
+}
+
+void emu_sound_stop(void)
+{
+	sndout_stop();
+}
+
+void emu_sound_wait(void)
+{
+	sndout_wait();
+}
+
+static void emu_loop_prep(void)
+{
+	static int pal_old = -1;
+	static int filter_old = -1;
+
+	if (currentConfig.CPUclock != plat_target_cpu_clock_get())
+		plat_target_cpu_clock_set(currentConfig.CPUclock);
+
+	if (Pico.m.pal != pal_old) {
+		plat_target_lcdrate_set(Pico.m.pal);
+		pal_old = Pico.m.pal;
+	}
+
+	if (currentConfig.filter != filter_old) {
+		plat_target_hwfilter_set(currentConfig.filter);
+		filter_old = currentConfig.filter;
+	}
+
+	plat_target_gamma_set(currentConfig.gamma, 0);
+
+	pemu_loop_prep();
+}
+
+/* our tick here is 1 us right now */
+#define ms_to_ticks(x) (unsigned int)(x * 1000)
+#define get_ticks() plat_get_ticks_us()
+#define vsync_delay_x3	3*ms_to_ticks(1)
+
+void emu_loop(void)
+{
+	int frames_done, frames_shown;	/* actual frames for fps counter */
+	int frame_nb = 0;
+	int target_frametime_x3;
+	unsigned int timestamp_x3 = 0;
+	unsigned int timestamp_aim_x3 = 0;
+	unsigned int timestamp_fps_x3 = 0;
+	char *notice_msg = NULL;
+	char fpsbuff[24];
+	int fskip_cnt = 0;
+
+	fpsbuff[0] = 0;
+
+	PicoLoopPrepare();
+
+	plat_video_loop_prepare();
+	emu_loop_prep();
+	pemu_sound_start();
+
+	/* number of ticks per frame */
+	if (Pico.m.pal)
+		target_frametime_x3 = 3 * ms_to_ticks(1000) / 50;
+	else
+		target_frametime_x3 = 3 * ms_to_ticks(1000) / 60;
+
+	reset_timing = 1;
+	frames_done = frames_shown = 0;
+
+	/* loop with resync every 1 sec. */
+	while (engineState == PGS_Running)
+	{
+		int skip = 0;
+		int diff;
+
+		pprof_start(main);
+
+		if (reset_timing) {
+			reset_timing = 0;
+			plat_video_wait_vsync();
+			timestamp_aim_x3 = get_ticks() * 3;
+			timestamp_fps_x3 = timestamp_aim_x3;
+			fskip_cnt = 0;
+		}
+		else if (currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) {
+			timestamp_aim_x3 = get_ticks() * 3;
+		}
+
+		timestamp_x3 = get_ticks() * 3;
+
+		// show notice_msg message?
+		if (notice_msg_time != 0)
+		{
+			static int noticeMsgSum;
+			if (timestamp_x3 - ms_to_ticks(notice_msg_time) * 3
+			     > ms_to_ticks(STATUS_MSG_TIMEOUT) * 3)
+			{
+				notice_msg_time = 0;
+				notice_msg = NULL;
+				plat_status_msg_clear();
+			}
+			else {
+				int sum = noticeMsg[0] + noticeMsg[1] + noticeMsg[2];
+				if (sum != noticeMsgSum) {
+					plat_status_msg_clear();
+					noticeMsgSum = sum;
+				}
+				notice_msg = noticeMsg;
+			}
+		}
+
+		// second changed?
+		if (timestamp_x3 - timestamp_fps_x3 >= ms_to_ticks(1000) * 3)
+		{
+
+#ifdef BENCHMARK
+			static int bench = 0, bench_fps = 0, bench_fps_s = 0, bfp = 0, bf[4];
+			if (++bench == 10) {
+				bench = 0;
+				bench_fps_s = bench_fps;
+				bf[bfp++ & 3] = bench_fps;
+				bench_fps = 0;
+			}
+			bench_fps += frames_shown;
+			sprintf(fpsbuff, "%02i/%02i/%02i", frames_shown, bench_fps_s, (bf[0]+bf[1]+bf[2]+bf[3])>>2);
+			printf("%s\n", fpsbuff);
+#else
+			if (currentConfig.EmuOpt & EOPT_SHOW_FPS || show_fps_bypass){
+				printf("%02i/%02i  \n", frames_shown, frames_done);
+				snprintf(fpsbuff, 8, "%02i/%02i  ", frames_shown, frames_done);
+			}
+#endif
+			frames_shown = frames_done = 0;
+			timestamp_fps_x3 += ms_to_ticks(1000) * 3;
+		}
+#ifdef PFRAMES
+		sprintf(fpsbuff, "%i", Pico.m.frame_count);
+#endif
+
+		diff = timestamp_aim_x3 - timestamp_x3;
+
+		if (currentConfig.Frameskip >= 0) // frameskip enabled (or 0)
+		{
+			if (fskip_cnt < currentConfig.Frameskip) {
+				fskip_cnt++;
+				skip = 1;
+			}
+			else {
+				fskip_cnt = 0;
+			}
+		}
+		else if (diff < -target_frametime_x3)
+		{
+			/* no time left for this frame - skip */
+			/* limit auto frameskip to max_skip */
+			if (fskip_cnt < currentConfig.max_skip) {
+				fskip_cnt++;
+				skip = 1;
+			}
+			else {
+				fskip_cnt = 0;
+			}
+		} else
+			fskip_cnt = 0;
+
+		// don't go in debt too much
+		while (diff < -target_frametime_x3 * 3) {
+			timestamp_aim_x3 += target_frametime_x3;
+			diff = timestamp_aim_x3 - timestamp_x3;
+		}
+
+        /* Quick save and poweroff */
+        if(mQuickSaveAndPoweroff){
+			quick_save_and_poweroff();
+			mQuickSaveAndPoweroff = 0;
+        }
+
+		emu_update_input();
+		if (skip) {
+			int do_audio = diff > -target_frametime_x3 * 2;
+			PicoIn.skipFrame = do_audio ? 1 : 2;
+			PicoFrame();
+			PicoIn.skipFrame = 0;
+		}
+		else {
+			PicoFrame();
+			pemu_finalize_frame(fpsbuff, notice_msg);
+			frames_shown++;
+		}
+		frames_done++;
+		timestamp_aim_x3 += target_frametime_x3;
+
+		if (!skip && !flip_after_sync)
+			plat_video_flip();
+
+		/* frame limiter */
+		if (!skip && !reset_timing
+		    && !(currentConfig.EmuOpt & (EOPT_NO_FRMLIMIT|EOPT_EXT_FRMLIMIT)))
+		{
+			unsigned int timestamp = get_ticks();
+			diff = timestamp_aim_x3 - timestamp * 3;
+
+			// sleep or vsync if we are still too fast
+			if (diff > target_frametime_x3 + vsync_delay_x3 && (currentConfig.EmuOpt & EOPT_VSYNC)) {
+				// we are too fast
+				plat_video_wait_vsync();
+				timestamp = get_ticks();
+				diff = timestamp * 3 - timestamp_aim_x3;
+			}
+			if (diff > target_frametime_x3 + vsync_delay_x3) {
+				// still too fast
+				plat_wait_till_us(timestamp + (diff - target_frametime_x3) / 3);
+			}
+		}
+
+		if (!skip && flip_after_sync)
+			plat_video_flip();
+
+
+		/* FOR SEGA CD */
+		/* Leave some time for the bios to load before starting a quick load at boot */
+		/* (Sega CD doesn't quick load at boot otherwise) */
+		/* Should have a much cleaner implementation thant this fix */
+		if((PicoIn.AHW & PAHW_MCD) && frame_nb >= 120){
+
+			/* Load slot */
+			if(load_state_slot != -1){
+				printf("LOADING FROM SLOT (SEGA CD) %d...\n", load_state_slot+1);
+				char fname[1024];
+				emu_save_load_game(1, 0);
+				printf("LOADED FROM SLOT (SEGA CD) %d\n", load_state_slot+1);
+				load_state_slot = -1;
+			}
+			if(need_quick_load != -1){
+				load_state_file = quick_save_file;
+				need_quick_load = -1;
+			}
+			/* Load file */
+			if(load_state_file != NULL){
+				printf("LOADING FROM FILE (SEGA CD) %s...\n", load_state_file);
+				emu_save_load_game_from_file(1, load_state_file);
+				printf("LOADED FROM SLOT (SEGA CD) %s\n", load_state_file);
+				load_state_file = NULL;
+			}
+		}
+
+		frame_nb++;
+
+		pprof_end(main);
+	}
+
+	emu_set_fastforward(0);
+
+	// save SRAM
+	if ((currentConfig.EmuOpt & EOPT_EN_SRAM) && Pico.sv.changed) {
+		plat_status_msg_busy_first("Writing SRAM/BRAM...");
+		emu_save_load_game(0, 1);
+		Pico.sv.changed = 0;
+	}
+
+	pemu_loop_end();
+	emu_sound_stop();
+}

+ 24 - 5
platform/common/emu.h

@@ -37,8 +37,26 @@ extern int g_screen_ppitch; // pitch in pixels
 
 enum {
 	EOPT_SCALE_NONE = 0,
-	EOPT_SCALE_SW,
+	// linux, GP2X:
+	EOPT_SCALE_SW = 1,
 	EOPT_SCALE_HW,
+	// PSP horiz:
+	EOPT_SCALE_43 = 1,	// DAR 4:3 (12:9)
+	EOPT_SCALE_WIDE,	// DAR 14:9
+	EOPT_SCALE_FULL,	// DAR 16:9
+	// PSP vert:
+	EOPT_VSCALE_PAL = 1,	// always 240 lines
+	EOPT_VSCALE_FULL,	// zoomed to full height
+};
+
+enum {
+	EOPT_FILTER_NONE = 0,
+	// PSP texture filtering
+	EOPT_FILTER_BILINEAR = 1,
+	// software scalers
+	EOPT_FILTER_SMOOTHER = 1,
+	EOPT_FILTER_BILINEAR1,
+	EOPT_FILTER_BILINEAR2,
 };
 
 enum {
@@ -54,6 +72,7 @@ typedef struct _currentConfig_t {
 	int s_PsndRate;
 	int s_PicoRegion;
 	int s_PicoAutoRgnOrder;
+	int s_hwSelect;
 	int s_PicoCDBuffers;
 	int s_PicoSndFilterAlpha;
 	int Frameskip;
@@ -63,16 +82,15 @@ typedef struct _currentConfig_t {
 	int CPUclock;
 	int volume;
 	int gamma;
-	int scaling;  // gp2x: EOPT_SCALE_*; psp: bilinear filtering
+	int scaling;  // EOPT_SCALE_*
 	int vscaling;
 	int rotation; // for UIQ
-	float scale; // psp: screen scale
-	float hscale32, hscale40; // psp: horizontal scale
 	int gamma2;  // psp: black level
 	int turbo_rate;
 	int renderer;
 	int renderer32x;
-	int filter; // pandora
+	int filter;  // EOPT_FILTER_* video filter
+	int ghosting;
 	int analog_deadzone;
 	int msh2_khz;
 	int ssh2_khz;
@@ -196,6 +214,7 @@ void plat_update_volume(int has_changed, int is_up);
 /* should be in libpicofe/plat.h */
 void plat_video_clear_status(void);
 void plat_video_clear_buffers(void);
+void plat_video_set_size(int w, int h);
 
 #ifdef __cplusplus
 } // extern "C"

+ 7 - 10
platform/common/inputmap_kbd.c

@@ -108,19 +108,16 @@ const struct in_default_bind in_sdl_defbinds_SMS[] = {
 };
 
 const struct menu_keymap in_sdl_key_map[] = {
-	{ SDLK_UP,	PBTN_UP },
-	{ SDLK_DOWN,	PBTN_DOWN },
-	{ SDLK_LEFT,	PBTN_LEFT },
-	{ SDLK_RIGHT,	PBTN_RIGHT },
+	{ SDLK_u,	PBTN_UP },
+	{ SDLK_d,	PBTN_DOWN },
+	{ SDLK_l,	PBTN_LEFT },
+	{ SDLK_r,	PBTN_RIGHT },
 	{ SDLK_RETURN,	PBTN_MOK },
-	{ SDLK_ESCAPE,	PBTN_MBACK },
+	{ SDLK_b,	PBTN_MBACK },
 	{ SDLK_SEMICOLON,	PBTN_MA2 },
 	{ SDLK_QUOTE,	PBTN_MA3 },
-	{ SDLK_LEFTBRACKET,  PBTN_L },
-	{ SDLK_RIGHTBRACKET, PBTN_R },
-{ SDLK_COMMA,	PBTN_MA2 },
-{ SDLK_o,  PBTN_L },
-{ SDLK_p,  PBTN_R },
+	{ SDLK_m,  PBTN_L },
+	{ SDLK_n, PBTN_R },
 };
 const int in_sdl_key_map_sz = sizeof(in_sdl_key_map) / sizeof(in_sdl_key_map[0]);
 

+ 37 - 3
platform/common/menu_pico.c

@@ -48,7 +48,7 @@ static const char *rom_exts[] = {
 	"bin", "smd", "gen", "md",
 	"iso", "cso", "cue", "chd",
 	"32x",
-	"sms",
+	"sms", "gg",
 	NULL
 };
 
@@ -1814,6 +1814,33 @@ static int menu_loop_32x_options(int id, int keys)
 
 #endif
 
+// ------------ SMS options menu ------------
+
+#ifndef NO_SMS
+
+static const char *sms_hardwares[] = { "auto", "Game Gear", "Master System", NULL };
+static const char *sms_mappers[] = { "auto", "Sega", "Codemasters", "Korea", "Korea MSX", "Korea X-in-1", "Korea 4-Pak", "Korea Janggun", "Korea Nemesis", NULL };
+static const char h_smsfm[] = "FM sound is only supported by few games\nOther games may crash with FM enabled";
+
+static menu_entry e_menu_sms_options[] =
+{
+	mee_enum      ("System",        MA_SMSOPT_HARDWARE,    PicoIn.hwSelect, sms_hardwares),
+	mee_onoff_h   ("FM Sound Unit", MA_OPT2_ENABLE_YM2413, PicoIn.opt, POPT_EN_YM2413, h_smsfm),
+	mee_enum      ("Cartridge mapping", MA_SMSOPT_MAPPER,  PicoIn.mapper, sms_mappers),
+	mee_end,
+};
+
+static int menu_loop_sms_options(int id, int keys)
+{
+	static int sel = 0;
+
+	me_loop(e_menu_sms_options, &sel);
+
+	return 0;
+}
+
+#endif
+
 // ------------ adv options menu ------------
 
 static const char h_ovrclk[] = "Will break some games, keep at 0";
@@ -1825,8 +1852,9 @@ static menu_entry e_menu_adv_options[] =
 	mee_onoff     ("Emulate Z80",              MA_OPT2_ENABLE_Z80,    PicoIn.opt, POPT_EN_Z80),
 	mee_onoff     ("Emulate YM2612 (FM)",      MA_OPT2_ENABLE_YM2612, PicoIn.opt, POPT_EN_FM),
 	mee_onoff     ("Disable YM2612 SSG-EG",    MA_OPT2_DISABLE_YM_SSG,PicoIn.opt, POPT_DIS_FM_SSGEG),
+	mee_onoff     ("Enable YM2612 DAC noise",  MA_OPT2_ENABLE_YM_DAC, PicoIn.opt, POPT_EN_FM_DAC),
 	mee_onoff     ("Emulate SN76496 (PSG)",    MA_OPT2_ENABLE_SN76496,PicoIn.opt, POPT_EN_PSG),
-	mee_onoff     ("Emulate YM2413 (FM)",      MA_OPT2_ENABLE_YM2413 ,PicoIn.opt, POPT_EN_YM2413),
+	mee_onoff     ("Emulate Game Gear LCD",    MA_OPT2_ENABLE_GGLCD  ,PicoIn.opt, POPT_EN_GG_LCD),
 	mee_onoff     ("Disable idle loop patching",MA_OPT2_NO_IDLE_LOOPS,PicoIn.opt, POPT_DIS_IDLE_DET),
 	mee_onoff     ("Disable frame limiter",    MA_OPT2_NO_FRAME_LIMIT,currentConfig.EmuOpt, EOPT_NO_FRMLIMIT),
 	mee_onoff     ("Enable dynarecs",          MA_OPT2_DYNARECS,      PicoIn.opt, POPT_EN_DRC),
@@ -2118,6 +2146,9 @@ static menu_entry e_menu_options[] =
 	mee_handler   ("[Sega/Mega CD options]",   menu_loop_cd_options),
 #ifndef NO_32X
 	mee_handler   ("[32X options]",            menu_loop_32x_options),
+#endif
+#ifndef NO_SMS
+	mee_handler   ("[SMS options]",            menu_loop_sms_options),
 #endif
 	mee_handler   ("[Advanced options]",       menu_loop_adv_options),
 	mee_cust_nosave("Save global config",      MA_OPT_SAVECFG, mh_saveloadcfg, mgn_saveloadcfg),
@@ -2307,7 +2338,7 @@ static void draw_frame_credits(void)
 
 static const char credits[] =
 	"PicoDrive v" VERSION "\n"
-	"(c) notaz, 2006-2013; irixxxx, 2018-2020\n\n"
+	"(c) notaz, 2006-2013; irixxxx, 2018-2021\n\n"
 	"Credits:\n"
 	"fDave: initial code\n"
 #ifdef EMU_C68K
@@ -2597,6 +2628,9 @@ static menu_entry *e_menu_table[] =
 	e_menu_cd_options,
 #ifndef NO_32X
 	e_menu_32x_options,
+#endif
+#ifndef NO_SMS
+	e_menu_sms_options,
 #endif
 	e_menu_keyconfig,
 	e_menu_hidden,

+ 6 - 7
platform/common/menu_pico.h

@@ -113,8 +113,10 @@ typedef enum
 	MA_OPT2_ENABLE_Z80,
 	MA_OPT2_ENABLE_YM2612,
 	MA_OPT2_DISABLE_YM_SSG,
+	MA_OPT2_ENABLE_YM_DAC,
 	MA_OPT2_ENABLE_SN76496,
 	MA_OPT2_ENABLE_YM2413,
+	MA_OPT2_ENABLE_GGLCD,
 	MA_OPT2_NO_LAST_ROM,
 	MA_OPT2_RAMTIMINGS,	/* gp2x */
 	MA_OPT2_NO_FRAME_LIMIT,	/* psp */
@@ -125,13 +127,7 @@ typedef enum
 	MA_OPT2_MAX_FRAMESKIP,
 	MA_OPT2_PWM_IRQ_OPT,
 	MA_OPT2_DONE,
-	MA_OPT3_SCALE,		/* psp (all OPT3) */
-	MA_OPT3_HSCALE32,
-	MA_OPT3_HSCALE40,
-	MA_OPT3_GAMMAA,
-	MA_OPT3_PRES_NOSCALE,
-	MA_OPT3_PRES_SCALE43,
-	MA_OPT3_PRES_FULLSCR,
+	MA_OPT3_GAMMAA,		/* psp (all OPT3) */
 	MA_OPT3_FILTERING,
 	MA_OPT3_VSYNC,
 	MA_OPT3_BLACKLVL,
@@ -155,6 +151,9 @@ typedef enum
 	MA_32XOPT_PWM,
 	MA_32XOPT_MSH2_CYCLES,
 	MA_32XOPT_SSH2_CYCLES,
+	MA_SMSOPT_HARDWARE,
+	MA_SMSOPT_MAPPER,
+	MA_SMSOPT_GHOSTING,
 	MA_CTRL_PLAYER1,
 	MA_CTRL_PLAYER2,
 	MA_CTRL_EMU,

+ 1 - 1
platform/common/minimp3

@@ -1 +1 @@
-Subproject commit ef9e212fa29bb72d23558da21bb5694fd2d01768
+Subproject commit afb604c06bc8beb145fecd42c0ceb5bda8795144

+ 73 - 33
platform/common/plat_sdl.c

@@ -59,6 +59,7 @@
 #define Weight1_1(A, B)   (Half(A) + Half(B) + Corr1_1(A, B))
 
 static void *shadow_fb;
+static struct area { int w, h; } area;
 
 static struct in_pdata in_sdl_platform_data = {
 	.defbinds = in_sdl_defbinds,
@@ -144,39 +145,44 @@ void bgr_to_uyvy_init(void)
   }
 }
 
-void rgb565_to_uyvy(void *d, const void *s, int pixels, int x2)
+void rgb565_to_uyvy(void *d, const void *s, int w, int h, int pitch, int x2)
 {
   uint32_t *dst = d;
   const uint16_t *src = s;
+  int i;
 
-  if (x2)
-  for (; pixels > 0; src += 4, dst += 4, pixels -= 4)
-  {
-    struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
-    struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
+  if (x2) while (h--) {
+    for (i = w; i > 0; src += 4, dst += 4, i -= 4)
+    {
+      struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
+      struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
 #if CPU_IS_LE
-    dst[0] = (uyvy0->y << 24) | uyvy0->vyu;
-    dst[1] = (uyvy1->y << 24) | uyvy1->vyu;
-    dst[2] = (uyvy2->y << 24) | uyvy2->vyu;
-    dst[3] = (uyvy3->y << 24) | uyvy3->vyu;
+      dst[0] = (uyvy0->y << 24) | uyvy0->vyu;
+      dst[1] = (uyvy1->y << 24) | uyvy1->vyu;
+      dst[2] = (uyvy2->y << 24) | uyvy2->vyu;
+      dst[3] = (uyvy3->y << 24) | uyvy3->vyu;
 #else
-    dst[0] = uyvy0->y | (uyvy0->vyu << 8);
-    dst[1] = uyvy1->y | (uyvy1->vyu << 8);
-    dst[2] = uyvy2->y | (uyvy2->vyu << 8);
-    dst[3] = uyvy3->y | (uyvy3->vyu << 8);
+      dst[0] = uyvy0->y | (uyvy0->vyu << 8);
+      dst[1] = uyvy1->y | (uyvy1->vyu << 8);
+      dst[2] = uyvy2->y | (uyvy2->vyu << 8);
+      dst[3] = uyvy3->y | (uyvy3->vyu << 8);
 #endif
-  } else 
-  for (; pixels > 0; src += 4, dst += 2, pixels -= 4)
-  {
-    struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
-    struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
+      }
+    src += pitch - w;
+  } else while (h--) {
+    for (i = w; i > 0; src += 4, dst += 2, i -= 4)
+    {
+      struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
+      struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
 #if CPU_IS_LE
-    dst[0] = (uyvy1->y << 24) | uyvy0->vyu;
-    dst[1] = (uyvy3->y << 24) | uyvy2->vyu;
+      dst[0] = (uyvy1->y << 24) | uyvy0->vyu;
+      dst[1] = (uyvy3->y << 24) | uyvy2->vyu;
 #else
-    dst[0] = uyvy1->y | (uyvy0->vyu << 8);
-    dst[1] = uyvy3->y | (uyvy2->vyu << 8);
+      dst[0] = uyvy1->y | (uyvy0->vyu << 8);
+      dst[1] = uyvy3->y | (uyvy2->vyu << 8);
 #endif
+    }
+    src += pitch - w;
   }
 }
 
@@ -1511,16 +1517,34 @@ void SDL_Rotate_270(SDL_Surface * hw_surface, SDL_Surface * virtual_hw_surface){
 
 static int clear_buf_cnt, clear_stat_cnt;
 
+void plat_video_set_size(int w, int h)
+{
+	if (area.w != w || area.h != h) {
+		area = (struct area) { w, h };
+
+		if (plat_sdl_change_video_mode(w, h, 0) < 0) {
+			// failed, revert to original resolution
+			plat_sdl_change_video_mode(g_screen_width, g_screen_height, 0);
+			w = g_screen_width, h = g_screen_height;
+		}
+		if (!plat_sdl_overlay && !plat_sdl_gl_active) {
+			g_screen_width = w;
+			g_screen_height = h;
+			g_screen_ppitch = w;
+			g_screen_ptr = plat_sdl_screen->pixels;
+		}
+	}
+}
+
 void plat_video_flip(void)
 {
 	if (plat_sdl_overlay != NULL) {
 		SDL_Rect dstrect =
 			{ 0, 0, plat_sdl_screen->w, plat_sdl_screen->h };
-
 		SDL_LockYUVOverlay(plat_sdl_overlay);
 		rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
-				g_screen_ppitch * g_screen_height,
-				plat_sdl_overlay->w > 2*plat_sdl_overlay->h);
+				area.w, area.h, g_screen_ppitch,
+				plat_sdl_overlay->w >= 2*area.w);
 		SDL_UnlockYUVOverlay(plat_sdl_overlay);
 		SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
 	}
@@ -1548,12 +1572,27 @@ void plat_video_flip(void)
 		/* Surface with game data */
 		SDL_Surface *game_surface;
 
+		/* Sega Game Gear -> 160*144 res in 320*240 surface */
+		if ((PicoIn.AHW & PAHW_SMS) && (Pico.m.hardware & 0x3) == 0x3){
+
+			/* Copy sms game pixels */
+			int offset_y = (plat_sdl_screen->h - sms_game_screen->h)/2;
+			int offset_x = (plat_sdl_screen->w - sms_game_screen->w)/2 - 1;
+			int y;
+			for(y=0; y<192; y++){
+				memcpy((uint16_t*)sms_game_screen->pixels + sms_game_screen->w*y,
+				       (uint16_t*)plat_sdl_screen->pixels + plat_sdl_screen->w*(y+offset_y) + offset_x,
+				       sms_game_screen->w*sizeof(uint16_t));
+			}
+
+			game_surface = sms_game_screen;
+		}
 		/* Sega Master System -> 256*192 res in 320*240 surface */
-		if (PicoIn.AHW & PAHW_SMS){
+		else if (PicoIn.AHW & PAHW_SMS){
 
 			/* Copy sms game pixels */
 			int offset_y = (plat_sdl_screen->h - sms_game_screen->h)/2;
-			int offset_x = (plat_sdl_screen->w - sms_game_screen->w)/2 + 3;
+			int offset_x = (plat_sdl_screen->w - sms_game_screen->w)/2 + 1;
 			int y;
 			for(y=0; y<192; y++){
 				memcpy((uint16_t*)sms_game_screen->pixels + sms_game_screen->w*y,
@@ -1696,7 +1735,7 @@ void plat_video_menu_end(void)
 
 		SDL_LockYUVOverlay(plat_sdl_overlay);
 		rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
-				g_menuscreen_pp * g_menuscreen_h, 0);
+			g_menuscreen_w, g_menuscreen_h, g_menuscreen_pp, 0);
 		SDL_UnlockYUVOverlay(plat_sdl_overlay);
 
 		SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
@@ -1723,11 +1762,11 @@ void plat_video_menu_leave(void)
 
 void plat_video_loop_prepare(void)
 {
-	// take over any new vout settings XXX ask plat_sdl for scaling instead!
+	// take over any new vout settings
 	plat_sdl_change_video_mode(g_menuscreen_w, g_menuscreen_h, 0);
-	// switch over to scaled output if available
-	if (plat_sdl_overlay != NULL || plat_sdl_gl_active || currentConfig.scaling != EOPT_SCALE_NONE) {
-		g_screen_width = 320;
+	// switch over to scaled output if available, but keep the aspect ratio
+	if (plat_sdl_overlay != NULL || plat_sdl_gl_active) {
+		g_screen_width = (240 * g_menuscreen_w / g_menuscreen_h) & ~1;
 		g_screen_height = 240;
 		g_screen_ppitch = g_screen_width;
 		plat_sdl_change_video_mode(g_screen_width, g_screen_height, 0);
@@ -1742,6 +1781,7 @@ void plat_video_loop_prepare(void)
 		g_screen_ptr = plat_sdl_screen->pixels;
 	}
 	plat_video_set_buffer(g_screen_ptr);
+	plat_video_set_size(g_screen_width, g_screen_height);
 }
 
 void plat_early_init(void)

+ 609 - 0
platform/common/upscale.c

@@ -0,0 +1,609 @@
+/*
+ * upscale.c		image upscaling
+ *
+ * This file contains upscalers for picodrive.
+ *
+ * scaler types:
+ * nn:	nearest neighbour
+ * snn:	"smoothed" nearest neighbour (see below)
+ * bln:	n-level-bilinear with n quantized weights
+ *	quantization: 0: a<1/(2*n), 1/n: 1/(2*n)<=a<3/(2*n), etc
+ *	currently n=2, n=4 are implemented (there's n=8 mixing, but no filters)
+ *	[NB this has been brought to my attn, which is probably the same as bl2:
+ *	https://www.drdobbs.com/image-scaling-with-bresenham/184405045?pgno=1]
+ *
+ * "smoothed" nearest neighbour: uses the average of the source pixels if no
+ *	source pixel covers more than 65% of the result pixel. It definitely
+ *	looks better than nearest neighbour and is still quite fast. It creates
+ *	a sharper look than a bilinear filter, at the price of some visible jags
+ *	on diagonal edges.
+ * 
+ * example scaling modes:
+ * 256x_Y_ -> 320x_Y_, H32/mode 4, PAR 5:4, for PAL DAR 4:3 (NTSC 7% aspect err)
+ * 256x224 -> 320x240, H32/mode 4, PAR 5:4, for NTSC DAR 4:3 (PAL 7% aspect err)
+ * 320x224 -> 320x240, PAR 1:1, for NTSC, DAR 4:3 (PAL 7% etc etc...)
+ * 160x144 -> 320x240: GG, PAR 6:5, scaling to 320x240 for DAR 4:3
+ * 
+ * (C) 2021 kub <derkub@gmail.com>
+ *
+ * This work is licensed under the terms of any of these licenses
+ * (at your option):
+ *  - GNU GPL, version 2 or later.
+ *  - MAME license.
+ */
+
+#include "upscale.h"
+
+/* X x Y -> X*5/4 x Y */
+void upscale_clut_nn_x_4_5(u8 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_nn_4_5(di, ds, si, ss, width, f_nop);
+	}
+}
+
+void upscale_rgb_nn_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_nn_4_5(di, ds, si, ss, width, f_pal);
+	}
+}
+
+void upscale_rgb_snn_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_snn_4_5(di, ds, si, ss, width, f_pal);
+	}
+}
+
+void upscale_rgb_bl2_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_bl2_4_5(di, ds, si, ss, width, f_pal);
+	}
+}
+
+void upscale_rgb_bl4_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_bl4_4_5(di, ds, si, ss, width, f_pal);
+	}
+}
+
+/* X x Y -> X*5/4 x Y*17/16 */
+void upscale_clut_nn_x_4_5_y_16_17(u8 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 8; j++) {
+			h_upscale_nn_4_5(di, ds, si, ss, width, f_nop);
+		}
+		di +=  ds;
+		for (j = 0; j < 8; j++) {
+			h_upscale_nn_4_5(di, ds, si, ss, width, f_nop);
+		}
+
+		di -= 9*ds;
+		v_copy(&di[0], &di[-ds], swidth, f_nop);
+		di += 9*ds;
+	}
+}
+
+void upscale_rgb_nn_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 8; j++) {
+			h_upscale_nn_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di +=  ds;
+		for (j = 0; j < 8; j++) {
+			h_upscale_nn_4_5(di, ds, si, ss, width, f_pal);
+		}
+
+		di -= 9*ds;
+		v_copy(&di[0], &di[-ds], swidth, f_nop);
+		di += 9*ds;
+	}
+}
+
+void upscale_rgb_snn_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 8; j++) {
+			h_upscale_snn_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di +=  ds;
+		for (j = 0; j < 8; j++) {
+			h_upscale_snn_4_5(di, ds, si, ss, width, f_pal);
+		}
+
+		/* mix lines 6-8 */
+		di -= 9*ds;
+		v_mix(&di[0], &di[-ds], &di[ds], swidth, p_05, f_nop);
+		v_mix(&di[-ds], &di[-2*ds], &di[-ds], swidth, p_05, f_nop);
+		v_mix(&di[ ds], &di[ ds], &di[ 2*ds], swidth, p_05, f_nop);
+		di += 9*ds;
+	}
+}
+
+void upscale_rgb_bl2_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 4; j++) {
+			h_upscale_bl2_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di +=  ds;
+		for (j = 0; j < 12; j++) {
+			h_upscale_bl2_4_5(di, ds, si, ss, width, f_pal);
+		}
+		/* mix lines 3-10 */
+		di -= 13*ds;
+			v_mix(&di[0], &di[-ds], &di[ds], swidth, p_05, f_nop);
+		for (j = 0; j < 7; j++) {
+			di += ds;
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_05, f_nop);
+		}
+		di += 6*ds;
+	}
+}
+
+void upscale_rgb_bl4_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 2; j++) {
+			h_upscale_bl4_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di += ds;
+		for (j = 0; j < 14; j++) {
+			h_upscale_bl4_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di -= 15*ds;
+		/* mixing line 2: line 1 = -ds, line 2 = +ds */
+			v_mix(&di[0], &di[-ds], &di[ds], swidth, p_025, f_nop);
+			di += ds;
+		/* mixing lines 3-5: line n-1 = 0, line n = +ds */
+		for (j = 0; j < 3; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_025, f_nop);
+			di += ds;
+			}
+		/* mixing lines 6-9 */
+		for (j = 0; j < 4; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_05, f_nop);
+			di += ds;
+		}
+		/* mixing lines 10-13 */
+		for (j = 0; j < 4; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_075, f_nop);
+			di += ds;
+		}
+		/* lines 14-16, already in place */
+		di += 3*ds;
+	}
+}
+
+/* "classic" upscaler as found in several emulators. It's really more like a
+ * x*4/3, y*16/15 upscaler, with an additional 5th row/17th line just inserted
+ * from the source image. That gives nice n/4,n/16 alpha values plus better
+ * symmetry in each block and avoids "borrowing" a row/line between blocks.
+ */
+void upscale_rgb_bln_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 4; j++) {
+			h_upscale_bln_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di += ds;
+		for (j = 0; j < 12; j++) {
+			h_upscale_bln_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di -= 13*ds;
+		/* mixing line 4: line 3 = -ds, line 4 = +ds */
+			v_mix(&di[0], &di[-ds], &di[ds], swidth, p_025, f_nop);
+			di += ds;
+		/* mixing lines 5-6: line n-1 = 0, line n = +ds */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_025, f_nop);
+			di += ds;
+			}
+		/* mixing line 7-9 */
+		for (j = 0; j < 3; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_05, f_nop);
+			di += ds;
+		}
+		/* mixing lines 10-12 */
+		for (j = 0; j < 3; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_075, f_nop);
+			di += ds;
+		}
+		/* lines 13-16, already in place */
+		di += 4*ds;
+	}
+}
+
+/* experimental 8 level bilinear for quality assessment */
+void upscale_rgb_bl8_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 5/4;
+	int y, j;
+
+	for (y = 0; y < 224; y += 16) {
+		for (j = 0; j < 2; j++) {
+			h_upscale_bl8_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di += ds;
+		for (j = 0; j < 14; j++) {
+			h_upscale_bl8_4_5(di, ds, si, ss, width, f_pal);
+		}
+		di -= 15*ds;
+		/* mixing line 2: line 2 = -ds, line 3 = +ds */
+			v_mix(&di[0], &di[-ds], &di[ds], swidth, p_0125, f_nop);
+			di += ds;
+		/* mixing line 3: line 3 = 0, line 4 = +ds */
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_0125, f_nop);
+			di += ds;
+		/* mixing lines 4-5: line n-1 = 0, line n = +ds */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], swidth, p_025, f_nop);
+			di += ds;
+			}
+		/* mixing lines 6-7 */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], 320, p_0375, f_nop);
+			di += ds;
+		}
+		/* mixing lines 8-9 */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], 320, p_05, f_nop);
+			di += ds;
+		}
+		/* mixing lines 10-11 */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], 320, p_0625, f_nop);
+			di += ds;
+		}
+		/* mixing lines 12-13 */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], 320, p_075, f_nop);
+			di += ds;
+		}
+		/* mixing lines 14-15 */
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[0], &di[ds], 320, p_0875, f_nop);
+			di += ds;
+		}
+		/* line 16, already in place */
+		di += ds;
+	}
+}
+
+/* X x Y -> X x Y*17/16 */
+void upscale_clut_nn_y_16_17(u8 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 8; j++) {
+			h_copy(di, ds, si, ss, width, f_nop);
+		}
+		di +=  ds;
+		for (j = 0; j < 8; j++) {
+			h_copy(di, ds, si, ss, width, f_nop);
+		}
+
+		di -= 9*ds;
+		v_copy(&di[0], &di[-ds], width, f_nop);
+		di += 9*ds;
+	}
+}
+
+void upscale_rgb_nn_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 8; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		di +=  ds;
+		for (j = 0; j < 8; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+
+		di -= 9*ds;
+		v_copy(&di[0], &di[-ds], width, f_nop);
+		di += 9*ds;
+	}
+}
+
+void upscale_rgb_snn_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 8; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		di +=  ds;
+		for (j = 0; j < 8; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+
+		/* mix lines 6-8 */
+		di -= 9*ds;
+		v_mix(&di[0], &di[-ds], &di[ds], width, p_05, f_nop);
+		v_mix(&di[-ds], &di[-2*ds], &di[-ds], width, p_05, f_nop);
+		v_mix(&di[ ds], &di[ ds], &di[ 2*ds], width, p_05, f_nop);
+		di += 9*ds;
+	}
+}
+
+void upscale_rgb_bl2_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 4; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		di +=  ds;
+		for (j = 0; j < 12; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		/* mix lines 4-11 */
+		di -= 13*ds;
+			v_mix(&di[0], &di[-ds], &di[ds], width, p_05, f_nop);
+		for (j = 0; j < 7; j++) {
+			di += ds;
+			v_mix(&di[0], &di[0], &di[ds], width, p_05, f_nop);
+		}
+		di += 6*ds;
+	}
+}
+
+void upscale_rgb_bl4_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 16) {
+		for (j = 0; j < 2; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		di += ds;
+		for (j = 0; j < 14; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		di -= 15*ds;
+		/* mixing line 2: line 1 = -ds, line 2 = +ds */
+			v_mix(&di[0], &di[-ds], &di[ds], width, p_025, f_nop);
+			di += ds;
+		/* mixing lines 3-5: line n-1 = 0, line n = +ds */
+		for (j = 0; j < 3; j++) {
+			v_mix(&di[0], &di[0], &di[ds], width, p_025, f_nop);
+			di += ds;
+			}
+		/* mixing lines 6-9 */
+		for (j = 0; j < 4; j++) {
+			v_mix(&di[0], &di[0], &di[ds], width, p_05, f_nop);
+			di += ds;
+		}
+		/* mixing lines 10-13 */
+		for (j = 0; j < 4; j++) {
+			v_mix(&di[0], &di[0], &di[ds], width, p_075, f_nop);
+			di += ds;
+		}
+		/* lines 14-16, already in place */
+		di += 3*ds;
+	}
+}
+
+/* X x Y -> X*2/1 x Y, e.g. for X 160->320 (GG) */
+void upscale_clut_nn_x_1_2(u8 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_nn_1_2(di, ds, si, ss, width, f_nop);
+	}
+}
+
+void upscale_rgb_nn_x_1_2(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_nn_1_2(di, ds, si, ss, width, f_pal);
+	}
+}
+
+void upscale_rgb_bl2_x_1_2(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y;
+
+	for (y = 0; y < height; y++) {
+		h_upscale_bl2_1_2(di, ds, si, ss, width, f_pal);
+	}
+}
+
+/* X x Y -> X*2/1 x Y*5/3 (GG) */
+void upscale_clut_nn_x_1_2_y_3_5(u8 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height)
+{
+	int swidth = width * 2;
+	int y, j;
+
+	for (y = 0; y < height; y += 3) {
+		/* lines 0,2,4 */
+		for (j = 0; j < 3; j++) {
+			h_upscale_nn_1_2(di, ds, si, ss, width, f_nop);
+			di += ds;
+		}
+		/* lines 1,3 */
+		di -= 5*ds;
+		for (j = 0; j < 2; j++) {
+			v_copy(&di[0], &di[-ds], swidth, f_nop);
+			di += 2*ds;
+		}
+	}
+}
+
+void upscale_rgb_nn_x_1_2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 2;
+	int y, j;
+
+	for (y = 0; y < height; y += 3) {
+		for (j = 0; j < 3; j++) {
+			h_upscale_nn_1_2(di, ds, si, ss, width, f_pal);
+			di += ds;
+		}
+		di -= 5*ds;
+		for (j = 0; j < 2; j++) {
+			v_copy(&di[0], &di[-ds], swidth, f_nop);
+			di += 2*ds;
+		}
+	}
+}
+
+void upscale_rgb_bl2_x_1_2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 2;
+	int y, j;
+
+	for (y = 0; y < height; y += 3) {
+		for (j = 0; j < 3; j++) {
+			h_upscale_bl2_1_2(di, ds, si, ss, width, f_pal);
+			di += ds;
+		}
+		di -= 5*ds;
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[-ds], &di[ds], swidth, p_05, f_nop);
+			di += 2*ds;
+		}
+	}
+}
+
+void upscale_rgb_bl4_x_1_2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int swidth = width * 2;
+	int y, j, d;
+
+	/* for 1st block backwards reference virtually duplicate source line 0 */
+	for (y = 0, d = 2*ds; y < height; y += 3, d = -ds) {
+		di += 2*ds;
+		for (j = 0; j < 3; j++) {
+			h_upscale_bl2_1_2(di, ds, si, ss, width, f_pal);
+		}
+		di -= 5*ds;
+		v_mix(&di[0], &di[d ], &di[2*ds], swidth, p_05, f_nop); /*-1+0 */
+		di += ds;
+		v_mix(&di[0], &di[ds], &di[2*ds], swidth, p_075, f_nop);/* 0+1 */
+		di += ds;
+		v_mix(&di[0], &di[ 0], &di[  ds], swidth, p_025, f_nop);/* 0+1 */
+		di += ds;
+		v_mix(&di[0], &di[ 0], &di[  ds], swidth, p_05, f_nop); /* 1+2 */
+		di += 2*ds;
+	}
+}
+
+/* X x Y -> X x Y*5/3, e.g. for Y 144->240 (GG) */
+void upscale_clut_nn_y_3_5(u8 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 3) {
+		/* lines 0,2,4 */
+		for (j = 0; j < 3; j++) {
+			h_copy(di, ds, si, ss, width, f_nop);
+			di += ds;
+		}
+		/* lines 1,3 */
+		di -= 5*ds;
+		for (j = 0; j < 2; j++) {
+			v_copy(&di[0], &di[-ds], width, f_nop);
+			di += 2*ds;
+		}
+	}
+}
+
+void upscale_rgb_nn_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 3) {
+		for (j = 0; j < 3; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+			di += ds;
+		}
+		di -= 5*ds;
+		for (j = 0; j < 2; j++) {
+			v_copy(&di[0], &di[-ds], width, f_nop);
+			di += 2*ds;
+		}
+	}
+}
+
+void upscale_rgb_bl2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j;
+
+	for (y = 0; y < height; y += 3) {
+		for (j = 0; j < 3; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+			di += ds;
+		}
+		di -= 5*ds;
+		for (j = 0; j < 2; j++) {
+			v_mix(&di[0], &di[-ds], &di[ds], width, p_05, f_nop);
+			di += 2*ds;
+		}
+	}
+}
+
+void upscale_rgb_bl4_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal)
+{
+	int y, j, d;
+
+	/* for 1st block backwards reference virtually duplicate source line 0 */
+	for (y = 0, d = 2*ds; y < height; y += 3, d = -ds) {
+		di += 2*ds;
+		for (j = 0; j < 3; j++) {
+			h_copy(di, ds, si, ss, width, f_pal);
+		}
+		di -= 5*ds;
+		v_mix(&di[0], &di[d ], &di[2*ds], width, p_05, f_nop); /*-1+0 */
+		di += ds;
+		v_mix(&di[0], &di[ds], &di[2*ds], width, p_075, f_nop);/* 0+1 */
+		di += ds;
+		v_mix(&di[0], &di[ 0], &di[  ds], width, p_025, f_nop);/* 0+1 */
+		di += ds;
+		v_mix(&di[0], &di[ 0], &di[  ds], width, p_05, f_nop); /* 1+2 */
+		di += 2*ds;
+	}
+}
+

+ 650 - 0
platform/common/upscale.h

@@ -0,0 +1,650 @@
+/*
+ * upscale.h		image upscaling
+ *
+ * This file contains upscalers for picodrive.
+ *
+ * scaler types:
+ * nn:	nearest neighbour
+ * snn:	"smoothed" nearest neighbour (see below)
+ * bln:	n-level-bilinear with n quantized weights
+ *	quantization: 0: a<1/(2*n), 1/n: 1/(2*n)<=a<3/(2*n), etc
+ *	currently n=2, n=4 are implemented (there's n=8 mixing, but no filters)
+ *	[NB this has been brought to my attn, which is probably the same as bl2:
+ *	https://www.drdobbs.com/image-scaling-with-bresenham/184405045?pgno=1]
+ *
+ * "smoothed" nearest neighbour: uses the average of the source pixels if no
+ *	source pixel covers more than 65% of the result pixel. It definitely
+ *	looks better than nearest neighbour and is still quite fast. It creates
+ *	a sharper look than a bilinear filter, at the price of some visible jags
+ *	on diagonal edges.
+ * 
+ * example scaling modes:
+ * 256x_Y_ -> 320x_Y_, H32/mode 4, PAR 5:4, for PAL DAR 4:3 (NTSC 7% aspect err)
+ * 256x224 -> 320x240, H32/mode 4, PAR 5:4, for NTSC DAR 4:3 (PAL 7% aspect err)
+ * 320x224 -> 320x240, PAR 1:1, for NTSC, DAR 4:3 (PAL 7% etc etc...)
+ * 160x144 -> 320x240: GG, PAR 6:5, scaling to 320x240 for DAR 4:3
+ *
+ *
+ * (C) 2021 kub <derkub@gmail.com>
+ *
+ * This work is licensed under the terms of any of these licenses
+ * (at your option):
+ *  - GNU GPL, version 2 or later.
+ *  - MAME license.
+ * See COPYING file in the top-level directory.
+ */
+#include <pico/pico_types.h>
+
+
+/* LSB of all colors in 1 or 2 pixels */
+#if defined(USE_BGR555)
+#define PXLSB		0x04210421
+#else
+#define PXLSB		0x08210821
+#endif
+
+/* RGB565 pixel mixing, see https://www.compuphase.com/graphic/scale3.htm and
+  			    http://blargg.8bitalley.com/info/rgb_mixing.html */
+/* 2-level mixing. NB blargg version isn't 2-pixel-at-once safe for RGB565 */
+//#define p_05(d,p1,p2)	d=(((p1)+(p2)  + ( ((p1)^(p2))&PXLSB))>>1) // round up
+//#define p_05(d,p1,p2)	d=(((p1)+(p2)  - ( ((p1)^(p2))&PXLSB))>>1) // round down
+#define p_05(d,p1,p2)	d=(((p1)&(p2)) + ((((p1)^(p2))&~PXLSB)>>1))
+/* 4-level mixing, 2 times slower */
+// 1/4*p1 + 3/4*p2 = 1/2*(1/2*(p1+p2) + p2)
+#define p_025(d,p1,p2)	p_05(t, p1, p2); p_05( d, t, p2)
+#define p_075(d,p1,p2)	p_025(d,p2,p1)
+/* 8-level mixing, 3 times slower */
+// 1/8*p1 + 7/8*p2 = 1/2*(1/2*(1/2*(p1+p2) + p2) + p2)
+#define p_0125(d,p1,p2)	p_05(t, p1, p2); p_05( u, t, p2); p_05( d, u, p2)
+// 3/8*p1 + 5/8*p2 = 1/2*(1/2*(1/2*(p1+p2) + p2) + 1/2*(p1+p2))
+#define p_0375(d,p1,p2)	p_05(t, p1, p2); p_05( u, t, p2); p_05( d, u,  t)
+#define p_0625(d,p1,p2)	p_0375(d,p2,p1)
+#define p_0875(d,p1,p2)	p_0125(d,p2,p1)
+
+/* pixel transforms */
+#define	f_pal(v)	pal[v]	// convert CLUT index -> RGB565
+#define f_nop(v)	(v)	// source already in dest format (CLUT/RGB)
+#define f_or(v)		(v|pal)	// CLUT, add palette selection
+
+/*
+scalers h:
+256->320:       - (4:5)         (256x224/240 -> 320x224/240)
+256->299:	- (6:7)		(256x224 -> 299x224, alt?)
+160->320:       - (1:2) 2x      (160x144 -> 320x240, GG)
+160->288:	- (5:9)		(160x144 -> 288x216, GG alt?)
+*/
+
+/* scale 4:5 */
+#define h_upscale_nn_4_5(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/4; i > 0; i--, si += 4, di += 5) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[1]);			\
+		di[2] = f(si[1]);			\
+		di[3] = f(si[2]);			\
+		di[4] = f(si[3]);			\
+	}						\
+	di += ds - w/4*5;				\
+	si += ss - w;					\
+} while (0)
+
+// reverse version for overlapping buffers
+#define rh_upscale_nn_4_5(di,ds,si,ss,w,f) do {	\
+	int i;						\
+	di += w/4*5;					\
+	si += w;					\
+	for (i = w/4; i > 0; i--, si -= 4, di -= 5) {	\
+		di[-1] = f(si[-1]);			\
+		di[-2] = f(si[-2]);			\
+		di[-3] = f(si[-3]);			\
+		di[-4] = f(si[-3]);			\
+		di[-5] = f(si[-4]);			\
+	}						\
+	di += ds;					\
+	si += ss;					\
+} while (0)
+
+#define h_upscale_snn_4_5(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/4; i > 0; i--, si += 4, di += 5) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[1]);			\
+		p_05(di[2], f(si[1]),f(si[2]));		\
+		di[3] = f(si[2]);			\
+		di[4] = f(si[3]);			\
+	}						\
+	di += ds - w/4*5;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bln_4_5(di,ds,si,ss,w,f) do {		\
+	int i; u16 t; 					\
+	for (i = w/4; i > 0; i--, si += 4, di += 5) {	\
+		di[0] = f(si[0]);			\
+		p_025(di[1], f(si[0]),f(si[1]));	\
+		p_05 (di[2], f(si[1]),f(si[2]));	\
+		p_075(di[3], f(si[2]),f(si[3]));	\
+		di[4] = f(si[3]);			\
+	}						\
+	di += ds - w/4*5;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl2_4_5(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/4; i > 0; i--, si += 4, di += 5) {	\
+		di[0] = f(si[0]);			\
+		p_05(di[1], f(si[0]),f(si[1]));		\
+		p_05(di[2], f(si[1]),f(si[2]));		\
+		di[3] = f(si[2]);			\
+		di[4] = f(si[3]);			\
+	}						\
+	di += ds - w/4*5;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl4_4_5(di,ds,si,ss,w,f) do {		\
+	int i; u16 t, p = f(si[0]);			\
+	for (i = w/4; i > 0; i--, si += 4, di += 5) {	\
+		p_025(di[0], p,       f(si[0]));	\
+		p_05 (di[1], f(si[0]),f(si[1]));	\
+		p_05 (di[2], f(si[1]),f(si[2]));	\
+		p_075(di[3], f(si[2]),f(si[3]));	\
+		di[4] = p = f(si[3]);			\
+	}						\
+	di += ds - w/4*5;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl8_4_5(di,ds,si,ss,w,f) do {		\
+	int i; u16 t, u, p = f(si[0]);			\
+	for (i = w/4; i > 0; i--, si += 4, di += 5) {	\
+		p_025 (di[0], p,       f(si[0]));	\
+		p_0375(di[1], f(si[0]),f(si[1]));	\
+		p_0625(di[2], f(si[1]),f(si[2]));	\
+		p_075 (di[3], f(si[2]),f(si[3]));	\
+		di[4] = p = f(si[3]);			\
+	}						\
+	di += ds - w/4*5;				\
+	si += ss - w;					\
+} while (0)
+
+/* scale 6:7 */
+#define h_upscale_nn_6_7(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/6; i > 0; i--, si += 6, di += 7) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[1]);			\
+		di[2] = f(si[2]);			\
+		di[3] = f(si[2]);			\
+		di[4] = f(si[3]);			\
+		di[5] = f(si[4]);			\
+		di[6] = f(si[5]);			\
+	}						\
+	di += ds - w/6*7;				\
+	si += ss - w;					\
+} while (0)
+
+// reverse version for overlapping buffers
+#define rh_upscale_nn_6_7(di,ds,si,ss,w,f) do {	\
+	int i;						\
+	di += w/6*7;					\
+	si += w;					\
+	for (i = w/6; i > 0; i--, si -= 6, di -= 7) {	\
+		di[-1] = f(si[-1]);			\
+		di[-2] = f(si[-2]);			\
+		di[-3] = f(si[-3]);			\
+		di[-4] = f(si[-4]);			\
+		di[-5] = f(si[-4]);			\
+		di[-6] = f(si[-5]);			\
+		di[-7] = f(si[-6]);			\
+	}						\
+	di += ds;					\
+	si += ss;					\
+} while (0)
+
+#define h_upscale_snn_6_7(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/6; i > 0; i--, si += 6, di += 7) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[1]);			\
+		di[2] = f(si[2]);			\
+		p_05(di[3], f(si[2]),f(si[3]));		\
+		di[4] = f(si[3]);			\
+		di[5] = f(si[4]);			\
+		di[6] = f(si[5]);			\
+	}						\
+	di += ds - w/6*7;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl2_6_7(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/6; i > 0; i--, si += 6, di += 7) {	\
+		di[0] = f(si[0]);			\
+		p_05(di[1], f(si[0]),f(si[1]));		\
+		p_05(di[2], f(si[1]),f(si[2]));		\
+		p_05(di[3], f(si[2]),f(si[3]));		\
+		p_05(di[4], f(si[3]),f(si[4]));		\
+		di[5] = f(si[4]);			\
+		di[6] = f(si[5]);			\
+	}						\
+	di += ds - w/6*7;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl4_6_7(di,ds,si,ss,w,f) do {		\
+	int i; u16 t p = f(si[0]);			\
+	for (i = w/6; i > 0; i--, si += 6, di += 7) {	\
+		p_025(di[0], p,       f(si[0]));	\
+		p_025(di[1], f(si[0]),f(si[1]));	\
+		p_05 (di[2], f(si[1]),f(si[2]));	\
+		p_05 (di[3], f(si[2]),f(si[3]));	\
+		p_075(di[4], f(si[3]),f(si[4]));	\
+		p_075(di[5], f(si[4]),f(si[5]));	\
+		di[6] = p = f(si[5]);			\
+	}						\
+	di += ds - w/6*7;				\
+	si += ss - w;					\
+} while (0)
+
+/* scale 5:9 */
+#define h_upscale_nn_5_9(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/5; i > 0; i--, si += 5, di += 9) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[0]);			\
+		di[2] = f(si[1]);			\
+		di[3] = f(si[1]);			\
+		di[4] = f(si[2]);			\
+		di[5] = f(si[3]);			\
+		di[6] = f(si[3]);			\
+		di[7] = f(si[4]);			\
+		di[8] = f(si[4]);			\
+	}						\
+	di += ds - w/5*9;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_snn_5_9(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/5; i > 0; i--, si += 5, di += 9) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[0]);			\
+		di[2] = f(si[1]);			\
+		p_05(di[3], f(si[1]),f(si[2]));		\
+		di[4] = f(si[2]);			\
+		p_05(di[5], f(si[2]),f(si[3]));		\
+		di[6] = f(si[3]);			\
+		di[7] = f(si[4]);			\
+		di[8] = f(si[4]);			\
+	}						\
+	di += ds - w/5*9;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl2_5_9(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/5; i > 0; i--, si += 5, di += 9) {	\
+		di[0] = f(si[0]);			\
+		p_05(di[1], f(si[0]),f(si[1]));		\
+		di[2] = f(si[1]);			\
+		p_05(di[3], f(si[1]),f(si[2]));		\
+		di[4] = f(si[2]);			\
+		p_05(di[5], f(si[2]),f(si[3]));		\
+		di[6] = f(si[3]);			\
+		p_05(di[7], f(si[3]),f(si[4]));		\
+		di[8] = f(si[4]);			\
+	}						\
+	di += ds - w/5*9;				\
+	si += ss - w;					\
+} while (0)
+
+#define h_upscale_bl4_5_9(di,ds,si,ss,w,f) do {		\
+	int i; u16 t, p = f(si[0]);			\
+	for (i = w/5; i > 0; i--, si += 5, di += 9) {	\
+		p_05 (di[0], p,       f(si[0]));	\
+		di[1] = f(si[0]);			\
+		p_025(di[2], f(si[0]),f(si[1]));	\
+		p_075(di[3], f(si[1]),f(si[2]));	\
+		p_025(di[4], f(si[1]),f(si[2]));	\
+		p_075(di[5], f(si[2]),f(si[3]));	\
+		di[6] = f(si[3]);			\
+		p_05 (di[7], f(si[3]),f(si[4]));	\
+		di[8] = p = f(si[4]);			\
+	}						\
+	di += ds - w/5*9;				\
+	si += ss - w;					\
+} while (0)
+
+/* scale 1:2 integer scale */
+#define h_upscale_nn_1_2(di,ds,si,ss,w,f) do {		\
+	int i;						\
+	for (i = w/2; i > 0; i--, si += 2, di += 4) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[0]);			\
+		di[2] = f(si[1]);			\
+		di[3] = f(si[1]);			\
+	}						\
+	di += ds - w*2;					\
+	si += ss - w;					\
+} while (0)
+
+// reverse version for overlapping buffers
+#define rh_upscale_nn_1_2(di,ds,si,ss,w,f) do {	\
+	int i;						\
+	di += w*2;					\
+	si += w;					\
+	for (i = w/2; i > 0; i--, si -= 2, di -= 4) {	\
+		di[-1] = f(si[-1]);			\
+		di[-2] = f(si[-1]);			\
+		di[-3] = f(si[-2]);			\
+		di[-4] = f(si[-2]);			\
+	}						\
+	di += ds;					\
+	si += ss;					\
+} while (0)
+
+#define h_upscale_bl2_1_2(di,ds,si,ss,w,f) do {		\
+	int i; uint p = f(si[0]);			\
+	for (i = w/2; i > 0; i--, si += 2, di += 4) {	\
+		p_05 (di[0], p,       f(si[0]));	\
+		di[1] = f(si[0]);			\
+		p_05 (di[2], f(si[0]),f(si[1]));	\
+		di[3] = p = f(si[1]);			\
+	}						\
+	di += ds - w*2;					\
+	si += ss - w;					\
+} while (0)
+
+/* scale 1:1, copy */
+#define h_copy(di,ds,si,ss,w,f) do {			\
+	int i;						\
+	for (i = w/4; i > 0; i--, si += 4, di += 4) {	\
+		di[0] = f(si[0]);			\
+		di[1] = f(si[1]);			\
+		di[2] = f(si[2]);			\
+		di[3] = f(si[3]);			\
+	}						\
+	di += ds - w;					\
+	si += ss - w;					\
+} while (0)
+
+/*
+scalers v:
+224->240:       - (14:15)       (256/320x224 -> 320x240)
+224->238:       - (16:17)       (256/320x224 -> 320x238 alt?)
+144->240:       - (3:5)         (160x144 -> 320x240, GG)
+144->216:	- (2:3)		(160x144 -> 288x216, GG alt?)
+*/
+
+#define v_mix(di,li,ri,w,p_mix,f) do {			\
+	int i; u32 t, u; (void)t, (void)u;		\
+	for (i = 0; i < w; i += 4) {			\
+		p_mix((di)[i  ], f((li)[i  ]),f((ri)[i  ])); \
+		p_mix((di)[i+1], f((li)[i+1]),f((ri)[i+1])); \
+		p_mix((di)[i+2], f((li)[i+2]),f((ri)[i+2])); \
+		p_mix((di)[i+3], f((li)[i+3]),f((ri)[i+3])); \
+	}						\
+} while (0)
+
+#define v_copy(di,ri,w,f) do {				\
+	int i;						\
+	for (i = 0; i < w; i += 4) {			\
+		(di)[i  ] = f((ri)[i  ]);		\
+		(di)[i+1] = f((ri)[i+1]);		\
+		(di)[i+2] = f((ri)[i+2]);		\
+		(di)[i+3] = f((ri)[i+3]);		\
+	}						\
+} while (0)
+
+/* scale 14:15 */
+#define v_upscale_nn_14_15(di,ds,w,l) do {		\
+	if (++l == 7) {					\
+		di += ds; 				\
+	} else if (l >= 14) {				\
+		l = 0;					\
+		di -= 7*ds;				\
+		v_copy(&di[0], &di[-ds], w, f_nop);	\
+		di += 7*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_snn_14_15(di,ds,w,l) do {		\
+	if (++l == 7) {					\
+		di += ds; 				\
+	} else if (l >= 14) {				\
+		l = 0;					\
+		di -= 7*ds;				\
+		v_mix(&di[0], &di[-ds], &di[ds], w, p_05, f_nop); \
+		v_mix(&di[-ds], &di[-2*ds], &di[-ds], w, p_05, f_nop); \
+		v_mix(&di[ ds], &di[ ds], &di[ 2*ds], w, p_05, f_nop); \
+		di += 7*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_bl2_14_15(di,ds,w,l) do {		\
+	if (++l == 3) {					\
+		di += ds; 				\
+	} else if (l >= 14) {				\
+		int j;					\
+		l = 0;					\
+		di -= 11*ds;				\
+			v_mix(&di[0], &di[-ds], &di[ds], w, p_05, f_nop); \
+		for (j = 0; j < 7; j++)	{		\
+			di += ds;			\
+			v_mix(&di[0], &di[0], &di[ds], w, p_05, f_nop); \
+		}					\
+		di += 4*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_bl4_14_15(di,ds,w,l) do {		\
+	if (++l == 1) {					\
+		di += ds; 				\
+	} else if (l >= 14) {				\
+		int j;					\
+		l = 0;					\
+		di -= 13*ds;				\
+			v_mix(&di[0], &di[-ds], &di[ds], w, p_025, f_nop); \
+			di += ds;			\
+		for (j = 0; j < 3; j++) {		\
+			v_mix(&di[0], &di[0], &di[ds], w, p_025, f_nop); \
+			di += ds;			\
+			}				\
+		for (j = 0; j < 4; j++) {		\
+			v_mix(&di[0], &di[0], &di[ds], w, p_05, f_nop); \
+			di += ds;			\
+		}					\
+		for (j = 0; j < 4; j++) {		\
+			v_mix(&di[0], &di[0], &di[ds], w, p_075, f_nop); \
+			di += ds;			\
+		}					\
+		di += 1*ds;				\
+	}						\
+} while (0)
+
+/* scale 16:17 */
+#define v_upscale_nn_16_17(di,ds,w,l) do {		\
+	if (++l == 8) {					\
+		di += ds; 				\
+	} else if (l >= 16) {				\
+		l = 0;					\
+		di -= 8*ds;				\
+		v_copy(&di[0], &di[-ds], w, f_nop);	\
+		di += 8*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_snn_16_17(di,ds,w,l) do {		\
+	if (++l == 8) {					\
+		di += ds; 				\
+	} else if (l >= 16) {				\
+		l = 0;					\
+		di -= 8*ds;				\
+		v_mix(&di[0], &di[-ds], &di[ds], w, p_05, f_nop); \
+		v_mix(&di[-ds], &di[-2*ds], &di[-ds], w, p_05, f_nop); \
+		v_mix(&di[ ds], &di[ ds], &di[ 2*ds], w, p_05, f_nop); \
+		di += 8*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_bl2_16_17(di,ds,w,l) do {		\
+	if (++l == 4) {					\
+		di += ds; 				\
+	} else if (l >= 16) {				\
+		int j;					\
+		l = 0;					\
+		di -= 12*ds;				\
+			v_mix(&di[0], &di[-ds], &di[ds], w, p_05, f_nop); \
+		for (j = 0; j < 7; j++)	{		\
+			di += ds;			\
+			v_mix(&di[0], &di[0], &di[ds], w, p_05, f_nop); \
+		}					\
+		di += 5*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_bl4_16_17(di,ds,w,l) do {		\
+	if (++l == 2) {					\
+		di += ds; 				\
+	} else if (l >= 16) {				\
+		int j;					\
+		l = 0;					\
+		di -= 14*ds;				\
+			v_mix(&di[0], &di[-ds], &di[ds], w, p_025, f_nop); \
+			di += ds;			\
+		for (j = 0; j < 3; j++) {		\
+			v_mix(&di[0], &di[0], &di[ds], w, p_025, f_nop); \
+			di += ds;			\
+			}				\
+		for (j = 0; j < 4; j++) {		\
+			v_mix(&di[0], &di[0], &di[ds], w, p_05, f_nop); \
+			di += ds;			\
+		}					\
+		for (j = 0; j < 4; j++) {		\
+			v_mix(&di[0], &di[0], &di[ds], w, p_075, f_nop); \
+			di += ds;			\
+		}					\
+		di += 2*ds;				\
+	}						\
+} while (0)
+
+/* scale 3:5 */
+#define v_upscale_nn_3_5(di,ds,w,l) do {		\
+	if (++l < 3) {					\
+		di += ds; 				\
+	} else  {					\
+		int j;					\
+		l = 0;					\
+		di -= 3*ds;				\
+		for (j = 0; j < 2; j++) {		\
+			v_copy(&di[0], &di[-ds], w, f_nop); \
+			di += 2*ds;			\
+		}					\
+		di -= ds;			\
+	}						\
+} while (0)
+
+#define v_upscale_snn_3_5(di,ds,w,l) do {		\
+	if (++l < 3) {					\
+		di += ds; 				\
+	} else  {					\
+		int j;					\
+		l = 0;					\
+		di -= 3*ds;				\
+		for (j = 0; j < 2; j++) {		\
+			v_mix(&di[0], &di[-ds], &di[ds], w, p_05, f_nop); \
+			di += 2*ds;			\
+		}					\
+		di -= ds;			\
+	}						\
+} while (0)
+
+/* scale 2:3 */
+#define v_upscale_nn_2_3(di,ds,w,l) do {		\
+	if (++l < 2) {					\
+		di += ds; 				\
+	} else  {					\
+		int j;					\
+		l = 0;					\
+		di -= 2*ds;				\
+		v_copy(&di[0], &di[-ds], w, f_nop);	\
+		di += 2*ds;				\
+	}						\
+} while (0)
+
+#define v_upscale_snn_2_3(di,ds,w,l) do {		\
+	if (++l < 2) {					\
+		di += ds; 				\
+	} else  {					\
+		int j;					\
+		l = 0;					\
+		di -= 2*ds;				\
+		v_mix(&di[0], &di[-ds], &di[ds], w, p_05, f_nop); \
+		di += 2*ds;				\
+	}						\
+} while (0)
+
+
+/* exponentially smoothing (for LCD ghosting): y[n] = x[n]*a + y[n-1]*(1-a) */
+
+#define PXLSBn (PXLSB*15) // using 4 LSBs of each subpixel for subtracting
+// NB implement rounding to x[n] by adding 1 to counter round down if y[n] is
+// smaller than x[n]: use some of the lower bits to implement subtraction on
+// subpixels, with an additional bit to detect borrow, then add the borrow.
+// It's doing the increment wrongly in a lot of cases, which doesn't matter
+// much since it will converge to x[n] in a few frames anyway if x[n] is static
+#define p_05_round(d,p1,p2)				\
+	p_05(u, p1, p2);				\
+	t=(u|~PXLSBn)-(p1&PXLSBn); d = u+(~(t>>4)&PXLSB)
+// Unfortunately this won't work for p_025, where adding 1 isn't enough and
+// adding 2 would be too much, so offer only p_075 here
+#define p_075_round(d,p1,p2)				\
+	p_075(u, p1, p2);				\
+	t=(u|~PXLSBn)-(p1&PXLSBn); d = u+(~(t>>4)&PXLSB)
+
+// this is essentially v_mix and v_copy combined
+#define v_blend(di,ri,w,p_mix) do {			\
+	int i; u32 t, u; (void)t, (void)u;		\
+	for (i = 0; i < w; i += 4) {			\
+		p_mix((ri)[i  ], (di)[i  ],(ri)[i  ]); (di)[i  ] = (ri)[i  ]; \
+		p_mix((ri)[i+1], (di)[i+1],(ri)[i+1]); (di)[i+1] = (ri)[i+1]; \
+		p_mix((ri)[i+2], (di)[i+2],(ri)[i+2]); (di)[i+2] = (ri)[i+2]; \
+		p_mix((ri)[i+3], (di)[i+3],(ri)[i+3]); (di)[i+3] = (ri)[i+3]; \
+	}						\
+} while (0)
+
+
+/* X x Y -> X*5/4 x Y, for X 256->320 */
+void upscale_rgb_nn_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_snn_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl2_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl4_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+
+/* X x Y -> X x Y*17/16, for Y 224->238 or 192->204 (SMS) */
+void upscale_rgb_nn_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_snn_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl2_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl4_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+
+/* X x Y -> X*5/4 x Y*17/16 */
+void upscale_rgb_nn_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_snn_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl2_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl4_x_4_5_y_16_17(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+
+/* X x Y -> X*2/1 x Y, e.g. for X 160->320 (GG) */
+void upscale_rgb_nn_x_1_2(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl2_x_1_2(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+
+/* X x Y -> X x Y*5/3, e.g. for Y 144->240 (GG) */
+void upscale_rgb_nn_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl4_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+
+/* X x Y -> X*2/1 x Y*5/3 (GG) */
+void upscale_rgb_nn_x_1_2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl2_x_1_2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+void upscale_rgb_bl4_x_1_2_y_3_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
+

+ 1 - 1
platform/common/version.h

@@ -1 +1 @@
-#define VERSION "1.98"
+#define VERSION "1.99"

+ 71 - 45
platform/gp2x/emu.c

@@ -83,6 +83,10 @@ void pemu_validate_config(void)
 
 static int get_renderer(void)
 {
+	if (doing_bg_frame)
+		return RT_16BIT;
+	if ((PicoIn.AHW & PAHW_SMS) && currentConfig.renderer == RT_8BIT_FAST)
+		return RT_8BIT_ACC;	// 8bpp fast is not there (yet?)
 	if (PicoIn.AHW & PAHW_32X)
 		return currentConfig.renderer32x;
 	else
@@ -105,7 +109,7 @@ static void change_renderer(int diff)
 }
 
 #define is_16bit_mode() \
-	(currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X))
+	(currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X) || doing_bg_frame)
 
 static void (*osd_text)(int x, int y, const char *text);
 
@@ -248,12 +252,15 @@ static int EmuScanEnd8_rot(unsigned int num)
 
 /* line doublers */
 static unsigned int ld_counter;
-static int ld_left, ld_lines;
+static int ld_left, ld_lines; // numbers in Q1 format
 
 static int EmuScanBegin16_ld(unsigned int num)
 {
-	if ((signed int)(ld_counter - num) > 100)
-		ld_counter = 0;
+	if ((signed int)(ld_counter - num) > 100) {
+		// vsync, offset so that the upscaled image is centered
+		ld_counter = 120 - (120-num) * (ld_lines+2)/ld_lines;
+		ld_left = ld_lines;
+	}
 
 	if (emu_scan_begin)
 		return emu_scan_begin(ld_counter);
@@ -271,9 +278,9 @@ static int EmuScanEnd16_ld(unsigned int num)
 		emu_scan_end(ld_counter);
 
 	ld_counter++;
-	ld_left--;
+	ld_left -= 2;
 	if (ld_left <= 0) {
-		ld_left = ld_lines;
+		ld_left += ld_lines;
 
 		EmuScanBegin16_ld(num);
 		memcpy(Pico.est.DrawLineDest, oldline, 320 * gp2x_current_bpp / 8);
@@ -297,25 +304,26 @@ static int make_local_pal_md(int fast_mode)
 	int pallen = 0x100;
 
 	if (fast_mode) {
-		bgr444_to_rgb32(localPal, PicoMem.cram);
+		bgr444_to_rgb32(localPal, PicoMem.cram, 64);
 		pallen = 0x40;
 		Pico.m.dirtyPal = 0;
 	}
 	else if (Pico.est.rendstatus & PDRAW_SONIC_MODE) { // mid-frame palette changes
 		switch (Pico.est.SonicPalCount) {
-		case 3: bgr444_to_rgb32(localPal+0xc0, Pico.est.SonicPal+0xc0);
-		case 2: bgr444_to_rgb32(localPal+0x80, Pico.est.SonicPal+0x80);
-		case 1: bgr444_to_rgb32(localPal+0x40, Pico.est.SonicPal+0x40);
-		default:bgr444_to_rgb32(localPal, Pico.est.SonicPal);
+		case 3: bgr444_to_rgb32(localPal+0xc0, Pico.est.SonicPal+0xc0, 64);
+		case 2: bgr444_to_rgb32(localPal+0x80, Pico.est.SonicPal+0x80, 64);
+		case 1: bgr444_to_rgb32(localPal+0x40, Pico.est.SonicPal+0x40, 64);
+		default:bgr444_to_rgb32(localPal, Pico.est.SonicPal, 64);
 		}
 		pallen = (Pico.est.SonicPalCount+1)*0x40;
 	}
 	else if (Pico.video.reg[0xC] & 8) { // shadow/hilight mode
-		bgr444_to_rgb32(localPal, Pico.est.SonicPal);
+		bgr444_to_rgb32(localPal, Pico.est.SonicPal, 64);
 		bgr444_to_rgb32_sh(localPal, Pico.est.SonicPal);
+		memcpy(localPal+0xc0, localPal, 0x40*4); // for spr prio mess
 	}
 	else {
-		bgr444_to_rgb32(localPal, Pico.est.SonicPal);
+		bgr444_to_rgb32(localPal, Pico.est.SonicPal, 64);
 		memcpy(localPal+0x40, localPal, 0x40*4); // for spr prio mess
 		memcpy(localPal+0x80, localPal, 0x80*4); // for spr prio mess
 	}
@@ -331,20 +339,27 @@ static int make_local_pal_md(int fast_mode)
 
 static int make_local_pal_sms(int fast_mode)
 {
-	unsigned short *spal = PicoMem.cram;
-	unsigned int *dpal = (void *)localPal;
-	unsigned int i, t;
-
-	for (i = 0x40; i > 0; i--) {
-		t = *spal++;
-		t = ((t & 0x0003) << 22) | ((t & 0x000c) << 12) | ((t & 0x0030) << 2);
-		t |= t >> 2;
-		t |= t >> 4;
-		*dpal++ = t;
+	static u16 tmspal[32] = {
+		// SMS palette for TMS modes
+		0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
+		0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
+	};
+	int i;
+	
+	if (!(Pico.video.reg[0] & 0x4)) {
+		for (i = Pico.est.SonicPalCount; i >= 0; i--) {
+			bgr444_to_rgb32(localPal+i*0x40, tmspal, 32);
+			memcpy(localPal+i*0x40+0x20, localPal+i*0x40, 0x20*4);
+		}
+	} else {
+		for (i = Pico.est.SonicPalCount; i >= 0; i--) {
+			bgr444_to_rgb32(localPal+i*0x40, Pico.est.SonicPal+i*0x40, 32);
+			memcpy(localPal+i*0x40+0x20, localPal+i*0x40, 0x20*4);
+		}
 	}
-
-	Pico.m.dirtyPal = 0;
-	return 0x40;
+	if (Pico.m.dirtyPal == 2)
+		Pico.m.dirtyPal = 0;
+	return (Pico.est.SonicPalCount+1)*0x40;
 }
 
 void pemu_finalize_frame(const char *fps, const char *notice)
@@ -401,7 +416,7 @@ void plat_video_flip(void)
 		stride *= 2;
 	// the fast renderer has overlap areas and can't directly render to
 	// screen buffers. Its output is copied to screen in finalize_frame
-	if (currentConfig.renderer != RT_8BIT_FAST || (PicoIn.AHW & PAHW_32X))
+	if (get_renderer() != RT_8BIT_FAST || (PicoIn.AHW & PAHW_32X))
 		PicoDrawSetOutBuf(g_screen_ptr, stride);
 }
 
@@ -486,9 +501,13 @@ static void vid_reset_mode(void)
 	int gp2x_mode = 16;
 	int renderer = get_renderer();
 
-	PicoIn.opt &= ~POPT_ALT_RENDERER;
-	emu_scan_begin = NULL;
-	emu_scan_end = NULL;
+	PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_DIS_32C_BORDER|POPT_EN_SOFTSCALE);
+	if (currentConfig.scaling == EOPT_SCALE_SW) {
+		PicoIn.opt |= POPT_EN_SOFTSCALE;
+		PicoIn.filter = EOPT_FILTER_BILINEAR2;
+	} else if (currentConfig.scaling == EOPT_SCALE_HW)
+		// hw scaling, render without any padding
+		PicoIn.opt |= POPT_DIS_32C_BORDER;
 
 	switch (renderer) {
 	case RT_16BIT:
@@ -520,6 +539,9 @@ static void vid_reset_mode(void)
 		gp2x_mode = 16;
 	}
 
+	emu_scan_begin = NULL;
+	emu_scan_end = NULL;
+
 	if (currentConfig.EmuOpt & EOPT_WIZ_TEAR_FIX) {
 		if ((PicoIn.AHW & PAHW_32X) || renderer == RT_16BIT) {
 			emu_scan_begin = EmuScanBegin16_rot;
@@ -557,19 +579,27 @@ static void vid_reset_mode(void)
 
 	Pico.m.dirtyPal = 1;
 
-	PicoIn.opt &= ~POPT_EN_SOFTSCALE;
-	if (currentConfig.scaling == EOPT_SCALE_SW)
-		PicoIn.opt |= POPT_EN_SOFTSCALE;
-
 	// palette converters for 8bit modes
 	make_local_pal = (PicoIn.AHW & PAHW_SMS) ? make_local_pal_sms : make_local_pal_md;
 }
 
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
 {
 	int scalex = 320, scaley = 240;
 	int ln_offs = 0;
 
+	/* NTSC always has 224 visible lines, anything smaller has bars */
+	if (line_count < 224 && line_count > 144) {
+		start_line -= (224-line_count) /2;
+		line_count = 224;
+	}
+
+	/* line doubling for swscaling, also needed for bg frames */
+	if (currentConfig.vscaling == EOPT_SCALE_SW && line_count < 240) {
+		ld_lines = ld_left = 2*line_count / (240 - line_count);
+		PicoDrawSetCallbacks(EmuScanBegin16_ld, EmuScanEnd16_ld);
+	}
+
 	if (doing_bg_frame)
 		return;
 
@@ -577,11 +607,9 @@ void emu_video_mode_change(int start_line, int line_count, int is_32cols)
 	osd_y = 232;
 
 	/* set up hwscaling here */
-	PicoIn.opt &= ~POPT_DIS_32C_BORDER;
-	if (is_32cols && currentConfig.scaling == EOPT_SCALE_HW) {
-		scalex = 256;
-		PicoIn.opt |= POPT_DIS_32C_BORDER;
-		osd_fps_x = OSD_FPS_X - 64;
+	if (col_count < 320 && currentConfig.scaling == EOPT_SCALE_HW) {
+		scalex = col_count;
+		osd_fps_x = col_count - (320-OSD_FPS_X);
 	}
 
 	if (currentConfig.vscaling == EOPT_SCALE_HW) {
@@ -592,11 +620,6 @@ void emu_video_mode_change(int start_line, int line_count, int is_32cols)
 
 	gp2x_video_RGB_setscaling(ln_offs, scalex, scaley);
 
-	/* line doubling */
-	if (currentConfig.vscaling == EOPT_SCALE_SW && line_count < 240) {
-		ld_lines = ld_left = line_count / (240 - line_count);
-		PicoDrawSetCallbacks(EmuScanBegin16_ld, EmuScanEnd16_ld);
-	}
 
 	// clear whole screen in all buffers
 	if (!is_16bit_mode())
@@ -729,6 +752,8 @@ void pemu_forced_frame(int no_scale, int do_emu)
 	doing_bg_frame = 1;
 	PicoDrawSetCallbacks(NULL, NULL);
 	Pico.m.dirtyPal = 1;
+	PicoIn.opt &= ~POPT_DIS_32C_BORDER;
+	gp2x_current_bpp = 16;
 
 	if (!no_scale)
 		no_scale = currentConfig.scaling == EOPT_SCALE_NONE;
@@ -747,6 +772,7 @@ void plat_video_loop_prepare(void)
 	// make sure we are in correct mode
 	change_renderer(0);
 	vid_reset_mode();
+	rendstatus_old = -1;
 }
 
 void pemu_loop_prep(void)

+ 1 - 1
platform/libpicofe

@@ -1 +1 @@
-Subproject commit aa471f7f59f3dcc3cf0b6efc6213e53ea0453c9c
+Subproject commit e87e15f1702e4182ebabb1ab9df1fb9ff13a2e95

+ 803 - 2
platform/libretro/libretro-common/include/libretro.h

@@ -69,7 +69,7 @@ extern "C" {
 #      endif
 #    endif
 #  else
-#      if defined(__GNUC__) && __GNUC__ >= 4 && !defined(__CELLOS_LV2__)
+#      if defined(__GNUC__) && __GNUC__ >= 4
 #        define RETRO_API RETRO_CALLCONV __attribute__((__visibility__("default")))
 #      else
 #        define RETRO_API RETRO_CALLCONV
@@ -282,6 +282,7 @@ enum retro_language
    RETRO_LANGUAGE_PERSIAN             = 20,
    RETRO_LANGUAGE_HEBREW              = 21,
    RETRO_LANGUAGE_ASTURIAN            = 22,
+   RETRO_LANGUAGE_FINNISH             = 23,
    RETRO_LANGUAGE_LAST,
 
    /* Ensure sizeof(enum) == sizeof(int) */
@@ -712,6 +713,9 @@ enum retro_mod
                                             * state of rumble motors in controllers.
                                             * A strong and weak motor is supported, and they can be
                                             * controlled indepedently.
+                                            * Should be called from either retro_init() or retro_load_game().
+                                            * Should not be called from retro_set_environment().
+                                            * Returns false if rumble functionality is unavailable.
                                             */
 #define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24
                                            /* uint64_t * --
@@ -1127,6 +1131,13 @@ enum retro_mod
                                             * retro_core_option_definition structs to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL.
                                             * This allows the core to additionally set option sublabel information
                                             * and/or provide localisation support.
+                                            *
+                                            * If version is >= 2, core options may instead be set by passing
+                                            * a retro_core_options_v2 struct to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2,
+                                            * or an array of retro_core_options_v2 structs to
+                                            * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL. This allows the core
+                                            * to additionally set optional core option category information
+                                            * for frontends with core option category support.
                                             */
 
 #define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53
@@ -1168,7 +1179,7 @@ enum retro_mod
                                             * default value is NULL, the first entry in the
                                             * retro_core_option_definition::values array is treated as the default.
                                             *
-                                            * The number of possible options should be very limited,
+                                            * The number of possible option values should be very limited,
                                             * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX.
                                             * i.e. it should be feasible to cycle through options
                                             * without a keyboard.
@@ -1201,6 +1212,7 @@ enum retro_mod
                                             * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION
                                             * returns an API version of >= 1.
                                             * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS.
                                             * This should be called the first time as early as
                                             * possible (ideally in retro_set_environment).
                                             * Afterwards it may be called again for the core to communicate
@@ -1374,6 +1386,373 @@ enum retro_mod
                                             * call will target the newly initialized driver.
                                             */
 
+#define RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE 64
+                                           /* const struct retro_fastforwarding_override * --
+                                            * Used by a libretro core to override the current
+                                            * fastforwarding mode of the frontend.
+                                            * If NULL is passed to this function, the frontend
+                                            * will return true if fastforwarding override
+                                            * functionality is supported (no change in
+                                            * fastforwarding state will occur in this case).
+                                            */
+
+#define RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE 65
+                                           /* const struct retro_system_content_info_override * --
+                                            * Allows an implementation to override 'global' content
+                                            * info parameters reported by retro_get_system_info().
+                                            * Overrides also affect subsystem content info parameters
+                                            * set via RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO.
+                                            * This function must be called inside retro_set_environment().
+                                            * If callback returns false, content info overrides
+                                            * are unsupported by the frontend, and will be ignored.
+                                            * If callback returns true, extended game info may be
+                                            * retrieved by calling RETRO_ENVIRONMENT_GET_GAME_INFO_EXT
+                                            * in retro_load_game() or retro_load_game_special().
+                                            *
+                                            * 'data' points to an array of retro_system_content_info_override
+                                            * structs terminated by a { NULL, false, false } element.
+                                            * If 'data' is NULL, no changes will be made to the frontend;
+                                            * a core may therefore pass NULL in order to test whether
+                                            * the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and
+                                            * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks are supported
+                                            * by the frontend.
+                                            *
+                                            * For struct member descriptions, see the definition of
+                                            * struct retro_system_content_info_override.
+                                            *
+                                            * Example:
+                                            *
+                                            * - struct retro_system_info:
+                                            * {
+                                            *    "My Core",                      // library_name
+                                            *    "v1.0",                         // library_version
+                                            *    "m3u|md|cue|iso|chd|sms|gg|sg", // valid_extensions
+                                            *    true,                           // need_fullpath
+                                            *    false                           // block_extract
+                                            * }
+                                            *
+                                            * - Array of struct retro_system_content_info_override:
+                                            * {
+                                            *    {
+                                            *       "md|sms|gg", // extensions
+                                            *       false,       // need_fullpath
+                                            *       true         // persistent_data
+                                            *    },
+                                            *    {
+                                            *       "sg",        // extensions
+                                            *       false,       // need_fullpath
+                                            *       false        // persistent_data
+                                            *    },
+                                            *    { NULL, false, false }
+                                            * }
+                                            *
+                                            * Result:
+                                            * - Files of type m3u, cue, iso, chd will not be
+                                            *   loaded by the frontend. Frontend will pass a
+                                            *   valid path to the core, and core will handle
+                                            *   loading internally
+                                            * - Files of type md, sms, gg will be loaded by
+                                            *   the frontend. A valid memory buffer will be
+                                            *   passed to the core. This memory buffer will
+                                            *   remain valid until retro_deinit() returns
+                                            * - Files of type sg will be loaded by the frontend.
+                                            *   A valid memory buffer will be passed to the core.
+                                            *   This memory buffer will remain valid until
+                                            *   retro_load_game() (or retro_load_game_special())
+                                            *   returns
+                                            *
+                                            * NOTE: If an extension is listed multiple times in
+                                            * an array of retro_system_content_info_override
+                                            * structs, only the first instance will be registered
+                                            */
+
+#define RETRO_ENVIRONMENT_GET_GAME_INFO_EXT 66
+                                           /* const struct retro_game_info_ext ** --
+                                            * Allows an implementation to fetch extended game
+                                            * information, providing additional content path
+                                            * and memory buffer status details.
+                                            * This function may only be called inside
+                                            * retro_load_game() or retro_load_game_special().
+                                            * If callback returns false, extended game information
+                                            * is unsupported by the frontend. In this case, only
+                                            * regular retro_game_info will be available.
+                                            * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT is guaranteed
+                                            * to return true if RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE
+                                            * returns true.
+                                            *
+                                            * 'data' points to an array of retro_game_info_ext structs.
+                                            *
+                                            * For struct member descriptions, see the definition of
+                                            * struct retro_game_info_ext.
+                                            *
+                                            * - If function is called inside retro_load_game(),
+                                            *   the retro_game_info_ext array is guaranteed to
+                                            *   have a size of 1 - i.e. the returned pointer may
+                                            *   be used to access directly the members of the
+                                            *   first retro_game_info_ext struct, for example:
+                                            *
+                                            *      struct retro_game_info_ext *game_info_ext;
+                                            *      if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &game_info_ext))
+                                            *         printf("Content Directory: %s\n", game_info_ext->dir);
+                                            *
+                                            * - If the function is called inside retro_load_game_special(),
+                                            *   the retro_game_info_ext array is guaranteed to have a
+                                            *   size equal to the num_info argument passed to
+                                            *   retro_load_game_special()
+                                            */
+
+#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 67
+                                           /* const struct retro_core_options_v2 * --
+                                            * Allows an implementation to signal the environment
+                                            * which variables it might want to check for later using
+                                            * GET_VARIABLE.
+                                            * This allows the frontend to present these variables to
+                                            * a user dynamically.
+                                            * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION
+                                            * returns an API version of >= 2.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS.
+                                            * This should be called the first time as early as
+                                            * possible (ideally in retro_set_environment).
+                                            * Afterwards it may be called again for the core to communicate
+                                            * updated options to the frontend, but the number of core
+                                            * options must not change from the number in the initial call.
+                                            * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API
+                                            * version of >= 2, this callback is guaranteed to succeed
+                                            * (i.e. callback return value does not indicate success)
+                                            * If callback returns true, frontend has core option category
+                                            * support.
+                                            * If callback returns false, frontend does not have core option
+                                            * category support.
+                                            *
+                                            * 'data' points to a retro_core_options_v2 struct, containing
+                                            * of two pointers:
+                                            * - retro_core_options_v2::categories is an array of
+                                            *   retro_core_option_v2_category structs terminated by a
+                                            *   { NULL, NULL, NULL } element. If retro_core_options_v2::categories
+                                            *   is NULL, all core options will have no category and will be shown
+                                            *   at the top level of the frontend core option interface. If frontend
+                                            *   does not have core option category support, categories array will
+                                            *   be ignored.
+                                            * - retro_core_options_v2::definitions is an array of
+                                            *   retro_core_option_v2_definition structs terminated by a
+                                            *   { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL }
+                                            *   element.
+                                            *
+                                            * >> retro_core_option_v2_category notes:
+                                            *
+                                            * - retro_core_option_v2_category::key should contain string
+                                            *   that uniquely identifies the core option category. Valid
+                                            *   key characters are [a-z, A-Z, 0-9, _, -]
+                                            *   Namespace collisions with other implementations' category
+                                            *   keys are permitted.
+                                            * - retro_core_option_v2_category::desc should contain a human
+                                            *   readable description of the category key.
+                                            * - retro_core_option_v2_category::info should contain any
+                                            *   additional human readable information text that a typical
+                                            *   user may need to understand the nature of the core option
+                                            *   category.
+                                            *
+                                            * Example entry:
+                                            * {
+                                            *     "advanced_settings",
+                                            *     "Advanced",
+                                            *     "Options affecting low-level emulation performance and accuracy."
+                                            * }
+                                            *
+                                            * >> retro_core_option_v2_definition notes:
+                                            *
+                                            * - retro_core_option_v2_definition::key should be namespaced to not
+                                            *   collide with other implementations' keys. e.g. A core called
+                                            *   'foo' should use keys named as 'foo_option'. Valid key characters
+                                            *   are [a-z, A-Z, 0-9, _, -].
+                                            * - retro_core_option_v2_definition::desc should contain a human readable
+                                            *   description of the key. Will be used when the frontend does not
+                                            *   have core option category support. Examples: "Aspect Ratio" or
+                                            *   "Video > Aspect Ratio".
+                                            * - retro_core_option_v2_definition::desc_categorized should contain a
+                                            *   human readable description of the key, which will be used when
+                                            *   frontend has core option category support. Example: "Aspect Ratio",
+                                            *   where associated retro_core_option_v2_category::desc is "Video".
+                                            *   If empty or NULL, the string specified by
+                                            *   retro_core_option_v2_definition::desc will be used instead.
+                                            *   retro_core_option_v2_definition::desc_categorized will be ignored
+                                            *   if retro_core_option_v2_definition::category_key is empty or NULL.
+                                            * - retro_core_option_v2_definition::info should contain any additional
+                                            *   human readable information text that a typical user may need to
+                                            *   understand the functionality of the option.
+                                            * - retro_core_option_v2_definition::info_categorized should contain
+                                            *   any additional human readable information text that a typical user
+                                            *   may need to understand the functionality of the option, and will be
+                                            *   used when frontend has core option category support. This is provided
+                                            *   to accommodate the case where info text references an option by
+                                            *   name/desc, and the desc/desc_categorized text for that option differ.
+                                            *   If empty or NULL, the string specified by
+                                            *   retro_core_option_v2_definition::info will be used instead.
+                                            *   retro_core_option_v2_definition::info_categorized will be ignored
+                                            *   if retro_core_option_v2_definition::category_key is empty or NULL.
+                                            * - retro_core_option_v2_definition::category_key should contain a
+                                            *   category identifier (e.g. "video" or "audio") that will be
+                                            *   assigned to the core option if frontend has core option category
+                                            *   support. A categorized option will be shown in a subsection/
+                                            *   submenu of the frontend core option interface. If key is empty
+                                            *   or NULL, or if key does not match one of the
+                                            *   retro_core_option_v2_category::key values in the associated
+                                            *   retro_core_option_v2_category array, option will have no category
+                                            *   and will be shown at the top level of the frontend core option
+                                            *   interface.
+                                            * - retro_core_option_v2_definition::values is an array of
+                                            *   retro_core_option_value structs terminated by a { NULL, NULL }
+                                            *   element.
+                                            * --> retro_core_option_v2_definition::values[index].value is an
+                                            *     expected option value.
+                                            * --> retro_core_option_v2_definition::values[index].label is a
+                                            *     human readable label used when displaying the value on screen.
+                                            *     If NULL, the value itself is used.
+                                            * - retro_core_option_v2_definition::default_value is the default
+                                            *   core option setting. It must match one of the expected option
+                                            *   values in the retro_core_option_v2_definition::values array. If
+                                            *   it does not, or the default value is NULL, the first entry in the
+                                            *   retro_core_option_v2_definition::values array is treated as the
+                                            *   default.
+                                            *
+                                            * The number of possible option values should be very limited,
+                                            * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX.
+                                            * i.e. it should be feasible to cycle through options
+                                            * without a keyboard.
+                                            *
+                                            * Example entries:
+                                            *
+                                            * - Uncategorized:
+                                            *
+                                            * {
+                                            *     "foo_option",
+                                            *     "Speed hack coprocessor X",
+                                            *     NULL,
+                                            *     "Provides increased performance at the expense of reduced accuracy.",
+                                            *     NULL,
+                                            *     NULL,
+                                            * 	  {
+                                            *         { "false",    NULL },
+                                            *         { "true",     NULL },
+                                            *         { "unstable", "Turbo (Unstable)" },
+                                            *         { NULL, NULL },
+                                            *     },
+                                            *     "false"
+                                            * }
+                                            *
+                                            * - Categorized:
+                                            *
+                                            * {
+                                            *     "foo_option",
+                                            *     "Advanced > Speed hack coprocessor X",
+                                            *     "Speed hack coprocessor X",
+                                            *     "Setting 'Advanced > Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy",
+                                            *     "Setting 'Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy",
+                                            *     "advanced_settings",
+                                            * 	  {
+                                            *         { "false",    NULL },
+                                            *         { "true",     NULL },
+                                            *         { "unstable", "Turbo (Unstable)" },
+                                            *         { NULL, NULL },
+                                            *     },
+                                            *     "false"
+                                            * }
+                                            *
+                                            * Only strings are operated on. The possible values will
+                                            * generally be displayed and stored as-is by the frontend.
+                                            */
+
+#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL 68
+                                           /* const struct retro_core_options_v2_intl * --
+                                            * Allows an implementation to signal the environment
+                                            * which variables it might want to check for later using
+                                            * GET_VARIABLE.
+                                            * This allows the frontend to present these variables to
+                                            * a user dynamically.
+                                            * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION
+                                            * returns an API version of >= 2.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL.
+                                            * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2.
+                                            * This should be called the first time as early as
+                                            * possible (ideally in retro_set_environment).
+                                            * Afterwards it may be called again for the core to communicate
+                                            * updated options to the frontend, but the number of core
+                                            * options must not change from the number in the initial call.
+                                            * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API
+                                            * version of >= 2, this callback is guaranteed to succeed
+                                            * (i.e. callback return value does not indicate success)
+                                            * If callback returns true, frontend has core option category
+                                            * support.
+                                            * If callback returns false, frontend does not have core option
+                                            * category support.
+                                            *
+                                            * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2,
+                                            * with the addition of localisation support. The description of the
+                                            * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 callback should be consulted
+                                            * for further details.
+                                            *
+                                            * 'data' points to a retro_core_options_v2_intl struct.
+                                            *
+                                            * - retro_core_options_v2_intl::us is a pointer to a
+                                            *   retro_core_options_v2 struct defining the US English
+                                            *   core options implementation. It must point to a valid struct.
+                                            *
+                                            * - retro_core_options_v2_intl::local is a pointer to a
+                                            *   retro_core_options_v2 struct defining core options for
+                                            *   the current frontend language. It may be NULL (in which case
+                                            *   retro_core_options_v2_intl::us is used by the frontend). Any items
+                                            *   missing from this struct will be read from
+                                            *   retro_core_options_v2_intl::us instead.
+                                            *
+                                            * NOTE: Default core option values are always taken from the
+                                            * retro_core_options_v2_intl::us struct. Any default values in
+                                            * the retro_core_options_v2_intl::local struct will be ignored.
+                                            */
+
+#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK 69
+                                           /* const struct retro_core_options_update_display_callback * --
+                                            * Allows a frontend to signal that a core must update
+                                            * the visibility of any dynamically hidden core options,
+                                            * and enables the frontend to detect visibility changes.
+                                            * Used by the frontend to update the menu display status
+                                            * of core options without requiring a call of retro_run().
+                                            * Must be called in retro_set_environment().
+                                            */
+
+#define RETRO_ENVIRONMENT_SET_VARIABLE 70
+                                           /* const struct retro_variable * --
+                                            * Allows an implementation to notify the frontend
+                                            * that a core option value has changed.
+                                            *
+                                            * retro_variable::key and retro_variable::value
+                                            * must match strings that have been set previously
+                                            * via one of the following:
+                                            *
+                                            * - RETRO_ENVIRONMENT_SET_VARIABLES
+                                            * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS
+                                            * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL
+                                            * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2
+                                            * - RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL
+                                            *
+                                            * After changing a core option value via this
+                                            * callback, RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE
+                                            * will return true.
+                                            *
+                                            * If data is NULL, no changes will be registered
+                                            * and the callback will return true; an
+                                            * implementation may therefore pass NULL in order
+                                            * to test whether the callback is supported.
+                                            */
+
+#define RETRO_ENVIRONMENT_GET_THROTTLE_STATE (71 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+                                           /* struct retro_throttle_state * --
+                                            * Allows an implementation to get details on the actual rate
+                                            * the frontend is attempting to call retro_run().
+                                            */
+
 /* VFS functionality */
 
 /* File paths:
@@ -2777,6 +3156,213 @@ struct retro_system_info
    bool        block_extract;
 };
 
+/* Defines overrides which modify frontend handling of
+ * specific content file types.
+ * An array of retro_system_content_info_override is
+ * passed to RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE
+ * NOTE: In the following descriptions, references to
+ *       retro_load_game() may be replaced with
+ *       retro_load_game_special() */
+struct retro_system_content_info_override
+{
+   /* A list of file extensions for which the override
+    * should apply, delimited by a 'pipe' character
+    * (e.g. "md|sms|gg")
+    * Permitted file extensions are limited to those
+    * included in retro_system_info::valid_extensions
+    * and/or retro_subsystem_rom_info::valid_extensions */
+   const char *extensions;
+
+   /* Overrides the need_fullpath value set in
+    * retro_system_info and/or retro_subsystem_rom_info.
+    * To reiterate:
+    *
+    * If need_fullpath is true and retro_load_game() is called:
+    *    - retro_game_info::path is guaranteed to contain a valid
+    *      path to an existent file
+    *    - retro_game_info::data and retro_game_info::size are invalid
+    *
+    * If need_fullpath is false and retro_load_game() is called:
+    *    - retro_game_info::path may be NULL
+    *    - retro_game_info::data and retro_game_info::size are guaranteed
+    *      to be valid
+    *
+    * In addition:
+    *
+    * If need_fullpath is true and retro_load_game() is called:
+    *    - retro_game_info_ext::full_path is guaranteed to contain a valid
+    *      path to an existent file
+    *    - retro_game_info_ext::archive_path may be NULL
+    *    - retro_game_info_ext::archive_file may be NULL
+    *    - retro_game_info_ext::dir is guaranteed to contain a valid path
+    *      to the directory in which the content file exists
+    *    - retro_game_info_ext::name is guaranteed to contain the
+    *      basename of the content file, without extension
+    *    - retro_game_info_ext::ext is guaranteed to contain the
+    *      extension of the content file in lower case format
+    *    - retro_game_info_ext::data and retro_game_info_ext::size
+    *      are invalid
+    *
+    * If need_fullpath is false and retro_load_game() is called:
+    *    - If retro_game_info_ext::file_in_archive is false:
+    *       - retro_game_info_ext::full_path is guaranteed to contain
+    *         a valid path to an existent file
+    *       - retro_game_info_ext::archive_path may be NULL
+    *       - retro_game_info_ext::archive_file may be NULL
+    *       - retro_game_info_ext::dir is guaranteed to contain a
+    *         valid path to the directory in which the content file exists
+    *       - retro_game_info_ext::name is guaranteed to contain the
+    *         basename of the content file, without extension
+    *       - retro_game_info_ext::ext is guaranteed to contain the
+    *         extension of the content file in lower case format
+    *    - If retro_game_info_ext::file_in_archive is true:
+    *       - retro_game_info_ext::full_path may be NULL
+    *       - retro_game_info_ext::archive_path is guaranteed to
+    *         contain a valid path to an existent compressed file
+    *         inside which the content file is located
+    *       - retro_game_info_ext::archive_file is guaranteed to
+    *         contain a valid path to an existent content file
+    *         inside the compressed file referred to by
+    *         retro_game_info_ext::archive_path
+    *            e.g. for a compressed file '/path/to/foo.zip'
+    *            containing 'bar.sfc'
+    *             > retro_game_info_ext::archive_path will be '/path/to/foo.zip'
+    *             > retro_game_info_ext::archive_file will be 'bar.sfc'
+    *       - retro_game_info_ext::dir is guaranteed to contain a
+    *         valid path to the directory in which the compressed file
+    *         (containing the content file) exists
+    *       - retro_game_info_ext::name is guaranteed to contain
+    *         EITHER
+    *         1) the basename of the compressed file (containing
+    *            the content file), without extension
+    *         OR
+    *         2) the basename of the content file inside the
+    *            compressed file, without extension
+    *         In either case, a core should consider 'name' to
+    *         be the canonical name/ID of the the content file
+    *       - retro_game_info_ext::ext is guaranteed to contain the
+    *         extension of the content file inside the compressed file,
+    *         in lower case format
+    *    - retro_game_info_ext::data and retro_game_info_ext::size are
+    *      guaranteed to be valid */
+   bool need_fullpath;
+
+   /* If need_fullpath is false, specifies whether the content
+    * data buffer available in retro_load_game() is 'persistent'
+    *
+    * If persistent_data is false and retro_load_game() is called:
+    *    - retro_game_info::data and retro_game_info::size
+    *      are valid only until retro_load_game() returns
+    *    - retro_game_info_ext::data and retro_game_info_ext::size
+    *      are valid only until retro_load_game() returns
+    *
+    * If persistent_data is true and retro_load_game() is called:
+    *    - retro_game_info::data and retro_game_info::size
+    *      are valid until retro_deinit() returns
+    *    - retro_game_info_ext::data and retro_game_info_ext::size
+    *      are valid until retro_deinit() returns */
+   bool persistent_data;
+};
+
+/* Similar to retro_game_info, but provides extended
+ * information about the source content file and
+ * game memory buffer status.
+ * And array of retro_game_info_ext is returned by
+ * RETRO_ENVIRONMENT_GET_GAME_INFO_EXT
+ * NOTE: In the following descriptions, references to
+ *       retro_load_game() may be replaced with
+ *       retro_load_game_special() */
+struct retro_game_info_ext
+{
+   /* - If file_in_archive is false, contains a valid
+    *   path to an existent content file (UTF-8 encoded)
+    * - If file_in_archive is true, may be NULL */
+   const char *full_path;
+
+   /* - If file_in_archive is false, may be NULL
+    * - If file_in_archive is true, contains a valid path
+    *   to an existent compressed file inside which the
+    *   content file is located (UTF-8 encoded) */
+   const char *archive_path;
+
+   /* - If file_in_archive is false, may be NULL
+    * - If file_in_archive is true, contain a valid path
+    *   to an existent content file inside the compressed
+    *   file referred to by archive_path (UTF-8 encoded)
+    *      e.g. for a compressed file '/path/to/foo.zip'
+    *      containing 'bar.sfc'
+    *      > archive_path will be '/path/to/foo.zip'
+    *      > archive_file will be 'bar.sfc' */
+   const char *archive_file;
+
+   /* - If file_in_archive is false, contains a valid path
+    *   to the directory in which the content file exists
+    *   (UTF-8 encoded)
+    * - If file_in_archive is true, contains a valid path
+    *   to the directory in which the compressed file
+    *   (containing the content file) exists (UTF-8 encoded) */
+   const char *dir;
+
+   /* Contains the canonical name/ID of the content file
+    * (UTF-8 encoded). Intended for use when identifying
+    * 'complementary' content named after the loaded file -
+    * i.e. companion data of a different format (a CD image
+    * required by a ROM), texture packs, internally handled
+    * save files, etc.
+    * - If file_in_archive is false, contains the basename
+    *   of the content file, without extension
+    * - If file_in_archive is true, then string is
+    *   implementation specific. A frontend may choose to
+    *   set a name value of:
+    *   EITHER
+    *   1) the basename of the compressed file (containing
+    *      the content file), without extension
+    *   OR
+    *   2) the basename of the content file inside the
+    *      compressed file, without extension
+    *   RetroArch sets the 'name' value according to (1).
+    *   A frontend that supports routine loading of
+    *   content from archives containing multiple unrelated
+    *   content files may set the 'name' value according
+    *   to (2). */
+   const char *name;
+
+   /* - If file_in_archive is false, contains the extension
+    *   of the content file in lower case format
+    * - If file_in_archive is true, contains the extension
+    *   of the content file inside the compressed file,
+    *   in lower case format */
+   const char *ext;
+
+   /* String of implementation specific meta-data. */
+   const char *meta;
+
+   /* Memory buffer of loaded game content. Will be NULL:
+    * IF
+    * - retro_system_info::need_fullpath is true and
+    *   retro_system_content_info_override::need_fullpath
+    *   is unset
+    * OR
+    * - retro_system_content_info_override::need_fullpath
+    *   is true */
+   const void *data;
+
+   /* Size of game content memory buffer, in bytes */
+   size_t size;
+
+   /* True if loaded content file is inside a compressed
+    * archive */
+   bool file_in_archive;
+
+   /* - If data is NULL, value is unset/ignored
+    * - If data is non-NULL:
+    *   - If persistent_data is false, data and size are
+    *     valid only until retro_load_game() returns
+    *   - If persistent_data is true, data and size are
+    *     are valid until retro_deinit() returns */
+   bool persistent_data;
+};
+
 struct retro_game_geometry
 {
    unsigned base_width;    /* Nominal video width of game. */
@@ -2888,6 +3474,143 @@ struct retro_core_options_intl
    struct retro_core_option_definition *local;
 };
 
+struct retro_core_option_v2_category
+{
+   /* Variable uniquely identifying the
+    * option category. Valid key characters
+    * are [a-z, A-Z, 0-9, _, -] */
+   const char *key;
+
+   /* Human-readable category description
+    * > Used as category menu label when
+    *   frontend has core option category
+    *   support */
+   const char *desc;
+
+   /* Human-readable category information
+    * > Used as category menu sublabel when
+    *   frontend has core option category
+    *   support
+    * > Optional (may be NULL or an empty
+    *   string) */
+   const char *info;
+};
+
+struct retro_core_option_v2_definition
+{
+   /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE.
+    * Valid key characters are [a-z, A-Z, 0-9, _, -] */
+   const char *key;
+
+   /* Human-readable core option description
+    * > Used as menu label when frontend does
+    *   not have core option category support
+    *   e.g. "Video > Aspect Ratio" */
+   const char *desc;
+
+   /* Human-readable core option description
+    * > Used as menu label when frontend has
+    *   core option category support
+    *   e.g. "Aspect Ratio", where associated
+    *   retro_core_option_v2_category::desc
+    *   is "Video"
+    * > If empty or NULL, the string specified by
+    *   desc will be used as the menu label
+    * > Will be ignored (and may be set to NULL)
+    *   if category_key is empty or NULL */
+   const char *desc_categorized;
+
+   /* Human-readable core option information
+    * > Used as menu sublabel */
+   const char *info;
+
+   /* Human-readable core option information
+    * > Used as menu sublabel when frontend
+    *   has core option category support
+    *   (e.g. may be required when info text
+    *   references an option by name/desc,
+    *   and the desc/desc_categorized text
+    *   for that option differ)
+    * > If empty or NULL, the string specified by
+    *   info will be used as the menu sublabel
+    * > Will be ignored (and may be set to NULL)
+    *   if category_key is empty or NULL */
+   const char *info_categorized;
+
+   /* Variable specifying category (e.g. "video",
+    * "audio") that will be assigned to the option
+    * if frontend has core option category support.
+    * > Categorized options will be displayed in a
+    *   subsection/submenu of the frontend core
+    *   option interface
+    * > Specified string must match one of the
+    *   retro_core_option_v2_category::key values
+    *   in the associated retro_core_option_v2_category
+    *   array; If no match is not found, specified
+    *   string will be considered as NULL
+    * > If specified string is empty or NULL, option will
+    *   have no category and will be shown at the top
+    *   level of the frontend core option interface */
+   const char *category_key;
+
+   /* Array of retro_core_option_value structs, terminated by NULL */
+   struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX];
+
+   /* Default core option value. Must match one of the values
+    * in the retro_core_option_value array, otherwise will be
+    * ignored */
+   const char *default_value;
+};
+
+struct retro_core_options_v2
+{
+   /* Array of retro_core_option_v2_category structs,
+    * terminated by NULL
+    * > If NULL, all entries in definitions array
+    *   will have no category and will be shown at
+    *   the top level of the frontend core option
+    *   interface
+    * > Will be ignored if frontend does not have
+    *   core option category support */
+   struct retro_core_option_v2_category *categories;
+
+   /* Array of retro_core_option_v2_definition structs,
+    * terminated by NULL */
+   struct retro_core_option_v2_definition *definitions;
+};
+
+struct retro_core_options_v2_intl
+{
+   /* Pointer to a retro_core_options_v2 struct
+    * > US English implementation
+    * > Must point to a valid struct */
+   struct retro_core_options_v2 *us;
+
+   /* Pointer to a retro_core_options_v2 struct
+    * - Implementation for current frontend language
+    * - May be NULL */
+   struct retro_core_options_v2 *local;
+};
+
+/* Used by the frontend to monitor changes in core option
+ * visibility. May be called each time any core option
+ * value is set via the frontend.
+ * - On each invocation, the core must update the visibility
+ *   of any dynamically hidden options using the
+ *   RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY environment
+ *   callback.
+ * - On the first invocation, returns 'true' if the visibility
+ *   of any core option has changed since the last call of
+ *   retro_load_game() or retro_load_game_special().
+ * - On each subsequent invocation, returns 'true' if the
+ *   visibility of any core option has changed since the last
+ *   time the function was called. */
+typedef bool (RETRO_CALLCONV *retro_core_options_update_display_callback_t)(void);
+struct retro_core_options_update_display_callback
+{
+   retro_core_options_update_display_callback_t callback;
+};
+
 struct retro_game_info
 {
    const char *path;       /* Path to game, UTF-8 encoded.
@@ -2934,6 +3657,84 @@ struct retro_framebuffer
                                        Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */
 };
 
+/* Used by a libretro core to override the current
+ * fastforwarding mode of the frontend */
+struct retro_fastforwarding_override
+{
+   /* Specifies the runtime speed multiplier that
+    * will be applied when 'fastforward' is true.
+    * For example, a value of 5.0 when running 60 FPS
+    * content will cap the fast-forward rate at 300 FPS.
+    * Note that the target multiplier may not be achieved
+    * if the host hardware has insufficient processing
+    * power.
+    * Setting a value of 0.0 (or greater than 0.0 but
+    * less than 1.0) will result in an uncapped
+    * fast-forward rate (limited only by hardware
+    * capacity).
+    * If the value is negative, it will be ignored
+    * (i.e. the frontend will use a runtime speed
+    * multiplier of its own choosing) */
+   float ratio;
+
+   /* If true, fastforwarding mode will be enabled.
+    * If false, fastforwarding mode will be disabled. */
+   bool fastforward;
+
+   /* If true, and if supported by the frontend, an
+    * on-screen notification will be displayed while
+    * 'fastforward' is true.
+    * If false, and if supported by the frontend, any
+    * on-screen fast-forward notifications will be
+    * suppressed */
+   bool notification;
+
+   /* If true, the core will have sole control over
+    * when fastforwarding mode is enabled/disabled;
+    * the frontend will not be able to change the
+    * state set by 'fastforward' until either
+    * 'inhibit_toggle' is set to false, or the core
+    * is unloaded */
+   bool inhibit_toggle;
+};
+
+/* During normal operation. Rate will be equal to the core's internal FPS. */
+#define RETRO_THROTTLE_NONE              0
+
+/* While paused or stepping single frames. Rate will be 0. */
+#define RETRO_THROTTLE_FRAME_STEPPING    1
+
+/* During fast forwarding.
+ * Rate will be 0 if not specifically limited to a maximum speed. */
+#define RETRO_THROTTLE_FAST_FORWARD      2
+
+/* During slow motion. Rate will be less than the core's internal FPS. */
+#define RETRO_THROTTLE_SLOW_MOTION       3
+
+/* While rewinding recorded save states. Rate can vary depending on the rewind
+ * speed or be 0 if the frontend is not aiming for a specific rate. */
+#define RETRO_THROTTLE_REWINDING         4
+
+/* While vsync is active in the video driver and the target refresh rate is
+ * lower than the core's internal FPS. Rate is the target refresh rate. */
+#define RETRO_THROTTLE_VSYNC             5
+
+/* When the frontend does not throttle in any way. Rate will be 0.
+ * An example could be if no vsync or audio output is active. */
+#define RETRO_THROTTLE_UNBLOCKED         6
+
+struct retro_throttle_state
+{
+   /* The current throttling mode. Should be one of the values above. */
+   unsigned mode;
+
+   /* How many times per second the frontend aims to call retro_run.
+    * Depending on the mode, it can be 0 if there is no known fixed rate.
+    * This won't be accurate if the total processing time of the core and
+    * the frontend is longer than what is available for one frame. */
+   float rate;
+};
+
 /* Callbacks */
 
 /* Environment callback. Gives implementations a way of performing

+ 180 - 87
platform/libretro/libretro.c

@@ -40,7 +40,10 @@
 #include <malloc.h>
 #include "libretro-common/include/libretro_gskit_ps2.h"
 #include "ps2/asm.h"
+#else
+#include <platform/common/upscale.h>
 #endif
+#include <platform/common/emu.h>
 
 #ifdef _3DS
 #include "3ds/3ds_utils.h"
@@ -84,28 +87,26 @@ static retro_environment_t environ_cb;
 static retro_audio_sample_batch_t audio_batch_cb;
 
 #define VOUT_MAX_WIDTH 320
-#define VOUT_32COL_WIDTH 256
 #define VOUT_MAX_HEIGHT 240
 
 #define INITIAL_SND_RATE 44100
 
 static const float VOUT_PAR = 0.0;
-static const float VOUT_4_3 = (224.0f * (4.0f / 3.0f));
-static const float VOUT_CRT = (224.0f * 1.29911f);
-
-static bool show_overscan = false;
-static bool old_show_overscan = false;
+static const float VOUT_4_3 = (4.0f / 3.0f);
+static const float VOUT_CRT = (1.29911f);
 
-/* Required to allow on the fly changes to 'show overscan' */
+/* Required to allow on the fly changes to 'renderer' */
 static int vm_current_start_line = -1;
 static int vm_current_line_count = -1;
-static int vm_current_is_32cols = -1;
+static int vm_current_start_col = -1;
+static int vm_current_col_count = -1;
 
 static int vout_16bit = 1;
 static int vout_format = PDF_RGB555;
-static void *vout_buf;
+static void *vout_buf, *vout_ghosting_buf;
 static int vout_width, vout_height, vout_offset;
-static float user_vout_width = 0.0;
+static float vout_aspect = 0.0;
+static int vout_ghosting = 0;
 
 #if defined(RENDER_GSKIT_PS2)
 #define VOUT_8BIT_WIDTH 328
@@ -572,8 +573,12 @@ void plat_munmap(void *ptr, size_t size)
 void *plat_mem_get_for_drc(size_t size)
 {
    void *mem = NULL;
-#ifdef VITA
+#if defined VITA
    sceKernelGetMemBlockBase(sceBlock, &mem);
+#elif defined HW_WUP
+   // For WiiU, a slice of RWX memory left from the exploit is used, see:
+   // https://github.com/embercold/pcsx_rearmed/commit/af0453223
+   mem = (void *)(0x01000000 - size);
 #endif
    return mem;
 }
@@ -615,29 +620,33 @@ int plat_mem_set_exec(void *ptr, size_t size)
    return ret;
 }
 
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)
+static void apply_renderer()
+{
+   PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);
+   PicoIn.opt |= POPT_DIS_32C_BORDER;
+   if (vout_format == PDF_NONE)
+      PicoIn.opt |= POPT_ALT_RENDERER;
+   PicoDrawSetOutFormat(vout_format, 0);
+   if (!vout_16bit && vout_format == PDF_8BIT)
+      PicoDrawSetOutBuf(Pico.est.Draw2FB, 328);
+}
+
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
 {
    struct retro_system_av_info av_info;
 
    vm_current_start_line = start_line;
    vm_current_line_count = line_count;
-   vm_current_is_32cols = is_32cols;
+   vm_current_start_col = start_col;
+   vm_current_col_count = col_count;
 
    // 8bit renderes create a 328x256 CLUT image, while 16bit creates 320x240 RGB
-   vout_16bit = vout_format == PDF_RGB555 || (PicoIn.AHW & PAHW_32X);
-
 #if defined(RENDER_GSKIT_PS2)
    // calculate the borders of the real image inside the picodrive image
    vout_width = (vout_16bit ? VOUT_MAX_WIDTH : VOUT_8BIT_WIDTH);
    vout_height = (vout_16bit ? VOUT_MAX_HEIGHT : VOUT_8BIT_HEIGHT);
    vout_offset = (vout_16bit ? 0 : 8); // 8bit has 8 px overlap area on the left
-   if (is_32cols) {
-      // 256x240, with or w/o overlap on the left and 64 px on the right
-      padding = (struct retro_hw_ps2_insets){start_line, vout_offset, vout_height - line_count - start_line, vout_width - 256.0f - vout_offset};
-   } else {
-      // 320x240, with or w/o overlap on the left and none on the right
-      padding = (struct retro_hw_ps2_insets){start_line, vout_offset, vout_height - line_count - start_line, vout_width - 320.0f - vout_offset};
-   }
+   padding = (struct retro_hw_ps2_insets){start_line, vout_offset, vout_height - line_count - start_line, vout_width - col_count - vout_offset};
 
    int pxsz = (vout_16bit ? 2 : 1); // pixel size: RGB = 16 bits, CLUT = 8 bits
    memset(vout_buf, 0, pxsz * vout_width * vout_height);
@@ -651,28 +660,26 @@ void emu_video_mode_change(int start_line, int line_count, int is_32cols)
       ps2->padding = padding;
    }
 #else
-   vout_width = is_32cols ? VOUT_32COL_WIDTH : VOUT_MAX_WIDTH;
+   vout_width = col_count;
    memset(vout_buf, 0, VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2);  
    if (vout_16bit)
       PicoDrawSetOutBuf(vout_buf, vout_width * 2);
 
-   if (show_overscan)
-   {
-      vout_height = line_count + (start_line * 2);
-      vout_offset = 0;
-   }
-   else
-   {
-      vout_height = line_count;
-      /* Note: We multiply by 2 here to account for pitch */
-      vout_offset = vout_width * start_line * 2;
-   }
+   vout_height = line_count;
+   /* Note: We multiply by 2 here to account for pitch */
+   vout_offset = vout_width * start_line * 2;
 
    /* Redundant sanity check... */
    vout_height = (vout_height > VOUT_MAX_HEIGHT) ?
          VOUT_MAX_HEIGHT : vout_height;
    vout_offset = (vout_offset > vout_width * (VOUT_MAX_HEIGHT - 1) * 2) ?
          vout_width * (VOUT_MAX_HEIGHT - 1) * 2 : vout_offset;
+
+   /* LCD ghosting */
+   if (vout_ghosting && vout_height == 144) {
+      vout_ghosting_buf = realloc(vout_ghosting_buf, VOUT_MAX_HEIGHT*vout_width*2);
+      memset(vout_ghosting_buf, 0, vout_width*vout_height*2);
+   }
 #endif
    Pico.m.dirtyPal = 1;
 
@@ -683,14 +690,17 @@ void emu_video_mode_change(int start_line, int line_count, int is_32cols)
 
 void emu_32x_startup(void)
 {
+   PicoIn.filter = EOPT_FILTER_SMOOTHER; // for H32 upscaling
    PicoDrawSetOutFormat(vout_format, 0);
+   vout_16bit = 1;
+
    if ((vm_current_start_line != -1) &&
        (vm_current_line_count != -1) &&
-       (vm_current_is_32cols != -1))
+       (vm_current_start_col != -1) &&
+       (vm_current_col_count != -1))
       emu_video_mode_change(
-            vm_current_start_line,
-            vm_current_line_count,
-            vm_current_is_32cols);
+            vm_current_start_line, vm_current_line_count,
+            vm_current_start_col, vm_current_col_count);
 }
 
 void lprintf(const char *fmt, ...)
@@ -710,13 +720,15 @@ bool libretro_supports_bitmasks = false;
 
 void retro_set_environment(retro_environment_t cb)
 {
+   bool option_categories_supported;
 #ifdef USE_LIBRETRO_VFS
    struct retro_vfs_interface_info vfs_iface_info;
 #endif
 
    environ_cb = cb;
 
-   libretro_set_core_options(environ_cb);
+   libretro_set_core_options(environ_cb,
+         &option_categories_supported);
 
 #ifdef USE_LIBRETRO_VFS
    vfs_iface_info.required_interface_version = 1;
@@ -751,12 +763,13 @@ void retro_get_system_info(struct retro_system_info *info)
 #define _GIT_VERSION "-" GIT_VERSION
 #endif
    info->library_version = VERSION _GIT_VERSION;
-   info->valid_extensions = "bin|gen|smd|md|32x|cue|iso|chd|sms";
+   info->valid_extensions = "bin|gen|smd|md|32x|cue|iso|chd|sms|gg";
    info->need_fullpath = true;
 }
 
 void retro_get_system_av_info(struct retro_system_av_info *info)
 {
+   float tv_height = (vout_height > 144 ? Pico.m.pal ? 240 : 224 : 144);
    float common_width;
 
    memset(info, 0, sizeof(*info));
@@ -768,8 +781,8 @@ void retro_get_system_av_info(struct retro_system_av_info *info)
    info->geometry.max_height   = vout_height;
 
    common_width = vout_width;
-   if (user_vout_width != 0)
-      common_width = user_vout_width;
+   if (vout_aspect != 0)
+      common_width = vout_aspect * tv_height;
 
    info->geometry.aspect_ratio = common_width / vout_height;
 }
@@ -904,7 +917,7 @@ typedef struct patch
 } patch;
 
 extern void decode(char *buff, patch *dest);
-extern uint16_t m68k_read16(uint32_t a);
+extern uint32_t m68k_read16(uint32_t a);
 extern void m68k_write16(uint32_t a, uint16_t d);
 
 void retro_cheat_reset(void)
@@ -1302,7 +1315,7 @@ bool retro_load_game(const struct retro_game_info *info)
    PicoIn.sndOut = sndBuffer;
    PsndRerate(0);
 
-   PicoDrawSetOutFormat(vout_format, 0);
+   apply_renderer();
 
    /* Setup retro memory maps */
    set_memory_maps();
@@ -1398,18 +1411,18 @@ void retro_reset(void)
 }
 
 static const unsigned short retro_pico_map[] = {
-   1 << GBTN_B,
-   1 << GBTN_A,
-   1 << GBTN_MODE,
-   1 << GBTN_START,
-   1 << GBTN_UP,
-   1 << GBTN_DOWN,
-   1 << GBTN_LEFT,
-   1 << GBTN_RIGHT,
-   1 << GBTN_C,
-   1 << GBTN_Y,
-   1 << GBTN_X,
-   1 << GBTN_Z,
+   [RETRO_DEVICE_ID_JOYPAD_B]      = 1 << GBTN_B,
+   [RETRO_DEVICE_ID_JOYPAD_Y]      = 1 << GBTN_A,
+   [RETRO_DEVICE_ID_JOYPAD_SELECT] = 1 << GBTN_MODE,
+   [RETRO_DEVICE_ID_JOYPAD_START]  = 1 << GBTN_START,
+   [RETRO_DEVICE_ID_JOYPAD_UP]     = 1 << GBTN_UP,
+   [RETRO_DEVICE_ID_JOYPAD_DOWN]   = 1 << GBTN_DOWN,
+   [RETRO_DEVICE_ID_JOYPAD_LEFT]   = 1 << GBTN_LEFT,
+   [RETRO_DEVICE_ID_JOYPAD_RIGHT]  = 1 << GBTN_RIGHT,
+   [RETRO_DEVICE_ID_JOYPAD_A]      = 1 << GBTN_C,
+   [RETRO_DEVICE_ID_JOYPAD_X]      = 1 << GBTN_Y,
+   [RETRO_DEVICE_ID_JOYPAD_L]      = 1 << GBTN_X,
+   [RETRO_DEVICE_ID_JOYPAD_R]      = 1 << GBTN_Z,
 };
 #define RETRO_PICO_MAP_LEN (sizeof(retro_pico_map) / sizeof(retro_pico_map[0]))
 
@@ -1436,7 +1449,7 @@ static void update_variables(bool first_run)
 {
    struct retro_variable var;
    int OldPicoRegionOverride;
-   float old_user_vout_width;
+   float old_vout_aspect;
    unsigned old_frameskip_type;
    int old_vout_format;
    double new_sound_rate;
@@ -1454,21 +1467,66 @@ static void update_variables(bool first_run)
       PicoSetInputDevice(1, input_name_to_val(var.value));
 
    var.value = NULL;
-   var.key = "picodrive_sprlim";
+   var.key = "picodrive_ramcart";
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
       if (strcmp(var.value, "enabled") == 0)
-         PicoIn.opt |= POPT_DIS_SPRITE_LIM;
+         PicoIn.opt |= POPT_EN_MCD_RAMCART;
       else
-         PicoIn.opt &= ~POPT_DIS_SPRITE_LIM;
+         PicoIn.opt &= ~POPT_EN_MCD_RAMCART;
    }
 
    var.value = NULL;
-   var.key = "picodrive_ramcart";
+   var.key = "picodrive_smstype";
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
-      if (strcmp(var.value, "enabled") == 0)
-         PicoIn.opt |= POPT_EN_MCD_RAMCART;
+      if (strcmp(var.value, "Auto") == 0)
+         PicoIn.hwSelect = PHWS_AUTO;
+      else if (strcmp(var.value, "Game Gear") == 0)
+         PicoIn.hwSelect = PHWS_GG;
       else
-         PicoIn.opt &= ~POPT_EN_MCD_RAMCART;
+         PicoIn.hwSelect = PHWS_SMS;
+   }
+
+   var.value = NULL;
+   var.key = "picodrive_smsfm";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
+      if (strcmp(var.value, "on") == 0)
+         PicoIn.opt |= POPT_EN_YM2413;
+      else
+         PicoIn.opt &= ~POPT_EN_YM2413;
+   }
+
+   var.value = NULL;
+   var.key = "picodrive_smsmapper";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
+      if (strcmp(var.value, "Auto") == 0)
+         PicoIn.mapper = PMS_MAP_AUTO;
+      else if (strcmp(var.value, "Codemasters") == 0)
+         PicoIn.mapper = PMS_MAP_CODEM;
+      else if (strcmp(var.value, "Korea") == 0)
+         PicoIn.mapper = PMS_MAP_KOREA;
+      else if (strcmp(var.value, "Korea MSX") == 0)
+         PicoIn.mapper = PMS_MAP_MSX;
+      else if (strcmp(var.value, "Korea X-in-1") == 0)
+         PicoIn.mapper = PMS_MAP_N32K;
+      else if (strcmp(var.value, "Korea 4-Pak") == 0)
+         PicoIn.mapper = PMS_MAP_N16K;
+      else if (strcmp(var.value, "Korea Janggun") == 0)
+         PicoIn.mapper = PMS_MAP_JANGGUN;
+      else if (strcmp(var.value, "Korea Nemesis") == 0)
+         PicoIn.mapper = PMS_MAP_NEMESIS;
+      else
+         PicoIn.mapper = PMS_MAP_SEGA;
+   }
+
+   var.value = NULL;
+   var.key = "picodrive_ggghost";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
+      if (strcmp(var.value, "normal") == 0)
+         vout_ghosting = 2;
+      else if (strcmp(var.value, "weak") == 0)
+         vout_ghosting = 1;
+      else
+         vout_ghosting = 0;
    }
 
    OldPicoRegionOverride = PicoIn.regionOverride;
@@ -1495,19 +1553,19 @@ static void update_variables(bool first_run)
       PsndRerate(1);
    }
 
-   old_user_vout_width = user_vout_width;
+   old_vout_aspect = vout_aspect;
    var.value = NULL;
    var.key = "picodrive_aspect";
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
       if (strcmp(var.value, "4/3") == 0)
-         user_vout_width = VOUT_4_3;
+         vout_aspect = VOUT_4_3;
       else if (strcmp(var.value, "CRT") == 0)
-         user_vout_width = VOUT_CRT;
+         vout_aspect = VOUT_CRT;
       else
-         user_vout_width = VOUT_PAR;
+         vout_aspect = VOUT_PAR;
    }
 
-   if (user_vout_width != old_user_vout_width)
+   if (vout_aspect != old_vout_aspect)
    {
       // Update the geometry
       struct retro_system_av_info av_info;
@@ -1515,13 +1573,13 @@ static void update_variables(bool first_run)
       environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &av_info);
    }
 
-   old_show_overscan = show_overscan;
    var.value = NULL;
-   var.key = "picodrive_overscan";
-   show_overscan = false;
+   var.key = "picodrive_sprlim";
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
       if (strcmp(var.value, "enabled") == 0)
-         show_overscan = true;
+         PicoIn.opt |= POPT_DIS_SPRITE_LIM;
+      else
+         PicoIn.opt &= ~POPT_DIS_SPRITE_LIM;
    }
 
    var.value = NULL;
@@ -1547,6 +1605,15 @@ static void update_variables(bool first_run)
       PicoIn.opt &= ~POPT_EN_DRC;
 #endif
 
+   var.value = NULL;
+   var.key = "picodrive_dacnoise";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
+      if (strcmp(var.value, "enabled") == 0)
+         PicoIn.opt |= POPT_EN_FM_DAC;
+      else
+         PicoIn.opt &= ~POPT_EN_FM_DAC;
+   }
+
    old_snd_filter = PicoIn.opt & POPT_EN_SNDFILTER;
    var.value = NULL;
    var.key = "picodrive_audio_filter";
@@ -1595,11 +1662,9 @@ static void update_variables(bool first_run)
          vout_format = PDF_8BIT;
       else if (strcmp(var.value, "accurate") == 0)
          vout_format = PDF_RGB555;
+      vout_16bit = vout_format == PDF_RGB555 || (PicoIn.AHW & PAHW_32X);
 
-      PicoIn.opt &= ~POPT_ALT_RENDERER;
-      if (vout_format == PDF_NONE)
-         PicoIn.opt |= POPT_ALT_RENDERER;
-      PicoDrawSetOutFormat(vout_format, 0);
+      apply_renderer();
    }
 
    var.value = NULL;
@@ -1617,15 +1682,15 @@ static void update_variables(bool first_run)
    }
 
    /* setup video if required */
-   if (show_overscan != old_show_overscan || vout_format != old_vout_format)
+   if (vout_format != old_vout_format)
    {
       if ((vm_current_start_line != -1) &&
           (vm_current_line_count != -1) &&
-          (vm_current_is_32cols != -1))
+          (vm_current_start_col != -1) &&
+          (vm_current_col_count != -1))
          emu_video_mode_change(
-               vm_current_start_line,
-               vm_current_line_count,
-               vm_current_is_32cols);
+               vm_current_start_line, vm_current_line_count,
+               vm_current_start_col, vm_current_col_count);
    }
 
    /* Reinitialise frameskipping, if required */
@@ -1755,15 +1820,15 @@ void retro_run(void)
        * with the fast renderer this is improving performance, at the expense of accuracy.
        */
       /* This section is mostly copied from pemu_finalize_frame in platform/linux/emu.c */
-      unsigned short *pd = (unsigned short *)vout_buf;
+      unsigned short *pd = (unsigned short *)((char *)vout_buf + vout_offset);
       /* Skip the leftmost 8 columns (it is used as an overlap area for rendering) */
-      unsigned char *ps = Pico.est.Draw2FB + 8;
+      unsigned char *ps = Pico.est.Draw2FB + vm_current_start_line * 328 + 8;
       unsigned short *pal = Pico.est.HighPal;
       int x;
       if (Pico.m.dirtyPal)
          PicoDrawUpdateHighPal();
-      /* Copy up to the max height to include the overscan area, and skip the leftmost 8 columns again */
-      for (i = 0; i < VOUT_MAX_HEIGHT; i++, ps += 8) {
+      /* Copy, and skip the leftmost 8 columns again */
+      for (i = 0; i < vout_height; i++, ps += 8) {
          for (x = 0; x < vout_width; x+=4) {
             *pd++ = pal[*ps++];
             *pd++ = pal[*ps++];
@@ -1774,6 +1839,20 @@ void retro_run(void)
       }
    }
 
+   if (vout_ghosting && vout_height == 144) {
+      unsigned short *pd = (unsigned short *)vout_buf;
+      unsigned short *ps = (unsigned short *)vout_ghosting_buf;
+      int y;
+      for (y = 0; y < vout_height; y++) {
+         if (vout_ghosting == 1)
+            v_blend(pd, ps, vout_width, p_075_round);
+         else
+            v_blend(pd, ps, vout_width, p_05_round);
+         pd += vout_width;
+         ps += vout_width;
+      }
+   }
+
    buff = (char*)vout_buf + vout_offset;
 #endif
 
@@ -1806,7 +1885,8 @@ void retro_init(void)
    sceBlock = getVMBlock();
 #endif
 
-   PicoIn.opt = POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80|POPT_EN_YM2413
+   PicoIn.opt = POPT_EN_STEREO|POPT_EN_FM
+      | POPT_EN_PSG|POPT_EN_Z80|POPT_EN_GG_LCD
       | POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_MCD_GFX
       | POPT_EN_32X|POPT_EN_PWM
       | POPT_ACC_SPRITES|POPT_DIS_32C_BORDER;
@@ -1856,6 +1936,8 @@ void retro_init(void)
 
 void retro_deinit(void)
 {
+   size_t i;
+
 #ifdef _3DS
    linearFree(vout_buf);
 #elif defined(RENDER_GSKIT_PS2)
@@ -1866,7 +1948,18 @@ void retro_deinit(void)
    free(vout_buf);
 #endif
    vout_buf = NULL;
+   if (vout_ghosting_buf)
+      free(vout_ghosting_buf);
+   vout_ghosting_buf = NULL;
+
    PicoExit();
 
+   for (i = 0; i < sizeof(disks) / sizeof(disks[0]); i++) {
+      if (disks[i].fname != NULL) {
+         free(disks[i].fname);
+         disks[i].fname = NULL;
+      }
+   }
+
    libretro_supports_bitmasks = false;
 }

+ 454 - 171
platform/libretro/libretro_core_options.h

@@ -13,9 +13,10 @@
 
 /*
  ********************************
- * VERSION: 1.3
+ * VERSION: 2.0
  ********************************
  *
+ * - 2.0: Add support for core options v2 interface
  * - 1.3: Move translations to libretro_core_options_intl.h
  *        - libretro_core_options_intl.h includes BOM and utf-8
  *          fix for MSVC 2010-2013
@@ -48,46 +49,106 @@ extern "C" {
  * - Will be used as a fallback for any missing entries in
  *   frontend language definition */
 
-struct retro_core_option_definition option_defs_us[] = {
+
+struct retro_core_option_v2_category option_cats_us[] = {
    {
-      "picodrive_input1",
-      "Input device 1",
-      "Choose which kind of controller is plugged in slot 1.",
+      "system",
+      "System",
+      "Configure region / base hardware selection / ROM mapping / Sega CD RAM cart."
+   },
+   {
+      "video",
+      "Video",
+      "Configure aspect ratio / LCD filter / video renderer."
+   },
+   {
+      "audio",
+      "Audio",
+      "Configure sample rate / emulated audio devices / low pass filter / DAC noise."
+   },
+   {
+      "input",
+      "Input",
+      "Configure input devices."
+   },
+   {
+      "performance",
+      "Performance",
+#ifdef DRC_SH2
+      "Configure dynamic recompiler / frameskipping."
+#else
+      "Configure frameskipping parameters."
+#endif
+   },
+   {
+      "hacks",
+      "Emulation Hacks",
+      "Configure sprite limit removal / processor overclocking."
+   },
+   { NULL, NULL, NULL },
+};
+
+struct retro_core_option_v2_definition option_defs_us[] = {
+   {
+      "picodrive_region",
+      "System Region",
+      "Region",
+      "Specify which region the system is from. 'PAL'/'Europe' is 50hz while 'NTSC'/'US' is 60hz. Games may run faster or slower than normal (or produce errors) if the incorrect region is selected.",
+      NULL,
+      "system",
       {
-         { "3 button pad", "3 Button Pad" },
-         { "6 button pad", "6 Button Pad" },
-         { "None", NULL },
+         { "Auto",       NULL },
+         { "Japan NTSC", NULL },
+         { "Japan PAL",  NULL },
+         { "US",         NULL },
+         { "Europe",     NULL },
          { NULL, NULL },
       },
-      "3 button pad"
+      "Auto"
    },
    {
-      "picodrive_input2",
-      "Input device 2",
-      "Choose which kind of controller is plugged in slot 2.",
+      "picodrive_smstype",
+      "Master System Type",
+      NULL,
+      "Choose which type of hardware (SMS or Game Gear) the core should emulate when running Master System content.",
+      NULL,
+      "system",
       {
-         { "3 button pad", NULL },
-         { "6 button pad", NULL },
-         { "None", NULL },
+         { "Auto",          NULL },
+         { "Game Gear",     NULL },
+         { "Master System", NULL },
          { NULL, NULL },
-      },
-      "3 button pad"
+       },
+      "Auto"
    },
    {
-      "picodrive_sprlim",
-      "No sprite limit",
-      "Enable this to remove the sprite limit.",
+      "picodrive_smsmapper",
+      "Master System ROM Mapping",
+      NULL,
+      "Choose which ROM mapper the core should apply. 'Auto' will work for a wide range of content, but in some cases automatic detection fails.",
+      NULL,
+      "system",
       {
-         { "disabled", NULL },
-         { "enabled",  NULL },
+         { "Auto",          NULL },
+         { "Sega",          NULL },
+         { "Codemasters",   NULL },
+         { "Korea",         NULL },
+         { "Korea MSX",     NULL },
+         { "Korea X-in-1",  NULL },
+         { "Korea 4-Pak",   NULL },
+         { "Korea Janggun", NULL },
+         { "Korea Nemesis", NULL },
          { NULL, NULL },
-      },
-      "disabled"
+       },
+      "Auto"
    },
    {
       "picodrive_ramcart",
-      "MegaCD RAM cart",
-      "Emulate a MegaCD RAM cart, used for save game data. WARNING: When enabled, internal save data (BRAM) will be discarded.",
+      "Sega CD RAM Cart",
+      NULL,
+      "Emulate a Sega CD RAM cart, used for save game data. WARNING: When enabled, internal save data (BRAM) will be discarded.",
+      NULL,
+      "system",
       {
          { "disabled", NULL },
          { "enabled",  NULL },
@@ -96,75 +157,101 @@ struct retro_core_option_definition option_defs_us[] = {
       "disabled"
    },
    {
-      "picodrive_region",
-      "Region",
-      "Force a specific region.",
+      "picodrive_aspect",
+      "Core-Provided Aspect Ratio",
+      NULL,
+      "Choose the preferred content aspect ratio. This will only apply when RetroArch's aspect ratio is set to 'Core Provided' in the Video settings.",
+      NULL,
+      "video",
       {
-         { "Auto",       NULL },
-         { "Japan NTSC", NULL },
-         { "Japan PAL",  NULL },
-         { "US",         NULL },
-         { "Europe",     NULL },
+         { "PAR", NULL },
+         { "4/3", "4:3" },
+         { "CRT", NULL },
          { NULL, NULL },
       },
-      "Auto"
+      "PAR"
    },
    {
-      "picodrive_aspect",
-      "Core-provided aspect ratio",
-      "Choose the core-provided aspect ratio. RetroArch's aspect ratio must be set to Core provided in the Video settings.",
+      "picodrive_ggghost",
+      "LCD Ghosting Filter",
+      NULL,
+      "Apply an image 'ghosting' filter to mimic the display characteristics of the Game Gear and 'Genesis Nomad' LCD panels.",
+      NULL,
+      "video",
       {
-         { "PAR", NULL },
-         { "4/3", NULL },
-         { "CRT", NULL },
+         { "off",    "disabled" },
+         { "weak",   "Weak" },
+         { "normal", "Normal" },
          { NULL, NULL },
       },
-      "PAR"
+      "off"
    },
    {
-      "picodrive_overscan",
-      "Show Overscan",
-      "Crop out the potentially random glitchy video output that would have been hidden by the bezel around the edge of a standard-definition television screen.",
+      "picodrive_renderer",
+      "Video Renderer",
+      "Renderer",
+      "Specify video rendering method. 'Accurate' is slowest, but most exact. 'Good' is faster, but fails if colors change mid-frame too often. 'Fast' renders the complete frame in one pass, and is incompatible with games that rely on mid-frame palette/sprite table updates.",
+      NULL,
+      "video",
       {
-         { "disabled", NULL },
-         { "enabled",  NULL },
+         { "accurate", "Accurate" },
+         { "good",     "Good" },
+         { "fast",     "Fast" },
          { NULL, NULL },
       },
-      "disabled"
+      "accurate"
    },
    {
-      "picodrive_overclk68k",
-      "68K Overclock",
-      "Overclock the emulated 68K chip.",
+      "picodrive_sound_rate",
+      "Audio Sample Rate (Hz)",
+      "Sample Rate (Hz)",
+      "Higher values increase sound quality. Lower values may increase performance.",
+      NULL,
+      "audio",
       {
-         { "disabled", NULL },
-         { "+25%",     NULL },
-         { "+50%",     NULL },
-         { "+75%",     NULL },
-         { "+100%",    NULL },
-         { "+200%",    NULL },
-         { "+400%",    NULL },
+         { "16000", NULL },
+         { "22050", NULL },
+         { "32000", NULL },
+         { "44100", NULL },
          { NULL, NULL },
       },
-      "disabled"
+      "44100"
    },
-#ifdef DRC_SH2
    {
-      "picodrive_drc",
-      "Dynamic recompilers",
-      "Enable dynamic recompilers which help to improve performance. Less accurate than interpreter CPU cores, but much faster.",
+      "picodrive_smsfm",
+      "Master System FM Sound Unit",
+      NULL,
+      "Enable emulation of the SMS FM Sound Unit. This produces higher quality audio in some games, but non-Japanese titles may fail to run.",
+      NULL,
+      "audio",
       {
-         { "enabled",  NULL },
-         { "disabled", NULL },
+         { "off", "disabled" },
+         { "on",  "enabled" },
          { NULL, NULL },
       },
-      "enabled"
+      "off"
+   },
+   {
+      "picodrive_dacnoise",
+      "Mega Drive FM DAC noise",
+      NULL,
+      "Enable emulation of YM2612 DAC noise. This option generates a distortion which existed on most Model 1 Mega Drive/Genesis, but not on newer models.",
+      NULL,
+      "audio",
+      {
+         { "off", "disabled" },
+         { "on",  "enabled" },
+         { NULL, NULL },
+      },
+      "off"
    },
-#endif
    {
       "picodrive_audio_filter",
-      "Audio filter",
-      "Enable a low pass audio filter to better simulate the characteristic sound of a Model 1 Genesis. This option is ignored when running Master System and PICO titles. Only the Genesis and its add-on hardware (Sega CD, 32X) employed a physical low pass filter.",
+      "Audio Filter",
+      NULL,
+      "Enable a low pass audio filter to better simulate the characteristic sound of a Model 1 Mega Drive/Genesis. Note that although only the Genesis and its add-on hardware (Sega CD, 32X) employed a physical low pass filter, the filter setting is not restricted to that.",
+      NULL,
+      "audio",
       {
          { "disabled", NULL },
          { "low-pass", "Low-Pass" },
@@ -174,8 +261,11 @@ struct retro_core_option_definition option_defs_us[] = {
    },
    {
       "picodrive_lowpass_range",
-      "Low-pass filter %",
-      "Specify the cut-off frequency of the audio low pass filter. A higher value increases the perceived 'strength' of the filter, since a wider range of the high frequency spectrum is attenuated.",
+      "Low-Pass Filter %",
+      NULL,
+      "Specify the cut-off frequency of the low pass audio filter. A higher value increases the perceived 'strength' of the filter, since a wider range of the high frequency spectrum is attenuated.",
+      NULL,
+      "audio",
       {
          { "5",  NULL },
          { "10", NULL },
@@ -200,10 +290,59 @@ struct retro_core_option_definition option_defs_us[] = {
       },
       "60"
    },
+   {
+      "picodrive_input1",
+      "Input Device 1",
+      NULL,
+      "Choose which type of controller is plugged into slot 1.",
+      NULL,
+      "input",
+      {
+         { "3 button pad", "3 Button Pad" },
+         { "6 button pad", "6 Button Pad" },
+         { "None", NULL },
+         { NULL, NULL },
+      },
+      "3 button pad"
+   },
+   {
+      "picodrive_input2",
+      "Input Device 2",
+      NULL,
+      "Choose which type of controller is plugged into slot 2.",
+      NULL,
+      "input",
+      {
+         { "3 button pad", "3 Button Pad" },
+         { "6 button pad", "6 Button Pad" },
+         { "None", NULL },
+         { NULL, NULL },
+      },
+      "3 button pad"
+   },
+#ifdef DRC_SH2
+   {
+      "picodrive_drc",
+      "Dynamic Recompilers",
+      NULL,
+      "Enable dynamic recompilers which help to improve performance. Less accurate than interpreter CPU cores, but significantly faster.",
+      NULL,
+      "performance",
+      {
+         { "enabled",  NULL },
+         { "disabled", NULL },
+         { NULL, NULL },
+      },
+      "enabled"
+   },
+#endif
    {
       "picodrive_frameskip",
       "Frameskip",
+      NULL,
       "Skip frames to avoid audio buffer under-run (crackling). Improves performance at the expense of visual smoothness. 'Auto' skips frames when advised by the frontend. 'Manual' utilises the 'Frameskip Threshold (%)' setting.",
+      NULL,
+      "performance",
       {
          { "disabled", NULL },
          { "auto",     "Auto" },
@@ -215,7 +354,10 @@ struct retro_core_option_definition option_defs_us[] = {
    {
       "picodrive_frameskip_threshold",
       "Frameskip Threshold (%)",
+      NULL,
       "When 'Frameskip' is set to 'Manual', specifies the audio buffer occupancy threshold (percentage) below which frames will be skipped. Higher values reduce the risk of crackling by causing frames to be dropped more frequently.",
+      NULL,
+      "performance",
       {
          { "15", NULL },
          { "18", NULL },
@@ -238,31 +380,44 @@ struct retro_core_option_definition option_defs_us[] = {
       "33"
    },
    {
-      "picodrive_renderer",
-      "Renderer",
-      "Fast renderer can't render any mid-frame image changes so it is useful only for some games.",
+      "picodrive_sprlim",
+      "No Sprite Limit",
+      NULL,
+      "Removes the original sprite-per-scanline hardware limit. This reduces flickering but can cause visual glitches, as some games exploit the hardware limit to generate special effects.",
+      NULL,
+      "hacks",
       {
-         { "accurate", "Accurate" },
-         { "good",     "Good" },
-         { "fast",     "Fast" },
+         { "disabled", NULL },
+         { "enabled",  NULL },
          { NULL, NULL },
       },
-      "accurate"
+      "disabled"
    },
    {
-      "picodrive_sound_rate",
-      "Sound quality",
-      "Sound quality (in Hz). A lower value may increase performance.",
+      "picodrive_overclk68k",
+      "68K Overclock",
+      NULL,
+      "Overclock the emulated 68K chip. Can reduce slowdown, but may cause glitches.",
+      NULL,
+      "hacks",
       {
-         { "16000", NULL },
-         { "22050", NULL },
-         { "32000", NULL },
-         { "44100", NULL },
+         { "disabled", NULL },
+         { "+25%",     NULL },
+         { "+50%",     NULL },
+         { "+75%",     NULL },
+         { "+100%",    NULL },
+         { "+200%",    NULL },
+         { "+400%",    NULL },
          { NULL, NULL },
       },
-      "44100"
+      "disabled"
    },
-   { NULL, NULL, NULL, {{0}}, NULL },
+   { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL },
+};
+
+struct retro_core_options_v2 options_us = {
+   option_cats_us,
+   option_defs_us
 };
 
 /*
@@ -272,26 +427,31 @@ struct retro_core_option_definition option_defs_us[] = {
 */
 
 #ifndef HAVE_NO_LANGEXTRA
-struct retro_core_option_definition *option_defs_intl[RETRO_LANGUAGE_LAST] = {
-   option_defs_us, /* RETRO_LANGUAGE_ENGLISH */
-   NULL,           /* RETRO_LANGUAGE_JAPANESE */
-   NULL,           /* RETRO_LANGUAGE_FRENCH */
-   NULL,           /* RETRO_LANGUAGE_SPANISH */
-   NULL,           /* RETRO_LANGUAGE_GERMAN */
-   NULL,           /* RETRO_LANGUAGE_ITALIAN */
-   NULL,           /* RETRO_LANGUAGE_DUTCH */
-   NULL,           /* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */
-   NULL,           /* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */
-   NULL,           /* RETRO_LANGUAGE_RUSSIAN */
-   NULL,           /* RETRO_LANGUAGE_KOREAN */
-   NULL,           /* RETRO_LANGUAGE_CHINESE_TRADITIONAL */
-   NULL,           /* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */
-   NULL,           /* RETRO_LANGUAGE_ESPERANTO */
-   NULL,           /* RETRO_LANGUAGE_POLISH */
-   NULL,           /* RETRO_LANGUAGE_VIETNAMESE */
-   NULL,           /* RETRO_LANGUAGE_ARABIC */
-   NULL,           /* RETRO_LANGUAGE_GREEK */
-   option_defs_tr, /* RETRO_LANGUAGE_TURKISH */
+struct retro_core_options_v2 *options_intl[RETRO_LANGUAGE_LAST] = {
+   &options_us, /* RETRO_LANGUAGE_ENGLISH */
+   NULL,        /* RETRO_LANGUAGE_JAPANESE */
+   NULL,        /* RETRO_LANGUAGE_FRENCH */
+   NULL,        /* RETRO_LANGUAGE_SPANISH */
+   NULL,        /* RETRO_LANGUAGE_GERMAN */
+   NULL,        /* RETRO_LANGUAGE_ITALIAN */
+   NULL,        /* RETRO_LANGUAGE_DUTCH */
+   NULL,        /* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */
+   NULL,        /* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */
+   NULL,        /* RETRO_LANGUAGE_RUSSIAN */
+   NULL,        /* RETRO_LANGUAGE_KOREAN */
+   NULL,        /* RETRO_LANGUAGE_CHINESE_TRADITIONAL */
+   NULL,        /* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */
+   NULL,        /* RETRO_LANGUAGE_ESPERANTO */
+   NULL,        /* RETRO_LANGUAGE_POLISH */
+   NULL,        /* RETRO_LANGUAGE_VIETNAMESE */
+   NULL,        /* RETRO_LANGUAGE_ARABIC */
+   NULL,        /* RETRO_LANGUAGE_GREEK */
+   &options_tr, /* RETRO_LANGUAGE_TURKISH */
+   NULL,        /* RETRO_LANGUAGE_SLOVAK */
+   NULL,        /* RETRO_LANGUAGE_PERSIAN */
+   NULL,        /* RETRO_LANGUAGE_HEBREW */
+   NULL,        /* RETRO_LANGUAGE_ASTURIAN */
+   NULL,        /* RETRO_LANGUAGE_FINNISH */
 };
 #endif
 
@@ -309,39 +469,61 @@ struct retro_core_option_definition *option_defs_intl[RETRO_LANGUAGE_LAST] = {
  *   be as painless as possible for core devs)
  */
 
-static INLINE void libretro_set_core_options(retro_environment_t environ_cb)
+static INLINE void libretro_set_core_options(retro_environment_t environ_cb,
+      bool *categories_supported)
 {
-   unsigned version = 0;
+   unsigned version  = 0;
+#ifndef HAVE_NO_LANGEXTRA
+   unsigned language = 0;
+#endif
 
-   if (!environ_cb)
+   if (!environ_cb || !categories_supported)
       return;
 
-   if (environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version) && (version >= 1))
+   *categories_supported = false;
+
+   if (!environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version))
+      version = 0;
+
+   if (version >= 2)
    {
 #ifndef HAVE_NO_LANGEXTRA
-      struct retro_core_options_intl core_options_intl;
-      unsigned language = 0;
+      struct retro_core_options_v2_intl core_options_intl;
 
-      core_options_intl.us    = option_defs_us;
+      core_options_intl.us    = &options_us;
       core_options_intl.local = NULL;
 
       if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) &&
           (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH))
-         core_options_intl.local = option_defs_intl[language];
+         core_options_intl.local = options_intl[language];
 
-      environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_intl);
+      *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL,
+            &core_options_intl);
 #else
-      environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, &option_defs_us);
+      *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2,
+            &options_us);
 #endif
    }
    else
    {
-      size_t i;
+      size_t i, j;
+      size_t option_index              = 0;
       size_t num_options               = 0;
+      struct retro_core_option_definition
+            *option_v1_defs_us         = NULL;
+#ifndef HAVE_NO_LANGEXTRA
+      size_t num_options_intl          = 0;
+      struct retro_core_option_v2_definition
+            *option_defs_intl          = NULL;
+      struct retro_core_option_definition
+            *option_v1_defs_intl       = NULL;
+      struct retro_core_options_intl
+            core_options_v1_intl;
+#endif
       struct retro_variable *variables = NULL;
       char **values_buf                = NULL;
 
-      /* Determine number of options */
+      /* Determine total number of options */
       while (true)
       {
          if (option_defs_us[num_options].key)
@@ -350,86 +532,187 @@ static INLINE void libretro_set_core_options(retro_environment_t environ_cb)
             break;
       }
 
-      /* Allocate arrays */
-      variables  = (struct retro_variable *)calloc(num_options + 1, sizeof(struct retro_variable));
-      values_buf = (char **)calloc(num_options, sizeof(char *));
+      if (version >= 1)
+      {
+         /* Allocate US array */
+         option_v1_defs_us = (struct retro_core_option_definition *)
+               calloc(num_options + 1, sizeof(struct retro_core_option_definition));
 
-      if (!variables || !values_buf)
-         goto error;
+         /* Copy parameters from option_defs_us array */
+         for (i = 0; i < num_options; i++)
+         {
+            struct retro_core_option_v2_definition *option_def_us = &option_defs_us[i];
+            struct retro_core_option_value *option_values         = option_def_us->values;
+            struct retro_core_option_definition *option_v1_def_us = &option_v1_defs_us[i];
+            struct retro_core_option_value *option_v1_values      = option_v1_def_us->values;
+
+            option_v1_def_us->key           = option_def_us->key;
+            option_v1_def_us->desc          = option_def_us->desc;
+            option_v1_def_us->info          = option_def_us->info;
+            option_v1_def_us->default_value = option_def_us->default_value;
+
+            /* Values must be copied individually... */
+            while (option_values->value)
+            {
+               option_v1_values->value = option_values->value;
+               option_v1_values->label = option_values->label;
 
-      /* Copy parameters from option_defs_us array */
-      for (i = 0; i < num_options; i++)
-      {
-         const char *key                        = option_defs_us[i].key;
-         const char *desc                       = option_defs_us[i].desc;
-         const char *default_value              = option_defs_us[i].default_value;
-         struct retro_core_option_value *values = option_defs_us[i].values;
-         size_t buf_len                         = 3;
-         size_t default_index                   = 0;
+               option_values++;
+               option_v1_values++;
+            }
+         }
 
-         values_buf[i] = NULL;
+#ifndef HAVE_NO_LANGEXTRA
+         if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) &&
+             (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH) &&
+             options_intl[language])
+            option_defs_intl = options_intl[language]->definitions;
 
-         if (desc)
+         if (option_defs_intl)
          {
-            size_t num_values = 0;
-
-            /* Determine number of values */
+            /* Determine number of intl options */
             while (true)
             {
-               if (values[num_values].value)
-               {
-                  /* Check if this is the default value */
-                  if (default_value)
-                     if (strcmp(values[num_values].value, default_value) == 0)
-                        default_index = num_values;
-
-                  buf_len += strlen(values[num_values].value);
-                  num_values++;
-               }
+               if (option_defs_intl[num_options_intl].key)
+                  num_options_intl++;
                else
                   break;
             }
 
-            /* Build values string */
-            if (num_values > 0)
+            /* Allocate intl array */
+            option_v1_defs_intl = (struct retro_core_option_definition *)
+                  calloc(num_options_intl + 1, sizeof(struct retro_core_option_definition));
+
+            /* Copy parameters from option_defs_intl array */
+            for (i = 0; i < num_options_intl; i++)
             {
-               size_t j;
+               struct retro_core_option_v2_definition *option_def_intl = &option_defs_intl[i];
+               struct retro_core_option_value *option_values           = option_def_intl->values;
+               struct retro_core_option_definition *option_v1_def_intl = &option_v1_defs_intl[i];
+               struct retro_core_option_value *option_v1_values        = option_v1_def_intl->values;
+
+               option_v1_def_intl->key           = option_def_intl->key;
+               option_v1_def_intl->desc          = option_def_intl->desc;
+               option_v1_def_intl->info          = option_def_intl->info;
+               option_v1_def_intl->default_value = option_def_intl->default_value;
+
+               /* Values must be copied individually... */
+               while (option_values->value)
+               {
+                  option_v1_values->value = option_values->value;
+                  option_v1_values->label = option_values->label;
+
+                  option_values++;
+                  option_v1_values++;
+               }
+            }
+         }
+
+         core_options_v1_intl.us    = option_v1_defs_us;
+         core_options_v1_intl.local = option_v1_defs_intl;
+
+         environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_v1_intl);
+#else
+         environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, option_v1_defs_us);
+#endif
+      }
+      else
+      {
+         /* Allocate arrays */
+         variables  = (struct retro_variable *)calloc(num_options + 1,
+               sizeof(struct retro_variable));
+         values_buf = (char **)calloc(num_options, sizeof(char *));
 
-               buf_len += num_values - 1;
-               buf_len += strlen(desc);
+         if (!variables || !values_buf)
+            goto error;
 
-               values_buf[i] = (char *)calloc(buf_len, sizeof(char));
-               if (!values_buf[i])
-                  goto error;
+         /* Copy parameters from option_defs_us array */
+         for (i = 0; i < num_options; i++)
+         {
+            const char *key                        = option_defs_us[i].key;
+            const char *desc                       = option_defs_us[i].desc;
+            const char *default_value              = option_defs_us[i].default_value;
+            struct retro_core_option_value *values = option_defs_us[i].values;
+            size_t buf_len                         = 3;
+            size_t default_index                   = 0;
 
-               strcpy(values_buf[i], desc);
-               strcat(values_buf[i], "; ");
+            values_buf[i] = NULL;
 
-               /* Default value goes first */
-               strcat(values_buf[i], values[default_index].value);
+            if (desc)
+            {
+               size_t num_values = 0;
 
-               /* Add remaining values */
-               for (j = 0; j < num_values; j++)
+               /* Determine number of values */
+               while (true)
                {
-                  if (j != default_index)
+                  if (values[num_values].value)
+                  {
+                     /* Check if this is the default value */
+                     if (default_value)
+                        if (strcmp(values[num_values].value, default_value) == 0)
+                           default_index = num_values;
+
+                     buf_len += strlen(values[num_values].value);
+                     num_values++;
+                  }
+                  else
+                     break;
+               }
+
+               /* Build values string */
+               if (num_values > 0)
+               {
+                  buf_len += num_values - 1;
+                  buf_len += strlen(desc);
+
+                  values_buf[i] = (char *)calloc(buf_len, sizeof(char));
+                  if (!values_buf[i])
+                     goto error;
+
+                  strcpy(values_buf[i], desc);
+                  strcat(values_buf[i], "; ");
+
+                  /* Default value goes first */
+                  strcat(values_buf[i], values[default_index].value);
+
+                  /* Add remaining values */
+                  for (j = 0; j < num_values; j++)
                   {
-                     strcat(values_buf[i], "|");
-                     strcat(values_buf[i], values[j].value);
+                     if (j != default_index)
+                     {
+                        strcat(values_buf[i], "|");
+                        strcat(values_buf[i], values[j].value);
+                     }
                   }
                }
             }
+
+            variables[option_index].key   = key;
+            variables[option_index].value = values_buf[i];
+            option_index++;
          }
 
-         variables[i].key   = key;
-         variables[i].value = values_buf[i];
+         /* Set variables */
+         environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables);
       }
 
-      /* Set variables */
-      environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables);
-
 error:
-
       /* Clean up */
+
+      if (option_v1_defs_us)
+      {
+         free(option_v1_defs_us);
+         option_v1_defs_us = NULL;
+      }
+
+#ifndef HAVE_NO_LANGEXTRA
+      if (option_v1_defs_intl)
+      {
+         free(option_v1_defs_intl);
+         option_v1_defs_intl = NULL;
+      }
+#endif
+
       if (values_buf)
       {
          for (i = 0; i < num_options; i++)

+ 44 - 12
platform/libretro/libretro_core_options_intl.h

@@ -11,9 +11,10 @@
 
 /*
  ********************************
- * VERSION: 1.3
+ * VERSION: 2.0
  ********************************
  *
+ * - 2.0: Add support for core options v2 interface
  * - 1.3: Move translations to libretro_core_options_intl.h
  *        - libretro_core_options_intl.h includes BOM and utf-8
  *          fix for MSVC 2010-2013
@@ -73,11 +74,19 @@ extern "C" {
 
 /* RETRO_LANGUAGE_TURKISH */
 
-struct retro_core_option_definition option_defs_tr[] = {
+
+struct retro_core_option_v2_category option_cats_tr[] = {
+   { NULL, NULL, NULL },
+};
+
+struct retro_core_option_v2_definition option_defs_tr[] = {
    {
       "picodrive_input1",
       "Giriş cihazı 1",
+      NULL,
       "Hangi tür kontrolör'ün yuva 1'e takılı olduğunu seçin.",
+      NULL,
+      NULL,
       {
          { "3 button pad", NULL },
          { "6 button pad", NULL },
@@ -89,7 +98,10 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_input2",
       "Giriş cihazı 2",
+      NULL,
       "Hangi tür kontrolör'ün yuva 2'e takılı olduğunu seçin",
+      NULL,
+      NULL,
       {
          { "3 button pad", NULL },
          { "6 button pad", NULL },
@@ -101,7 +113,10 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_sprlim",
       "Sprite sınırı yok",
+      NULL,
       "Sprite sınırını kaldırmak için bunu etkinleştirin.",
+      NULL,
+      NULL,
       {
          { NULL, NULL },
       },
@@ -110,7 +125,10 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_ramcart",
       "Sega CD RAM Kartuşu",
+      NULL,
       "Oyun verilerini kaydetmek için kullanılan bir MegaCD RAM kartuşunu taklit edin.",
+      NULL,
+      NULL,
       {
          { NULL, NULL },
       },
@@ -119,7 +137,10 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_region",
       "Bölge",
+      NULL,
       "Belirli bir bölgeye zorlayın.",
+      NULL,
+      NULL,
       {
          { "Auto",       "Otomatik" },
          { "Japan NTSC", "Japonya NTSC" },
@@ -133,25 +154,22 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_aspect",
       "Core tarafından belirlenen en boy oranı",
+      NULL,
       "Core tarafından sağlanan en boy oranını seçin. RetroArch'ın en boy oranı, Video ayarlarında sağlanan Core olarak ayarlanmalıdır.",
+      NULL,
+      NULL,
       {
          { NULL, NULL },
       },
       "PAR"
    },
-   {
-      "picodrive_overscan",
-      "Tarama Taşmasını Göster",
-      "Standart tanımlı bir televizyon ekranının kenar çevresinde, çerçeve tarafından gizlenmiş olabilecek rastgele video çıkışını kesin.",
-      {
-         { NULL, NULL },
-      },
-      "disabled"
-   },
    {
       "picodrive_overclk68k",
       "68K Hızaşırtma",
+      NULL,
       "Öykünülmüş 68K yongasına Hızaşırtma uygulayın.",
+      NULL,
+      NULL,
       {
          { NULL, NULL },
       },
@@ -161,7 +179,10 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_drc",
       "Dinamik Yeniden Derleyici",
+      NULL,
       "Performansı artırmaya yardımcı olan dinamik yeniden derleyicileri etkinleştirin. Tercüman CPU çekirdeğinden daha az hassas, ancak çok daha hızlıdır.",
+      NULL,
+      NULL,
       {
          { NULL, NULL },
       },
@@ -171,7 +192,10 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_audio_filter",
       "Ses Filtresi",
+      NULL,
       "Model 1 Genesis'in karakteristik sesini daha iyi simüle etmek için düşük geçişli bir ses filtresini etkinleştirin. Master System ve PICO başlıkları kullanılırken bu seçenek yoksayılır. Sadece Genesis ve eklenti donanımı (Sega CD, 32X) fiziksel düşük geçiş filtresi kullanır.",
+      NULL,
+      NULL,
       {
          { "disabled", "devre dışı" },
          { "low-pass", "alçak geçiş" },
@@ -182,13 +206,21 @@ struct retro_core_option_definition option_defs_tr[] = {
    {
       "picodrive_lowpass_range",
       "Alçak geçiş filtresi %",
+      NULL,
       "Ses düşük geçiş filtresinin kesme frekansını belirtin. Daha yüksek bir değer, yüksek frekans spektrumunun daha geniş bir aralığı azaltıldığı için filtrenin algılanan gücünü arttırır.",
+      NULL,
+      NULL,
       {
          { NULL, NULL },
       },
       "60"
    },
-   { NULL, NULL, NULL, {{0}}, NULL },
+   { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL },
+};
+
+struct retro_core_options_v2 options_tr = {
+   option_cats_tr,
+   option_defs_tr
 };
 
 #ifdef __cplusplus

+ 2 - 2
platform/linux/blit.c

@@ -10,13 +10,13 @@
 // to      00000000 rrr00000 ggg00000 bbb00000 ...
 // TODO: rm when gp2x/emu.c is no longer used
 
-void bgr444_to_rgb32(void *to, void *from)
+void bgr444_to_rgb32(void *to, void *from, unsigned entries)
 {
 	unsigned short *ps = from;
 	unsigned int   *pd = to;
 	int pixels;
 
-	for (pixels = 0x40; pixels; pixels--, ps++, pd++)
+	for (pixels = entries; pixels; pixels--, ps++, pd++)
 	{
 		*pd = ((*ps<<20)&0xe00000) | ((*ps<<8)&0xe000) | ((*ps>>4)&0xe0);
 		*pd |= *pd >> 3;

+ 241 - 40
platform/linux/emu.c

@@ -14,6 +14,7 @@
 #include "../libpicofe/plat.h"
 #include "../common/emu.h"
 #include "../common/arm_utils.h"
+#include "../common/upscale.h"
 #include "../common/version.h"
 
 #include <pico/pico_int.h>
@@ -23,8 +24,10 @@ const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast
 const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL };
 enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT };
 
-static int out_x, out_y;
-static int out_w, out_h;
+static int out_x, out_y, out_w, out_h;	// renderer output in render buffer
+static int screen_x, screen_y, screen_w, screen_h; // final render destination 
+static int render_bg;			// force 16bit mode for bg render
+static u16 *ghost_buf;			// backbuffer to simulate LCD ghosting
 
 void pemu_prep_defconfig(void)
 {
@@ -38,7 +41,7 @@ void pemu_validate_config(void)
 }
 
 #define is_16bit_mode() \
-	(currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X))
+	(currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X) || render_bg)
 
 static int get_renderer(void)
 {
@@ -82,33 +85,108 @@ static void draw_cd_leds(void)
 #undef p
 }
 
-static unsigned short *get_16bit_start(unsigned short *buf)
+/* render/screen buffer handling:
+ * In 16 bit mode, render output is directly placed in the screen buffer.
+ * SW scaling is handled in renderer (x) and in vscaling callbacks here (y).
+ * In 8 bit modes, output goes to the internal Draw2FB buffer in alternate
+ * renderer format (8 pix overscan at left/top/bottom), left aligned (DIS_32C).
+ * It is converted to 16 bit and SW scaled in pemu_finalize_frame.
+ *
+ * HW scaling always aligns the image to the left/top, since selecting an area
+ * for display isn't always possible.
+ */
+
+static inline u16 *screen_buffer(u16 *buf)
 {
-	// center the output on the screen
-	int offs = (g_screen_height-240)/2 * g_screen_ppitch + (g_screen_width-320)/2;
-	return buf + offs;
+	return buf + screen_y * g_screen_ppitch + screen_x -
+			(out_y * g_screen_ppitch + out_x);
 }
 
-void pemu_finalize_frame(const char *fps, const char *notice)
+void screen_blit(u16 *pd, int pp, u8* ps, int ss, u16 *pal)
 {
+	typedef void (*upscale_t)
+			(u16 *di,int ds, u8 *si,int ss, int w,int h, u16 *pal);
+	static const upscale_t upscale_256_224_hv[] = {
+		upscale_rgb_nn_x_4_5_y_16_17,	upscale_rgb_snn_x_4_5_y_16_17,
+		upscale_rgb_bl2_x_4_5_y_16_17,	upscale_rgb_bl4_x_4_5_y_16_17,
+	};
+	static const upscale_t upscale_256_____h[] = {
+		upscale_rgb_nn_x_4_5,		upscale_rgb_snn_x_4_5,
+		upscale_rgb_bl2_x_4_5,		upscale_rgb_bl4_x_4_5,
+	};
+	static const upscale_t upscale_____224_v[] = {
+		upscale_rgb_nn_y_16_17,		upscale_rgb_snn_y_16_17,
+		upscale_rgb_bl2_y_16_17,	upscale_rgb_bl4_y_16_17,
+	};
+	static const upscale_t upscale_160_144_hv[] = {
+		upscale_rgb_nn_x_1_2_y_3_5,	upscale_rgb_nn_x_1_2_y_3_5,
+		upscale_rgb_bl2_x_1_2_y_3_5,	upscale_rgb_bl4_x_1_2_y_3_5,
+	};
+	static const upscale_t upscale_160_____h[] = {
+		upscale_rgb_nn_x_1_2,		upscale_rgb_nn_x_1_2,
+		upscale_rgb_bl2_x_1_2,		upscale_rgb_bl2_x_1_2,
+	};
+	static const upscale_t upscale_____144_v[] = {
+		upscale_rgb_nn_y_3_5,		upscale_rgb_nn_y_3_5,
+		upscale_rgb_bl2_y_3_5,		upscale_rgb_bl4_y_3_5,
+	};
+	const upscale_t *upscale;
+	int y;
+
+	// handle software upscaling
+	upscale = NULL;
+	if (currentConfig.scaling == EOPT_SCALE_SW && out_w <= 256) {
+	    if (currentConfig.vscaling == EOPT_SCALE_SW && out_h <= 224)
+		// h+v scaling
+		upscale = out_w >= 256 ? upscale_256_224_hv: upscale_160_144_hv;
+	    else
+		// h scaling
+		upscale = out_w >= 256 ? upscale_256_____h : upscale_160_____h;
+	} else if (currentConfig.vscaling == EOPT_SCALE_SW && out_h <= 224)
+		// v scaling
+		upscale = out_w >= 256 ? upscale_____224_v : upscale_____144_v;
+	if (!upscale) {
+		// no scaling
+		for (y = 0; y < out_h; y++)
+			h_copy(pd, pp, ps, 328, out_w, f_pal);
+		return;
+	}
 
+	upscale[currentConfig.filter & 0x3](pd, pp, ps, ss, out_w, out_h, pal);
+}
 
+void pemu_finalize_frame(const char *fps, const char *notice)
+{
 	if (!is_16bit_mode()) {
 		// convert the 8 bit CLUT output to 16 bit RGB
-		unsigned short *pd = (unsigned short *)g_screen_ptr +
-					out_y * g_screen_ppitch + out_x;
-		unsigned char *ps = Pico.est.Draw2FB + 328*out_y + 8;
-		unsigned short *pal = Pico.est.HighPal;
-		int i, x;
+		u16 *pd = screen_buffer(g_screen_ptr) +
+				out_y * g_screen_ppitch + out_x;
+		u8  *ps = Pico.est.Draw2FB + out_y * 328 + out_x + 8;
 
-		pd = get_16bit_start(pd);
 		PicoDrawUpdateHighPal();
-		for (i = 0; i < out_h; i++, ps += 8) {
-			for (x = 0; x < out_w; x++)
-				*pd++ = pal[*ps++];
-			pd += g_screen_ppitch - out_w;
-			ps += 320 - out_w;
-		}
+
+		screen_blit(pd, g_screen_ppitch, ps, 328, Pico.est.HighPal);
+	}
+
+	if (currentConfig.ghosting && out_h == 144) {
+		// GG LCD ghosting emulation
+		u16 *pd = screen_buffer(g_screen_ptr) +
+				out_y * g_screen_ppitch + out_x;
+		u16 *ps = ghost_buf;
+		int y, h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h;
+		int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w;
+		if (currentConfig.ghosting == 1)
+			for (y = 0; y < h; y++) {
+				v_blend((u32 *)pd, (u32 *)ps, w/2, p_075_round);
+				pd += g_screen_ppitch;
+				ps += w;
+			}
+		else
+			for (y = 0; y < h; y++) {
+				v_blend((u32 *)pd, (u32 *)ps, w/2, p_05_round);
+				pd += g_screen_ppitch;
+				ps += w;
+			}
 	}
 
 //#define FUNKEY_AUTHORIZE_TEXT_OVERLAY
@@ -127,34 +205,45 @@ void pemu_finalize_frame(const char *fps, const char *notice)
 void plat_video_set_buffer(void *buf)
 {
 	if (is_16bit_mode())
-		PicoDrawSetOutBuf(get_16bit_start(buf), g_screen_ppitch * 2);
+		PicoDrawSetOutBuf(screen_buffer(buf), g_screen_ppitch * 2);
 }
 
 static void apply_renderer(void)
 {
+	PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE|POPT_DIS_32C_BORDER);
+	if (is_16bit_mode()) {
+		if (currentConfig.scaling == EOPT_SCALE_SW) {
+			PicoIn.opt |= POPT_EN_SOFTSCALE;
+			PicoIn.filter = currentConfig.filter;
+		} else if (currentConfig.scaling == EOPT_SCALE_HW)
+			// hw scaling, render without any padding
+			PicoIn.opt |= POPT_DIS_32C_BORDER;
+	} else
+		PicoIn.opt |= POPT_DIS_32C_BORDER;
+
 	switch (get_renderer()) {
 	case RT_16BIT:
-		PicoIn.opt &= ~POPT_ALT_RENDERER;
-		PicoIn.opt &= ~POPT_DIS_32C_BORDER;
-		PicoDrawSetOutFormat(PDF_RGB555, 0);
-		PicoDrawSetOutBuf(get_16bit_start(g_screen_ptr), g_screen_ppitch * 2);
+		// 32X uses line mode for vscaling with accurate renderer, since
+		// the MD VDP layer must be unscaled and merging the scaled 32X
+		// image data will fail.
+		PicoDrawSetOutFormat(PDF_RGB555,
+			(PicoIn.AHW & PAHW_32X) && currentConfig.vscaling);
+		PicoDrawSetOutBuf(screen_buffer(g_screen_ptr), g_screen_ppitch * 2);
 		break;
 	case RT_8BIT_ACC:
-		PicoIn.opt &= ~POPT_ALT_RENDERER;
-		PicoIn.opt |=  POPT_DIS_32C_BORDER;
+		// for simplification the 8 bit accurate renderer uses the same
+		// storage format as the fast renderer
 		PicoDrawSetOutFormat(PDF_8BIT, 0);
 		PicoDrawSetOutBuf(Pico.est.Draw2FB, 328);
 		break;
 	case RT_8BIT_FAST:
 		PicoIn.opt |=  POPT_ALT_RENDERER;
-		PicoIn.opt |=  POPT_DIS_32C_BORDER;
 		PicoDrawSetOutFormat(PDF_NONE, 0);
 		break;
 	}
 
 	if (PicoIn.AHW & PAHW_32X)
-		PicoDrawSetOutBuf(get_16bit_start(g_screen_ptr), g_screen_ppitch * 2);
-
+		PicoDrawSetOutBuf(screen_buffer(g_screen_ptr), g_screen_ppitch * 2);
 	Pico.m.dirtyPal = 1;
 }
 
@@ -196,37 +285,145 @@ void plat_update_volume(int has_changed, int is_up)
 {
 }
 
+void pemu_sound_start(void)
+{
+	emu_sound_start();
+}
+
+void plat_debug_cat(char *str)
+{
+}
+
 void pemu_forced_frame(int no_scale, int do_emu)
 {
-	unsigned short *pd = get_16bit_start(g_screen_ptr);
+	int hs = currentConfig.scaling, vs = currentConfig.vscaling;
 
+	// create centered and sw scaled (if scaling enabled) 16 bit output
 	PicoIn.opt &= ~POPT_DIS_32C_BORDER;
-	PicoDrawSetCallbacks(NULL, NULL);
 	Pico.m.dirtyPal = 1;
+	if (currentConfig.scaling)  currentConfig.scaling  = EOPT_SCALE_SW;
+	if (currentConfig.vscaling) currentConfig.vscaling = EOPT_SCALE_SW;
+	plat_video_set_size(g_menuscreen_w, g_menuscreen_h);
 
-	emu_cmn_forced_frame(no_scale, do_emu, pd);
+	// render a frame in 16 bit mode
+	render_bg = 1;
+	emu_cmn_forced_frame(no_scale, do_emu, screen_buffer(g_screen_ptr));
+	render_bg = 0;
 
 	g_menubg_src_ptr = g_screen_ptr;
+	currentConfig.scaling = hs, currentConfig.vscaling = vs;
 }
 
-void pemu_sound_start(void)
+/* vertical sw scaling, 16 bit mode */
+static int vscale_state;
+
+static int cb_vscaling_begin(unsigned int line)
 {
-	emu_sound_start();
+	// at start of new frame?
+	if (line <= out_y) {
+		// set y frame offset (see emu_video_mode_change)
+		Pico.est.DrawLineDest = screen_buffer(g_screen_ptr) +
+				(out_y * g_screen_ppitch /*+ out_x*/);
+		vscale_state = 0;
+		return out_y - line;
+	} else if (line > out_y + out_h)
+		return 1;
+
+	return 0;
 }
 
-void plat_debug_cat(char *str)
+static int cb_vscaling_nop(unsigned int line)
+{
+	return 0;
+}
+
+static int cb_vscaling_end(unsigned int line)
 {
+	u16 *dest = (u16 *)Pico.est.DrawLineDest + out_x;
+	// helpers for 32 bit operation (2 pixels at once):
+	u32 *dest32 = (u32 *)dest;
+	int pp = g_screen_ppitch;
+
+	if (out_h == 144)
+	  switch (currentConfig.filter) {
+	  case 0: v_upscale_nn_3_5(dest32, pp/2, out_w/2, vscale_state);
+		  break;
+	  default: v_upscale_snn_3_5(dest32, pp/2, out_w/2, vscale_state);
+		  break;
+	  }
+	else
+	  switch (currentConfig.filter) {
+	  case 3: v_upscale_bl4_16_17(dest32, pp/2, out_w/2, vscale_state);
+		  break;
+	  case 2: v_upscale_bl2_16_17(dest32, pp/2, out_w/2, vscale_state);
+		  break;
+	  case 1: v_upscale_snn_16_17(dest32, pp/2, out_w/2, vscale_state);
+		  break;
+	  default: v_upscale_nn_16_17(dest32, pp/2, out_w/2, vscale_state);
+		  break;
+	  }
+	Pico.est.DrawLineDest = (u16 *)dest32 - out_x;
+	return 0;
 }
 
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
 {
+	// relative position in core fb and screen fb
+	out_y = start_line; out_x = start_col;
+	out_h = line_count; out_w = col_count;
+
+	PicoDrawSetCallbacks(NULL, NULL);
+	// center output in screen
+	screen_w = g_screen_width,  screen_x = (screen_w - out_w)/2;
+	screen_h = g_screen_height, screen_y = (screen_h - out_h)/2;
+
+	switch (currentConfig.scaling) {
+	case EOPT_SCALE_HW:
+		screen_w = out_w;
+		screen_x = 0;
+		break;
+	case EOPT_SCALE_SW:
+		screen_x = (screen_w - 320)/2;
+		break;
+	}
+	switch (currentConfig.vscaling) {
+	case EOPT_SCALE_HW:
+		screen_h = (out_h < 224 && out_h > 144 ? 224 : out_h);
+		screen_y = 0;
+		// NTSC always has 224 visible lines, anything smaller has bars
+		if (out_h < 224 && out_h > 144)
+			screen_y += (224 - out_h)/2;
+		// handle vertical centering for 16 bit mode
+		if (is_16bit_mode())
+			PicoDrawSetCallbacks(cb_vscaling_begin,cb_vscaling_nop);
+		break;
+	case EOPT_SCALE_SW:
+		screen_y = (screen_h - 240)/2 + (out_h < 240 && out_h > 144);
+		// NTSC always has 224 visible lines, anything smaller has bars
+		if (out_h < 224 && out_h > 144)
+			screen_y += (224 - out_h)/2;
+		// in 16 bit mode sw scaling is divided between core and platform
+		if (is_16bit_mode() && out_h < 240)
+			PicoDrawSetCallbacks(cb_vscaling_begin,cb_vscaling_end);
+		break;
+	}
+
+	if (screen_w != g_screen_width || screen_h != g_screen_height)
+		plat_video_set_size(screen_w, screen_h);
+	plat_video_set_buffer(g_screen_ptr);
+
+	// create a backing buffer for emulating the bad GG lcd display
+	if (currentConfig.ghosting && out_h == 144) {
+		int h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h;
+		int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w;
+		ghost_buf = realloc(ghost_buf, w * h * 2);
+		memset(ghost_buf, 0, w * h * 2);
+	}
+
 	// clear whole screen in all buffers
 	if (!is_16bit_mode())
 		memset32(Pico.est.Draw2FB, 0xe0e0e0e0, (320+8) * (8+240+8) / 4);
 	plat_video_clear_buffers();
-
-	out_y = start_line; out_x = (is_32cols ? 32 : 0);
-	out_h = line_count; out_w = (is_32cols ? 256:320);
 }
 
 void pemu_loop_prep(void)
@@ -239,6 +436,10 @@ void pemu_loop_end(void)
 {
 	/* do one more frame for menu bg */
 	pemu_forced_frame(0, 1);
+	if (ghost_buf) {
+		free(ghost_buf);
+		ghost_buf = NULL;
+	}
 }
 
 void plat_wait_till_us(unsigned int us_to)

+ 9 - 4
platform/linux/menu.c

@@ -1,12 +1,17 @@
-#include "../libpicofe/gp2x/plat_gp2x.h"
-
 // ------------ gfx options menu ------------
 
+static const char *men_scaling_opts[] = { "OFF", "software", "hardware", NULL };
+static const char *men_filter_opts[] = { "nearest", "smoother", "bilinear 1", "bilinear 2", NULL };
+static const char *men_ghosting_opts[] = { "OFF", "weak", "normal", NULL };
 
-const char *men_scaling_opts[] = { "OFF", "ON", NULL };
+static const char h_scale[] = "hardware scaling may not be working on some devices";
+static const char h_ghost[] = "when active simulates inertia of the GG LCD display";
 
 #define MENU_OPTIONS_GFX \
-	// mee_enum      ("screen scaling",           MA_OPT_SCALING,        currentConfig.scaling, men_scaling_opts), \
+	mee_enum_h    ("Horizontal scaling",     MA_OPT_SCALING, currentConfig.scaling, men_scaling_opts, h_scale), \
+	mee_enum_h    ("Vertical scaling",       MA_OPT_VSCALING, currentConfig.vscaling, men_scaling_opts, h_scale), \
+	mee_enum_h    ("Scaler type",            MA_OPT3_FILTERING, currentConfig.filter, men_filter_opts, NULL), \
+	mee_enum_h    ("Game Gear LCD ghosting", MA_SMSOPT_GHOSTING, currentConfig.ghosting, men_ghosting_opts, h_ghost), \
 
 #define MENU_OPTIONS_ADV
 

+ 1 - 1
platform/opendingux/data/default.gcw0.desktop

@@ -6,4 +6,4 @@ Exec=PicoDrive %f
 Icon=megadrive
 Terminal=false
 Categories=emulators;
-MimeType=.md;.smd;.bin;.sms;.cue;.chd;.32x;.zip;.7z
+MimeType=.md;.smd;.bin;.sms;.gg;.cue;.chd;.32x;.zip;.7z

+ 1 - 1
platform/opendingux/data/default.retrofw.desktop

@@ -6,7 +6,7 @@ Icon=megadrive
 Categories=emulators;
 X-OD-Manual=
 X-OD-Selector=
-X-OD-Filter=.md,.smd,.bin,.sms,.cue,.chd,.32x,.zip,.7z
+X-OD-Filter=.md,.smd,.bin,.sms,.gg,.cue,.chd,.32x,.zip,.7z
 Terminal=true
 Type=Application
 StartupNotify=true

+ 6 - 7
platform/pandora/plat.c

@@ -331,17 +331,16 @@ void pnd_restore_layer_data(void)
 	plat_video_flip();
 }
 
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
 {
 	int fb_w = 320, fb_h = 240, fb_left = 0, fb_right = 0, fb_top = 0, fb_bottom = 0;
 
 	if (doing_bg_frame)
 		return;
 
-	if (is_32cols) {
-		fb_w = 256;
-		fb_left = fb_right = 32;
-	}
+	fb_w = col_count;
+	fb_left = start_col;
+	fb_right = 320 - (fb_w+fb_left);;
 
 	switch (currentConfig.scaling) {
 	case SCALE_1x1:
@@ -349,7 +348,7 @@ void emu_video_mode_change(int start_line, int line_count, int is_32cols)
 		g_layer_h = fb_h;
 		break;
 	case SCALE_2x2_3x2:
-		g_layer_w = fb_w * (is_32cols ? 3 : 2);
+		g_layer_w = fb_w * (col_count < 320 ? 3 : 2);
 		g_layer_h = fb_h * 2;
 		break;
 	case SCALE_2x2_2x2:
@@ -381,7 +380,7 @@ void emu_video_mode_change(int start_line, int line_count, int is_32cols)
 		fb_h = line_count;
 		break;
 	}
-	g_osd_fps_x = is_32cols ? 232 : 264;
+	g_osd_fps_x = col_count < 320 ? 232 : 264;
 	g_osd_y = fb_top + fb_h - 8;
 
 	pnd_setup_layer(1, g_layer_x, g_layer_y, g_layer_w, g_layer_h);

+ 78 - 59
platform/psp/emu.c

@@ -50,9 +50,9 @@ static int need_pal_upload = 0;
 
 static u16 __attribute__((aligned(16))) osd_buf[512*8]; // buffer for osd text
 
-static int h32_mode = 0;
 static int out_x, out_y;
 static int out_w, out_h;
+static float hscale, vscale;
 
 static struct in_default_bind in_psp_defbinds[] =
 {
@@ -104,7 +104,7 @@ static void change_renderer(int diff)
 
 static void apply_renderer(void)
 {
-	PicoIn.opt &= ~POPT_ALT_RENDERER;
+	PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);
 
 	switch (get_renderer()) {
 	case RT_16BIT:
@@ -152,11 +152,8 @@ static void set_scaling_params(void)
 	int fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0;
 	g_vertices[0].z = g_vertices[1].z = 0;
 
-	fbimg_height = (int)(out_h * currentConfig.scale + 0.5);
-	if (!h32_mode)
-		fbimg_width  = (int)(out_w * currentConfig.scale * currentConfig.hscale40 + 0.5);
-	else
-		fbimg_width  = (int)(out_w * currentConfig.scale * currentConfig.hscale32 + 0.5);
+	fbimg_height = (int)(out_h * vscale + 0.5);
+	fbimg_width  = (int)(out_w * hscale + 0.5);
 
 	if (fbimg_width  & 1) fbimg_width++;  // make even
 	if (fbimg_height & 1) fbimg_height++;
@@ -207,32 +204,42 @@ static void set_scaling_params(void)
 	*/
 }
 
+static void do_pal_update_sms(void)
+{
+	static u16 tmspal[32] = {
+		// SMS palette
+		0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
+		0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
+	};
+	int i;
+	
+	if (!(Pico.video.reg[0] & 0x4)) {
+		for (i = Pico.est.SonicPalCount; i >= 0; i--)
+			do_pal_convert(localPal+i*0x40, tmspal, currentConfig.gamma, currentConfig.gamma2);
+	} else {
+		for (i = Pico.est.SonicPalCount; i >= 0; i--)
+			do_pal_convert(localPal+i*0x40, Pico.est.SonicPal+i*0x40, currentConfig.gamma, currentConfig.gamma2);
+	}
+	if (Pico.m.dirtyPal == 2)
+		Pico.m.dirtyPal = 0;
+	need_pal_upload = 1;
+}
+
 static void do_pal_update(void)
 {
 	u32 *dpal=(void *)localPal;
 	int i;
 
-	//for (i = 0x3f/2; i >= 0; i--)
-	//	dpal[i] = ((spal[i]&0x000f000f)<< 1)|((spal[i]&0x00f000f0)<<3)|((spal[i]&0x0f000f00)<<4);
-	if (PicoIn.AHW & PAHW_SMS) {
-		u32 *spal = (void *)PicoMem.cram; 
-		for (i = 0; i < 0x20 / 2; i++) {
-			u32 t = spal[i];
-			t = ((t & 0x00030003)<< 3) | ((t & 0x000c000c)<<7) | ((t & 0x00300030)<<10);
-			t |= (t >> 2) | ((t >> 4) & 0x08610861);
-			dpal[i] = t;
-		}
-		Pico.m.dirtyPal = 0;
-	} else if (PicoIn.opt & POPT_ALT_RENDERER) {
+	if (PicoIn.opt & POPT_ALT_RENDERER) {
 		do_pal_convert(localPal, PicoMem.cram, currentConfig.gamma, currentConfig.gamma2);
 		Pico.m.dirtyPal = 0;
 	}
 	else if (Pico.est.rendstatus & PDRAW_SONIC_MODE)
 	{
 		switch (Pico.est.SonicPalCount) {
-		case 3: do_pal_convert(localPal+0xc0/2, Pico.est.SonicPal+0xc0, currentConfig.gamma, currentConfig.gamma2);
-		case 2: do_pal_convert(localPal+0x80/2, Pico.est.SonicPal+0x80, currentConfig.gamma, currentConfig.gamma2);
-		case 1:	do_pal_convert(localPal+0x40/2, Pico.est.SonicPal+0x40, currentConfig.gamma, currentConfig.gamma2);
+		case 3: do_pal_convert(localPal+0xc0, Pico.est.SonicPal+0xc0, currentConfig.gamma, currentConfig.gamma2);
+		case 2: do_pal_convert(localPal+0x80, Pico.est.SonicPal+0x80, currentConfig.gamma, currentConfig.gamma2);
+		case 1:	do_pal_convert(localPal+0x40, Pico.est.SonicPal+0x40, currentConfig.gamma, currentConfig.gamma2);
 		default:do_pal_convert(localPal, Pico.est.SonicPal, currentConfig.gamma, currentConfig.gamma2);
 		}
 	}
@@ -270,9 +277,12 @@ static void blitscreen_clut(void)
 	sceGuTexMode(is_16bit_mode() ? GU_PSM_5650:GU_PSM_T8,0,0,0);
 	sceGuTexImage(0,512,512,512,g_screen_ptr);
 
-	if (!is_16bit_mode() && Pico.m.dirtyPal)
-		do_pal_update();
-
+	if (!is_16bit_mode() && Pico.m.dirtyPal) {
+		if (PicoIn.AHW & PAHW_SMS)
+			do_pal_update_sms();
+		else
+			do_pal_update();
+	}
 
 	if (need_pal_upload) {
 		need_pal_upload = 0;
@@ -370,7 +380,7 @@ static void vidResetMode(void)
 
 	sceGuClutMode(GU_PSM_5650,0,0xff,0);
 	sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB);
-	if (currentConfig.scaling)
+	if (currentConfig.filter)
 	     sceGuTexFilter(GU_LINEAR, GU_LINEAR);
 	else sceGuTexFilter(GU_NEAREST, GU_NEAREST);
 	sceGuTexScale(1.0f,1.0f);
@@ -575,10 +585,9 @@ void pemu_prep_defconfig(void)
 	defaultConfig.s_PsndRate = 22050;
 	defaultConfig.s_PicoCDBuffers = 64;
 	defaultConfig.CPUclock = 333;
-	defaultConfig.scaling = 1;     // bilinear filtering for psp
-	defaultConfig.scale = 1.20;    // fullscreen
-	defaultConfig.hscale40 = 1.25;
-	defaultConfig.hscale32 = 1.56;
+	defaultConfig.filter = EOPT_FILTER_BILINEAR; // bilinear filtering
+	defaultConfig.scaling = EOPT_SCALE_43;
+	defaultConfig.vscaling = EOPT_VSCALE_FULL;
 	defaultConfig.EmuOpt |= EOPT_SHOW_RTC;
 }
 
@@ -587,12 +596,6 @@ void pemu_validate_config(void)
 {
 	if (currentConfig.CPUclock < 33 || currentConfig.CPUclock > 333)
 		currentConfig.CPUclock = 333;
-	if (currentConfig.scaling < 0.01)
-		currentConfig.scaling = 0.01;
-	if (currentConfig.hscale40 < 0.01)
-		currentConfig.hscale40 = 0.01;
-	if (currentConfig.hscale32 < 0.01)
-		currentConfig.hscale32 = 0.01;
 	if (currentConfig.gamma < -4 || currentConfig.gamma >  16)
 		currentConfig.gamma = 0;
 	if (currentConfig.gamma2 < 0 || currentConfig.gamma2 > 2)
@@ -674,14 +677,45 @@ void plat_update_volume(int has_changed, int is_up)
 }
 
 /* prepare for MD screen mode change */
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
 {
-	h32_mode = is_32cols;
-	out_y = start_line; out_x = (is_32cols ? 32 : 0);
-	out_h = line_count; out_w = (is_32cols ? 256:320);
+	/* NTSC always has 224 visible lines, anything smaller has bars */
+	if (line_count < 224 && line_count > 144) {
+		start_line -= (224-line_count) /2;
+		line_count = 224;
+	}
+
+	out_y = start_line; out_x = start_col;
+	out_h = line_count; out_w = col_count;
+
+	switch (currentConfig.vscaling) {
+	case EOPT_VSCALE_PAL:
+		vscale = (float)270/240;
+		break;
+	case EOPT_VSCALE_FULL:
+		vscale = (float)270/line_count;
+		break;
+	default:
+		vscale = 1;
+		break;
+	}
+	switch (currentConfig.scaling) {
+	case EOPT_SCALE_43:
+		hscale = (float)360/col_count;
+		break;
+	case EOPT_SCALE_WIDE:
+		hscale = (float)420/col_count;
+		break;
+	case EOPT_SCALE_FULL:
+		hscale = (float)480/col_count;
+		break;
+	default:
+		hscale = 1;
+		break;
+	}
 
 	vidResetMode();
-	if (h32_mode)	// clear borders from h40 remnants
+	if (col_count < 320)	// clear borders from h40 remnants
 		clearArea(1);
 }
 
@@ -741,25 +775,10 @@ void pemu_loop_end(void)
 	pemu_sound_stop();
 }
 
-// TODO 
+/* resume from suspend: change to main menu if emu was running */
 void emu_handle_resume(void)
 {
-	if (!(PicoIn.AHW & PAHW_MCD)) return;
-
-	// reopen first CD track
-	if (cdd.toc.tracks[0].fd != NULL)
-	{
-		lprintf("emu_HandleResume: reopen %s\n", cdd.toc.tracks[0].fname);
-		pm_close(cdd.toc.tracks[0].fd);
-		cdd.toc.tracks[0].fd = pm_open(cdd.toc.tracks[0].fname);
-		lprintf("reopen %s\n", cdd.toc.tracks[0].fd != NULL ? "ok" : "failed");
-	}
-
-	mp3_reopen_file();
-
-#if 0	// TODO
-	if (!(Pico_mcd->s68k_regs[0x36] & 1))
-		cdd_change_track(cdd.index, cdd.lba);
-#endif
+	if (engineState == PGS_Running)
+		engineState = PGS_Menu;
 }
 

+ 10 - 76
platform/psp/menu.c

@@ -1,84 +1,18 @@
 
-static const char h_scale43[]	= "Scales low and high res to 4:3 screen size.\n"
-				  "For 240 line PAL use Scale factor 1.12";
-static const char h_scalefull[] = "Scales low and high res to full screen.\n"
-				  "For 240 line PAL use scaling 1.12, 1.6, 1.28";
+static const char *men_hscaling_opts[] = { "OFF", "4:3", "wide", "fullscreen", NULL };
+static const char *men_vscaling_opts[] = { "OFF", "PAL", "fullscreen", NULL };
+static const char *men_filter_opts[] = { "OFF", "bilinear" };
+
 #define MENU_OPTIONS_GFX \
-	mee_cust("Scale factor",                    MA_OPT3_SCALE,    mh_scale, ms_scale), \
-	mee_cust("Hor. scale (for low res. games)", MA_OPT3_HSCALE32, mh_scale, ms_scale), \
-	mee_cust("Hor. scale (for hi res. games)",  MA_OPT3_HSCALE40, mh_scale, ms_scale), \
-	mee_onoff("Bilinear filtering",             MA_OPT3_FILTERING, currentConfig.scaling, 1), \
-	mee_range("Gamma adjustment",               MA_OPT3_GAMMAA,    currentConfig.gamma, -4, 16), \
-	mee_range("Black level",                    MA_OPT3_BLACKLVL,  currentConfig.gamma2,  0,  2), \
-	mee_onoff("wait for vsync",                 MA_OPT3_VSYNC,     currentConfig.EmuOpt, EOPT_VSYNC), \
-	mee_cust_nosave("Set to unscaled centered", MA_OPT3_PRES_NOSCALE, mh_preset_scale, NULL), \
-	mee_cust_nosave("Set to 4:3 scaled",        MA_OPT3_PRES_SCALE43, mh_preset_scale, NULL), \
-	mee_cust_nosave("Set to fullscreen",        MA_OPT3_PRES_FULLSCR, mh_preset_scale, NULL), \
+	mee_enum    ("Horizontal scaling", MA_OPT_SCALING,    currentConfig.scaling, men_hscaling_opts), \
+	mee_enum    ("Vertical scaling",   MA_OPT_VSCALING,   currentConfig.vscaling, men_vscaling_opts), \
+	mee_enum_h  ("Scaler type",        MA_OPT3_FILTERING, currentConfig.filter, men_filter_opts, NULL), \
+	mee_range   ("Gamma adjustment",   MA_OPT3_GAMMAA,    currentConfig.gamma, -4, 16), \
+	mee_range   ("Black level",        MA_OPT3_BLACKLVL,  currentConfig.gamma2, 0,  2), \
+	mee_onoff   ("Wait for vsync",     MA_OPT3_VSYNC,     currentConfig.EmuOpt, EOPT_VSYNC), \
 
 #define MENU_OPTIONS_ADV
 
-
-static const char *ms_scale(int id, int *offs)
-{
-	float val = 0;
-	switch (id) {
-	case MA_OPT3_SCALE:	val = currentConfig.scale; break;
-	case MA_OPT3_HSCALE32:	val = currentConfig.hscale32; break;
-	case MA_OPT3_HSCALE40:	val = currentConfig.hscale40; break;
-	}
-	sprintf(static_buff, "%.2f", val);
-	return static_buff;
-}
-
-static int mh_scale(int id, int keys)
-{
-	float *val = NULL;
-	switch (id) {
-	case MA_OPT3_SCALE:	val = &currentConfig.scale; break;
-	case MA_OPT3_HSCALE32:	val = &currentConfig.hscale32; break;
-	case MA_OPT3_HSCALE40:	val = &currentConfig.hscale40; break;
-	}
-	if (keys & PBTN_LEFT)	*val += -0.01;
-	if (keys & PBTN_RIGHT)	*val += +0.01;
-	if (*val < 0.01)	*val  = +0.01;
-	return 0;
-}
-
-
-static int mh_preset_scale(int id, int keys)
-{
-	switch (id) {
-	case MA_OPT3_PRES_NOSCALE:
-		currentConfig.scale = 1.0;
-		currentConfig.hscale32 = 1.0;
-		currentConfig.hscale40 = 1.0;
-		break;
-	case MA_OPT3_PRES_SCALE43:
-		// parameters for 224 lines; for 240 lines scale = 1.125
-		// moreover, H32 and H40 had the same width on a TV.
-		currentConfig.scale = 1.2;
-		currentConfig.hscale32 = 1.25;
-		currentConfig.hscale40 = 1.0;
-		break;
-	case MA_OPT3_PRES_FULLSCR:
-		// uses width 460 to avoid some ugly moiree effects
-		currentConfig.scale = 1.2;
-		currentConfig.hscale32 = 1.5;
-		currentConfig.hscale40 = 1.2;
-		break;
-	}
-	return 0;
-}
-
-static menu_entry e_menu_gfx_options[];
-
 void psp_menu_init(void)
 {
-	int i;
-	for (i = 0; e_menu_gfx_options[i].name; i++) {
-		switch (e_menu_gfx_options[i].id) {
-		case MA_OPT3_PRES_SCALE43: e_menu_gfx_options[i].help = h_scale43; break;
-		case MA_OPT3_PRES_FULLSCR: e_menu_gfx_options[i].help = h_scalefull; break;
-		}
-	}
 }

+ 3 - 3
platform/win32/plat.c

@@ -107,10 +107,10 @@ void plat_video_toggle_renderer(int change, int is_menu)
 	PicoDrawSetOutFormat(PDF_RGB555, 1);
 }
 
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)
 {
-	EmuScreenRect.left = is_32cols ? 32 : 0;
-	EmuScreenRect.right = is_32cols ? 256+32 : 320;
+	EmuScreenRect.left = start_col;
+	EmuScreenRect.right = start_col + col_count;
 	EmuScreenRect.top = start_line;
 	EmuScreenRect.bottom = start_line + line_count;
 

+ 32 - 0
submodule_update.sh

@@ -0,0 +1,32 @@
+#!/bin/sh
+echo ""
+echo "===== Updating Cyclone..."
+cd /opt/picodrive-funkey/cpu/cyclone
+git checkout master --quiet
+git pull
+echo ""
+echo "===== Updating libchdr..."
+cd /opt/picodrive-funkey/pico/cd/libchdr
+git checkout master --quiet
+git pull
+echo ""
+echo "===== Updating YM2413..."
+cd /opt/picodrive-funkey/pico/sound/emu2413
+git checkout master --quiet
+git pull
+echo ""
+echo "===== Updating libpicofe..."
+cd /opt/picodrive-funkey/platform/libpicofe
+git checkout FunKey --quiet
+git pull
+echo ""
+echo "===== Updating dr_libs..."
+cd /opt/picodrive-funkey/platform/common/dr_libs
+git checkout master --quiet
+git pull
+echo ""
+echo "===== Updating minimp3..."
+cd /opt/picodrive-funkey/platform/common/minimp3
+git checkout master --quiet
+git pull
+echo ""

+ 5 - 0
tools/mkoffsets.sh

@@ -134,9 +134,14 @@ get_define OFS_Pico_ Pico rom			; echo "$line" >>$fn
 get_define OFS_Pico_ Pico romsize		; echo "$line" >>$fn
 get_define OFS_Pico_ Pico est			; echo "$line" >>$fn
 
+get_define OFS_PicoIn_ PicoInterface opt	; echo "$line" >>$fn
+get_define OFS_PicoIn_ PicoInterface filter	; echo "$line" >>$fn
+get_define OFS_PicoIn_ PicoInterface AHW	; echo "$line" >>$fn
+
 get_define OFS_EST_ PicoEState DrawScanline	; echo "$line" >>$fn
 get_define OFS_EST_ PicoEState rendstatus	; echo "$line" >>$fn
 get_define OFS_EST_ PicoEState DrawLineDest	; echo "$line" >>$fn
+get_define OFS_EST_ PicoEState DrawLineDestIncr	; echo "$line" >>$fn
 get_define OFS_EST_ PicoEState HighCol		; echo "$line" >>$fn
 get_define OFS_EST_ PicoEState HighPreSpr	; echo "$line" >>$fn
 get_define OFS_EST_ PicoEState Pico		; echo "$line" >>$fn