summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--Makefile.linux41
-rw-r--r--Makefile.trimui52
-rw-r--r--arm/arm_stub.S5
-rw-r--r--common.h6
-rw-r--r--cpu.c2
-rw-r--r--cpu.h8
-rw-r--r--cpu_threaded.c77
-rw-r--r--frontend/audio_hotplug.h49
-rw-r--r--frontend/config.c91
-rw-r--r--frontend/config.h9
m---------frontend/libpicofe0
-rw-r--r--frontend/main.c442
-rw-r--r--frontend/main.h70
-rw-r--r--frontend/menu.c335
-rw-r--r--frontend/menu.h10
-rw-r--r--frontend/plat.h19
-rw-r--r--frontend/plat_linux.c258
-rw-r--r--frontend/plat_trimui.c286
-rw-r--r--frontend/scale.c604
-rw-r--r--frontend/scale.h9
-rw-r--r--gba_memory.c24
-rw-r--r--gba_memory.h6
-rw-r--r--gpsp_config.h2
-rw-r--r--input.c34
-rw-r--r--libretro.c19
-rw-r--r--psp/mips_stub.S3
-rw-r--r--sound.c10
-rw-r--r--sound.h8
-rw-r--r--x86/x86_stub.S2
-rw-r--r--zip.c160
-rw-r--r--zip.h26
32 files changed, 2660 insertions, 10 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..1f260d5
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "frontend/libpicofe"]
+ path = frontend/libpicofe
+ url = https://github.com/notaz/libpicofe
diff --git a/Makefile.linux b/Makefile.linux
new file mode 100644
index 0000000..066b5b9
--- /dev/null
+++ b/Makefile.linux
@@ -0,0 +1,41 @@
+# gpSP makefile
+# Gilead Kutnick - Exophase
+# pandora port - notaz
+# respberry pi - DPR
+
+# Global definitions
+
+CC = $(CROSS_COMPILE)gcc
+SYSROOT = $(shell $(CC) --print-sysroot)
+
+OBJS = main.o cpu.o gba_memory.o video.o input.o sound.o cheats.o cpu_threaded.o bios_data.o zip.o x86/x86_stub.o gba_cc_lut.o \
+ frontend/libpicofe/input.o frontend/libpicofe/in_sdl.o frontend/libpicofe/linux/in_evdev.o frontend/libpicofe/linux/plat.o frontend/libpicofe/fonts.o frontend/libpicofe/readpng.o frontend/libpicofe/config_file.o \
+ frontend/config.o frontend/menu.o frontend/plat_linux.o frontend/main.o frontend/scale.o
+
+BIN = picogpsp
+
+# Platform specific definitions
+
+CFLAGS += -DPC_BUILD -Wall -m32 -DX86_ARCH -DHAVE_DYNAREC -DHAVE_MMAP
+CFLAGS += -Ofast -fdata-sections -ffunction-sections -fno-PIC -DPICO_HOME_DIR='"/.picogpsp/"'
+CFLAGS += -I./ $(shell $(SYSROOT)/usr/bin/sdl-config --cflags)
+
+# expecting to have PATH set up to get correct sdl-config first
+
+LIBS = -m32 -lc -lgcc -lSDL -lasound -lpng -lz -Wl,--as-needed -Wl,--gc-sections -flto -s
+
+# Compilation:
+
+all: $(BIN)
+
+%.o: %.S
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+
+cpu.o cpu_threaded.o: CFLAGS += -Wno-unused-variable -Wno-unused-label
+
+$(BIN): $(OBJS)
+ $(CC) $(OBJS) $(LIBS) -o $(BIN)
+
+clean:
+ rm -f $(OBJS) $(BIN)
diff --git a/Makefile.trimui b/Makefile.trimui
new file mode 100644
index 0000000..25fe88e
--- /dev/null
+++ b/Makefile.trimui
@@ -0,0 +1,52 @@
+# gpSP makefile
+# Gilead Kutnick - Exophase
+# pandora port - notaz
+# respberry pi - DPR
+
+# Global definitions
+
+CC = $(CROSS_COMPILE)gcc
+SYSROOT = $(shell $(CC) --print-sysroot)
+
+OBJS = main.o cpu.o gba_memory.o video.o input.o sound.o gba_cc_lut.o \
+ bios_data.o cheats.o zip.o arm/arm_stub.o cpu_threaded.o arm/video_blend.o \
+ frontend/libpicofe/input.o frontend/libpicofe/in_sdl.o \
+ frontend/libpicofe/linux/in_evdev.o frontend/libpicofe/linux/plat.o \
+ frontend/libpicofe/fonts.o frontend/libpicofe/readpng.o frontend/libpicofe/config_file.o \
+ frontend/config.o frontend/menu.o frontend/plat_trimui.o frontend/main.o frontend/scale.o
+
+BIN = picogpsp
+
+# Platform specific definitions
+
+VPATH += .. ../arm
+CFLAGS += -DARM_ARCH -DARM_ARCH_BLENDING_OPTS -DPC_BUILD -Wall -DHAVE_DYNAREC
+CFLAGS += -Ofast -fdata-sections -ffunction-sections -flto -fno-PIC -DPICO_HOME_DIR='"/.picogpsp/"'
+CFLAGS += -I./ $(shell $(SYSROOT)/usr/bin/sdl-config --cflags)
+
+# expecting to have PATH set up to get correct sdl-config first
+
+LIBS = -lc -lgcc -lSDL -lasound -lpng -lz -Wl,--as-needed -Wl,--gc-sections -flto -s
+
+ifeq ($(PROFILE), YES)
+CFLAGS += -fprofile-generate=./profile
+LIBS += -lgcov
+else ifeq ($(PROFILE), APPLY)
+CFLAGS += -fprofile-use -fprofile-dir=./profile -fbranch-probabilities
+endif
+
+# Compilation:
+
+all: $(BIN)
+
+%.o: %.S
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+
+cpu.o cpu_threaded.o: CFLAGS += -Wno-unused-variable -Wno-unused-label
+
+$(BIN): $(OBJS)
+ $(CC) $(OBJS) $(LIBS) -o $(BIN)
+
+clean:
+ rm -f $(OBJS) $(BIN)
diff --git a/arm/arm_stub.S b/arm/arm_stub.S
index d7203f8..848d311 100644
--- a/arm/arm_stub.S
+++ b/arm/arm_stub.S
@@ -444,6 +444,8 @@ defsymbl(execute_swi_##mode) ;\
orr r0, r0, #0x13 /* set to supervisor mode */;\
str r0, [reg_base, #REG_CPSR] /* update cpsr */;\
;\
+ call_c_function(bios_region_read_allow) ;\
+ ;\
mov r0, #MODE_SUPERVISOR ;\
;\
store_registers_##mode() /* store regs for mode */;\
@@ -877,6 +879,9 @@ defsymbl(rom_translation_cache)
defsymbl(ram_translation_cache)
.space RAM_TRANSLATION_CACHE_SIZE
.size ram_translation_cache, .-ram_translation_cache
+defsymbl(bios_translation_cache)
+ .space BIOS_TRANSLATION_CACHE_SIZE
+.size bios_translation_cache, .-bios_translation_cache
#endif
diff --git a/common.h b/common.h
index ff4d522..714d607 100644
--- a/common.h
+++ b/common.h
@@ -31,6 +31,10 @@
#define PATH_SEPARATOR_CHAR '/'
#endif
+#define prefetch(a,b) __builtin_prefetch(a,b)
+#define likely(x) __builtin_expect((x),1)
+#define unlikely(x) __builtin_expect((x),0)
+
/* On x86 we pass arguments via registers instead of stack */
#ifdef X86_ARCH
#define function_cc __attribute__((regparm(2)))
@@ -40,7 +44,7 @@
#ifdef ARM_ARCH
-#define _BSD_SOURCE // sync
+#define _DEFAULT_SOURCE 1 // sync
#include <stdlib.h>
#include <string.h>
#include <math.h>
diff --git a/cpu.c b/cpu.c
index 60ff3bc..e334203 100644
--- a/cpu.c
+++ b/cpu.c
@@ -1614,6 +1614,8 @@ void raise_interrupt(irq_type irq_raised)
reg[REG_CPSR] = 0xD2;
reg[REG_PC] = 0x00000018;
+ bios_region_read_allow();
+
set_cpu_mode(MODE_IRQ);
reg[CPU_HALT_STATE] = CPU_ACTIVE;
reg[CHANGED_PC_STATUS] = 1;
diff --git a/cpu.h b/cpu.h
index 2dacd6a..2d252d1 100644
--- a/cpu.h
+++ b/cpu.h
@@ -93,6 +93,7 @@ typedef enum
{
TRANSLATION_REGION_RAM,
TRANSLATION_REGION_ROM,
+ TRANSLATION_REGION_BIOS
} translation_region_type;
extern u32 instruction_count;
@@ -126,22 +127,28 @@ s32 translate_block_thumb(u32 pc, translation_region_type translation_region,
#if defined(HAVE_MMAP)
extern u8* rom_translation_cache;
extern u8* ram_translation_cache;
+extern u8* bios_translation_cache;
#elif defined(_3DS)
#define rom_translation_cache ((u8*)0x02000000 - ROM_TRANSLATION_CACHE_SIZE)
#define ram_translation_cache (rom_translation_cache - RAM_TRANSLATION_CACHE_SIZE)
+#define bios_translation_cache (ram_translation_cache - BIOS_TRANSLATION_CACHE_SIZE)
extern u8* rom_translation_cache_ptr;
extern u8* ram_translation_cache_ptr;
+extern u8* bios_translation_cache_ptr;
#elif defined(VITA)
extern u8* rom_translation_cache;
extern u8* ram_translation_cache;
+extern u8* bios_translation_cache;
extern int sceBlock;
#else
extern u8 rom_translation_cache[ROM_TRANSLATION_CACHE_SIZE];
extern u8 ram_translation_cache[RAM_TRANSLATION_CACHE_SIZE];
+extern u8 bios_translation_cache[BIOS_TRANSLATION_CACHE_SIZE];
#endif
extern u32 stub_arena[STUB_ARENA_SIZE / 4];
extern u8 *rom_translation_ptr;
extern u8 *ram_translation_ptr;
+extern u8 *bios_translation_ptr;
#define MAX_TRANSLATION_GATES 8
@@ -156,6 +163,7 @@ extern u32 *rom_branch_hash[ROM_BRANCH_HASH_SIZE];
void flush_translation_cache_rom(void);
void flush_translation_cache_ram(void);
+void flush_translation_cache_bios(void);
void dump_translation_cache(void);
void init_caches(void);
void init_emitter(void);
diff --git a/cpu_threaded.c b/cpu_threaded.c
index 557b197..ad2c098 100644
--- a/cpu_threaded.c
+++ b/cpu_threaded.c
@@ -29,26 +29,34 @@
u8 *last_rom_translation_ptr = NULL;
u8 *last_ram_translation_ptr = NULL;
+u8 *last_bios_translation_ptr = NULL;
#if defined(HAVE_MMAP)
u8* rom_translation_cache;
u8* ram_translation_cache;
+u8* bios_translation_cache;
u8 *rom_translation_ptr;
u8 *ram_translation_ptr;
+u8 *bios_translation_ptr;
#elif defined(VITA)
u8* rom_translation_cache;
u8* ram_translation_cache;
+u8* bios_translation_cache;
u8 *rom_translation_ptr;
u8 *ram_translation_ptr;
+u8 *bios_translation_ptr;
int sceBlock;
#elif defined(_3DS)
u8* rom_translation_cache_ptr;
u8* ram_translation_cache_ptr;
+u8* bios_translation_cache_ptr;
u8 *rom_translation_ptr = rom_translation_cache;
u8 *ram_translation_ptr = ram_translation_cache;
+u8 *bios_translation_ptr = bios_translation_cache;
#else
u8 *rom_translation_ptr = rom_translation_cache;
u8 *ram_translation_ptr = ram_translation_cache;
+u8 *bios_translation_ptr = bios_translation_cache;
#endif
/* Note, see stub files for more cache definitions */
@@ -244,6 +252,10 @@ void translate_icache_sync() {
platform_cache_sync(last_ram_translation_ptr, ram_translation_ptr);
last_ram_translation_ptr = ram_translation_ptr;
}
+ if (last_bios_translation_ptr < bios_translation_ptr) {
+ platform_cache_sync(last_bios_translation_ptr, bios_translation_ptr);
+ last_bios_translation_ptr = bios_translation_ptr;
+ }
}
/* End of Cache invalidation */
@@ -2443,6 +2455,9 @@ void translate_icache_sync() {
u8 *ram_block_ptrs[1024 * 64];
u32 ram_block_tag_top = 0x0101;
+u8 *bios_block_ptrs[1024 * 8];
+u32 bios_block_tag_top = 0x0101;
+
// This function will return a pointer to a translated block of code. If it
// doesn't exist it will translate it, if it does it will pass it back.
@@ -2472,6 +2487,7 @@ u32 ram_block_tag_top = 0x0101;
#define ram_translation_region TRANSLATION_REGION_RAM
#define rom_translation_region TRANSLATION_REGION_ROM
+#define bios_translation_region TRANSLATION_REGION_BIOS
#define block_lookup_translate_arm(mem_type, smc_enable) \
translation_result = translate_block_arm(pc, mem_type##_translation_region, \
@@ -2567,17 +2583,28 @@ u8 function_cc *block_lookup_address_##type(u32 pc) \
\
switch(pc >> 24) \
{ \
+ case 0x0: \
+ bios_region_read_allow(); \
+ location = (u16 *)(bios_rom + pc + 0x4000); \
+ block_lookup_translate(type, bios, 0); \
+ if(translation_recursion_level == 0) \
+ bios_region_read_allow(); \
+ break; \
+ \
case 0x2: \
location = (u16 *)(ewram + (pc & 0x3FFFF) + 0x40000); \
block_lookup_translate(type, ram, 1); \
+ if(translation_recursion_level == 0) \
+ bios_region_read_protect(); \
break; \
\
case 0x3: \
location = (u16 *)(iwram + (pc & 0x7FFF)); \
block_lookup_translate(type, ram, 1); \
+ if(translation_recursion_level == 0) \
+ bios_region_read_protect(); \
break; \
\
- case 0x0: \
case 0x8 ... 0xD: \
{ \
u32 hash_target = ((pc * 2654435761U) >> 16) & \
@@ -2601,7 +2628,7 @@ u8 function_cc *block_lookup_address_##type(u32 pc) \
\
redo: \
\
- translation_recursion_level++; \
+ translation_recursion_level++; \
((u32 *)rom_translation_ptr)[0] = pc; \
((u32 **)rom_translation_ptr)[1] = NULL; \
*block_ptr_address = (u32 *)rom_translation_ptr; \
@@ -2623,6 +2650,8 @@ u8 function_cc *block_lookup_address_##type(u32 pc) \
if(translation_recursion_level == 0) \
translate_icache_sync(); \
} \
+ if(translation_recursion_level == 0) \
+ bios_region_read_protect(); \
break; \
} \
\
@@ -3037,6 +3066,12 @@ s32 translate_block_arm(u32 pc, translation_region_type
rom_translation_cache + ROM_TRANSLATION_CACHE_SIZE -
TRANSLATION_CACHE_LIMIT_THRESHOLD;
break;
+
+ case TRANSLATION_REGION_BIOS:
+ translation_ptr = bios_translation_ptr;
+ translation_cache_limit = bios_translation_cache +
+ BIOS_TRANSLATION_CACHE_SIZE;
+ break;
}
generate_block_prologue();
@@ -3104,6 +3139,10 @@ s32 translate_block_arm(u32 pc, translation_region_type
case TRANSLATION_REGION_ROM:
flush_translation_cache_rom();
break;
+
+ case TRANSLATION_REGION_BIOS:
+ flush_translation_cache_bios();
+ break;
}
return -1;
}
@@ -3171,6 +3210,10 @@ s32 translate_block_arm(u32 pc, translation_region_type
case TRANSLATION_REGION_ROM:
rom_translation_ptr = translation_ptr;
break;
+
+ case TRANSLATION_REGION_BIOS:
+ bios_translation_ptr = translation_ptr;
+ break;
}
for(i = 0; i < external_block_exit_position; i++)
@@ -3243,6 +3286,12 @@ s32 translate_block_thumb(u32 pc, translation_region_type
rom_translation_cache + ROM_TRANSLATION_CACHE_SIZE -
TRANSLATION_CACHE_LIMIT_THRESHOLD;
break;
+
+ case TRANSLATION_REGION_BIOS:
+ translation_ptr = bios_translation_ptr;
+ translation_cache_limit = bios_translation_cache +
+ BIOS_TRANSLATION_CACHE_SIZE;
+ break;
}
generate_block_prologue();
@@ -3308,6 +3357,10 @@ s32 translate_block_thumb(u32 pc, translation_region_type
case TRANSLATION_REGION_ROM:
flush_translation_cache_rom();
break;
+
+ case TRANSLATION_REGION_BIOS:
+ flush_translation_cache_bios();
+ break;
}
return -1;
}
@@ -3375,6 +3428,10 @@ s32 translate_block_thumb(u32 pc, translation_region_type
case TRANSLATION_REGION_ROM:
rom_translation_ptr = translation_ptr;
break;
+
+ case TRANSLATION_REGION_BIOS:
+ bios_translation_ptr = translation_ptr;
+ break;
}
for(i = 0; i < external_block_exit_position; i++)
@@ -3431,6 +3488,16 @@ void flush_translation_cache_rom(void)
memset(rom_branch_hash, 0, sizeof(rom_branch_hash));
}
+void flush_translation_cache_bios(void)
+{
+ bios_block_tag_top = 0x0101;
+
+ last_bios_translation_ptr = bios_translation_cache;
+ bios_translation_ptr = bios_translation_cache;
+
+ memset(bios_rom + 0x4000, 0, 0x4000);
+}
+
void init_caches(void)
{
/* Ensure we wipe everything including the SMC mirrors */
@@ -3440,6 +3507,7 @@ void init_caches(void)
iwram_code_min = 0;
iwram_code_max = 0x7FFF;
flush_translation_cache_ram();
+ flush_translation_cache_bios();
/* Ensure 0 and FFFF get zeroed out */
memset(ram_block_ptrs, 0, sizeof(ram_block_ptrs));
}
@@ -3457,6 +3525,11 @@ void dump_translation_cache(void)
fwrite(rom_translation_cache, 1,
rom_translation_ptr - rom_translation_cache, fd);
fclose(fd);
+
+ fd = fopen(cache_dump_prefix "bios_cache.bin", "wb");
+ fwrite(bios_translation_cache, 1,
+ bios_translation_ptr - bios_translation_cache, fd);
+ fclose(fd);
}
diff --git a/frontend/audio_hotplug.h b/frontend/audio_hotplug.h
new file mode 100644
index 0000000..db4eb59
--- /dev/null
+++ b/frontend/audio_hotplug.h
@@ -0,0 +1,49 @@
+#ifndef CHECK_AUDIO_H
+#define CHECK_AUDIO_H
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+/* Only check once every 60 calls, so this can be called every frame
+ * without much penalty */
+#define AUDIO_CHECK_THROTTLE 60
+
+bool found_usb_audio = false;
+bool has_usb_audio = false;
+
+/* Every AUDIO_CHECK_THROTTLE calls, check to see if the USB audio
+ * device has been plugged or unplugged. When the status changes,
+ * calls `reinit_callback` with the new status. */
+void audio_hotplug_check(void (*reinit_callback)(bool has_usb_audio)) {
+ static int count;
+
+ if (++count < AUDIO_CHECK_THROTTLE) return;
+
+ count = 0;
+
+ if (access("/dev/dsp1", R_OK | W_OK) == 0) {
+ found_usb_audio = true;
+ } else {
+ found_usb_audio = false;
+ }
+
+ if (!has_usb_audio && found_usb_audio) {
+ has_usb_audio = true;
+ reinit_callback(true);
+ } else if (has_usb_audio && !found_usb_audio) {
+ has_usb_audio = false;
+ reinit_callback(false);
+ }
+}
+
+/* For SDL apps, set audio to the correct device. */
+void audio_hotplug_set_device(bool has_usb_audio) {
+ if (has_usb_audio) {
+ SDL_putenv("AUDIODEV=/dev/dsp1");
+ } else {
+ SDL_putenv("AUDIODEV=/dev/dsp");
+ }
+}
+
+#endif
diff --git a/frontend/config.c b/frontend/config.c
new file mode 100644
index 0000000..027fb8e
--- /dev/null
+++ b/frontend/config.c
@@ -0,0 +1,91 @@
+#include "common.h"
+#include "frontend/main.h"
+#include "frontend/config.h"
+
+typedef enum {
+ CE_TYPE_STR = 0,
+ CE_TYPE_NUM = 4,
+} config_entry_type;
+
+#define CE_STR(val) \
+ { #val, CE_TYPE_STRING, val }
+
+#define CE_NUM(val) \
+ { #val, CE_TYPE_NUM, &val }
+
+static const struct {
+ const char *name;
+ config_entry_type type;
+ void *val;
+} config_data[] = {
+ CE_NUM(dynarec_enable),
+ CE_NUM(frameskip_style),
+ CE_NUM(max_frameskip),
+ CE_NUM(scaling_mode),
+ CE_NUM(color_correct),
+ CE_NUM(lcd_blend),
+ CE_NUM(show_fps),
+};
+
+void config_write(FILE *f)
+{
+ for (int i = 0; i < array_size(config_data); i++) {
+ switch (config_data[i].type)
+ {
+ case CE_TYPE_STR:
+ fprintf(f, "%s = %s\n", config_data[i].name, (char *)config_data[i].val);
+ break;
+ case CE_TYPE_NUM:
+ fprintf(f, "%s = %u\n", config_data[i].name, *(uint32_t *)config_data[i].val);
+ break;
+ default:
+ printf("unhandled type %d for %s\n", config_data[i].type, (char *)config_data[i].val);
+ break;
+ }
+ }
+}
+
+static void parse_str_val(char *cval, const char *src)
+{
+ char *tmp;
+ strncpy(cval, src, 256);
+ cval[256 - 1] = 0;
+ tmp = strchr(cval, '\n');
+ if (tmp == NULL)
+ tmp = strchr(cval, '\r');
+ if (tmp != NULL)
+ *tmp = 0;
+}
+
+static void parse_num_val(uint32_t *cval, const char *src)
+{
+ char *tmp = NULL;
+ uint32_t val;
+ val = strtoul(src, &tmp, 10);
+ if (tmp == NULL || src == tmp)
+ return; // parse failed
+
+ *cval = val;
+}
+
+void config_read(const char* cfg)
+{
+ for (int i = 0; i < array_size(config_data); i++) {
+ char *tmp;
+
+ tmp = strstr(cfg, config_data[i].name);
+ if (tmp == NULL)
+ continue;
+ tmp += strlen(config_data[i].name);
+ if (strncmp(tmp, " = ", 3) != 0)
+ continue;
+ tmp += 3;
+
+ if (config_data[i].type == CE_TYPE_STR) {
+ parse_str_val(config_data[i].val, tmp);
+ continue;
+ }
+
+ parse_num_val(config_data[i].val, tmp);
+ }
+}
diff --git a/frontend/config.h b/frontend/config.h
new file mode 100644
index 0000000..d45e435
--- /dev/null
+++ b/frontend/config.h
@@ -0,0 +1,9 @@
+#ifndef FRONTEND_CONFIG_H
+#define FRONTEND_CONFIG_H
+
+#include <stdio.h>
+
+void config_write(FILE *f);
+void config_read(const char *cfg);
+
+#endif
diff --git a/frontend/libpicofe b/frontend/libpicofe
new file mode 160000
+Subproject 76e45c3489a0c32d91744413c198f685b553f6c
diff --git a/frontend/main.c b/frontend/main.c
new file mode 100644
index 0000000..f303201
--- /dev/null
+++ b/frontend/main.c
@@ -0,0 +1,442 @@
+#include <stdio.h>
+#include <string.h>
+#include "common.h"
+#include "main.h"
+#include "memmap.h"
+#include "frontend/menu.h"
+#include "frontend/plat.h"
+#include "frontend/libpicofe/plat.h"
+
+/* Percentage of free space allowed in the audio buffer before
+ * skipping frames. Lower numbers mean more skipping but smoother
+ * audio, since the buffer will stay closer to filled. */
+#define FRAMESKIP_UNDERRUN_THRESHOLD 0.5
+
+int should_quit = 0;
+
+u32 idle_loop_target_pc = 0xFFFFFFFF;
+u32 iwram_stack_optimize = 1;
+u32 translation_gate_target_pc[MAX_TRANSLATION_GATES];
+u32 translation_gate_targets = 0;
+
+uint16_t *gba_screen_pixels_prev = NULL;
+uint16_t *gba_processed_pixels = NULL;
+
+int use_libretro_save_method = 0;
+bios_type selected_bios = auto_detect;
+boot_mode selected_boot_mode = boot_game;
+
+u32 skip_next_frame = 0;
+
+int dynarec_enable;
+int state_slot;
+frameskip_style_t frameskip_style;
+scaling_mode_t scaling_mode;
+int max_frameskip;
+int color_correct;
+int lcd_blend;
+int show_fps;
+int limit_frames;
+
+static float vsyncsps = 0.0;
+static float rendersps = 0.0;
+
+void quit();
+
+void gamepak_related_name(char *buf, size_t len, char *new_extension)
+{
+ char root_dir[512];
+ char filename[512];
+ char *p;
+
+ plat_get_root_dir(root_dir, len);
+ p = strrchr(gamepak_filename, PATH_SEPARATOR_CHAR);
+
+ if (p)
+ p++;
+ else
+ p = gamepak_filename;
+ strncpy(filename, p, sizeof(filename));
+ filename[sizeof(filename) - 1] = 0;
+ p = strrchr(filename, '.');
+ if (p)
+ *p = 0;
+
+ snprintf(buf, len, "%s%s%s", root_dir, filename, new_extension);
+}
+
+void toggle_fast_forward(int force_off)
+{
+ static frameskip_style_t frameskip_style_was;
+ static int max_frameskip_was;
+ static int limit_frames_was;
+ static int global_process_audio_was;
+ static int fast_forward;
+
+ if (force_off && !fast_forward)
+ return;
+
+ fast_forward = !fast_forward;
+
+ if (fast_forward) {
+ frameskip_style_was = frameskip_style;
+ max_frameskip_was = max_frameskip;
+ limit_frames_was = limit_frames;
+ global_process_audio_was = global_process_audio;
+
+ frameskip_style = FRAMESKIP_MANUAL;
+ max_frameskip = 5;
+ limit_frames = 0;
+ global_process_audio = 0;
+ } else {
+ frameskip_style = frameskip_style_was;
+ max_frameskip = max_frameskip_was;
+ limit_frames = limit_frames_was;
+ global_process_audio = global_process_audio_was;
+ }
+}
+
+void state_file_name(char *buf, size_t len, unsigned state_slot)
+{
+ char ext[20];
+ snprintf(ext, sizeof(ext), ".st%d", state_slot);
+
+ gamepak_related_name(buf, len, ext);
+}
+
+void config_file_name(char *buf, size_t len, int is_game)
+{
+ char root_dir[MAXPATHLEN];
+
+ if (is_game) {
+ gamepak_related_name(buf, len, ".cfg");
+ } else {
+ plat_get_root_dir(root_dir, MAXPATHLEN);
+ snprintf(buf, len, "%s%s", root_dir, "picogpsp.cfg");
+ }
+}
+
+void handle_emu_action(emu_action action)
+{
+ static frameskip_style_t prev_frameskip_style;
+ static emu_action prev_action = EACTION_NONE;
+ if (prev_action != EACTION_NONE && prev_action == action) return;
+
+ switch (action)
+ {
+ case EACTION_NONE:
+ break;
+ case EACTION_QUIT:
+ should_quit = 1;
+ break;
+ case EACTION_TOGGLE_FPS:
+ show_fps = !show_fps;
+ /* Force the hud to clear */
+ plat_video_set_msg(" ");
+ break;
+ case EACTION_SAVE_STATE:
+ save_state_file(0);
+ break;
+ case EACTION_LOAD_STATE:
+ load_state_file(0);
+ break;
+ case EACTION_TOGGLE_FSKIP:
+ if (prev_frameskip_style == FRAMESKIP_NONE)
+ prev_frameskip_style = FRAMESKIP_AUTO;
+
+ if (frameskip_style == FRAMESKIP_NONE) {
+ frameskip_style = prev_frameskip_style;
+ } else {
+ prev_frameskip_style = frameskip_style;
+ frameskip_style = FRAMESKIP_NONE;
+ }
+ break;
+ case EACTION_TOGGLE_FF:
+ toggle_fast_forward(0);
+ break;
+ case EACTION_MENU:
+ toggle_fast_forward(1);
+ update_backup();
+ menu_loop();
+ break;
+ default:
+ break;
+ }
+
+ prev_action = action;
+}
+
+void synchronize(void)
+{
+ static uint32_t vsyncs = 0;
+ static uint32_t renders = 0;
+ static uint32_t nextsec = 0;
+ static uint32_t skipped_frames = 0;
+ unsigned int ticks = 0;
+
+ float capacity = plat_sound_capacity();
+
+ switch (frameskip_style)
+ {
+ case FRAMESKIP_AUTO:
+ skip_next_frame = 0;
+
+ if (capacity > FRAMESKIP_UNDERRUN_THRESHOLD) {
+ skip_next_frame = 1;
+ skipped_frames++;
+ }
+ break;
+ case FRAMESKIP_MANUAL:
+ skip_next_frame = 1;
+ skipped_frames++;
+ break;
+ default:
+ skip_next_frame = 0;
+ break;
+ }
+
+ if (skipped_frames > max_frameskip) {
+ skip_next_frame = 0;
+ skipped_frames = 0;
+ }
+
+ if (show_fps) {
+ ticks = plat_get_ticks_ms();
+ if (ticks > nextsec) {
+ vsyncsps = vsyncs;
+ rendersps = renders;
+ vsyncs = 0;
+ renders = 0;
+ nextsec = ticks + 1000;
+ }
+ vsyncs++;
+ if (!skip_next_frame) renders++;
+ }
+}
+
+void print_hud()
+{
+ char msg[HUD_LEN];
+ if (show_fps) {
+ snprintf(msg, HUD_LEN, "FPS: %2.0f (%4.1f)", rendersps, vsyncsps);
+ plat_video_set_msg(msg);
+ }
+}
+
+int save_state_file(unsigned state_slot)
+{
+ char state_filename[MAXPATHLEN];
+ void *data;
+ FILE *f;
+ int ret = 0;
+ state_file_name(state_filename, MAXPATHLEN, state_slot);
+
+ f = fopen(state_filename, "wb");
+
+ if (!f)
+ return -1;
+
+ data = calloc(1, GBA_STATE_MEM_SIZE);
+ if (!data) {
+ ret = -1;
+ goto fail;
+ }
+
+ gba_save_state(data);
+
+ if (fwrite(data, 1, GBA_STATE_MEM_SIZE, f) != GBA_STATE_MEM_SIZE) {
+ ret = -1;
+ goto fail;
+ }
+
+fail:
+ if (data)
+ free(data);
+ if (f)
+ fclose(f);
+
+ return ret;
+}
+
+int load_state_file(unsigned state_slot)
+{
+ char state_filename[MAXPATHLEN];
+ void *data;
+ FILE *f;
+ int ret = 0;
+ state_file_name(state_filename, MAXPATHLEN, state_slot);
+
+ f = fopen(state_filename, "rb");
+
+ if (!f)
+ return -1;
+
+ data = calloc(1, GBA_STATE_MEM_SIZE);
+ if (!data) {
+ ret = -1;
+ goto fail;
+ }
+
+
+ if (fread(data, 1, GBA_STATE_MEM_SIZE, f) != GBA_STATE_MEM_SIZE) {
+ ret = -1;
+ goto fail;
+ }
+
+ gba_load_state(data);
+
+fail:
+ if (data)
+ free(data);
+ if (f)
+ fclose(f);
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ bool bios_loaded = false;
+ char bios_filename[MAXPATHLEN];
+ char filename[MAXPATHLEN];
+ char path[MAXPATHLEN];
+
+ if (argc < 2) {
+ printf("Usage: picogpsp FILE");
+ return 0;
+ };
+
+ strncpy(filename, argv[1], MAXPATHLEN);
+ if (filename[0] != '/') {
+ getcwd(path, MAXPATHLEN);
+ if (strlen(path) + strlen(filename) + 1 < MAXPATHLEN) {
+ strcat(path, "/");
+ strcat(path, filename);
+ strcpy(filename, path);
+ } else
+ filename[0] = 0;
+ }
+
+ if (selected_bios == auto_detect || selected_bios == official_bios) {
+ bios_loaded = true;
+ getcwd(bios_filename, MAXPATHLEN);
+ strncat(bios_filename, "/gba_bios.bin", MAXPATHLEN - strlen(bios_filename));
+
+ if (load_bios(bios_filename)) {
+ if (selected_bios == official_bios)
+ printf("Could not load BIOS image file\n");
+ bios_loaded = false;
+ }
+
+ if (bios_loaded && bios_rom[0] != 0x18) {
+ if (selected_bios == official_bios)
+ printf("BIOS image seems incorrect\n");
+ bios_loaded = false;
+ }
+ }
+
+ if (bios_loaded) {
+ printf("Using official BIOS\n");
+ } else {
+ /* Load the built-in BIOS */
+ memcpy(bios_rom, open_gba_bios_rom, sizeof(bios_rom));
+ printf("Using built-in BIOS\n");
+ }
+
+ getcwd(main_path, 512);
+ plat_get_root_dir(save_path, 512);
+
+ if (!gamepak_rom)
+ init_gamepak_buffer();
+
+ if(!gba_screen_pixels)
+ gba_screen_pixels = (uint16_t*)calloc(GBA_SCREEN_PITCH * GBA_SCREEN_HEIGHT, sizeof(uint16_t));
+
+ if (plat_init()) {
+ return -1;
+ };
+
+ if (load_gamepak(filename) != 0)
+ {
+ fprintf(stderr, "Could not load the game file.\n");
+ return -1;
+ }
+
+ init_main();
+ init_sound(1);
+ menu_init();
+
+#if defined(HAVE_DYNAREC)
+ if (dynarec_enable)
+ {
+#ifdef HAVE_MMAP
+ rom_translation_cache = mmap(NULL, ROM_TRANSLATION_CACHE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
+ ram_translation_cache = mmap(NULL, RAM_TRANSLATION_CACHE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
+ bios_translation_cache = mmap(NULL, BIOS_TRANSLATION_CACHE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
+#endif
+ }
+ else
+ dynarec_enable = 0;
+#else
+ dynarec_enable = 0;
+#endif
+
+ reset_gba();
+
+ do {
+ update_input();
+
+ synchronize();
+
+#ifdef HAVE_DYNAREC
+ if (dynarec_enable)
+ execute_arm_translate(execute_cycles);
+ else
+#endif
+ execute_arm(execute_cycles);
+
+ render_audio();
+
+ print_hud();
+
+ if (!skip_next_frame)
+ plat_video_flip();
+ } while (!should_quit);
+
+ quit();
+ return 0;
+}
+
+void quit()
+{
+ update_backup();
+
+ memory_term();
+
+ if (gba_screen_pixels_prev) {
+ free(gba_screen_pixels_prev);
+ gba_screen_pixels_prev = NULL;
+ }
+
+ if (gba_processed_pixels) {
+ free(gba_processed_pixels);
+ gba_processed_pixels = NULL;
+ }
+
+ free(gba_screen_pixels);
+ gba_screen_pixels = NULL;
+
+#if defined(HAVE_MMAP) && defined(HAVE_DYNAREC)
+ munmap(rom_translation_cache, ROM_TRANSLATION_CACHE_SIZE);
+ munmap(ram_translation_cache, RAM_TRANSLATION_CACHE_SIZE);
+ munmap(bios_translation_cache, BIOS_TRANSLATION_CACHE_SIZE);
+#endif
+
+ menu_finish();
+ plat_finish();
+
+ exit(0);
+}
diff --git a/frontend/main.h b/frontend/main.h
new file mode 100644
index 0000000..d039c3b
--- /dev/null
+++ b/frontend/main.h
@@ -0,0 +1,70 @@
+#ifndef __FRONTEND_MAIN_H__
+#define __FRONTEND_MAIN_H__
+
+#include <stddef.h>
+
+#define MAXPATHLEN 512
+
+typedef enum {
+ EACTION_NONE = 0,
+ EACTION_MENU,
+ EACTION_TOGGLE_FPS,
+ EACTION_TOGGLE_FSKIP,
+ EACTION_TOGGLE_FF,
+ EACTION_SAVE_STATE,
+ EACTION_LOAD_STATE,
+ EACTION_QUIT,
+} emu_action;
+
+typedef enum {
+ KBIT_A = 0,
+ KBIT_B,
+ KBIT_SELECT,
+ KBIT_START,
+ KBIT_RIGHT,
+ KBIT_LEFT,
+ KBIT_UP,
+ KBIT_DOWN,
+ KBIT_R,
+ KBIT_L
+} keybit;
+
+typedef enum {
+ FRAMESKIP_NONE = 0,
+ FRAMESKIP_AUTO,
+ FRAMESKIP_MANUAL,
+} frameskip_style_t;
+
+typedef enum {
+ SCALING_NONE = 0,
+ SCALING_ASPECT_SHARP,
+ SCALING_ASPECT_SMOOTH,
+ SCALING_FULL_SHARP,
+ SCALING_FULL_SMOOTH,
+} scaling_mode_t;
+
+
+extern int should_quit;
+
+extern int state_slot;
+extern int dynarec_enable;
+extern frameskip_style_t frameskip_style;
+extern scaling_mode_t scaling_mode;
+extern int max_frameskip;
+extern int color_correct;
+extern int lcd_blend;
+extern int show_fps;
+extern int limit_frames;
+
+extern uint16_t *gba_screen_pixels_prev;
+extern uint16_t *gba_processed_pixels;
+
+#define array_size(x) (sizeof(x) / sizeof(x[0]))
+
+void state_file_name(char *buf, size_t len, unsigned state_slot);
+void config_file_name(char *buf, size_t len, int is_game);
+void handle_emu_action(emu_action action);
+int save_state_file(unsigned state_slot);
+int load_state_file(unsigned state_slot);
+
+#endif /* __FRONTEND_MAIN_H__ */
diff --git a/frontend/menu.c b/frontend/menu.c
new file mode 100644
index 0000000..44bcd07
--- /dev/null
+++ b/frontend/menu.c
@@ -0,0 +1,335 @@
+#include <sys/stat.h>
+#include "common.h"
+
+#include "frontend/config.h"
+#include "frontend/main.h"
+#include "frontend/menu.h"
+#include "frontend/scale.h"
+#include "frontend/plat.h"
+
+#include "frontend/libpicofe/config_file.h"
+
+#define MENU_ALIGN_LEFT 0
+#define MENU_X2 0
+
+typedef enum
+{
+ MA_NONE = 1,
+ MA_MAIN_RESUME_GAME,
+ MA_MAIN_SAVE_STATE,
+ MA_MAIN_LOAD_STATE,
+ MA_MAIN_RESET_GAME,
+ MA_MAIN_CREDITS,
+ MA_MAIN_EXIT,
+ MA_OPT_SAVECFG,
+ MA_OPT_SAVECFG_GAME,
+ MA_CTRL_PLAYER1,
+ MA_CTRL_EMU,
+} menu_id;
+
+int menu_save_config(int is_game);
+void menu_set_defaults(void);
+
+me_bind_action me_ctrl_actions[] =
+{
+ { "UP ", 1 << KBIT_UP},
+ { "DOWN ", 1 << KBIT_DOWN },
+ { "LEFT ", 1 << KBIT_LEFT },
+ { "RIGHT ", 1 << KBIT_RIGHT },
+ { "A BUTTON ", 1 << KBIT_A },
+ { "B BUTTON ", 1 << KBIT_B },
+ { "START ", 1 << KBIT_START },
+ { "SELECT ", 1 << KBIT_SELECT },
+ { "L BUTTON ", 1 << KBIT_L },
+ { "R BUTTON ", 1 << KBIT_R },
+ { NULL, 0 }
+};
+
+me_bind_action emuctrl_actions[] =
+{
+ { "Save State ", 1 << EACTION_SAVE_STATE },
+ { "Load State ", 1 << EACTION_LOAD_STATE },
+ { "Toggle Frameskip ", 1 << EACTION_TOGGLE_FSKIP },
+ { "Show/Hide FPS ", 1 << EACTION_TOGGLE_FPS },
+ { "Toggle FF ", 1 << EACTION_TOGGLE_FF },
+ { "Enter Menu ", 1 << EACTION_MENU },
+ { NULL, 0 }
+};
+
+int emu_check_save_file(int slot, int *time)
+{
+ char fname[MAXPATHLEN];
+ struct stat status;
+ int ret;
+
+ state_file_name(fname, sizeof(fname), slot);
+
+ ret = stat(fname, &status);
+ if (ret != 0)
+ return 0;
+
+ return 1;
+}
+
+int emu_save_load_game(int load, int unused)
+{
+ int ret;
+
+ if (load)
+ ret = load_state_file(state_slot);
+ else
+ ret = save_state_file(state_slot);
+
+ return ret;
+}
+
+#include "frontend/libpicofe/menu.c"
+
+static const char *mgn_saveloadcfg(int id, int *offs)
+{
+ return "";
+}
+
+static int mh_restore_defaults(int id, int keys)
+{
+ menu_set_defaults();
+ menu_update_msg("defaults restored");
+ return 1;
+}
+
+static int mh_savecfg(int id, int keys)
+{
+ if (menu_save_config(id == MA_OPT_SAVECFG_GAME ? 1 : 0) == 0)
+ menu_update_msg("config saved");
+ else
+ menu_update_msg("failed to write config");
+
+ return 1;
+}
+
+static const char h_restore_def[] = "Switches back to default / recommended\n"
+ "configuration";
+
+static const char h_color_correct[] = "Modifies colors to simulate original display";
+static const char h_lcd_blend[] = "Blends frames to simulate LCD lag";
+static const char h_show_fps[] = "Shows frames and vsyncs per second";
+static const char h_dynarec_enable[] = "Improves performance, but may reduce accuracy";
+
+
+static const char *men_frameskip[] = { "OFF", "Auto", "Manual", NULL };
+
+static const char *men_scaling[] = { "Native", "3:2 Sharp", "3:2 Smooth", "4:3 Sharp", "4:3 Smooth", NULL};
+
+static menu_entry e_menu_options[] =
+{
+ mee_enum ("Frameskip", 0, frameskip_style, men_frameskip),
+ mee_range ("Max Frameskip", 0, max_frameskip, 1, 5),
+ mee_enum ("Scaling", 0, scaling_mode, men_scaling),
+ mee_onoff_h ("Color Correction", 0, color_correct, 1, h_color_correct),
+ mee_onoff_h ("LCD Ghosting", 0, lcd_blend, 1, h_lcd_blend),
+ mee_onoff_h ("Dynamic Recompiler", 0, dynarec_enable, 1, h_dynarec_enable),
+ mee_onoff_h ("Show FPS", 0, show_fps, 1, h_show_fps),
+ mee_cust_nosave ("Save global config", MA_OPT_SAVECFG, mh_savecfg, mgn_saveloadcfg),
+ mee_cust_nosave ("Save game config", MA_OPT_SAVECFG_GAME, mh_savecfg, mgn_saveloadcfg),
+ mee_handler_h ("Restore defaults", mh_restore_defaults, h_restore_def),
+ mee_end,
+};
+
+static int menu_loop_options(int id, int keys)
+{
+ static int sel = 0;
+ int prev_dynarec_enable = dynarec_enable;
+
+ me_loop(e_menu_options, &sel);
+
+ if (prev_dynarec_enable != dynarec_enable)
+ init_caches();
+
+ return 0;
+}
+
+static int key_config_loop_wrap(int id, int keys)
+{
+ switch (id) {
+ case MA_CTRL_PLAYER1:
+ key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, 0);
+ break;
+ case MA_CTRL_EMU:
+ key_config_loop(emuctrl_actions, array_size(emuctrl_actions) - 1, -1);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static menu_entry e_menu_keyconfig[] =
+{
+ mee_handler_id ("Player controls", MA_CTRL_PLAYER1, key_config_loop_wrap),
+ mee_handler_id ("Emulator controls", MA_CTRL_EMU, key_config_loop_wrap),
+ mee_cust_nosave ("Save global config", MA_OPT_SAVECFG, mh_savecfg, mgn_saveloadcfg),
+ mee_cust_nosave ("Save game config", MA_OPT_SAVECFG_GAME, mh_savecfg, mgn_saveloadcfg),
+ mee_end,
+};
+
+static int menu_loop_keyconfig(int id, int keys)
+{
+ static int sel = 0;
+ me_loop(e_menu_keyconfig, &sel);
+ return 0;
+}
+
+
+static int main_menu_handler(int id, int keys)
+{
+ switch (id)
+ {
+ case MA_MAIN_RESUME_GAME:
+ return 1;
+ case MA_MAIN_SAVE_STATE:
+ return menu_loop_savestate(0);
+ case MA_MAIN_LOAD_STATE:
+ return menu_loop_savestate(1);
+ case MA_MAIN_RESET_GAME:
+ reset_gba();
+ return 1;
+ case MA_MAIN_EXIT:
+ should_quit = 1;
+ return 1;
+ default:
+ lprintf("%s: something unknown selected\n", __FUNCTION__);
+ break;
+ }
+
+ return 0;
+}
+
+static menu_entry e_menu_main[] =
+{
+ mee_handler_id("Resume game", MA_MAIN_RESUME_GAME, main_menu_handler),
+ mee_handler_id("Save State", MA_MAIN_SAVE_STATE, main_menu_handler),
+ mee_handler_id("Load State", MA_MAIN_LOAD_STATE, main_menu_handler),
+ mee_handler_id("Reset game", MA_MAIN_RESET_GAME, main_menu_handler),
+ mee_handler ("Options", menu_loop_options),
+ mee_handler ("Controls", menu_loop_keyconfig),
+ /* mee_handler_id("Cheats", MA_MAIN_CHEATS, main_menu_handler), */
+ mee_handler_id("Exit", MA_MAIN_EXIT, main_menu_handler),
+ mee_end,
+};
+
+void draw_savestate_bg(int slot)
+{
+}
+
+void menu_set_defaults(void)
+{
+ dynarec_enable = 1;
+ frameskip_style = 1;
+ scaling_mode = 0;
+ max_frameskip = 3;
+ color_correct = 0;
+ lcd_blend = 0;
+ show_fps = 0;
+ limit_frames = 1;
+}
+
+void menu_loop(void)
+{
+ static int sel = 0;
+ plat_video_menu_enter(1);
+ video_scale(g_menubg_ptr, g_menuscreen_h, g_menuscreen_pp);
+ menu_darken_bg(g_menubg_ptr, g_menubg_ptr, g_menuscreen_h * g_menuscreen_pp, 0);
+ me_loop_d(e_menu_main, &sel, NULL, NULL);
+
+ /* wait until menu, ok, back is released */
+ while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK))
+ ;
+ memset(g_menubg_ptr, 0, g_menuscreen_h * g_menuscreen_pp * sizeof(uint16_t));
+
+ plat_video_menu_leave();
+}
+
+int menu_save_config(int is_game)
+{
+ char config_filename[MAXPATHLEN];
+ FILE *config_file;
+
+ config_file_name(config_filename, MAXPATHLEN, is_game);
+ config_file = fopen(config_filename, "wb");
+ if (!config_file) {
+ fprintf(stderr, "Could not write config to %s\n", config_filename);
+ return -1;
+ }
+
+ config_write(config_file);
+ config_write_keys(config_file);
+
+ fclose(config_file);
+ return 0;
+}
+
+
+void menu_load_config(void)
+{
+ char config_filename[MAXPATHLEN];
+ FILE *config_file;
+ size_t length;
+ char *config;
+
+ config_file_name(config_filename, MAXPATHLEN, 1);
+ config_file = fopen(config_filename, "rb");
+ if (!config_file) {
+ config_file_name(config_filename, MAXPATHLEN, 0);
+ config_file = fopen(config_filename, "rb");
+ }
+
+ if (!config_file)
+ return;
+
+ fseek(config_file, 0, SEEK_END);
+ length = ftell(config_file);
+ fseek(config_file, 0, SEEK_SET);
+
+ config = calloc(1, length);
+
+ if (fread(config, 1, length, config_file))
+ fclose(config_file);
+
+ config_read(config);
+ config_read_keys(config);
+
+ if (config)
+ free(config);
+}
+
+void menu_init(void)
+{
+ menu_init_base();
+
+ menu_set_defaults();
+ menu_load_config();
+
+ g_menubg_src_ptr = calloc(g_menuscreen_w * g_menuscreen_h * 2, 1);
+ g_menubg_ptr = calloc(g_menuscreen_w * g_menuscreen_h * 2, 1);
+ if (g_menubg_src_ptr == NULL || g_menubg_ptr == NULL) {
+ fprintf(stderr, "OOM\n");
+ exit(1);
+ }
+}
+
+void menu_finish(void)
+{
+}
+
+static void debug_menu_loop(void)
+{
+}
+
+void menu_update_msg(const char *msg)
+{
+ strncpy(menu_error_msg, msg, sizeof(menu_error_msg));
+ menu_error_msg[sizeof(menu_error_msg) - 1] = 0;
+
+ menu_error_time = plat_get_ticks_ms();
+ lprintf("msg: %s\n", menu_error_msg);
+}
diff --git a/frontend/menu.h b/frontend/menu.h
new file mode 100644
index 0000000..e151098
--- /dev/null
+++ b/frontend/menu.h
@@ -0,0 +1,10 @@
+#ifndef _FRONTEND_MENU_H__
+#define _FRONTEND_MENU_H__
+
+#include "frontend/libpicofe/menu.h"
+
+void menu_init(void);
+void menu_loop(void);
+void menu_finish(void);
+
+#endif
diff --git a/frontend/plat.h b/frontend/plat.h
new file mode 100644
index 0000000..dc4be73
--- /dev/null
+++ b/frontend/plat.h
@@ -0,0 +1,19 @@
+#ifndef __FRONTEND_PLAT_H__
+#define __FRONTEND_PLAT_H__
+
+#define HUD_LEN 39
+
+int plat_init(void);
+void plat_finish(void);
+void plat_minimize(void);
+void *plat_prepare_screenshot(int *w, int *h, int *bpp);
+
+void plat_video_open(void);
+void plat_video_set_msg(char *new_msg);
+void plat_video_flip(void);
+void plat_video_close(void);
+
+float plat_sound_capacity(void);
+void plat_sound_write(void *data, int bytes);
+
+#endif /* __FRONTEND_PLAT_H__ */
diff --git a/frontend/plat_linux.c b/frontend/plat_linux.c
new file mode 100644
index 0000000..09d9a48
--- /dev/null
+++ b/frontend/plat_linux.c
@@ -0,0 +1,258 @@
+#include <SDL/SDL.h>
+#include "common.h"
+
+#include "frontend/main.h"
+#include "frontend/plat.h"
+#include "frontend/scale.h"
+#include "frontend/libpicofe/menu.h"
+#include "frontend/libpicofe/plat.h"
+#include "frontend/libpicofe/input.h"
+#include "frontend/libpicofe/in_sdl.h"
+
+static SDL_Surface* screen;
+
+#define BUF_LEN 8192
+static short buf[BUF_LEN];
+static int buf_w, buf_r;
+
+static char msg[HUD_LEN];
+
+static const struct in_default_bind in_sdl_defbinds[] = {
+ { SDLK_UP, IN_BINDTYPE_PLAYER12, KBIT_UP },
+ { SDLK_DOWN, IN_BINDTYPE_PLAYER12, KBIT_DOWN },
+ { SDLK_LEFT, IN_BINDTYPE_PLAYER12, KBIT_LEFT },
+ { SDLK_RIGHT, IN_BINDTYPE_PLAYER12, KBIT_RIGHT },
+ { SDLK_LCTRL, IN_BINDTYPE_PLAYER12, KBIT_B },
+ { SDLK_SPACE, IN_BINDTYPE_PLAYER12, KBIT_A },
+ { SDLK_RETURN, IN_BINDTYPE_PLAYER12, KBIT_START },
+ { SDLK_RCTRL, IN_BINDTYPE_PLAYER12, KBIT_SELECT },
+ { SDLK_TAB, IN_BINDTYPE_PLAYER12, KBIT_L },
+ { SDLK_BACKSPACE, IN_BINDTYPE_PLAYER12, KBIT_R },
+ { SDLK_ESCAPE, IN_BINDTYPE_EMU, EACTION_MENU },
+ { 0, 0, 0 }
+};
+
+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_SPACE, PBTN_MOK },
+ { SDLK_LCTRL, PBTN_MBACK },
+ { SDLK_LALT, PBTN_MA2 },
+ { SDLK_LSHIFT, PBTN_MA3 },
+ { SDLK_TAB, PBTN_L },
+ { SDLK_BACKSPACE, PBTN_R },
+};
+
+const struct menu_keymap in_sdl_joy_map[] =
+{
+ { SDLK_UP, PBTN_UP },
+ { SDLK_DOWN, PBTN_DOWN },
+ { SDLK_LEFT, PBTN_LEFT },
+ { SDLK_RIGHT, PBTN_RIGHT },
+ { SDLK_WORLD_0, PBTN_MOK },
+ { SDLK_WORLD_1, PBTN_MBACK },
+ { SDLK_WORLD_2, PBTN_MA2 },
+ { SDLK_WORLD_3, PBTN_MA3 },
+};
+
+static const struct in_pdata in_sdl_platform_data = {
+ .defbinds = in_sdl_defbinds,
+ .key_map = in_sdl_key_map,
+ .kmap_size = array_size(in_sdl_key_map),
+ .joy_map = in_sdl_joy_map,
+ .jmap_size = array_size(in_sdl_joy_map),
+};
+
+static void *fb_flip(void)
+{
+ SDL_Flip(screen);
+ return screen->pixels;
+}
+
+void plat_video_menu_enter(int is_rom_loaded)
+{
+}
+
+void plat_video_menu_begin(void)
+{
+ g_menuscreen_ptr = fb_flip();
+}
+
+void plat_video_menu_end(void)
+{
+ g_menuscreen_ptr = fb_flip();
+}
+
+void plat_video_menu_leave(void)
+{
+ SDL_LockSurface(screen);
+ memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t));
+ SDL_UnlockSurface(screen);
+ g_menuscreen_ptr = fb_flip();
+ SDL_LockSurface(screen);
+ memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t));
+ SDL_UnlockSurface(screen);
+}
+
+void plat_video_open(void)
+{
+}
+
+void plat_video_set_msg(char *new_msg)
+{
+ snprintf(msg, HUD_LEN, "%s", new_msg);
+}
+
+void plat_video_flip(void)
+{
+ video_post_process();
+ SDL_LockSurface(screen);
+ if (msg[0])
+ video_clear_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t));
+
+ video_scale(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t));
+
+ if (msg[0])
+ video_print_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t), msg);
+ SDL_UnlockSurface(screen);
+
+ g_menuscreen_ptr = fb_flip();
+ msg[0] = 0;
+}
+
+void plat_video_close(void)
+{
+}
+
+void plat_sound_callback(void *unused, u8 *stream, int len)
+{
+ short *p = (short *)stream;
+ len /= sizeof(short);
+
+ while (buf_r != buf_w && len > 0) {
+ *p++ = buf[buf_r++];
+ buf_r %= BUF_LEN;
+ --len;
+ }
+
+ while(len > 0) {
+ *p++ = 0;
+ --len;
+ }
+}
+
+void plat_sound_finish(void)
+{
+ SDL_PauseAudio(1);
+ SDL_CloseAudio();
+}
+
+int plat_sound_init(void)
+{
+
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ return -1;
+ }
+
+ SDL_AudioSpec spec;
+
+ spec.freq = sound_frequency;
+ spec.format = AUDIO_S16;
+ spec.channels = 2;
+ spec.samples = 512;
+ spec.callback = plat_sound_callback;
+
+ if (SDL_OpenAudio(&spec, NULL) < 0) {
+ plat_sound_finish();
+ return -1;
+ }
+
+ SDL_PauseAudio(0);
+ return 0;
+}
+
+float plat_sound_capacity(void)
+{
+ int buffered = 0;
+ if (buf_w != buf_r) {
+ buffered = buf_w > buf_r ? buf_w - buf_r : (buf_w + BUF_LEN) - buf_r;
+ }
+
+ return 1.0 - (float)buffered / BUF_LEN;
+}
+
+void plat_sound_write(void *data, int bytes)
+{
+ short *sound_data = (short *)data;
+ SDL_LockAudio();
+
+ while (bytes > 0) {
+ while (((buf_w + 1) % BUF_LEN) == buf_r) {
+ SDL_UnlockAudio();
+
+ if (!limit_frames)
+ return;
+
+ plat_sleep_ms(1);
+ SDL_LockAudio();
+ }
+
+ buf[buf_w] = *sound_data++;
+
+ ++buf_w;
+ buf_w %= BUF_LEN;
+ bytes -= sizeof(short);
+ }
+ SDL_UnlockAudio();
+}
+
+void plat_sdl_event_handler(void *event_)
+{
+}
+
+int plat_init(void)
+{
+ SDL_Init(SDL_INIT_VIDEO);
+ screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE);
+
+ if (screen == NULL) {
+ printf("%s, failed to set video mode\n", __func__);
+ return -1;
+ }
+
+ SDL_ShowCursor(0);
+
+ g_menuscreen_w = 320;
+ g_menuscreen_h = 240;
+ g_menuscreen_pp = 320;
+ g_menuscreen_ptr = fb_flip();
+
+ if (in_sdl_init(&in_sdl_platform_data, plat_sdl_event_handler)) {
+ fprintf(stderr, "SDL input failed to init: %s\n", SDL_GetError());
+ return -1;
+ }
+ in_probe();
+
+ if (plat_sound_init()) {
+ fprintf(stderr, "SDL sound failed to init: %s\n", SDL_GetError());
+ return -1;
+ }
+ return 0;
+}
+
+void plat_pre_finish(void)
+{
+}
+
+void plat_finish(void)
+{
+ plat_sound_finish();
+ SDL_Quit();
+}
+
+void plat_trigger_vibrate(int pad, int low, int high)
+{
+}
diff --git a/frontend/plat_trimui.c b/frontend/plat_trimui.c
new file mode 100644
index 0000000..2925f5f
--- /dev/null
+++ b/frontend/plat_trimui.c
@@ -0,0 +1,286 @@
+#include <SDL/SDL.h>
+#include "common.h"
+
+#include "frontend/audio_hotplug.h"
+#include "frontend/main.h"
+#include "frontend/plat.h"
+#include "frontend/scale.h"
+#include "frontend/libpicofe/menu.h"
+#include "frontend/libpicofe/plat.h"
+#include "frontend/libpicofe/input.h"
+#include "frontend/libpicofe/in_sdl.h"
+
+static SDL_Surface* screen;
+
+#define BUF_LEN 8192
+
+static short buf[BUF_LEN];
+static int buf_w, buf_r;
+
+static char msg[HUD_LEN];
+
+static const struct in_default_bind in_sdl_defbinds[] = {
+ { SDLK_UP, IN_BINDTYPE_PLAYER12, KBIT_UP },
+ { SDLK_DOWN, IN_BINDTYPE_PLAYER12, KBIT_DOWN },
+ { SDLK_LEFT, IN_BINDTYPE_PLAYER12, KBIT_LEFT },
+ { SDLK_RIGHT, IN_BINDTYPE_PLAYER12, KBIT_RIGHT },
+ { SDLK_LCTRL, IN_BINDTYPE_PLAYER12, KBIT_B },
+ { SDLK_SPACE, IN_BINDTYPE_PLAYER12, KBIT_A },
+ { SDLK_RETURN, IN_BINDTYPE_PLAYER12, KBIT_START },
+ { SDLK_RCTRL, IN_BINDTYPE_PLAYER12, KBIT_SELECT },
+ { SDLK_TAB, IN_BINDTYPE_PLAYER12, KBIT_L },
+ { SDLK_BACKSPACE, IN_BINDTYPE_PLAYER12, KBIT_R },
+ { SDLK_ESCAPE, IN_BINDTYPE_EMU, EACTION_MENU },
+ { 0, 0, 0 }
+};
+
+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_SPACE, PBTN_MOK },
+ { SDLK_LCTRL, PBTN_MBACK },
+ { SDLK_LALT, PBTN_MA2 },
+ { SDLK_LSHIFT, PBTN_MA3 },
+ { SDLK_TAB, PBTN_L },
+ { SDLK_BACKSPACE, PBTN_R },
+};
+
+const struct menu_keymap in_sdl_joy_map[] =
+{
+ { SDLK_UP, PBTN_UP },
+ { SDLK_DOWN, PBTN_DOWN },
+ { SDLK_LEFT, PBTN_LEFT },
+ { SDLK_RIGHT, PBTN_RIGHT },
+ { SDLK_WORLD_0, PBTN_MOK },
+ { SDLK_WORLD_1, PBTN_MBACK },
+ { SDLK_WORLD_2, PBTN_MA2 },
+ { SDLK_WORLD_3, PBTN_MA3 },
+};
+
+static const char * const in_sdl_key_names[SDLK_LAST] = {
+ [SDLK_UP] = "up",
+ [SDLK_DOWN] = "down",
+ [SDLK_LEFT] = "left",
+ [SDLK_RIGHT] = "right",
+ [SDLK_LSHIFT] = "x",
+ [SDLK_LCTRL] = "b",
+ [SDLK_SPACE] = "a",
+ [SDLK_LALT] = "y",
+ [SDLK_RETURN] = "start",
+ [SDLK_RCTRL] = "select",
+ [SDLK_TAB] = "l",
+ [SDLK_BACKSPACE] = "r",
+ [SDLK_ESCAPE] = "menu",
+};
+
+static const struct in_pdata in_sdl_platform_data = {
+ .defbinds = in_sdl_defbinds,
+ .key_map = in_sdl_key_map,
+ .kmap_size = array_size(in_sdl_key_map),
+ .joy_map = in_sdl_joy_map,
+ .jmap_size = array_size(in_sdl_joy_map),
+ .key_names = in_sdl_key_names,
+};
+
+static void *fb_flip(void)
+{
+ SDL_Flip(screen);
+ return screen->pixels;
+}
+
+void plat_video_menu_enter(int is_rom_loaded)
+{
+}
+
+void plat_video_menu_begin(void)
+{
+ g_menuscreen_ptr = fb_flip();
+}
+
+void plat_video_menu_end(void)
+{
+ g_menuscreen_ptr = fb_flip();
+}
+
+void plat_video_menu_leave(void)
+{
+ SDL_LockSurface(screen);
+ memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t));
+ SDL_UnlockSurface(screen);
+ g_menuscreen_ptr = fb_flip();
+ SDL_LockSurface(screen);
+ memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t));
+ SDL_UnlockSurface(screen);
+}
+
+void plat_video_open(void)
+{
+}
+
+void plat_video_set_msg(char *new_msg)
+{
+ snprintf(msg, HUD_LEN, "%s", new_msg);
+}
+
+void plat_video_flip(void)
+{
+ video_post_process();
+ SDL_LockSurface(screen);
+ if (msg[0])
+ video_clear_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t));
+
+ video_scale(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t));
+
+ if (msg[0])
+ video_print_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t), msg);
+ SDL_UnlockSurface(screen);
+
+ g_menuscreen_ptr = fb_flip();
+ msg[0] = 0;
+}
+
+void plat_video_close(void)
+{
+}
+
+void plat_sound_callback(void *unused, u8 *stream, int len)
+{
+ short *p = (short *)stream;
+ len /= sizeof(short);
+
+ while (buf_r != buf_w && len > 0) {
+ *p++ = buf[buf_r++];
+ buf_r %= BUF_LEN;
+ --len;
+ }
+
+ while(len > 0) {
+ *p++ = 0;
+ --len;
+ }
+}
+
+void plat_sound_finish(void)
+{
+ SDL_PauseAudio(1);
+ SDL_CloseAudio();
+}
+
+int plat_sound_init(void)
+{
+
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ return -1;
+ }
+
+ SDL_AudioSpec spec;
+
+ spec.freq = sound_frequency;
+ spec.format = AUDIO_S16SYS;
+ spec.channels = 2;
+ spec.samples = 512;
+ spec.callback = plat_sound_callback;
+
+ if (SDL_OpenAudio(&spec, NULL) < 0) {
+ plat_sound_finish();
+ fprintf(stderr, "SDL audio failed to open: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ SDL_PauseAudio(0);
+ return 0;
+}
+
+static void plat_sound_reinit(bool has_usb_audio) {
+ plat_sound_finish();
+ audio_hotplug_set_device(has_usb_audio);
+ plat_sound_init();
+}
+
+float plat_sound_capacity(void)
+{
+ int buffered = 0;
+ if (buf_w != buf_r) {
+ buffered = buf_w > buf_r ? buf_w - buf_r : (buf_w + BUF_LEN) - buf_r;
+ }
+
+ return 1.0 - (float)buffered / BUF_LEN;
+}
+
+void plat_sound_write(void *data, int bytes)
+{
+ short *sound_data = (short *)data;
+ audio_hotplug_check(plat_sound_reinit);
+ SDL_LockAudio();
+
+ while (bytes > 0) {
+ while (((buf_w + 1) % BUF_LEN) == buf_r) {
+ SDL_UnlockAudio();
+
+ if (!limit_frames)
+ return;
+
+ plat_sleep_ms(1);
+ SDL_LockAudio();
+ }
+ buf[buf_w] = *sound_data++;
+
+ ++buf_w;
+ buf_w %= BUF_LEN;
+ bytes -= sizeof(short);
+ }
+ SDL_UnlockAudio();
+}
+
+void plat_sdl_event_handler(void *event_)
+{
+}
+
+int plat_init(void)
+{
+ SDL_Init(SDL_INIT_VIDEO);
+ screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE);
+
+ if (screen == NULL) {
+ printf("%s, failed to set video mode\n", __func__);
+ return -1;
+ }
+
+ SDL_ShowCursor(0);
+
+ g_menuscreen_w = 320;
+ g_menuscreen_h = 240;
+ g_menuscreen_pp = 320;
+ g_menuscreen_ptr = fb_flip();
+
+ if (in_sdl_init(&in_sdl_platform_data, plat_sdl_event_handler)) {
+ fprintf(stderr, "SDL input failed to init: %s\n", SDL_GetError());
+ return -1;
+ }
+ in_probe();
+
+ sound_frequency = 48000;
+
+ if (plat_sound_init()) {
+ fprintf(stderr, "SDL sound failed to init: %s\n", SDL_GetError());
+ return -1;
+ }
+ return 0;
+}
+
+void plat_pre_finish(void)
+{
+}
+
+void plat_finish(void)
+{
+ plat_sound_finish();
+ SDL_Quit();
+}
+
+void plat_trigger_vibrate(int pad, int low, int high)
+{
+}
diff --git a/frontend/scale.c b/frontend/scale.c
new file mode 100644
index 0000000..3998f9f
--- /dev/null
+++ b/frontend/scale.c
@@ -0,0 +1,604 @@
+#include "common.h"
+#include "gba_cc_lut.h"
+#include "frontend/main.h"
+#include "frontend/libpicofe/fonts.h"
+
+#define Average(A, B) ((((A) & 0xF7DE) >> 1) + (((B) & 0xF7DE) >> 1) + ((A) & (B) & 0x0821))
+
+/* Calculates the average of two pairs of RGB565 pixels. The result is, in
+ * the lower bits, the average of both lower pixels, and in the upper bits,
+ * the average of both upper pixels. */
+#define Average32(A, B) ((((A) & 0xF7DEF7DE) >> 1) + (((B) & 0xF7DEF7DE) >> 1) + ((A) & (B) & 0x08210821))
+
+/* Raises a pixel from the lower half to the upper half of a pair. */
+#define Raise(N) ((N) << 16)
+
+/* Extracts the upper pixel of a pair into the lower pixel of a pair. */
+#define Hi(N) ((N) >> 16)
+
+/* Extracts the lower pixel of a pair. */
+#define Lo(N) ((N) & 0xFFFF)
+
+/* Calculates the average of two RGB565 pixels. The source of the pixels is
+ * the lower 16 bits of both parameters. The result is in the lower 16 bits.
+ * The average is weighted so that the first pixel contributes 3/4 of its
+ * color and the second pixel contributes 1/4. */
+#define AverageQuarters3_1(A, B) ( (((A) & 0xF7DE) >> 1) + (((A) & 0xE79C) >> 2) + (((B) & 0xE79C) >> 2) + ((( (( ((A) & 0x1863) + ((A) & 0x0821) ) << 1) + ((B) & 0x1863) ) >> 2) & 0x1863) )
+
+static inline void gba_upscale(uint16_t *to, uint16_t *from,
+ uint32_t src_x, uint32_t src_y, uint32_t src_pitch, uint32_t dst_pitch)
+{
+ /* Before:
+ * a b c d e f
+ * g h i j k l
+ *
+ * After (multiple letters = average):
+ * a ab bc c d de ef f
+ * ag abgh bchi ci dj dejk efkl fl
+ * g gh hi i j jk kl l
+ */
+
+ const uint32_t dst_x = src_x * 4 / 3;
+ const uint32_t src_skip = src_pitch - src_x * sizeof(uint16_t),
+ dst_skip = dst_pitch - dst_x * sizeof(uint16_t);
+
+ uint32_t x, y;
+
+ for (y = 0; y < src_y; y += 2) {
+ for (x = 0; x < src_x / 6; x++) {
+ // -- Row 1 --
+ // Read RGB565 elements in the source grid.
+ // The notation is high_low (little-endian).
+ uint32_t b_a = (*(uint32_t*) (from )),
+ d_c = (*(uint32_t*) (from + 2)),
+ f_e = (*(uint32_t*) (from + 4));
+
+ // Generate ab_a from b_a.
+ *(uint32_t*) (to) = likely(Hi(b_a) == Lo(b_a))
+ ? b_a
+ : Lo(b_a) /* 'a' verbatim to low pixel */ |
+ Raise(Average(Hi(b_a), Lo(b_a))) /* ba to high pixel */;
+
+ // Generate c_bc from b_a and d_c.
+ *(uint32_t*) (to + 2) = likely(Hi(b_a) == Lo(d_c))
+ ? Lo(d_c) | Raise(Lo(d_c))
+ : Raise(Lo(d_c)) /* 'c' verbatim to high pixel */ |
+ Average(Lo(d_c), Hi(b_a)) /* bc to low pixel */;
+
+ // Generate de_d from d_c and f_e.
+ *(uint32_t*) (to + 4) = likely(Hi(d_c) == Lo(f_e))
+ ? Lo(f_e) | Raise(Lo(f_e))
+ : Hi(d_c) /* 'd' verbatim to low pixel */ |
+ Raise(Average(Lo(f_e), Hi(d_c))) /* de to high pixel */;
+
+ // Generate f_ef from f_e.
+ *(uint32_t*) (to + 6) = likely(Hi(f_e) == Lo(f_e))
+ ? f_e
+ : Raise(Hi(f_e)) /* 'f' verbatim to high pixel */ |
+ Average(Hi(f_e), Lo(f_e)) /* ef to low pixel */;
+
+ if (likely(y + 1 < src_y)) // Is there a source row 2?
+ {
+ // -- Row 2 --
+ uint32_t h_g = (*(uint32_t*) ((uint8_t*) from + src_pitch )),
+ j_i = (*(uint32_t*) ((uint8_t*) from + src_pitch + 4)),
+ l_k = (*(uint32_t*) ((uint8_t*) from + src_pitch + 8));
+
+ // Generate abgh_ag from b_a and h_g.
+ uint32_t bh_ag = Average32(b_a, h_g);
+ *(uint32_t*) ((uint8_t*) to + dst_pitch) = likely(Hi(bh_ag) == Lo(bh_ag))
+ ? bh_ag
+ : Lo(bh_ag) /* ag verbatim to low pixel */ |
+ Raise(Average(Hi(bh_ag), Lo(bh_ag))) /* abgh to high pixel */;
+
+ // Generate ci_bchi from b_a, d_c, h_g and j_i.
+ uint32_t ci_bh =
+ Hi(bh_ag) /* bh verbatim to low pixel */ |
+ Raise(Average(Lo(d_c), Lo(j_i))) /* ci to high pixel */;
+ *(uint32_t*) ((uint8_t*) to + dst_pitch + 4) = likely(Hi(ci_bh) == Lo(ci_bh))
+ ? ci_bh
+ : Raise(Hi(ci_bh)) /* ci verbatim to high pixel */ |
+ Average(Hi(ci_bh), Lo(ci_bh)) /* bchi to low pixel */;
+
+ // Generate fl_efkl from f_e and l_k.
+ uint32_t fl_ek = Average32(f_e, l_k);
+ *(uint32_t*) ((uint8_t*) to + dst_pitch + 12) = likely(Hi(fl_ek) == Lo(fl_ek))
+ ? fl_ek
+ : Raise(Hi(fl_ek)) /* fl verbatim to high pixel */ |
+ Average(Hi(fl_ek), Lo(fl_ek)) /* efkl to low pixel */;
+
+ // Generate dejk_dj from d_c, f_e, j_i and l_k.
+ uint32_t ek_dj =
+ Raise(Lo(fl_ek)) /* ek verbatim to high pixel */ |
+ Average(Hi(d_c), Hi(j_i)) /* dj to low pixel */;
+ *(uint32_t*) ((uint8_t*) to + dst_pitch + 8) = likely(Hi(ek_dj) == Lo(ek_dj))
+ ? ek_dj
+ : Lo(ek_dj) /* dj verbatim to low pixel */ |
+ Raise(Average(Hi(ek_dj), Lo(ek_dj))) /* dejk to high pixel */;
+
+ // -- Row 3 --
+ // Generate gh_g from h_g.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2) = likely(Hi(h_g) == Lo(h_g))
+ ? h_g
+ : Lo(h_g) /* 'g' verbatim to low pixel */ |
+ Raise(Average(Hi(h_g), Lo(h_g))) /* gh to high pixel */;
+
+ // Generate i_hi from g_h and j_i.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 4) = likely(Hi(h_g) == Lo(j_i))
+ ? Lo(j_i) | Raise(Lo(j_i))
+ : Raise(Lo(j_i)) /* 'i' verbatim to high pixel */ |
+ Average(Lo(j_i), Hi(h_g)) /* hi to low pixel */;
+
+ // Generate jk_j from j_i and l_k.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 8) = likely(Hi(j_i) == Lo(l_k))
+ ? Lo(l_k) | Raise(Lo(l_k))
+ : Hi(j_i) /* 'j' verbatim to low pixel */ |
+ Raise(Average(Hi(j_i), Lo(l_k))) /* jk to high pixel */;
+
+ // Generate l_kl from l_k.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 12) = likely(Hi(l_k) == Lo(l_k))
+ ? l_k
+ : Raise(Hi(l_k)) /* 'l' verbatim to high pixel */ |
+ Average(Hi(l_k), Lo(l_k)) /* kl to low pixel */;
+ }
+
+ from += 6;
+ to += 8;
+ }
+
+ // Skip past the waste at the end of the first line, if any,
+ // then past 1 whole lines of source and 2 of destination.
+ from = (uint16_t*) ((uint8_t*) from + src_skip + src_pitch);
+ to = (uint16_t*) ((uint8_t*) to + dst_skip + 2 * dst_pitch);
+ }
+}
+
+static inline void gba_upscale_aspect(uint16_t *to, uint16_t *from,
+ uint32_t src_x, uint32_t src_y, uint32_t src_pitch, uint32_t dst_pitch)
+{
+ /* Before:
+ * a b c d e f
+ * g h i j k l
+ * m n o p q r
+ *
+ * After (multiple letters = average):
+ * a ab bc c d de ef f
+ * ag abgh bchi ci dj dejk efkl fl
+ * gm ghmn hino io jp jkpq klqr lr
+ * m mn no o p pq qr r
+ */
+
+ const uint32_t dst_x = src_x * 4 / 3;
+ const uint32_t src_skip = src_pitch - src_x * sizeof(uint16_t),
+ dst_skip = dst_pitch - dst_x * sizeof(uint16_t);
+
+ uint32_t x, y;
+
+ for (y = 0; y < src_y; y += 3) {
+ for (x = 0; x < src_x / 6; x++) {
+ // -- Row 1 --
+ // Read RGB565 elements in the source grid.
+ // The notation is high_low (little-endian).
+ uint32_t b_a = (*(uint32_t*) (from )),
+ d_c = (*(uint32_t*) (from + 2)),
+ f_e = (*(uint32_t*) (from + 4));
+
+ // Generate ab_a from b_a.
+ *(uint32_t*) (to) = likely(Hi(b_a) == Lo(b_a))
+ ? b_a
+ : Lo(b_a) /* 'a' verbatim to low pixel */ |
+ Raise(Average(Hi(b_a), Lo(b_a))) /* ba to high pixel */;
+
+ // Generate c_bc from b_a and d_c.
+ *(uint32_t*) (to + 2) = likely(Hi(b_a) == Lo(d_c))
+ ? Lo(d_c) | Raise(Lo(d_c))
+ : Raise(Lo(d_c)) /* 'c' verbatim to high pixel */ |
+ Average(Lo(d_c), Hi(b_a)) /* bc to low pixel */;
+
+ // Generate de_d from d_c and f_e.
+ *(uint32_t*) (to + 4) = likely(Hi(d_c) == Lo(f_e))
+ ? Lo(f_e) | Raise(Lo(f_e))
+ : Hi(d_c) /* 'd' verbatim to low pixel */ |
+ Raise(Average(Lo(f_e), Hi(d_c))) /* de to high pixel */;
+
+ // Generate f_ef from f_e.
+ *(uint32_t*) (to + 6) = likely(Hi(f_e) == Lo(f_e))
+ ? f_e
+ : Raise(Hi(f_e)) /* 'f' verbatim to high pixel */ |
+ Average(Hi(f_e), Lo(f_e)) /* ef to low pixel */;
+
+ if (likely(y + 1 < src_y)) // Is there a source row 2?
+ {
+ // -- Row 2 --
+ uint32_t h_g = (*(uint32_t*) ((uint8_t*) from + src_pitch )),
+ j_i = (*(uint32_t*) ((uint8_t*) from + src_pitch + 4)),
+ l_k = (*(uint32_t*) ((uint8_t*) from + src_pitch + 8));
+
+ // Generate abgh_ag from b_a and h_g.
+ uint32_t bh_ag = Average32(b_a, h_g);
+ *(uint32_t*) ((uint8_t*) to + dst_pitch) = likely(Hi(bh_ag) == Lo(bh_ag))
+ ? bh_ag
+ : Lo(bh_ag) /* ag verbatim to low pixel */ |
+ Raise(Average(Hi(bh_ag), Lo(bh_ag))) /* abgh to high pixel */;
+
+ // Generate ci_bchi from b_a, d_c, h_g and j_i.
+ uint32_t ci_bh =
+ Hi(bh_ag) /* bh verbatim to low pixel */ |
+ Raise(Average(Lo(d_c), Lo(j_i))) /* ci to high pixel */;
+ *(uint32_t*) ((uint8_t*) to + dst_pitch + 4) = likely(Hi(ci_bh) == Lo(ci_bh))
+ ? ci_bh
+ : Raise(Hi(ci_bh)) /* ci verbatim to high pixel */ |
+ Average(Hi(ci_bh), Lo(ci_bh)) /* bchi to low pixel */;
+
+ // Generate fl_efkl from f_e and l_k.
+ uint32_t fl_ek = Average32(f_e, l_k);
+ *(uint32_t*) ((uint8_t*) to + dst_pitch + 12) = likely(Hi(fl_ek) == Lo(fl_ek))
+ ? fl_ek
+ : Raise(Hi(fl_ek)) /* fl verbatim to high pixel */ |
+ Average(Hi(fl_ek), Lo(fl_ek)) /* efkl to low pixel */;
+
+ // Generate dejk_dj from d_c, f_e, j_i and l_k.
+ uint32_t ek_dj =
+ Raise(Lo(fl_ek)) /* ek verbatim to high pixel */ |
+ Average(Hi(d_c), Hi(j_i)) /* dj to low pixel */;
+ *(uint32_t*) ((uint8_t*) to + dst_pitch + 8) = likely(Hi(ek_dj) == Lo(ek_dj))
+ ? ek_dj
+ : Lo(ek_dj) /* dj verbatim to low pixel */ |
+ Raise(Average(Hi(ek_dj), Lo(ek_dj))) /* dejk to high pixel */;
+
+ if (likely(y + 2 < src_y)) // Is there a source row 3?
+ {
+ // -- Row 3 --
+ uint32_t n_m = (*(uint32_t*) ((uint8_t*) from + src_pitch * 2 )),
+ p_o = (*(uint32_t*) ((uint8_t*) from + src_pitch * 2 + 4)),
+ r_q = (*(uint32_t*) ((uint8_t*) from + src_pitch * 2 + 8));
+
+ // Generate ghmn_gm from h_g and n_m.
+ uint32_t hn_gm = Average32(h_g, n_m);
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2) = likely(Hi(hn_gm) == Lo(hn_gm))
+ ? hn_gm
+ : Lo(hn_gm) /* gm verbatim to low pixel */ |
+ Raise(Average(Hi(hn_gm), Lo(hn_gm))) /* ghmn to high pixel */;
+
+ // Generate io_hino from h_g, j_i, n_m and p_o.
+ uint32_t io_hn =
+ Hi(hn_gm) /* hn verbatim to low pixel */ |
+ Raise(Average(Lo(j_i), Lo(p_o))) /* io to high pixel */;
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 4) = likely(Hi(io_hn) == Lo(io_hn))
+ ? io_hn
+ : Raise(Hi(io_hn)) /* io verbatim to high pixel */ |
+ Average(Hi(io_hn), Lo(io_hn)) /* hino to low pixel */;
+
+ // Generate lr_klqr from l_k and r_q.
+ uint32_t lr_kq = Average32(l_k, r_q);
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 12) = likely(Hi(lr_kq) == Lo(lr_kq))
+ ? lr_kq
+ : Raise(Hi(lr_kq)) /* lr verbatim to high pixel */ |
+ Average(Hi(lr_kq), Lo(lr_kq)) /* klqr to low pixel */;
+
+ // Generate jkpq_jp from j_i, l_k, p_o and r_q.
+ uint32_t kq_jp =
+ Raise(Lo(lr_kq)) /* kq verbatim to high pixel */ |
+ Average(Hi(j_i), Hi(p_o)) /* jp to low pixel */;
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 8) = likely(Hi(kq_jp) == Lo(kq_jp))
+ ? kq_jp
+ : Lo(kq_jp) /* jp verbatim to low pixel */ |
+ Raise(Average(Hi(kq_jp), Lo(kq_jp))) /* jkpq to high pixel */;
+
+ // -- Row 4 --
+ // Generate mn_m from n_m.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 3) = likely(Hi(n_m) == Lo(n_m))
+ ? n_m
+ : Lo(n_m) /* 'm' verbatim to low pixel */ |
+ Raise(Average(Hi(n_m), Lo(n_m))) /* mn to high pixel */;
+
+ // Generate o_no from n_m and p_o.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 3 + 4) = likely(Hi(n_m) == Lo(p_o))
+ ? Lo(p_o) | Raise(Lo(p_o))
+ : Raise(Lo(p_o)) /* 'o' verbatim to high pixel */ |
+ Average(Lo(p_o), Hi(n_m)) /* no to low pixel */;
+
+ // Generate pq_p from p_o and r_q.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 3 + 8) = likely(Hi(p_o) == Lo(r_q))
+ ? Lo(r_q) | Raise(Lo(r_q))
+ : Hi(p_o) /* 'p' verbatim to low pixel */ |
+ Raise(Average(Hi(p_o), Lo(r_q))) /* pq to high pixel */;
+
+ // Generate r_qr from r_q.
+ *(uint32_t*) ((uint8_t*) to + dst_pitch * 3 + 12) = likely(Hi(r_q) == Lo(r_q))
+ ? r_q
+ : Raise(Hi(r_q)) /* 'r' verbatim to high pixel */ |
+ Average(Hi(r_q), Lo(r_q)) /* qr to low pixel */;
+ }
+ }
+
+ from += 6;
+ to += 8;
+ }
+
+ // Skip past the waste at the end of the first line, if any,
+ // then past 2 whole lines of source and 3 of destination.
+ from = (uint16_t*) ((uint8_t*) from + src_skip + 2 * src_pitch);
+ to = (uint16_t*) ((uint8_t*) to + dst_skip + 3 * dst_pitch);
+ }
+}
+
+static inline void gba_nofilter_noscale(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch, uint16_t *src) {
+ int dst_x = ((dst_pitch - GBA_SCREEN_PITCH) / 2);
+ int dst_y = ((dst_h - GBA_SCREEN_HEIGHT) / 2);
+
+ for (int y = 0; y < GBA_SCREEN_HEIGHT; y++) {
+ memcpy(dst + (dst_y + y) * dst_pitch + dst_x,
+ src + y * GBA_SCREEN_PITCH,
+ GBA_SCREEN_PITCH * sizeof(src[0]));
+ }
+}
+
+/* drowsnug's nofilter upscaler, edited by eggs for smoothness */
+#define AVERAGE16(c1, c2) (((c1) + (c2) + (((c1) ^ (c2)) & 0x0821))>>1) //More accurate
+static inline void gba_smooth_upscale(uint16_t *dst, uint16_t *src, int h)
+{
+ int Eh = 0;
+ int dh = 0;
+ int width = 240;
+ int vf = 0;
+
+ dst += ((240-h)/2) * 320; // blank upper border. h=240(full) or h=214(aspect)
+
+ int x, y;
+ for (y = 0; y < h; y++)
+ {
+ int source = dh * width;
+ for (x = 0; x < 320/4; x++)
+ {
+ register uint16_t a, b, c;
+
+ a = src[source];
+ b = src[source+1];
+ c = src[source+2];
+
+ if(vf == 1){
+ a = AVERAGE16(a, src[source+width]);
+ b = AVERAGE16(b, src[source+width+1]);
+ c = AVERAGE16(c, src[source+width+2]);
+ }
+
+ *dst++ = a;
+ *dst++ = AVERAGE16(AVERAGE16(a,b),b);
+ *dst++ = AVERAGE16(b,AVERAGE16(b,c));
+ *dst++ = c;
+ source+=3;
+
+ }
+ Eh += 160;
+ if(Eh >= h) {
+ Eh -= h;
+ dh++;
+ vf = 0;
+ }
+ else
+ vf = 1;
+ }
+}
+
+#define EXTRACT(c, mask, offset) ((c >> offset) & mask)
+#define BLENDCHANNEL(cl, cm, cr, mask, offset) ((((EXTRACT(cl, mask, offset) + 2 * EXTRACT(cm, mask, offset) + EXTRACT(cr, mask, offset)) >> 2) & mask) << offset)
+#define BLENDB(cl, cm, cr) BLENDCHANNEL(cl, cm, cr, 0b0000000000011111, 0)
+#define BLENDG(cl, cm, cr) BLENDCHANNEL(cl, cm, cr, 0b0000011111100000, 0)
+#define BLENDR(cl, cm, cr) BLENDCHANNEL(cl, cm, cr, 0b0011111000000000, 2)
+static inline void gba_smooth_subpx_upscale(uint16_t *dst, uint16_t *src, int h)
+{
+ int Eh = 0;
+ int dh = 0;
+ int width = 240;
+ int vf = 0;
+
+ dst += ((240-h)/2) * 320; // blank upper border. h=240(full) or h=214(aspect)
+
+ int x, y;
+ for (y = 0; y < h; y++)
+ {
+ int source = dh * width;
+ for (x = 0; x < 320/4; x++)
+ {
+ register uint16_t a, b, c;
+
+ a = src[source];
+ b = src[source+1];
+ c = src[source+2];
+
+ if(vf == 1){
+ a = AVERAGE16(a, src[source+width]);
+ b = AVERAGE16(b, src[source+width+1]);
+ c = AVERAGE16(c, src[source+width+2]);
+ }
+
+ *dst++ = a;
+ *dst++ = BLENDB(a, a, b) | BLENDG(a, b, b) | BLENDR(b, b, b);
+ *dst++ = BLENDB(b, b, b) | BLENDG(b, b, c) | BLENDR(b, c, c);
+ *dst++ = c;
+
+ source+=3;
+ }
+ Eh += 160;
+ if(Eh >= h) {
+ Eh -= h;
+ dh++;
+ vf = 0;
+ }
+ else
+ vf = 1;
+ }
+}
+
+void video_clear_msg(uint16_t *dst, uint32_t h, uint32_t pitch)
+{
+ memset(dst + (h - 10) * pitch, 0, 10 * pitch * sizeof(uint16_t));
+}
+
+void video_print_msg(uint16_t *dst, uint32_t h, uint32_t pitch, char *msg)
+{
+ basic_text_out16_nf(dst, pitch, 2, h - 10, msg);
+}
+
+void video_scale(uint16_t *dst, uint32_t h, uint32_t pitch) {
+ uint16_t *gba_screen_pixels_buf = gba_screen_pixels;
+
+ if (color_correct || lcd_blend)
+ gba_screen_pixels_buf = gba_processed_pixels;
+
+ switch (scaling_mode)
+ {
+ case SCALING_ASPECT_SHARP:
+ gba_smooth_subpx_upscale(dst, gba_screen_pixels_buf, 214);
+ break;
+ case SCALING_ASPECT_SMOOTH:
+ gba_smooth_upscale(dst, gba_screen_pixels_buf, 214);
+ break;
+ case SCALING_FULL_SHARP:
+ gba_smooth_subpx_upscale(dst, gba_screen_pixels_buf, 240);
+ break;
+ case SCALING_FULL_SMOOTH:
+ gba_smooth_upscale(dst, gba_screen_pixels_buf, 240);
+ break;
+ default:
+ gba_nofilter_noscale(dst, h, pitch, gba_screen_pixels_buf);
+ break;
+ }
+}
+
+/* Video post processing START */
+
+/* Note: This code is intentionally W.E.T.
+ * (Write Everything Twice). These functions
+ * are performance critical, and we cannot
+ * afford to do unnecessary comparisons/switches
+ * inside the inner for loops */
+
+static void video_post_process_cc(void)
+{
+ uint16_t *src = gba_screen_pixels;
+ uint16_t *dst = gba_processed_pixels;
+ size_t x, y;
+
+ for (y = 0; y < GBA_SCREEN_HEIGHT; y++)
+ {
+ for (x = 0; x < GBA_SCREEN_PITCH; x++)
+ {
+ u16 src_color = *(src + x);
+
+ /* Convert colour to RGB555 and perform lookup */
+ *(dst + x) = *(gba_cc_lut + (((src_color & 0xFFC0) >> 1) | (src_color & 0x1F)));
+ }
+
+ src += GBA_SCREEN_PITCH;
+ dst += GBA_SCREEN_PITCH;
+ }
+}
+
+static void video_post_process_mix(void)
+{
+ uint16_t *src_curr = gba_screen_pixels;
+ uint16_t *src_prev = gba_screen_pixels_prev;
+ uint16_t *dst = gba_processed_pixels;
+ size_t x, y;
+
+ for (y = 0; y < GBA_SCREEN_HEIGHT; y++)
+ {
+ for (x = 0; x < GBA_SCREEN_PITCH; x++)
+ {
+ /* Get colours from current + previous frames (RGB565) */
+ uint16_t rgb_curr = *(src_curr + x);
+ uint16_t rgb_prev = *(src_prev + x);
+
+ /* Store colours for next frame */
+ *(src_prev + x) = rgb_curr;
+
+ /* Mix colours
+ * > "Mixing Packed RGB Pixels Efficiently"
+ * http://blargg.8bitalley.com/info/rgb_mixing.html */
+ *(dst + x) = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x821)) >> 1;
+ }
+
+ src_curr += GBA_SCREEN_PITCH;
+ src_prev += GBA_SCREEN_PITCH;
+ dst += GBA_SCREEN_PITCH;
+ }
+}
+
+static void video_post_process_cc_mix(void)
+{
+ uint16_t *src_curr = gba_screen_pixels;
+ uint16_t *src_prev = gba_screen_pixels_prev;
+ uint16_t *dst = gba_processed_pixels;
+ size_t x, y;
+
+ for (y = 0; y < GBA_SCREEN_HEIGHT; y++)
+ {
+ for (x = 0; x < GBA_SCREEN_PITCH; x++)
+ {
+ /* Get colours from current + previous frames (RGB565) */
+ uint16_t rgb_curr = *(src_curr + x);
+ uint16_t rgb_prev = *(src_prev + x);
+
+ /* Store colours for next frame */
+ *(src_prev + x) = rgb_curr;
+
+ /* Mix colours
+ * > "Mixing Packed RGB Pixels Efficiently"
+ * http://blargg.8bitalley.com/info/rgb_mixing.html */
+ uint16_t rgb_mix = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x821)) >> 1;
+
+ /* Convert colour to RGB555 and perform lookup */
+ *(dst + x) = *(gba_cc_lut + (((rgb_mix & 0xFFC0) >> 1) | (rgb_mix & 0x1F)));
+ }
+
+ src_curr += GBA_SCREEN_PITCH;
+ src_prev += GBA_SCREEN_PITCH;
+ dst += GBA_SCREEN_PITCH;
+ }
+}
+
+void video_post_process(void)
+{
+ size_t buf_size = GBA_SCREEN_PITCH * GBA_SCREEN_HEIGHT * sizeof(u16);
+
+ /* If post processing is disabled, return
+ * immediately */
+ if (!color_correct && !lcd_blend)
+ return;
+
+ /* Initialise output buffer, if required */
+ if (!gba_processed_pixels &&
+ (color_correct || lcd_blend))
+ {
+ gba_processed_pixels = (u16*)malloc(buf_size);
+
+ if (!gba_processed_pixels)
+ return;
+
+ memset(gba_processed_pixels, 0xFFFF, buf_size);
+ }
+
+ /* Initialise 'history' buffer, if required */
+ if (!gba_screen_pixels_prev &&
+ lcd_blend)
+ {
+ gba_screen_pixels_prev = (u16*)malloc(buf_size);
+
+ if (!gba_screen_pixels_prev)
+ return;
+
+ memset(gba_screen_pixels_prev, 0xFFFF, buf_size);
+ }
+
+ /* Assign post processing function */
+ if (color_correct && lcd_blend)
+ video_post_process_cc_mix();
+ else if (color_correct)
+ video_post_process_cc();
+ else if (lcd_blend)
+ video_post_process_mix();
+}
+
+/* Video post processing END */
diff --git a/frontend/scale.h b/frontend/scale.h
new file mode 100644
index 0000000..fd538eb
--- /dev/null
+++ b/frontend/scale.h
@@ -0,0 +1,9 @@
+#ifndef __FRONTEND_SCALE_H__
+#define __FRONTEND_SCALE_H__
+
+void video_post_process(void);
+void video_scale(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch);
+void video_clear_msg(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch);
+void video_print_msg(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch, char *msg);
+
+#endif /* __FRONTEND_SCALE_H__ */
diff --git a/gba_memory.c b/gba_memory.c
index 4376cf8..f397797 100644
--- a/gba_memory.c
+++ b/gba_memory.c
@@ -18,6 +18,7 @@
*/
#include "common.h"
+#include "zip.h"
/* Sound */
#define gbc_sound_tone_control_low(channel, address) \
@@ -309,7 +310,8 @@ u16 io_registers[1024 * 16];
u8 ewram[1024 * 256 * 2];
u8 iwram[1024 * 32 * 2];
u8 vram[1024 * 96];
-u8 bios_rom[1024 * 16];
+
+u8 bios_rom[1024 * 16 * 2];
u32 bios_read_protect;
// Up to 128kb, store SRAM, flash ROM, or EEPROM here.
@@ -2374,11 +2376,16 @@ char gamepak_code[5];
char gamepak_maker[3];
char gamepak_filename[512];
-u32 load_gamepak(const struct retro_game_info* info, const char *name)
+u32 load_gamepak(const char *name)
{
+ char *dot_position = strrchr(name, '.');
+ s32 file_size;
char *p;
- s32 file_size = load_gamepak_raw(name);
+ if(!strcmp(dot_position, ".zip"))
+ file_size = load_file_zip(name);
+ else
+ file_size = load_gamepak_raw(name);
if(file_size == -1)
return -1;
@@ -3259,6 +3266,17 @@ void memory_term(void)
}
}
+void bios_region_read_allow(void)
+{
+ memory_map_read[0] = bios_rom;
+}
+
+void bios_region_read_protect(void)
+{
+ memory_map_read[0] = NULL;
+}
+
+
#define savestate_block(type) \
cpu_##type##_savestate(); \
input_##type##_savestate(); \
diff --git a/gba_memory.h b/gba_memory.h
index 0f9dc3a..7e5141f 100644
--- a/gba_memory.h
+++ b/gba_memory.h
@@ -195,13 +195,15 @@ extern char gamepak_filename[512];
cpu_alert_type dma_transfer(dma_transfer_type *dma);
u8 *memory_region(u32 address, u32 *memory_limit);
-u32 load_gamepak(const struct retro_game_info* info, const char *name);
+u32 load_gamepak(const char *name);
u32 load_backup(char *name);
s32 load_bios(char *name);
void update_backup(void);
void init_memory(void);
void init_gamepak_buffer(void);
void memory_term(void);
+void bios_region_read_allow(void);
+void bios_region_read_protect(void);
u8 *load_gamepak_page(u32 physical_index);
extern u8 *gamepak_rom;
@@ -218,8 +220,8 @@ extern u16 oam_ram[512];
extern u16 palette_ram_converted[512];
extern u16 io_registers[1024 * 16];
extern u8 vram[1024 * 96];
-extern u8 bios_rom[1024 * 16];
// Double buffer used for SMC detection
+extern u8 bios_rom[1024 * 16 * 2];
extern u8 ewram[1024 * 256 * 2];
extern u8 iwram[1024 * 32 * 2];
diff --git a/gpsp_config.h b/gpsp_config.h
index ea8db95..a3d8ce1 100644
--- a/gpsp_config.h
+++ b/gpsp_config.h
@@ -6,10 +6,12 @@
#if defined(PSP)
#define ROM_TRANSLATION_CACHE_SIZE (1024 * 512 * 4)
#define RAM_TRANSLATION_CACHE_SIZE (1024 * 384)
+ #define BIOS_TRANSLATION_CACHE_SIZE (1024 * 128)
#define TRANSLATION_CACHE_LIMIT_THRESHOLD (1024)
#else
#define ROM_TRANSLATION_CACHE_SIZE (1024 * 512 * 4 * 5)
#define RAM_TRANSLATION_CACHE_SIZE (1024 * 384 * 2)
+ #define BIOS_TRANSLATION_CACHE_SIZE (1024 * 128 * 2)
#define TRANSLATION_CACHE_LIMIT_THRESHOLD (1024 * 32)
#endif
diff --git a/input.c b/input.c
index e030596..76cd861 100644
--- a/input.c
+++ b/input.c
@@ -18,6 +18,10 @@
*/
#include "common.h"
+#ifndef __LIBRETRO__
+#include "frontend/main.h"
+#include "frontend/libpicofe/input.h"
+#endif
bool libretro_supports_bitmasks = false;
bool libretro_supports_ff_override = false;
@@ -57,6 +61,7 @@ static void trigger_key(u32 key)
}
}
+#ifdef __LIBRETRO__
u32 update_input(void)
{
unsigned i;
@@ -132,6 +137,35 @@ u32 update_input(void)
return 0;
}
+#else
+
+u32 update_input(void)
+{
+ int actions[IN_BINDTYPE_COUNT] = { 0, };
+ uint32_t new_key = 0;
+ unsigned int emu_act;
+ int which = EACTION_NONE;
+
+ in_update(actions);
+ emu_act = actions[IN_BINDTYPE_EMU];
+ if (emu_act) {
+ for (; !(emu_act & 1); emu_act >>= 1, which++)
+ ;
+ emu_act = which;
+ }
+ handle_emu_action(which);
+
+ new_key = actions[IN_BINDTYPE_PLAYER12];
+
+ if ((new_key | old_key) != old_key)
+ trigger_key(new_key);
+
+ old_key = new_key;
+ io_registers[REG_P1] = (~old_key) & 0x3FF;
+
+ return 0;
+}
+#endif
#define input_savestate_builder(type) \
void input_##type##_savestate(void) \
diff --git a/libretro.c b/libretro.c
index 5dfe0df..a7d2765 100644
--- a/libretro.c
+++ b/libretro.c
@@ -22,7 +22,8 @@ static inline int align(int x, int n) {
#define MB_ALIGN(x) align(x, 20)
int _newlib_vm_size_user = ROM_TRANSLATION_CACHE_SIZE +
- RAM_TRANSLATION_CACHE_SIZE;
+ RAM_TRANSLATION_CACHE_SIZE +
+ BIOS_TRANSLATION_CACHE_SIZE;
int getVMBlock();
@@ -460,6 +461,8 @@ void retro_init(void)
PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
ram_translation_cache = mmap(NULL, RAM_TRANSLATION_CACHE_SIZE,
PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
+ bios_translation_cache = mmap(NULL, BIOS_TRANSLATION_CACHE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
#elif defined(_3DS)
if (__ctr_svchax && !translation_caches_inited)
{
@@ -468,6 +471,7 @@ void retro_init(void)
rom_translation_cache_ptr = memalign(0x1000, ROM_TRANSLATION_CACHE_SIZE);
ram_translation_cache_ptr = memalign(0x1000, RAM_TRANSLATION_CACHE_SIZE);
+ bios_translation_cache_ptr = memalign(0x1000, BIOS_TRANSLATION_CACHE_SIZE);
svcDuplicateHandle(&currentHandle, 0xFFFF8001);
svcControlProcessMemory(currentHandle,
@@ -476,9 +480,13 @@ void retro_init(void)
svcControlProcessMemory(currentHandle,
ram_translation_cache, ram_translation_cache_ptr,
RAM_TRANSLATION_CACHE_SIZE, MEMOP_MAP, 0b111);
+ svcControlProcessMemory(currentHandle,
+ bios_translation_cache, bios_translation_cache_ptr,
+ BIOS_TRANSLATION_CACHE_SIZE, MEMOP_MAP, 0b111);
svcCloseHandle(currentHandle);
rom_translation_ptr = rom_translation_cache;
ram_translation_ptr = ram_translation_cache;
+ bios_translation_ptr = bios_translation_cache;
ctr_flush_invalidate_cache();
translation_caches_inited = 1;
}
@@ -502,8 +510,10 @@ void retro_init(void)
rom_translation_cache = (u8*)currentHandle;
ram_translation_cache = rom_translation_cache + ROM_TRANSLATION_CACHE_SIZE;
+ bios_translation_cache = ram_translation_cache + RAM_TRANSLATION_CACHE_SIZE;
rom_translation_ptr = rom_translation_cache;
ram_translation_ptr = ram_translation_cache;
+ bios_translation_ptr = bios_translation_cache;
sceKernelOpenVMDomain();
translation_caches_inited = 1;
}
@@ -550,6 +560,7 @@ void retro_deinit(void)
#if defined(HAVE_MMAP) && defined(HAVE_DYNAREC)
munmap(rom_translation_cache, ROM_TRANSLATION_CACHE_SIZE);
munmap(ram_translation_cache, RAM_TRANSLATION_CACHE_SIZE);
+ munmap(bios_translation_cache, BIOS_TRANSLATION_CACHE_SIZE);
#endif
#if defined(_3DS) && defined(HAVE_DYNAREC)
@@ -563,9 +574,13 @@ void retro_deinit(void)
svcControlProcessMemory(currentHandle,
ram_translation_cache, ram_translation_cache_ptr,
RAM_TRANSLATION_CACHE_SIZE, MEMOP_UNMAP, 0b111);
+ svcControlProcessMemory(currentHandle,
+ bios_translation_cache, bios_translation_cache_ptr,
+ BIOS_TRANSLATION_CACHE_SIZE, MEMOP_UNMAP, 0b111);
svcCloseHandle(currentHandle);
free(rom_translation_cache_ptr);
free(ram_translation_cache_ptr);
+ free(bios_translation_cache_ptr);
translation_caches_inited = 0;
}
#endif
@@ -976,7 +991,7 @@ bool retro_load_game(const struct retro_game_info* info)
memset(gamepak_backup, -1, sizeof(gamepak_backup));
gamepak_filename[0] = 0;
- if (load_gamepak(info, info->path) != 0)
+ if (load_gamepak(info->path) != 0)
{
error_msg("Could not load the game file.");
return false;
diff --git a/psp/mips_stub.S b/psp/mips_stub.S
index 7b9bcb0..eb672c7 100644
--- a/psp/mips_stub.S
+++ b/psp/mips_stub.S
@@ -685,6 +685,7 @@ fnptrs:
.global stub_arena
.global rom_translation_cache
.global ram_translation_cache
+.global bios_translation_cache
stub_arena:
.space STUB_ARENA_SIZE
@@ -692,6 +693,8 @@ rom_translation_cache:
.space ROM_TRANSLATION_CACHE_SIZE
ram_translation_cache:
.space RAM_TRANSLATION_CACHE_SIZE
+bios_translation_cache:
+ .space BIOS_TRANSLATION_CACHE_SIZE
#endif
diff --git a/sound.c b/sound.c
index 76c3525..a9d2140 100644
--- a/sound.c
+++ b/sound.c
@@ -19,7 +19,12 @@
#include "common.h"
+#ifndef __LIBRETRO__
+#include "frontend/plat.h"
+#endif
+
u32 global_enable_audio = 1;
+u32 global_process_audio = 1;
direct_sound_struct direct_sound_channel[2];
gbc_sound_struct gbc_sound_channel[4];
@@ -658,7 +663,12 @@ void render_audio(void)
stream_base[i] = current_sample * 16;
source[i] = 0;
}
+#ifdef __LIBRETRO__
audio_batch_cb(stream_base, 256);
+#else
+ if (global_process_audio)
+ plat_sound_write(stream_base, 1024);
+#endif
sound_buffer_base += 512;
sound_buffer_base &= BUFFER_SIZE_MASK;
}
diff --git a/sound.h b/sound.h
index 48f227f..2dda1c5 100644
--- a/sound.h
+++ b/sound.h
@@ -24,8 +24,15 @@
#define BUFFER_SIZE_MASK (BUFFER_SIZE - 1)
#define GBA_SOUND_FREQUENCY (64 * 1024)
+#define GBA_60HZ_RATE 16853760.0f /* 228*(272+960)*60 */
+#if !defined(PSP_BUILD) && !defined(__LIBRETRO__)
+// run GBA at 60Hz (~0.5% faster) to better match host display
+#define GBC_BASE_RATE GBA_60HZ_RATE
+#else
#define GBC_BASE_RATE ((float)(16 * 1024 * 1024))
+#endif
+
typedef enum
{
@@ -108,6 +115,7 @@ extern u32 sound_frequency;
extern u32 sound_on;
extern u32 global_enable_audio;
+extern u32 global_process_audio;
extern u32 enable_low_pass_filter;
void sound_timer_queue8(u32 channel, u8 value);
diff --git a/x86/x86_stub.S b/x86/x86_stub.S
index 333c8fd..b110787 100644
--- a/x86/x86_stub.S
+++ b/x86/x86_stub.S
@@ -558,6 +558,8 @@ defsymbl(rom_translation_cache)
.space ROM_TRANSLATION_CACHE_SIZE
defsymbl(ram_translation_cache)
.space RAM_TRANSLATION_CACHE_SIZE
+defsymbl(bios_translation_cache)
+ .space BIOS_TRANSLATION_CACHE_SIZE
#endif
diff --git a/zip.c b/zip.c
new file mode 100644
index 0000000..d563d5f
--- /dev/null
+++ b/zip.c
@@ -0,0 +1,160 @@
+/* gameplaySP
+ *
+ * Copyright (C) 2006 Exophase <exophase@gmail.com>
+ * Copyright (C) 2006 SiberianSTAR
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "common.h"
+#include <zlib.h>
+
+#define ZIP_BUFFER_SIZE (128 * 1024)
+
+struct SZIPFileDataDescriptor
+{
+ s32 CRC32;
+ s32 CompressedSize;
+ s32 UncompressedSize;
+} __attribute__((packed));
+
+struct SZIPFileHeader
+{
+ char Sig[4]; // EDIT: Used to be s32 Sig;
+ s16 VersionToExtract;
+ s16 GeneralBitFlag;
+ s16 CompressionMethod;
+ s16 LastModFileTime;
+ s16 LastModFileDate;
+ struct SZIPFileDataDescriptor DataDescriptor;
+ s16 FilenameLength;
+ s16 ExtraFieldLength;
+} __attribute__((packed));
+
+u32 load_file_zip(const char *filename)
+{
+ struct SZIPFileHeader data;
+ char tmp[1024];
+ s32 retval = -1;
+ u8 *buffer = NULL;
+ u8 *cbuffer;
+ char *ext;
+ int ret;
+ FILE *fd;
+
+ fd = fopen(filename, "rb");
+
+ if(!fd)
+ return -1;
+
+ while (1)
+ {
+ ret = fread(&data, 1, sizeof(data), fd);
+ if (ret != sizeof(data))
+ break;
+
+ // It checks for the following: 0x50 0x4B 0x03 0x04 (PK..)
+ if( data.Sig[0] != 0x50 || data.Sig[1] != 0x4B ||
+ data.Sig[2] != 0x03 || data.Sig[3] != 0x04 )
+ {
+ break;
+ }
+
+ ret = fread(tmp, 1, data.FilenameLength, fd);
+ if (ret != data.FilenameLength)
+ break;
+
+ tmp[data.FilenameLength] = 0; // end string
+
+ if(data.ExtraFieldLength)
+ fseek(fd, data.ExtraFieldLength, SEEK_CUR);
+
+ if(data.GeneralBitFlag & 0x0008)
+ {
+ fread(&data.DataDescriptor,
+ 1, sizeof(struct SZIPFileDataDescriptor), fd);
+ }
+
+ ext = strrchr(tmp, '.') + 1;
+
+ // file is too big
+ if(data.DataDescriptor.UncompressedSize > gamepak_ram_buffer_size)
+ goto skip;
+
+ if(!strcasecmp(ext, "bin") || !strcasecmp(ext, "gba"))
+ {
+ buffer = gamepak_rom;
+
+ // ok, found
+ switch(data.CompressionMethod)
+ {
+ case 0:
+ retval = data.DataDescriptor.UncompressedSize;
+ fread(buffer, 1, retval, fd);
+ goto outcode;
+
+ case 8:
+ {
+ z_stream stream;
+ s32 err;
+
+ cbuffer = malloc(ZIP_BUFFER_SIZE);
+
+ stream.next_in = (Bytef*)cbuffer;
+ stream.avail_in = (u32)ZIP_BUFFER_SIZE;
+
+ stream.next_out = (Bytef*)buffer;
+
+ // EDIT: Now uses proper conversion of data types for retval.
+ retval = (u32)data.DataDescriptor.UncompressedSize;
+ stream.avail_out = data.DataDescriptor.UncompressedSize;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+
+ err = inflateInit2(&stream, -MAX_WBITS);
+
+ fread(cbuffer, 1, ZIP_BUFFER_SIZE, fd);
+
+ if(err == Z_OK)
+ {
+ while(err != Z_STREAM_END)
+ {
+ err = inflate(&stream, Z_SYNC_FLUSH);
+ if(err == Z_BUF_ERROR)
+ {
+ stream.avail_in = ZIP_BUFFER_SIZE;
+ stream.next_in = (Bytef*)cbuffer;
+ fread(cbuffer, 1, ZIP_BUFFER_SIZE, fd);
+ }
+ }
+ err = Z_OK;
+ inflateEnd(&stream);
+ }
+ free(cbuffer);
+ goto outcode;
+ }
+ }
+ }
+
+skip:
+ fseek(fd, data.DataDescriptor.CompressedSize, SEEK_CUR);
+ }
+
+outcode:
+ fclose(fd);
+
+ return retval;
+}
diff --git a/zip.h b/zip.h
new file mode 100644
index 0000000..12a51ee
--- /dev/null
+++ b/zip.h
@@ -0,0 +1,26 @@
+/* gameplaySP
+ *
+ * Copyright (C) 2006 Exophase <exophase@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef ZIP_H
+#define ZIP_H
+
+u32 load_file_zip(const char *filename);
+
+#endif
+