diff --git a/platform/linux/README b/platform/linux/README new file mode 100644 index 0000000..fa36b28 --- /dev/null +++ b/platform/linux/README @@ -0,0 +1,4 @@ +This port tries to emulate gp2x environment on a standard linux box for testing +(i.e. to be able to use things like valgrind and efence, gcc runtime +optimizations, etc.). + diff --git a/platform/linux/blit.c b/platform/linux/blit.c new file mode 100644 index 0000000..96326fe --- /dev/null +++ b/platform/linux/blit.c @@ -0,0 +1,90 @@ +/* + * PicoDrive + * (C) notaz, 2006,2009 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +// Convert 0000bbb0 ggg0rrr0 0000bbb0 ggg0rrr0 +// to 00000000 rrr00000 ggg00000 bbb00000 ... +// TODO: rm when gp2x/emu.c is no longer used + +void bgr444_to_rgb32(void *to, void *from) +{ + unsigned short *ps = from; + unsigned int *pd = to; + int pixels; + + for (pixels = 0x40; pixels; pixels--, ps++, pd++) + { + *pd = ((*ps<<20)&0xe00000) | ((*ps<<8)&0xe000) | ((*ps>>4)&0xe0); + *pd |= *pd >> 3; + } +} + +void bgr444_to_rgb32_sh(void *to, void *from) +{ + unsigned short *ps = from; + unsigned int *pd = to; + int pixels; + + pd += 0x40; + for (pixels = 0x40; pixels; pixels--, ps++, pd++) + { + *pd = ((*ps<<20)&0xe00000) | ((*ps<<8)&0xe000) | ((*ps>>4)&0xe0); + *pd >>= 1; + *pd |= *pd >> 3; + pd[0x40*2] = *pd; + } + + ps -= 0x40; + for (pixels = 0x40; pixels; pixels--, ps++, pd++) + { + *pd = ((*ps<<20)&0xe00000) | ((*ps<<8)&0xe000) | ((*ps>>4)&0xe0); + continue; + *pd += 0x00404040; + if (*pd & 0x01000000) *pd |= 0x00e00000; + if (*pd & 0x00010000) *pd |= 0x0000e000; + if (*pd & 0x00000100) *pd |= 0x000000e0; + *pd &= 0x00e0e0e0; + *pd |= *pd >> 3; + } +} + +void vidcpy_m2(void *dest, void *src, int m32col, int with_32c_border) +{ + unsigned char *pd = dest, *ps = src; + int i, u; + + if (m32col) { + for (i = 0; i < 224; i++) + { + ps += 8; + pd += 32; + for (u = 0; u < 256; u++) + *pd++ = *ps++; + ps += 64; + pd += 32; + } + } else { + for (i = 0; i < 224; i++) + { + ps += 8; + for (u = 0; u < 320; u++) + *pd++ = *ps++; + } + } +} + +void vidcpy_m2_rot(void *dest, void *src, int m32col, int with_32c_border) +{ +} + +void rotated_blit8 (void *dst, void *linesx4, int y, int is_32col) +{ +} + +void rotated_blit16(void *dst, void *linesx4, int y, int is_32col) +{ +} diff --git a/platform/linux/emu.c b/platform/linux/emu.c new file mode 100644 index 0000000..5d4432f --- /dev/null +++ b/platform/linux/emu.c @@ -0,0 +1,207 @@ +/* + * PicoDrive + * (C) notaz, 2006-2010 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +#include +#include + +#include "../libpicofe/menu.h" +#include "../libpicofe/plat.h" +#include "../common/emu.h" +#include "../common/arm_utils.h" +#include "../common/version.h" + +#include + + +const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast", NULL }; +const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL }; +enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT }; + + +void pemu_prep_defconfig(void) +{ +} + +void pemu_validate_config(void) +{ +#if !defined(__arm__) && !defined(__i386__) && !defined(__x86_64__) + PicoIn.opt &= ~POPT_EN_DRC; +#endif +} + +static void draw_cd_leds(void) +{ + int led_reg, pitch, scr_offs, led_offs; + led_reg = Pico_mcd->s68k_regs[0]; + + pitch = 320; + led_offs = 4; + scr_offs = pitch * 2 + 4; + + if (currentConfig.renderer != RT_16BIT) { + #define p(x) px[(x) >> 2] + // 8-bit modes + unsigned int *px = (unsigned int *)((char *)g_screen_ptr + scr_offs); + unsigned int col_g = (led_reg & 2) ? 0xc0c0c0c0 : 0xe0e0e0e0; + unsigned int col_r = (led_reg & 1) ? 0xd0d0d0d0 : 0xe0e0e0e0; + p(pitch*0) = p(pitch*1) = p(pitch*2) = col_g; + p(pitch*0 + led_offs) = p(pitch*1 + led_offs) = p(pitch*2 + led_offs) = col_r; + #undef p + } else { + #define p(x) px[(x)*2 >> 2] = px[((x)*2 >> 2) + 1] + // 16-bit modes + unsigned int *px = (unsigned int *)((short *)g_screen_ptr + scr_offs); + unsigned int col_g = (led_reg & 2) ? 0x06000600 : 0; + unsigned int col_r = (led_reg & 1) ? 0xc000c000 : 0; + p(pitch*0) = p(pitch*1) = p(pitch*2) = col_g; + p(pitch*0 + led_offs) = p(pitch*1 + led_offs) = p(pitch*2 + led_offs) = col_r; + #undef p + } +} + +void pemu_finalize_frame(const char *fps, const char *notice) +{ + if (currentConfig.renderer != RT_16BIT && !(PicoIn.AHW & PAHW_32X)) { + unsigned short *pd = (unsigned short *)g_screen_ptr + 8 * g_screen_ppitch; + unsigned char *ps = Pico.est.Draw2FB + 328*8 + 8; + unsigned short *pal = Pico.est.HighPal; + int i, x; + if (Pico.m.dirtyPal) + PicoDrawUpdateHighPal(); + for (i = 0; i < 224; i++, ps += 8) + for (x = 0; x < 320; x++) + *pd++ = pal[*ps++]; + } + + if (notice || (currentConfig.EmuOpt & EOPT_SHOW_FPS)) { + if (notice) + emu_osd_text16(4, g_screen_height - 8, notice); + if (currentConfig.EmuOpt & EOPT_SHOW_FPS) + emu_osd_text16(g_screen_width - 60, g_screen_height - 8, fps); + } + if ((PicoIn.AHW & PAHW_MCD) && (currentConfig.EmuOpt & EOPT_EN_CD_LEDS)) + draw_cd_leds(); +} + +static void apply_renderer(void) +{ + switch (currentConfig.renderer) { + case RT_16BIT: + PicoIn.opt &= ~POPT_ALT_RENDERER; + PicoDrawSetOutFormat(PDF_RGB555, 0); + PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2); + break; + case RT_8BIT_ACC: + PicoIn.opt &= ~POPT_ALT_RENDERER; + PicoDrawSetOutFormat(PDF_8BIT, 0); + PicoDrawSetOutBuf(Pico.est.Draw2FB + 8, 328); + break; + case RT_8BIT_FAST: + PicoIn.opt |= POPT_ALT_RENDERER; + PicoDrawSetOutFormat(PDF_NONE, 0); + break; + } + + if (PicoIn.AHW & PAHW_32X) + PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2); +} + +void plat_video_toggle_renderer(int change, int is_menu) +{ + currentConfig.renderer += change; + if (currentConfig.renderer >= RT_COUNT) + currentConfig.renderer = 0; + else if (currentConfig.renderer < 0) + currentConfig.renderer = RT_COUNT - 1; + + if (!is_menu) + apply_renderer(); + + emu_status_msg(renderer_names[currentConfig.renderer]); +} + +void plat_status_msg_clear(void) +{ + unsigned short *d = (unsigned short *)g_screen_ptr + g_screen_ppitch * g_screen_height; + int l = g_screen_ppitch * 8; + memset32((int *)(d - l), 0, l * 2 / 4); +} + +void plat_status_msg_busy_next(const char *msg) +{ + plat_status_msg_clear(); + pemu_finalize_frame("", msg); + plat_video_flip(); + emu_status_msg(""); + reset_timing = 1; +} + +void plat_status_msg_busy_first(const char *msg) +{ +// memset32(g_screen_ptr, 0, g_screen_ppitch * g_screen_height * 2 / 4); + plat_status_msg_busy_next(msg); +} + +void plat_update_volume(int has_changed, int is_up) +{ +} + +void pemu_forced_frame(int no_scale, int do_emu) +{ + PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2); + PicoDrawSetCallbacks(NULL, NULL); + Pico.m.dirtyPal = 1; + + emu_cmn_forced_frame(no_scale, do_emu); + + g_menubg_src_ptr = g_screen_ptr; +} + +void pemu_sound_start(void) +{ + emu_sound_start(); +} + +void plat_debug_cat(char *str) +{ +} + +void emu_video_mode_change(int start_line, int line_count, int is_32cols) +{ + // clear whole screen in all buffers + memset32(g_screen_ptr, 0, g_screen_ppitch * g_screen_height * 2 / 4); +} + +void pemu_loop_prep(void) +{ + apply_renderer(); +} + +void pemu_loop_end(void) +{ + /* do one more frame for menu bg */ + pemu_forced_frame(0, 1); +} + +void plat_wait_till_us(unsigned int us_to) +{ + unsigned int now; + + now = plat_get_ticks_us(); + + while ((signed int)(us_to - now) > 512) + { + usleep(1024); + now = plat_get_ticks_us(); + } +} + +void *plat_mem_get_for_drc(size_t size) +{ + return NULL; +} diff --git a/platform/linux/in_evdev.h b/platform/linux/in_evdev.h new file mode 100644 index 0000000..c5aef9b --- /dev/null +++ b/platform/linux/in_evdev.h @@ -0,0 +1,5 @@ + +struct in_default_bind; +extern int in_evdev_allow_abs_only; + +void in_evdev_init(const struct in_default_bind *defbinds); diff --git a/platform/linux/io.c b/platform/linux/io.c new file mode 100644 index 0000000..57601cc --- /dev/null +++ b/platform/linux/io.c @@ -0,0 +1,384 @@ +/* + * PicoDrive + * (C) notaz, 2007-2010 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ +#include +#include +#include + +#include "../libpicofe/menu.h" +#include "../libpicofe/input.h" +#include "../common/emu.h" +#include "../common/input_pico.h" +#include "version.h" + +#include "log_io.h" + +int current_keys; + +#ifdef FBDEV + +#include "fbdev.h" + +#else + +#include +#include + +static int current_bpp = 16; +static int current_pal[256]; +static const char *verstring = "PicoDrive " VERSION; +static int scr_changed = 0, scr_w = 320, scr_h = 240; + +/* faking GP2X pad */ +enum { GP2X_UP=0x1, GP2X_LEFT=0x4, GP2X_DOWN=0x10, GP2X_RIGHT=0x40, + GP2X_START=1<<8, GP2X_SELECT=1<<9, GP2X_L=1<<10, GP2X_R=1<<11, + GP2X_A=1<<12, GP2X_B=1<<13, GP2X_X=1<<14, GP2X_Y=1<<15, + GP2X_VOL_UP=1<<23, GP2X_VOL_DOWN=1<<22, GP2X_PUSH=1<<27 }; + +static void key_press_event(int keycode) +{ + switch (keycode) + { + case 111: + case 0x62: current_keys |= GP2X_UP; break; + case 116: + case 0x68: current_keys |= GP2X_DOWN; break; + case 113: + case 0x64: current_keys |= GP2X_LEFT; break; + case 114: + case 0x66: current_keys |= GP2X_RIGHT; break; + case 0x24: current_keys |= GP2X_START; break; // enter + case 0x23: current_keys |= GP2X_SELECT;break; // ] + case 0x34: current_keys |= GP2X_A; break; // z + case 0x35: current_keys |= GP2X_X; break; // x + case 0x36: current_keys |= GP2X_B; break; // c + case 0x37: current_keys |= GP2X_Y; break; // v + case 0x27: current_keys |= GP2X_L; break; // s + case 0x28: current_keys |= GP2X_R; break; // d + case 0x29: current_keys |= GP2X_PUSH; break; // f + case 0x18: current_keys |= GP2X_VOL_DOWN;break; // q + case 0x19: current_keys |= GP2X_VOL_UP;break; // w + case 0x2d: log_io_clear(); break; // k + case 0x2e: log_io_dump(); break; // l + case 0x17: { // tab + extern int PicoReset(void); + PicoReset(); + break; + } + } +} + +static void key_release_event(int keycode) +{ + switch (keycode) + { + case 111: + case 0x62: current_keys &= ~GP2X_UP; break; + case 116: + case 0x68: current_keys &= ~GP2X_DOWN; break; + case 113: + case 0x64: current_keys &= ~GP2X_LEFT; break; + case 114: + case 0x66: current_keys &= ~GP2X_RIGHT; break; + case 0x24: current_keys &= ~GP2X_START; break; // enter + case 0x23: current_keys &= ~GP2X_SELECT;break; // ] + case 0x34: current_keys &= ~GP2X_A; break; // z + case 0x35: current_keys &= ~GP2X_X; break; // x + case 0x36: current_keys &= ~GP2X_B; break; // c + case 0x37: current_keys &= ~GP2X_Y; break; // v + case 0x27: current_keys &= ~GP2X_L; break; // s + case 0x28: current_keys &= ~GP2X_R; break; // d + case 0x29: current_keys &= ~GP2X_PUSH; break; // f + case 0x18: current_keys &= ~GP2X_VOL_DOWN;break; // q + case 0x19: current_keys &= ~GP2X_VOL_UP;break; // w + } +} + +/* --- */ + +#include +#include + +static Display *xlib_display; +static Window xlib_window; +static XImage *ximage; + +static void ximage_realloc(Display *display, Visual *visual) +{ + void *xlib_screen; + + XLockDisplay(xlib_display); + + if (ximage != NULL) + XDestroyImage(ximage); + ximage = NULL; + + xlib_screen = calloc(scr_w * scr_h, 4); + if (xlib_screen != NULL) + ximage = XCreateImage(display, visual, 24, ZPixmap, 0, + xlib_screen, scr_w, scr_h, 32, 0); + if (ximage == NULL) + fprintf(stderr, "failed to alloc ximage\n"); + + XUnlockDisplay(xlib_display); +} + +static void xlib_update(void) +{ + Status xstatus; + + XLockDisplay(xlib_display); + + xstatus = XPutImage(xlib_display, xlib_window, DefaultGC(xlib_display, 0), ximage, + 0, 0, 0, 0, g_screen_ppitch, g_screen_height); + if (xstatus != 0) + fprintf(stderr, "XPutImage %d\n", xstatus); + + XUnlockDisplay(xlib_display); +} + +static void *xlib_threadf(void *targ) +{ + unsigned int width, height, display_width, display_height; + sem_t *sem = targ; + XTextProperty windowName; + Window win; + XEvent report; + Display *display; + Visual *visual; + int screen; + + XInitThreads(); + + xlib_display = display = XOpenDisplay(NULL); + if (display == NULL) + { + fprintf(stderr, "cannot connect to X server %s\n", + XDisplayName(NULL)); + sem_post(sem); + return NULL; + } + + visual = DefaultVisual(display, 0); + if (visual->class != TrueColor) + { + fprintf(stderr, "cannot handle non true color visual\n"); + XCloseDisplay(display); + sem_post(sem); + return NULL; + } + + printf("X vendor: %s, rel: %d, display: %s, protocol ver: %d.%d\n", ServerVendor(display), + VendorRelease(display), DisplayString(display), ProtocolVersion(display), + ProtocolRevision(display)); + + screen = DefaultScreen(display); + + ximage_realloc(display, visual); + sem_post(sem); + + display_width = DisplayWidth(display, screen); + display_height = DisplayHeight(display, screen); + + xlib_window = win = XCreateSimpleWindow(display, + RootWindow(display, screen), + display_width / 2 - scr_w / 2, + display_height / 2 - scr_h / 2, + scr_w + 2, scr_h + 2, 1, + BlackPixel(display, screen), + BlackPixel(display, screen)); + + XStringListToTextProperty((char **)&verstring, 1, &windowName); + XSetWMName(display, win, &windowName); + + XSelectInput(display, win, ExposureMask | + KeyPressMask | KeyReleaseMask | + StructureNotifyMask); + + XMapWindow(display, win); + + while (1) + { + XNextEvent(display, &report); + switch (report.type) + { + case Expose: + while (XCheckTypedEvent(display, Expose, &report)) + ; + xlib_update(); + break; + + case ConfigureNotify: + width = report.xconfigure.width; + height = report.xconfigure.height; + if (scr_w != width - 2 || scr_h != height - 2) { + scr_w = width - 2; + scr_h = height - 2; + scr_changed = 1; + } + break; + + case ButtonPress: + break; + + case KeyPress: + key_press_event(report.xkey.keycode); + break; + + case KeyRelease: + key_release_event(report.xkey.keycode); + break; + + default: + break; + } + } +} + +static void xlib_init(void) +{ + pthread_t x_thread; + sem_t xlib_sem; + + sem_init(&xlib_sem, 0, 0); + + pthread_create(&x_thread, NULL, xlib_threadf, &xlib_sem); + pthread_detach(x_thread); + + sem_wait(&xlib_sem); + sem_destroy(&xlib_sem); +} + +/* --- */ + +static void realloc_screen(void) +{ + int size = scr_w * scr_h * 2; + g_screen_width = g_menuscreen_w = scr_w; + g_screen_height = g_menuscreen_h = scr_h; + g_screen_ppitch = g_menuscreen_pp = scr_w; + g_screen_ptr = realloc(g_screen_ptr, size); + g_menubg_ptr = realloc(g_menubg_ptr, size); + memset(g_screen_ptr, 0, size); + memset(g_menubg_ptr, 0, size); + scr_changed = 0; +} + +void plat_video_flip(void) +{ + unsigned int *image; + int pixel_count, i; + + if (ximage == NULL) + return; + + pixel_count = g_screen_ppitch * g_screen_height; + image = (void *)ximage->data; + + if (current_bpp == 8) + { + unsigned char *pixels = g_screen_ptr; + int pix; + + for (i = 0; i < pixel_count; i++) + { + pix = current_pal[pixels[i]]; + image[i] = pix; + } + } + else + { + unsigned short *pixels = g_screen_ptr; + + for (i = 0; i < pixel_count; i++) + { + /* in: rrrr rggg gggb bbbb */ + /* out: rrrr r000 gggg gg00 bbbb b000 */ + image[i] = (pixels[i] << 8) & 0xf80000; + image[i] |= (pixels[i] << 5) & 0x00fc00; + image[i] |= (pixels[i] << 3) & 0x0000f8; + } + } + xlib_update(); + + if (scr_changed) { + realloc_screen(); + ximage_realloc(xlib_display, DefaultVisual(xlib_display, 0)); + + // propagate new ponters to renderers + plat_video_toggle_renderer(0, 0); + } +} + +void plat_video_wait_vsync(void) +{ +} + +#endif // !FBDEV + +void plat_early_init(void) +{ +} + +void plat_init(void) +{ +#ifdef FBDEV + int ret, w, h; + ret = vout_fbdev_init(&w, &h); + if (ret != 0) + exit(1); + g_screen_width = g_menuscreen_w = w; + g_screen_height = g_menuscreen_h = h; + g_screen_ppitch = g_menuscreen_pp = w; + g_menubg_ptr = realloc(g_menubg_ptr, w * g_screen_height * 2); +#else + realloc_screen(); + memset(g_screen_ptr, 0, g_screen_width * g_screen_height * 2); + xlib_init(); +#endif +} + +void plat_finish(void) +{ +#ifdef FBDEV + vout_fbdev_finish(); +#else + free(g_screen_ptr); +#endif +} + +/* misc */ +int mp3_get_bitrate(void *f, int size) +{ + return 128; +} + +void mp3_start_play(void *f, int pos) +{ +} + +void mp3_update(int *buffer, int length, int stereo) +{ +} + +#include + +struct in_default_bind in_evdev_defbinds[] = +{ + /* MXYZ SACB RLDU */ + { KEY_UP, IN_BINDTYPE_PLAYER12, 0 }, + { KEY_DOWN, IN_BINDTYPE_PLAYER12, 1 }, + { KEY_LEFT, IN_BINDTYPE_PLAYER12, 2 }, + { KEY_RIGHT, IN_BINDTYPE_PLAYER12, 3 }, + { KEY_S, IN_BINDTYPE_PLAYER12, 4 }, /* B */ + { KEY_D, IN_BINDTYPE_PLAYER12, 5 }, /* C */ + { KEY_A, IN_BINDTYPE_PLAYER12, 6 }, /* A */ + { KEY_ENTER, IN_BINDTYPE_PLAYER12, 7 }, + { KEY_BACKSLASH, IN_BINDTYPE_EMU, PEVB_MENU }, + { 0, 0, 0 } +}; + diff --git a/platform/linux/log_io.c b/platform/linux/log_io.c new file mode 100644 index 0000000..684215e --- /dev/null +++ b/platform/linux/log_io.c @@ -0,0 +1,88 @@ +/* + * PicoDrive + * (C) notaz, 2007 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ +#include + +typedef struct +{ + unsigned int addr_min, addr_max; + int r8, r16, r32, w8, w16, w32; +} io_log_location; + +static io_log_location io_locations[] = +{ + { 0x400000, 0x9FFFFF, 0, }, // unused + { 0xa00000, 0xa03fff, 0, }, // z80 RAM + { 0xa04000, 0xa05fff, 0, }, // ym2612 + { 0xa06000, 0xa060ff, 0, }, // bank reg + { 0xa06100, 0xa07eff, 0, }, // unused + { 0xa07f00, 0xa07fff, 0, }, // vdp + { 0xa08000, 0xa0ffff, 0, }, // 0xa00000-0xa07fff mirror + { 0xa10000, 0xa1001f, 0, }, // i/o + { 0xa10020, 0xa10fff, 0, }, // expansion + { 0xa11000, 0xa110ff, 0, }, // expansion + { 0xa11100, 0xa11101, 0, }, // z80 busreq + { 0xa11102, 0xa111ff, 0, }, // expansion + { 0xa11200, 0xa11201, 0, }, // z80 reset + { 0xa11202, 0xbfffff, 0, }, // expansion + { 0xc00000, 0xc00003, 0, }, // vdp data port + { 0xc00004, 0xc00007, 0, }, // vdp control + { 0xc00009, 0xc0000f, 0, }, // hv counter + { 0xc00010, 0xc00017, 0, }, // PSG + { 0xc00018, 0xc0001f, 0, }, // unused + { 0xc00020, 0xdfffff, 0, } // vdp mirrors +}; + + +void log_io(unsigned int a, int bits, int is_write) +{ + int i; + a &= 0x00ffffff; + if (bits > 8) a&=~1; + + for (i = 0; i < sizeof(io_locations)/sizeof(io_locations[0]); i++) + { + if (a >= io_locations[i].addr_min && a <= io_locations[i].addr_max) + { + switch (bits|(is_write<<8)) { + case 0x008: io_locations[i].r8 ++; break; + case 0x010: io_locations[i].r16++; break; + case 0x020: io_locations[i].r32++; break; + case 0x108: io_locations[i].w8 ++; break; + case 0x110: io_locations[i].w16++; break; + case 0x120: io_locations[i].w32++; break; + default: printf("%06x %i %i\n", a, bits, is_write); break; + } + } + } +} + +void log_io_clear(void) +{ + int i; + for (i = 0; i < sizeof(io_locations)/sizeof(io_locations[0]); i++) + { + io_log_location *iol = &io_locations[i]; + iol->r8 = iol->r16 = iol->r32 = iol->w8 = iol->w16 = iol->w32 = 0; + } +} + +void log_io_dump(void) +{ + int i; + printf(" range : r8 r16 r32 w8 w16 w32\n"); + for (i = 0; i < sizeof(io_locations)/sizeof(io_locations[0]); i++) + { + io_log_location *iol = &io_locations[i]; + if (iol->r8 == 0 && iol->r16 == 0 && iol->r32 == 0 && iol->w8 == 0 && iol->w16 == 0 && iol->w32 == 0) + continue; + printf("%06x - %06x : %8i %8i %8i %8i %8i %8i\n", iol->addr_min, iol->addr_max, + iol->r8, iol->r16, iol->r32, iol->w8, iol->w16, iol->w32); + } + printf("\n"); +} + diff --git a/platform/linux/log_io.h b/platform/linux/log_io.h new file mode 100644 index 0000000..eda84cf --- /dev/null +++ b/platform/linux/log_io.h @@ -0,0 +1,3 @@ +void log_io_clear(void); +void log_io_dump(void); + diff --git a/platform/linux/pprof.c b/platform/linux/pprof.c new file mode 100644 index 0000000..e1ecd1f --- /dev/null +++ b/platform/linux/pprof.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include + +#include + +struct pp_counters *pp_counters; +static int shmemid; + +void pprof_init(void) +{ + int this_is_new_shmem = 1; + key_t shmemkey; + void *shmem; + +#ifndef PPROF_TOOL + unsigned int tmp = pprof_get_one(); + printf("pprof: measured diff is %u\n", pprof_get_one() - tmp); +#endif + + shmemkey = ftok(".", 0x02ABC32E); + if (shmemkey == -1) + { + perror("pprof: ftok failed"); + return; + } + +#ifndef PPROF_TOOL + shmemid = shmget(shmemkey, sizeof(*pp_counters), + IPC_CREAT | IPC_EXCL | 0644); + if (shmemid == -1) +#endif + { + shmemid = shmget(shmemkey, sizeof(*pp_counters), + 0644); + if (shmemid == -1) + { + perror("pprof: shmget failed"); + return; + } + this_is_new_shmem = 0; + } + + shmem = shmat(shmemid, NULL, 0); + if (shmem == (void *)-1) + { + perror("pprof: shmat failed"); + return; + } + + pp_counters = shmem; + if (this_is_new_shmem) { + memset(pp_counters, 0, sizeof(*pp_counters)); + printf("pprof: pp_counters cleared.\n"); + } +} + +void pprof_finish(void) +{ + shmdt(pp_counters); + shmctl(shmemid, IPC_RMID, NULL); +} + +#ifdef PPROF_TOOL + +#define IT(n) { pp_##n, #n } +static const struct { + enum pprof_points pp; + const char *name; +} pp_tab[] = { + IT(main), + IT(frame), + IT(draw), + IT(sound), + IT(m68k), + IT(z80), + IT(msh2), + IT(ssh2), + IT(dummy), +}; + +int main(int argc, char *argv[]) +{ + unsigned long long old[pp_total_points], new[pp_total_points]; + int base = 0; + int l, i; + + pprof_init(); + if (pp_counters == NULL) + return 1; + + if (argc >= 2) + base = atoi(argv[1]); + + memset(old, 0, sizeof(old)); + for (l = 0; ; l++) + { + if ((l & 0x1f) == 0) { + for (i = 0; i < ARRAY_SIZE(pp_tab); i++) + printf("%6s ", pp_tab[i].name); + printf("\n"); + } + + memcpy(new, pp_counters->counter, sizeof(new)); + for (i = 0; i < ARRAY_SIZE(pp_tab); i++) + { + unsigned long long idiff = new[i] - old[i]; + unsigned long long bdiff = (new[base] - old[base]) | 1; + printf("%6.2f ", (double)idiff * 100.0 / bdiff); + } + printf("\n"); + memcpy(old, new, sizeof(old)); + + if (argc < 3) + break; + usleep(atoi(argv[2])); + } + + return 0; +} + +#endif // PPROF_TOOL + diff --git a/platform/linux/pprof.h b/platform/linux/pprof.h new file mode 100644 index 0000000..cccbcbd --- /dev/null +++ b/platform/linux/pprof.h @@ -0,0 +1,67 @@ +#ifndef __PPROF_H__ +#define __PPROF_H__ + +enum pprof_points { + pp_main, + pp_frame, + pp_draw, + pp_sound, + pp_m68k, + pp_z80, + pp_msh2, + pp_ssh2, + pp_dummy, + pp_total_points +}; + +struct pp_counters +{ + unsigned long long counter[pp_total_points]; +}; + +extern struct pp_counters *pp_counters; + +#ifdef __i386__ +static __attribute__((always_inline)) inline unsigned int pprof_get_one(void) +{ + unsigned long long ret; + __asm__ __volatile__ ("rdtsc" : "=A" (ret)); + return (unsigned int)ret; +} +#define unglitch_timer(x) + +#elif defined(__GP2X__) +// XXX: MMSP2 only, timer sometimes seems to return lower vals? +extern volatile unsigned long *gp2x_memregl; +#define pprof_get_one() (unsigned int)gp2x_memregl[0x0a00 >> 2] +#define unglitch_timer(di) \ + if ((signed int)(di) < 0) di = 0 + +#else +#error no timer +#endif + +#define pprof_start(point) { \ + unsigned int pp_start_##point = pprof_get_one() + +#define pprof_end(point) \ + { \ + unsigned int di = pprof_get_one() - pp_start_##point; \ + unglitch_timer(di); \ + pp_counters->counter[pp_##point] += di; \ + } \ + } + +// subtract for recursive stuff +#define pprof_end_sub(point) \ + { \ + unsigned int di = pprof_get_one() - pp_start_##point; \ + unglitch_timer(di); \ + pp_counters->counter[pp_##point] -= di; \ + } \ + } + +extern void pprof_init(void); +extern void pprof_finish(void); + +#endif // __PPROF_H__ diff --git a/platform/opendingux/data/default.gcw0.desktop b/platform/opendingux/data/default.gcw0.desktop new file mode 100644 index 0000000..80458bd --- /dev/null +++ b/platform/opendingux/data/default.gcw0.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Picodrive +Comment=A megadrive/genesis emulator +Exec=PicoDrive +Terminal=false +Type=Application +StartupNotify=true +Icon=megadrive +Categories=emulators; diff --git a/platform/opendingux/data/megadrive.png b/platform/opendingux/data/megadrive.png new file mode 100644 index 0000000..e1bc5f8 Binary files /dev/null and b/platform/opendingux/data/megadrive.png differ diff --git a/platform/opendingux/data/skin/background.png b/platform/opendingux/data/skin/background.png new file mode 100644 index 0000000..5dc2bb6 Binary files /dev/null and b/platform/opendingux/data/skin/background.png differ diff --git a/platform/opendingux/data/skin/font.png b/platform/opendingux/data/skin/font.png new file mode 100644 index 0000000..707a5b4 Binary files /dev/null and b/platform/opendingux/data/skin/font.png differ diff --git a/platform/opendingux/data/skin/readme.txt b/platform/opendingux/data/skin/readme.txt new file mode 100644 index 0000000..0f9e415 --- /dev/null +++ b/platform/opendingux/data/skin/readme.txt @@ -0,0 +1,7 @@ +The skin images can be customized, but there are several limitations: + +background.png - must be 320x240 image with 24bit RGB colors. +font.png - must be 128x160 8bit grayscale image. +selector.png - must be 8x10 8bit grayscale image. + +Font and selector colors can be changed by editing skin.txt. diff --git a/platform/opendingux/data/skin/skin.txt b/platform/opendingux/data/skin/skin.txt new file mode 100644 index 0000000..7f5bdb4 --- /dev/null +++ b/platform/opendingux/data/skin/skin.txt @@ -0,0 +1,3 @@ +// html-style hex color codes, ex. ff0000 is red, 0000ff is blue, etc. +text_color=ffffff +selection_color=0000c0 diff --git a/platform/opendingux/inputmap.c b/platform/opendingux/inputmap.c new file mode 100644 index 0000000..f644cdc --- /dev/null +++ b/platform/opendingux/inputmap.c @@ -0,0 +1,56 @@ +#include +#include + +#include "../libpicofe/input.h" +#include "../libpicofe/in_sdl.h" +#include "../common/input_pico.h" + +const struct in_default_bind in_sdl_defbinds[] = { + { SDLK_UP, IN_BINDTYPE_PLAYER12, GBTN_UP }, + { SDLK_DOWN, IN_BINDTYPE_PLAYER12, GBTN_DOWN }, + { SDLK_LEFT, IN_BINDTYPE_PLAYER12, GBTN_LEFT }, + { SDLK_RIGHT, IN_BINDTYPE_PLAYER12, GBTN_RIGHT }, + { SDLK_SPACE, IN_BINDTYPE_PLAYER12, GBTN_A }, + { SDLK_LCTRL, IN_BINDTYPE_PLAYER12, GBTN_B }, + { SDLK_LALT, IN_BINDTYPE_PLAYER12, GBTN_C }, + { SDLK_RETURN, IN_BINDTYPE_PLAYER12, GBTN_START }, + { SDLK_ESCAPE, IN_BINDTYPE_EMU, PEVB_MENU }, + { SDLK_TAB, IN_BINDTYPE_EMU, PEVB_PICO_PPREV }, + { SDLK_BACKSPACE, IN_BINDTYPE_EMU, PEVB_PICO_PNEXT }, + { SDLK_BACKSPACE, IN_BINDTYPE_EMU, PEVB_STATE_SAVE }, + { SDLK_TAB, IN_BINDTYPE_EMU, PEVB_STATE_LOAD }, + { SDLK_LSHIFT, IN_BINDTYPE_EMU, PEVB_FF }, + { 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_LALT, PBTN_MOK }, + { SDLK_LCTRL, PBTN_MBACK }, + { SDLK_SPACE, PBTN_MA2 }, + { SDLK_LSHIFT, PBTN_MA3 }, + { SDLK_TAB, PBTN_L }, + { SDLK_BACKSPACE, PBTN_R }, +}; + +const char * const in_sdl_key_names[SDLK_LAST] = { + [SDLK_UP] = "UP", + [SDLK_DOWN] = "DOWN", + [SDLK_LEFT] = "LEFT", + [SDLK_RIGHT] = "RIGHT", + [SDLK_LALT] = "A", + [SDLK_LCTRL] = "B", + [SDLK_LSHIFT] = "X", + [SDLK_SPACE] = "Y", + [SDLK_TAB] = "L1", + [SDLK_BACKSPACE] = "R1", + [SDLK_RETURN] = "START", + [SDLK_ESCAPE] = "SELECT", + [SDLK_POWER] = "POWER", + [SDLK_PAUSE] = "LOCK", + [SDLK_RCTRL] = "R", +}; diff --git a/platform/pandora/Makefile b/platform/pandora/Makefile new file mode 100644 index 0000000..a181e09 --- /dev/null +++ b/platform/pandora/Makefile @@ -0,0 +1,34 @@ +# release packaging makefile + +VER := $(shell head -n 1 ../common/version.h | \ + sed 's/.*"\(.*\)\.\(.*\)".*/\1\2/g') +BUILD := $(shell git describe HEAD | grep -- - | \ + sed -e 's/.*\-\(.*\)\-.*/\1/') +ifneq "$(BUILD)" "" +VER := $(VER)_$(BUILD) +endif + +PND_MAKE ?= $(HOME)/dev/pnd/src/pandora-libraries/testdata/scripts/pnd_make.sh + +all: rel + +../../tools/textfilter: ../../tools/textfilter.c + make -C ../../tools/ + +#readme.txt: ../../tools/textfilter ../base_readme.txt ../../ChangeLog +# ../../tools/textfilter ../base_readme.txt $@ PANDORA + +/tmp/PicoDrive.pxml: PicoDrive.pxml.template FORCE + ./make_pxml.sh $< $@ + +rel: ../../PicoDrive PicoDrive.sh picorestore \ + PicoDrive.png PicoDrive_p.png \ + ../../pico/carthw.cfg skin \ + ../../README /tmp/PicoDrive.pxml + rm -rf out + mkdir out + cp -r $^ out/ + $(PND_MAKE) -p PicoDrive_$(VER).pnd -d out -x out/PicoDrive.pxml -i out/PicoDrive.png -c + +FORCE: +.PHONY: all rel FORCE diff --git a/platform/pandora/PicoDrive.png b/platform/pandora/PicoDrive.png new file mode 100644 index 0000000..0bb5871 Binary files /dev/null and b/platform/pandora/PicoDrive.png differ diff --git a/platform/pandora/PicoDrive.pxml.template b/platform/pandora/PicoDrive.pxml.template new file mode 100644 index 0000000..96d7b1e --- /dev/null +++ b/platform/pandora/PicoDrive.pxml.template @@ -0,0 +1,44 @@ + + + + + PicoDrive + + + + + + + PicoDrive @major@.@minor@@build_post@ + + PicoDrive @major@.@minor@@build_post@ + + + Genesis/MegaDrive/SegaCD/32x Emulator. + Ein MegaDrive/SegaCD/32x Emulator + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/pandora/PicoDrive.sh b/platform/pandora/PicoDrive.sh new file mode 100644 index 0000000..729a7c1 --- /dev/null +++ b/platform/pandora/PicoDrive.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# 3.2.30 on pandora has broken hugetlb +if [ "`uname -r`" != "3.2.30" ]; then + # 2x2M hugepages should be enough + sudo -n /usr/pandora/scripts/op_hugetlb.sh 4 +fi + +./PicoDrive "$@" + +# restore stuff if pico crashes +./picorestore +sudo -n /usr/pandora/scripts/op_lcdrate.sh 60 +sudo -n /usr/pandora/scripts/op_gamma.sh 0 +sudo -n /usr/pandora/scripts/op_hugetlb.sh 0 diff --git a/platform/pandora/PicoDrive_p.png b/platform/pandora/PicoDrive_p.png new file mode 100644 index 0000000..1542202 Binary files /dev/null and b/platform/pandora/PicoDrive_p.png differ diff --git a/platform/pandora/asm_utils.h b/platform/pandora/asm_utils.h new file mode 100644 index 0000000..5b273d5 --- /dev/null +++ b/platform/pandora/asm_utils.h @@ -0,0 +1,3 @@ + +void clut_line2x2(void *dest, const void *src, const unsigned short *pal, int pixels_mask); +void clut_line3x2(void *dest, const void *src, const unsigned short *pal, int pixels_mask); diff --git a/platform/pandora/asm_utils.s b/platform/pandora/asm_utils.s new file mode 100644 index 0000000..74dd3fd --- /dev/null +++ b/platform/pandora/asm_utils.s @@ -0,0 +1,108 @@ +@ vim:filetype=armasm + + +@ FIXME: handle dual issue +.global clut_line2x2 @ void *dest, void *src, unsigned short *pal, int pixels_mask + +clut_line2x2: + stmfd sp!, {r4-r11,lr} + + and lr, r3, #0xff0000 + mov lr, lr, lsr #15 @ mask + mov r3, r3, lsr #3 + and r3, r3, #0xff @ counter + add r11,r0, #800*2 + +clut_line_2x2_loop: + ldmia r1!, {r10,r12} + + and r4, lr, r10, lsl #1 + ldrh r4, [r2, r4] + and r5, lr, r10, lsr #7 + ldrh r5, [r2, r5] + orr r4, r4, r4, lsl #16 + + and r6, lr, r10, lsr #15 + ldrh r6, [r2, r6] + orr r5, r5, r5, lsl #16 + + and r7, lr, r10, lsr #23 + ldrh r7, [r2, r7] + orr r6, r6, r6, lsl #16 + + and r8, lr, r12, lsl #1 + ldrh r8, [r2, r8] + orr r7, r7, r7, lsl #16 + + and r9, lr, r12, lsr #7 + ldrh r9, [r2, r9] + orr r8, r8, r8, lsl #16 + + and r10,lr, r12, lsr #15 + ldrh r10,[r2, r10] + orr r9, r9, r9, lsl #16 + + and r12,lr, r12, lsr #23 + ldrh r12,[r2, r12] + orr r10,r10,r10, lsl #16 + + subs r3, r3, #1 + orr r12,r12,r12, lsl #16 + + stmia r0!, {r4-r10,r12} + stmia r11!,{r4-r10,r12} + bne clut_line_2x2_loop + + ldmfd sp!, {r4-r11,pc} + + +@ 00 01 11 22 23 33 +@ r4 r5 r6 r7 r8 r9 + +.macro do_4_to_12 rs + and r4, lr, \rs, lsl #1 + and r6, lr, \rs, lsr #7 + and r7, lr, \rs, lsr #15 + and r9, lr, \rs, lsr #23 + ldrh r4, [r2, r4] + ldrh r6, [r2, r6] + ldrh r7, [r2, r7] + ldrh r9, [r2, r9] + + orr r5, r4, r6, lsl #16 + orr r4, r4, r4, lsl #16 + orr r6, r6, r6, lsl #16 + + orr r8, r7, r9, lsl #16 + orr r7, r7, r7, lsl #16 + orr r9, r9, r9, lsl #16 +.endm + + +.global clut_line3x2 @ void *dest, void *src, unsigned short *pal, int pixels_mask + +clut_line3x2: + stmfd sp!, {r4-r11,lr} + + and lr, r3, #0xff0000 + mov lr, lr, lsr #15 @ mask + mov r3, r3, lsr #3 + and r3, r3, #0xff @ counter + add r11,r0, #800*2 + +clut_line3x2_loop: + ldmia r1!, {r10,r12} + + do_4_to_12 r10 + stmia r0!, {r4-r9} + stmia r11!,{r4-r9} + + do_4_to_12 r12 + subs r3, r3, #1 + stmia r0!, {r4-r9} + stmia r11!,{r4-r9} + + bne clut_line3x2_loop + + ldmfd sp!, {r4-r11,pc} + diff --git a/platform/pandora/make_pxml.sh b/platform/pandora/make_pxml.sh new file mode 100644 index 0000000..2fcf833 --- /dev/null +++ b/platform/pandora/make_pxml.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +verfile=../common/version.h +test -f $verfile + +major=`head -n 1 $verfile | sed 's/.*"\([0-9]*\)\.\([0-9]*\).*/\1/g'` +minor=`head -n 1 $verfile | sed 's/.*"\([0-9]*\)\.\([0-9]*\).*/\2/g'` +# lame, I know.. +build=`git describe HEAD | grep -- - | sed -e 's/.*\-\(.*\)\-.*/\1/'` +test -n "$build" && build_post="-$build" +test -n "$build" || build=0 + +trap "rm -f $2" ERR + +sed -e 's/@major@/'$major'/' \ + -e 's/@minor@/'$minor'/' \ + -e 's/@build@/'$build'/' \ + -e 's/@build_post@/'$build_post'/' \ + "$1" > "$2" diff --git a/platform/pandora/menu.c b/platform/pandora/menu.c new file mode 100644 index 0000000..867b5c0 --- /dev/null +++ b/platform/pandora/menu.c @@ -0,0 +1,80 @@ +#include "plat.h" + +static const char *men_scaler[] = { "1x1, 1x1", "2x2, 3x2", "2x2, 2x2", "fullscreen", "custom", NULL }; +static const char h_scaler[] = "Scalers for 40 and 32 column modes\n" + "(320 and 256 pixel wide horizontal)"; +static const char h_cscaler[] = "Displays the scaler layer, you can resize it\n" + "using d-pad or move it using R+d-pad"; + +static int menu_loop_cscaler(int id, int keys) +{ + unsigned int inp; + + currentConfig.scaling = SCALE_CUSTOM; + + pnd_setup_layer(1, g_layer_cx, g_layer_cy, g_layer_cw, g_layer_ch); + pnd_restore_layer_data(); + + for (;;) + { + menu_draw_begin(0, 1); + menuscreen_memset_lines(g_menuscreen_ptr, 0, g_menuscreen_h); + text_out16(2, 480 - 18, "%dx%d | d-pad to resize, R+d-pad to move", g_layer_cw, g_layer_ch); + menu_draw_end(); + + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT + |PBTN_R|PBTN_MOK|PBTN_MBACK, NULL, 40); + if (inp & PBTN_UP) g_layer_cy--; + if (inp & PBTN_DOWN) g_layer_cy++; + if (inp & PBTN_LEFT) g_layer_cx--; + if (inp & PBTN_RIGHT) g_layer_cx++; + if (!(inp & PBTN_R)) { + if (inp & PBTN_UP) g_layer_ch += 2; + if (inp & PBTN_DOWN) g_layer_ch -= 2; + if (inp & PBTN_LEFT) g_layer_cw += 2; + if (inp & PBTN_RIGHT) g_layer_cw -= 2; + } + if (inp & (PBTN_MOK|PBTN_MBACK)) + break; + + if (inp & (PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT)) { + if (g_layer_cx < 0) g_layer_cx = 0; + if (g_layer_cx > 640) g_layer_cx = 640; + if (g_layer_cy < 0) g_layer_cy = 0; + if (g_layer_cy > 420) g_layer_cy = 420; + if (g_layer_cw < 160) g_layer_cw = 160; + if (g_layer_ch < 60) g_layer_ch = 60; + if (g_layer_cx + g_layer_cw > 800) + g_layer_cw = 800 - g_layer_cx; + if (g_layer_cy + g_layer_ch > 480) + g_layer_ch = 480 - g_layer_cy; + pnd_setup_layer(1, g_layer_cx, g_layer_cy, g_layer_cw, g_layer_ch); + } + } + + pnd_setup_layer(0, g_layer_cx, g_layer_cy, g_layer_cw, g_layer_ch); + + return 0; +} + +#define MENU_OPTIONS_GFX \ + mee_enum_h ("Scaler", MA_OPT_SCALING, currentConfig.scaling, \ + men_scaler, h_scaler), \ + mee_onoff ("Vsync", MA_OPT2_VSYNC, currentConfig.EmuOpt, EOPT_VSYNC), \ + mee_cust_h ("Setup custom scaler", MA_NONE, menu_loop_cscaler, NULL, h_cscaler), \ + mee_range_hide("layer_x", MA_OPT3_LAYER_X, g_layer_cx, 0, 640), \ + mee_range_hide("layer_y", MA_OPT3_LAYER_Y, g_layer_cy, 0, 420), \ + mee_range_hide("layer_w", MA_OPT3_LAYER_W, g_layer_cw, 160, 800), \ + mee_range_hide("layer_h", MA_OPT3_LAYER_H, g_layer_ch, 60, 480), \ + +#define MENU_OPTIONS_ADV + +static menu_entry e_menu_gfx_options[]; +static menu_entry e_menu_options[]; +static menu_entry e_menu_keyconfig[]; + +void pnd_menu_init(void) +{ + me_enable(e_menu_keyconfig, MA_CTRL_DEADZONE, 0); +} + diff --git a/platform/pandora/picorestore.c b/platform/pandora/picorestore.c new file mode 100644 index 0000000..77f5720 --- /dev/null +++ b/platform/pandora/picorestore.c @@ -0,0 +1,109 @@ +/* + * picorestore - clean up after an omapfb program crash + * + * Copyright (c) Gražvydas "notaz" Ignotas, 2010 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the organization nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + struct fb_var_screeninfo fbvar; + struct omapfb_plane_info pi; + struct omapfb_mem_info mi; + int ret, fbdev, kbdfd; + + fbdev = open("/dev/fb0", O_RDWR); + if (fbdev == -1) { + perror("open fb0"); + goto end_fb0; + } + + ret = ioctl(fbdev, FBIOGET_VSCREENINFO, &fbvar); + if (ret == -1) { + perror("FBIOGET_VSCREENINFO ioctl"); + goto end_fb0; + } + + if (fbvar.yoffset != 0) { + printf("fixing yoffset.. "); + fbvar.yoffset = 0; + ret = ioctl(fbdev, FBIOPAN_DISPLAY, &fbvar); + if (ret < 0) + perror("ioctl FBIOPAN_DISPLAY"); + else + printf("ok\n"); + } + +end_fb0: + if (fbdev >= 0) + close(fbdev); + + fbdev = open("/dev/fb1", O_RDWR); + if (fbdev == -1) { + perror("open fb1"); + goto end_fb1; + } + + ret = ioctl(fbdev, OMAPFB_QUERY_PLANE, &pi); + ret |= ioctl(fbdev, OMAPFB_QUERY_MEM, &mi); + if (ret != 0) + perror("QUERY_*"); + + pi.enabled = 0; + ret = ioctl(fbdev, OMAPFB_SETUP_PLANE, &pi); + if (ret != 0) + perror("SETUP_PLANE"); + + mi.size = 0; + ret = ioctl(fbdev, OMAPFB_SETUP_MEM, &mi); + if (ret != 0) + perror("SETUP_MEM"); + +end_fb1: + if (fbdev >= 0) + close(fbdev); + + kbdfd = open("/dev/tty", O_RDWR); + if (kbdfd == -1) { + perror("open /dev/tty"); + return 1; + } + + if (ioctl(kbdfd, KDSETMODE, KD_TEXT) == -1) + perror("KDSETMODE KD_TEXT"); + + close(kbdfd); + + return 0; +} diff --git a/platform/pandora/plat.c b/platform/pandora/plat.c new file mode 100644 index 0000000..5f10b17 --- /dev/null +++ b/platform/pandora/plat.c @@ -0,0 +1,543 @@ +/* + * PicoDrive + * (C) notaz, 2010,2011 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/emu.h" +#include "../common/arm_utils.h" +#include "../common/input_pico.h" +#include "../common/version.h" +#include "../libpicofe/input.h" +#include "../libpicofe/menu.h" +#include "../libpicofe/plat.h" +#include "../libpicofe/linux/in_evdev.h" +#include "../libpicofe/linux/sndout_oss.h" +#include "../libpicofe/linux/fbdev.h" +#include "../libpicofe/linux/xenv.h" +#include "plat.h" +#include "asm_utils.h" + +#include + +#define LAYER_MEM_SIZE (320*240*2 * 4) + +static struct vout_fbdev *main_fb, *layer_fb; +// g_layer_* - in use, g_layer_c* - configured custom +int g_layer_cx, g_layer_cy, g_layer_cw, g_layer_ch; +static int g_layer_x, g_layer_y; +static int g_layer_w = 320, g_layer_h = 240; +static int g_osd_fps_x, g_osd_y, doing_bg_frame; + +static unsigned char __attribute__((aligned(4))) fb_copy[320 * 240 * 2]; +static void *temp_frame; +const char *renderer_names[] = { NULL }; +const char *renderer_names32x[] = { NULL }; + +static const char * const pandora_gpio_keys[KEY_MAX + 1] = { + [0 ... KEY_MAX] = NULL, + [KEY_UP] = "Up", + [KEY_LEFT] = "Left", + [KEY_RIGHT] = "Right", + [KEY_DOWN] = "Down", + [KEY_HOME] = "A", + [KEY_PAGEDOWN] = "X", + [KEY_END] = "B", + [KEY_PAGEUP] = "Y", + [KEY_RIGHTSHIFT]= "L", + [KEY_RIGHTCTRL] = "R", + [KEY_LEFTALT] = "Start", + [KEY_LEFTCTRL] = "Select", + [KEY_MENU] = "Pandora", +}; + +static struct in_default_bind in_evdev_defbinds[] = +{ + { KEY_UP, IN_BINDTYPE_PLAYER12, GBTN_UP }, + { KEY_DOWN, IN_BINDTYPE_PLAYER12, GBTN_DOWN }, + { KEY_LEFT, IN_BINDTYPE_PLAYER12, GBTN_LEFT }, + { KEY_RIGHT, IN_BINDTYPE_PLAYER12, GBTN_RIGHT }, + { KEY_A, IN_BINDTYPE_PLAYER12, GBTN_A }, + { KEY_S, IN_BINDTYPE_PLAYER12, GBTN_B }, + { KEY_D, IN_BINDTYPE_PLAYER12, GBTN_C }, + { KEY_ENTER, IN_BINDTYPE_PLAYER12, GBTN_START }, + { KEY_R, IN_BINDTYPE_EMU, PEVB_RESET }, + { KEY_F, IN_BINDTYPE_EMU, PEVB_FF }, + { KEY_BACKSPACE,IN_BINDTYPE_EMU, PEVB_FF }, + { KEY_BACKSLASH,IN_BINDTYPE_EMU, PEVB_MENU }, + { KEY_SPACE, IN_BINDTYPE_EMU, PEVB_MENU }, + { KEY_LEFTCTRL, IN_BINDTYPE_EMU, PEVB_MENU }, + { KEY_HOME, IN_BINDTYPE_PLAYER12, GBTN_A }, + { KEY_PAGEDOWN, IN_BINDTYPE_PLAYER12, GBTN_B }, + { KEY_END, IN_BINDTYPE_PLAYER12, GBTN_C }, + { KEY_LEFTALT, IN_BINDTYPE_PLAYER12, GBTN_START }, + { KEY_1, IN_BINDTYPE_EMU, PEVB_STATE_SAVE }, + { KEY_2, IN_BINDTYPE_EMU, PEVB_STATE_LOAD }, + { KEY_3, IN_BINDTYPE_EMU, PEVB_SSLOT_PREV }, + { KEY_4, IN_BINDTYPE_EMU, PEVB_SSLOT_NEXT }, + { KEY_5, IN_BINDTYPE_EMU, PEVB_PICO_PPREV }, + { KEY_6, IN_BINDTYPE_EMU, PEVB_PICO_PNEXT }, + { KEY_7, IN_BINDTYPE_EMU, PEVB_PICO_SWINP }, + { 0, 0, 0 } +}; + +static const struct menu_keymap key_pbtn_map[] = +{ + { KEY_UP, PBTN_UP }, + { KEY_DOWN, PBTN_DOWN }, + { KEY_LEFT, PBTN_LEFT }, + { KEY_RIGHT, PBTN_RIGHT }, + /* Pandora */ + { KEY_END, PBTN_MOK }, + { KEY_PAGEDOWN, PBTN_MBACK }, + { KEY_HOME, PBTN_MA2 }, + { KEY_PAGEUP, PBTN_MA3 }, + { KEY_LEFTCTRL, PBTN_MENU }, + { KEY_RIGHTSHIFT, PBTN_L }, + { KEY_RIGHTCTRL, PBTN_R }, + /* "normal" keyboards */ + { KEY_ENTER, PBTN_MOK }, + { KEY_ESC, PBTN_MBACK }, + { KEY_SEMICOLON, PBTN_MA2 }, + { KEY_APOSTROPHE, PBTN_MA3 }, + { KEY_BACKSLASH, PBTN_MENU }, + { KEY_LEFTBRACE, PBTN_L }, + { KEY_RIGHTBRACE, PBTN_R }, +}; + +static const struct in_pdata pandora_evdev_pdata = { + .defbinds = in_evdev_defbinds, + .key_map = key_pbtn_map, + .kmap_size = sizeof(key_pbtn_map) / sizeof(key_pbtn_map[0]), +}; + +void pemu_prep_defconfig(void) +{ + defaultConfig.EmuOpt |= EOPT_VSYNC|EOPT_16BPP; + defaultConfig.s_PicoOpt |= POPT_EN_MCD_GFX; + defaultConfig.scaling = SCALE_2x2_3x2; +} + +void pemu_validate_config(void) +{ + currentConfig.CPUclock = plat_target_cpu_clock_get(); +} + +static void draw_cd_leds(void) +{ + int old_reg; + old_reg = Pico_mcd->s68k_regs[0]; + + if (0) { + // 8-bit modes + unsigned int col_g = (old_reg & 2) ? 0xc0c0c0c0 : 0xe0e0e0e0; + unsigned int col_r = (old_reg & 1) ? 0xd0d0d0d0 : 0xe0e0e0e0; + *(unsigned int *)((char *)g_screen_ptr + g_screen_width*2+ 4) = + *(unsigned int *)((char *)g_screen_ptr + g_screen_width*3+ 4) = + *(unsigned int *)((char *)g_screen_ptr + g_screen_width*4+ 4) = col_g; + *(unsigned int *)((char *)g_screen_ptr + g_screen_width*2+12) = + *(unsigned int *)((char *)g_screen_ptr + g_screen_width*3+12) = + *(unsigned int *)((char *)g_screen_ptr + g_screen_width*4+12) = col_r; + } else { + // 16-bit modes + unsigned int *p = (unsigned int *)((short *)g_screen_ptr + g_screen_width*2+4); + unsigned int col_g = (old_reg & 2) ? 0x06000600 : 0; + unsigned int col_r = (old_reg & 1) ? 0xc000c000 : 0; + *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += g_screen_width/2 - 12/2; + *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += g_screen_width/2 - 12/2; + *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; + } +} + +void pemu_finalize_frame(const char *fps, const char *notice) +{ + if (notice && notice[0]) + emu_osd_text16(2, g_osd_y, notice); + if (fps && fps[0] && (currentConfig.EmuOpt & EOPT_SHOW_FPS)) + emu_osd_text16(g_osd_fps_x, g_osd_y, fps); + if ((PicoIn.AHW & PAHW_MCD) && (currentConfig.EmuOpt & EOPT_EN_CD_LEDS)) + draw_cd_leds(); +} + +void plat_video_flip(void) +{ + g_screen_ptr = vout_fbdev_flip(layer_fb); + PicoDrawSetOutBuf(g_screen_ptr, g_screen_width * 2); + + // XXX: drain OS event queue here, maybe we'll actually use it someday.. + xenv_update(NULL, NULL, NULL, NULL); +} + +void plat_video_toggle_renderer(int change, int is_menu) +{ +} + +void plat_video_menu_enter(int is_rom_loaded) +{ +} + +void plat_video_menu_begin(void) +{ +} + +void plat_video_menu_end(void) +{ + g_menuscreen_ptr = vout_fbdev_flip(main_fb); +} + +void plat_video_menu_leave(void) +{ +} + +void plat_video_wait_vsync(void) +{ + vout_fbdev_wait_vsync(main_fb); +} + +void plat_status_msg_clear(void) +{ + vout_fbdev_clear_lines(layer_fb, g_osd_y, 8); +} + +void plat_status_msg_busy_next(const char *msg) +{ + plat_status_msg_clear(); + pemu_finalize_frame("", msg); + plat_video_flip(); + emu_status_msg(""); + reset_timing = 1; +} + +void plat_status_msg_busy_first(const char *msg) +{ + plat_status_msg_busy_next(msg); +} + +void plat_update_volume(int has_changed, int is_up) +{ +} + +void pemu_forced_frame(int no_scale, int do_emu) +{ + doing_bg_frame = 1; + emu_cmn_forced_frame(no_scale, do_emu); + doing_bg_frame = 0; + + // making a copy because enabling the layer clears it's mem + memcpy((void *)fb_copy, g_screen_ptr, sizeof(fb_copy)); + g_menubg_src_ptr = fb_copy; +} + +void pemu_sound_start(void) +{ + emu_sound_start(); +} + +void plat_debug_cat(char *str) +{ +} + +static int pnd_setup_layer_(int fd, int enabled, int x, int y, int w, int h) +{ + struct omapfb_plane_info pi; + struct omapfb_mem_info mi; + int is_enabled; + int retval = 0; + int ret; + + ret = ioctl(fd, OMAPFB_QUERY_PLANE, &pi); + if (ret != 0) { + perror("QUERY_PLANE"); + return -1; + } + + ret = ioctl(fd, OMAPFB_QUERY_MEM, &mi); + if (ret != 0) { + perror("QUERY_MEM"); + return -1; + } + + /* must disable when changing stuff */ + is_enabled = pi.enabled; + if (is_enabled) { + pi.enabled = 0; + ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi); + if (ret != 0) + perror("SETUP_PLANE"); + else + is_enabled = 0; + } + + if (mi.size < LAYER_MEM_SIZE) { + unsigned int size_old = mi.size; + + mi.size = LAYER_MEM_SIZE; + ret = ioctl(fd, OMAPFB_SETUP_MEM, &mi); + if (ret != 0) { + perror("SETUP_MEM"); + fprintf(stderr, "(requested %u, had %u)\n", + mi.size, size_old); + return -1; + } + } + + pi.pos_x = x; + pi.pos_y = y; + pi.out_width = w; + pi.out_height = h; + pi.enabled = enabled; + + ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi); + if (ret == 0) { + is_enabled = pi.enabled; + } + else { + perror("SETUP_PLANE"); + retval = -1; + } + + plat_target_switch_layer(1, is_enabled); + + return retval; +} + +int pnd_setup_layer(int enabled, int x, int y, int w, int h) +{ + return pnd_setup_layer_(vout_fbdev_get_fd(layer_fb), enabled, x, y, w, h); +} + +void pnd_restore_layer_data(void) +{ + short *t = (short *)fb_copy + 320*240 / 2 + 160; + + // right now this is used by menu, which wants to preview something + // so try to get something on the layer. + if ((t[0] | t[5] | t[13]) == 0) + memset32((void *)fb_copy, 0x07000700, sizeof(fb_copy) / 4); + + memcpy(g_screen_ptr, (void *)fb_copy, 320*240*2); + plat_video_flip(); +} + +void emu_video_mode_change(int start_line, int line_count, int is_32cols) +{ + 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; + } + + switch (currentConfig.scaling) { + case SCALE_1x1: + g_layer_w = fb_w; + g_layer_h = fb_h; + break; + case SCALE_2x2_3x2: + g_layer_w = fb_w * (is_32cols ? 3 : 2); + g_layer_h = fb_h * 2; + break; + case SCALE_2x2_2x2: + g_layer_w = fb_w * 2; + g_layer_h = fb_h * 2; + break; + case SCALE_FULLSCREEN: + g_layer_w = 800; + g_layer_h = 480; + break; + case SCALE_CUSTOM: + g_layer_x = g_layer_cx; + g_layer_y = g_layer_cy; + g_layer_w = g_layer_cw; + g_layer_h = g_layer_ch; + break; + } + + if (currentConfig.scaling != SCALE_CUSTOM) { + // center the layer + g_layer_x = 800 / 2 - g_layer_w / 2; + g_layer_y = 480 / 2 - g_layer_h / 2; + } + + switch (currentConfig.scaling) { + case SCALE_FULLSCREEN: + case SCALE_CUSTOM: + fb_top = start_line; + fb_h = line_count; + break; + } + g_osd_fps_x = is_32cols ? 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); + vout_fbdev_clear(layer_fb); + vout_fbdev_resize(layer_fb, fb_w, fb_h, 16, fb_left, fb_right, fb_top, fb_bottom, 4); + plat_video_flip(); + + PicoDrawSetOutFormat(PDF_RGB555, 0); +} + +void plat_video_loop_prepare(void) +{ + // make sure there is no junk left behind the layer + memset(g_menuscreen_ptr, 0, g_menuscreen_w * g_menuscreen_h * 2); + g_menuscreen_ptr = vout_fbdev_flip(main_fb); + + // emu_video_mode_change will call pnd_setup_layer() +} + +void pemu_loop_prep(void) +{ + // dirty buffers better go now than during gameplay + fflush(stdout); + fflush(stderr); + sync(); + sleep(0); +} + +void pemu_loop_end(void) +{ + /* do one more frame for menu bg */ + pemu_forced_frame(0, 1); + + pnd_setup_layer(0, g_layer_x, g_layer_y, g_layer_w, g_layer_h); +} + +void plat_wait_till_us(unsigned int us_to) +{ + unsigned int now; + signed int diff; + + now = plat_get_ticks_us(); + + // XXX: need to check NOHZ + diff = (signed int)(us_to - now); + if (diff > 10000) { + //printf("sleep %d\n", us_to - now); + usleep(diff * 15 / 16); + now = plat_get_ticks_us(); + //printf(" wake %d\n", (signed)(us_to - now)); + } +/* + while ((signed int)(us_to - now) > 512) { + spend_cycles(1024); + now = plat_get_ticks_us(); + } +*/ +} + +void *plat_mem_get_for_drc(size_t size) +{ + return NULL; +} + +void plat_early_init(void) +{ +} + +void plat_init(void) +{ + const char *main_fb_name, *layer_fb_name; + int fd, ret, w, h; + + main_fb_name = getenv("FBDEV_MAIN"); + if (main_fb_name == NULL) + main_fb_name = "/dev/fb0"; + + layer_fb_name = getenv("FBDEV_LAYER"); + if (layer_fb_name == NULL) + layer_fb_name = "/dev/fb1"; + + // must set the layer up first to be able to use it + fd = open(layer_fb_name, O_RDWR); + if (fd == -1) { + fprintf(stderr, "%s: ", layer_fb_name); + perror("open"); + exit(1); + } + + ret = pnd_setup_layer_(fd, 0, g_layer_x, g_layer_y, g_layer_w, g_layer_h); + close(fd); + if (ret != 0) { + fprintf(stderr, "failed to set up layer, exiting.\n"); + exit(1); + } + + xenv_init(NULL, "PicoDrive " VERSION); + + w = h = 0; + main_fb = vout_fbdev_init(main_fb_name, &w, &h, 16, 2); + if (main_fb == NULL) { + fprintf(stderr, "couldn't init fb: %s\n", main_fb_name); + exit(1); + } + + g_menuscreen_w = g_menuscreen_pp = w; + g_menuscreen_h = h; + g_menuscreen_ptr = vout_fbdev_flip(main_fb); + + w = 320; h = 240; + layer_fb = vout_fbdev_init(layer_fb_name, &w, &h, 16, 4); + if (layer_fb == NULL) { + fprintf(stderr, "couldn't init fb: %s\n", layer_fb_name); + goto fail0; + } + + if (w != g_screen_width || h != g_screen_height) { + fprintf(stderr, "%dx%d not supported on %s\n", w, h, layer_fb_name); + goto fail1; + } + g_screen_ptr = vout_fbdev_flip(layer_fb); + + temp_frame = calloc(g_menuscreen_w * g_menuscreen_h * 2, 1); + if (temp_frame == NULL) { + fprintf(stderr, "OOM\n"); + goto fail1; + } + g_menubg_ptr = temp_frame; + g_menubg_src_ptr = temp_frame; + + pnd_menu_init(); + + // default ROM path + strcpy(rom_fname_loaded, "/media"); + + in_evdev_init(&pandora_evdev_pdata); + in_probe(); + plat_target_setup_input(); + + sndout_oss_frag_frames = 2; + + return; + +fail1: + vout_fbdev_finish(layer_fb); +fail0: + vout_fbdev_finish(main_fb); + exit(1); +} + +void plat_finish(void) +{ + vout_fbdev_finish(main_fb); + xenv_finish(); + + printf("all done\n"); +} + diff --git a/platform/pandora/plat.h b/platform/pandora/plat.h new file mode 100644 index 0000000..8e6816a --- /dev/null +++ b/platform/pandora/plat.h @@ -0,0 +1,16 @@ + +extern int g_layer_cx, g_layer_cy; +extern int g_layer_cw, g_layer_ch; + +void pnd_menu_init(void); +int pnd_setup_layer(int enabled, int x, int y, int w, int h); +void pnd_restore_layer_data(void); + +enum { + SCALE_1x1, + SCALE_2x2_3x2, + SCALE_2x2_2x2, + SCALE_FULLSCREEN, + SCALE_CUSTOM, +}; + diff --git a/platform/pandora/skin/background.png b/platform/pandora/skin/background.png new file mode 100644 index 0000000..08a7391 Binary files /dev/null and b/platform/pandora/skin/background.png differ diff --git a/platform/pandora/skin/font.png b/platform/pandora/skin/font.png new file mode 100644 index 0000000..707a5b4 Binary files /dev/null and b/platform/pandora/skin/font.png differ diff --git a/platform/pandora/skin/readme.txt b/platform/pandora/skin/readme.txt new file mode 100644 index 0000000..dd83963 --- /dev/null +++ b/platform/pandora/skin/readme.txt @@ -0,0 +1,8 @@ +The skin images can be customized, but there are several limitations: + +background.png - must be 320x240 image with 24bit RGB colors. +font.png - must be 128x160 8bit grayscale image. +selector.png - must be 8x10 8bit grayscale image. + +Font and selector colors can be changed by editing skin.txt. + diff --git a/platform/pandora/skin/selector.png b/platform/pandora/skin/selector.png new file mode 100644 index 0000000..a439169 Binary files /dev/null and b/platform/pandora/skin/selector.png differ diff --git a/platform/pandora/skin/skin.txt b/platform/pandora/skin/skin.txt new file mode 100644 index 0000000..c2baeb2 --- /dev/null +++ b/platform/pandora/skin/skin.txt @@ -0,0 +1,4 @@ +// html-style hex color codes, ex. ff0000 is red, 0000ff is blue, etc. +text_color=ffffff +selection_color=c00000 + diff --git a/platform/psp/Makefile b/platform/psp/Makefile new file mode 100644 index 0000000..026249e --- /dev/null +++ b/platform/psp/Makefile @@ -0,0 +1,205 @@ + +# pspdev is expected to be in path +PSPSDK = $(shell psp-config --pspsdk-path) + +# settings +#use_musashi = 1 +#use_mz80 = 1 +amalgamate = 0 +for_15fw = 1 +# :!touch platform/psp/psp.c + + +CFLAGS += -I../.. -I. -DNO_SYNC -D_ASM_DRAW_C_AMIPS +CFLAGS += -Wall -Winline -G0 +#CFLAGS += -DLPRINTF_STDIO +#CFLAGS += -pg +ifeq ($(DEBUG),) +CFLAGS += -O2 -ftracer -fstrength-reduce -ffast-math +else +CFLAGS += -ggdb +endif +ifeq "$(for_15fw)" "1" +CFLAGS += -DFW15 +endif + + +# frontend and stuff +OBJS += main.o emu.o mp3.o menu.o psp.o asm_utils.o + +# common +OBJS += platform/common/emu.o platform/common/menu.o platform/common/fonts.o platform/common/config.o platform/common/readpng.o + +# Pico +ifeq "$(amalgamate)" "1" +OBJS += ../../picoAll.o +else +OBJS += pico/area.o pico/cart.o pico/memory.o pico/misc.o pico/pico.o pico/sek.o pico/videoport.o \ + pico/draw2.o pico/draw.o pico/z80if.o pico/patch.o pico/draw_amips.o pico/memory_amips.o \ + pico/misc_amips.o pico/debug.o +# Pico - CD +OBJS += pico/cd/pico.o pico/cd/memory.o pico/cd/sek.o pico/cd/LC89510.o \ + pico/cd/cd_sys.o pico/cd/cd_file.o pico/cd/cue.o pico/cd/gfx_cd.o \ + pico/cd/area.o pico/cd/misc.o pico/cd/pcm.o pico/cd/buffering.o +# Pico - carthw +OBJS += pico/carthw/carthw.o pico/carthw/svp/svp.o pico/carthw/svp/memory.o \ + pico/carthw/svp/ssp16.o +# Pico - Pico +OBJS += pico/pico/pico.o pico/pico/memory.o pico/pico/xpcm.o +endif + +# Pico - sound +ifneq "$(amalgamate)" "1" +OBJS += pico/sound/sound.o +endif +OBJS += pico/sound/mix.o +OBJS += pico/sound/sn76496.o pico/sound/ym2612.o +# zlib (hacked) +OBJS += zlib/gzio.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o \ + zlib/deflate.o zlib/crc32.o zlib/adler32.o zlib/zutil.o zlib/compress.o \ + zlib/uncompr.o +# unzip +OBJS += unzip/unzip.o unzip/unzip_stream.o +# CPU cores +ifeq "$(use_musashi)" "1" +CFLAGS += -DEMU_M68K +OBJS += cpu/musashi/m68kops.o cpu/musashi/m68kcpu.o +else +CFLAGS += -DEMU_F68K +OBJS += cpu/fame/famec.o +endif +# z80 +ifeq "$(use_mz80)" "1" +CFLAGS += -D_USE_MZ80 +OBJS += cpu/mz80/mz80.o +else +CFLAGS += -D_USE_CZ80 +OBJS += cpu/cz80/cz80.o +endif +# bg images +OBJS += data/bg32.o data/bg40.o + +vpath %.c = ../.. +vpath %.s = ../.. +DIRS = platform platform/psp platform/common pico pico/cd pico/pico pico/sound pico/carthw/svp \ + zlib unzip cpu cpu/musashi cpu/fame cpu/mz80 cpu/cz80 + + +LIBS += -lpng -lm -lpspgu -lpsppower -lpspaudio -lpsprtc -lpspaudiocodec -lpspkubridge +#LIBS += -lpspprof +LDFLAGS += -Wl,-Map=PicoDrive.map + + +# target +TARGET = PicoDrive +EXTRA_TARGETS = mkdirs EBOOT.PBP +PSP_EBOOT_TITLE = PicoDrive +PSP_EBOOT_ICON = data/icon.png +#PSP_EBOOT_PIC1 = .png +ifneq "$(for_15fw)" "1" +BUILD_PRX = 1 +endif + +CUSTOM_CLEAN = myclean + +include $(PSPSDK)/lib/build.mak + +# some additional rules +mkdirs: + mkdir -p $(DIRS) + +.c.o: + @echo ">>>" $< + $(CC) $(CFLAGS) -c $< -o $@ + +AS := psp-as + +.s.o: + @echo ">>>" $< + $(AS) -march=allegrex -mtune=allegrex $< -o $@ + +../../cpu/musashi/m68kops.c : + make -C ../../cpu/musashi + +cpu/fame/famec.o : ../../cpu/fame/famec.c + @echo ">>>" $< + $(CC) $(CFLAGS) -Wno-unused -c $< -o $@ + +pico/misc.o : ../../pico/misc.c + @echo ">>>" $< + $(CC) $(CFLAGS) -c $< -o $@ -D_ASM_MISC_C_AMIPS + +pico/memory.o : ../../pico/memory.c + @echo ">>>" $< + $(CC) $(CFLAGS) -O2 -c $< -o $@ -D_ASM_MEMORY_C -D_ASM_MEMORY_C_AMIPS + +pico/cd/memory.o : ../../pico/cd/memory.c + @echo ">>>" $< + $(CC) $(CFLAGS) -O2 -c $< -o $@ + +pico/cd/gfx_cd.o : ../../pico/cd/gfx_cd.c + @echo ">>>" $< + $(CC) $(CFLAGS) -O2 -c $< -o $@ + +readme.txt: ../../tools/textfilter ../base_readme.txt + ../../tools/textfilter ../base_readme.txt $@ PSP + +../../tools/textfilter: ../../tools/textfilter.c + make -C ../../tools/ textfilter + +data/bg32.o: data/bg32.bin + bin2o -i $< $@ bgdatac32 + +data/bg40.o: data/bg40.bin + bin2o -i $< $@ bgdatac40 + +# +ifndef UPDIR +UPDIR = /media/disk/PSP/GAME/PicoDrive/ +endif +up: EBOOT.PBP + @cp -v $^ $(UPDIR) + + +# cleanup + +myclean: + #rm -rf $(DIRS) + $(RM) PicoDrive.map + make -C ../../cpu/musashi clean + + +clean_prof: + find ../.. -name '*.gcno' -delete + find ../.. -name '*.gcda' -delete + +# ----------- release ----------- +ifneq ($(findstring rel,$(MAKECMDGOALS)),) +ifeq ($(VER),) +$(error need VER) +endif +endif + +# ? +rel: EBOOT.PBP readme.txt ../game_def.cfg + mkdir -p PicoDrive/skin/ + cp $^ PicoDrive/ + cp skin/* PicoDrive/skin/ + zip -9 -r ../../PicoDrive_psp_$(VER).zip PicoDrive + rm -rf PicoDrive + mkdir bin_to_cso_mp3 + cp ../../tools/bin_to_cso_mp3/* bin_to_cso_mp3/ + zip -9 -r ../../PicoDrive_psp_$(VER).zip bin_to_cso_mp3 + rm -rf bin_to_cso_mp3 + +rel_kxploit: readme.txt ../game_def.cfg + mkdir -p PicoDrive/skin/ + cp $^ PicoDrive/ + cp skin/* PicoDrive/skin/ + zip -9 -r ../../PicoDrive_psp_$(VER)_kxploit.zip PicoDrive + zip -9 -r ../../PicoDrive_psp_$(VER)_kxploit.zip PicoDrive% + mkdir bin_to_cso_mp3 + cp ../../tools/bin_to_cso_mp3/* bin_to_cso_mp3/ + zip -9 -r ../../PicoDrive_psp_$(VER)_kxploit.zip bin_to_cso_mp3 + rm -rf bin_to_cso_mp3 + diff --git a/platform/psp/asm_utils.h b/platform/psp/asm_utils.h new file mode 100644 index 0000000..acddac5 --- /dev/null +++ b/platform/psp/asm_utils.h @@ -0,0 +1,2 @@ +// pointers must be word aligned, gammaa_val = -4..16, black_lvl = {0,1,2} +void do_pal_convert(unsigned short *dest, unsigned short *src, int gammaa_val, int black_lvl); diff --git a/platform/psp/asm_utils.s b/platform/psp/asm_utils.s new file mode 100644 index 0000000..30ef7cd --- /dev/null +++ b/platform/psp/asm_utils.s @@ -0,0 +1,135 @@ +# vim:filetype=mips + +# some asm utils +# (c) Copyright 2007, Grazvydas "notaz" Ignotas +# All Rights Reserved + +.set noreorder +.set noat + +.data +.align 4 + +.byte 0, 1, 6, 11, 16, 21, 26, 31 # -4 +.byte 0, 2, 7, 12, 16, 21, 26, 31 # -3 +.byte 0, 3, 7, 12, 17, 22, 26, 31 # -2 +.byte 0, 4, 8, 13, 17, 22, 26, 31 # -1 +pal_gmtab: +.byte 0, 5, 10, 15, 16, 21, 26, 31 # 0 +.byte 0, 6, 10, 15, 19, 23, 27, 31 +.byte 0, 7, 11, 15, 19, 23, 27, 31 +.byte 0, 8, 12, 16, 19, 23, 27, 31 +.byte 0, 9, 12, 16, 20, 24, 27, 31 +.byte 0, 10, 13, 17, 20, 24, 27, 31 +.byte 0, 10, 14, 17, 21, 24, 28, 31 +.byte 0, 11, 15, 18, 21, 24, 28, 31 +.byte 0, 12, 15, 18, 22, 25, 28, 31 +.byte 0, 13, 16, 19, 22, 25, 28, 31 +.byte 0, 14, 17, 20, 22, 25, 28, 31 # 10 +.byte 0, 15, 17, 20, 23, 26, 28, 31 +.byte 0, 16, 18, 21, 23, 26, 28, 31 +.byte 0, 16, 19, 21, 24, 26, 29, 31 +.byte 0, 17, 20, 22, 24, 26, 29, 31 +.byte 0, 18, 20, 22, 25, 27, 29, 31 +.byte 0, 19, 21, 23, 25, 27, 29, 31 # 16 + +.text +.align 4 + +# bbbb bggg gggr rrrr + +#.global pal_gmtab +.global do_pal_convert # dest, src, gammaa_val, black_lvl + +do_pal_convert: + bnez $a2, dpc_gma + li $t0, 64/2 + bnez $a3, dpc_gma + lui $t2, 0x00e + ori $t2, 0x00e + lui $t3, 0x006 + ori $t3, 0x006 + lui $t4, 0x0e0 + ori $t4, 0x0e0 + lui $t6, 0xe00 + ori $t6, 0xe00 + lui $t7, 0x600 + ori $t7, 0x600 + +dpc_loop: + lw $v0, 0($a1) + addiu $a1, 4 + and $v1, $v0, $t2 # r + sll $v1, 1 + and $t9, $v0, $t3 + srl $t9, 1 + or $v1, $t9 # r + and $t9, $v0, $t4 # g + sll $t8, $t9, 3 + or $v1, $t8 + or $v1, $t9 # g + and $t9, $v0, $t6 # b + sll $t9, 4 + or $v1, $t9 + and $t9, $v0, $t7 + sll $t9, 2 + or $v1, $t9 # b + sw $v1, 0($a0) + addiu $t0, -1 + bnez $t0, dpc_loop + addiu $a0, 4 + + jr $ra + nop + +# non-zero gamma +dpc_gma: + slt $t2, $a2, $0 + sll $a2, 3 + lui $t1, %hi(pal_gmtab) + addiu $t1, %lo(pal_gmtab) + addu $a2, $t1 + beqz $a3, dpc_gma_loop + sb $0, 0($a2) # black level 0 + bnez $t2, dpc_gma_loop # gamma < 0, keep black at 0 + addiu $a3, -2 + slt $t2, $a3, $0 # t2 = a3_orig == 1 ? 1 : 0 + lb $t1, 1($a2) + addiu $t1, -2 + srlv $t1, $t1, $t2 + sb $t1, 0($a2) + +dpc_gma_loop: + lw $v0, 0($a1) + addiu $a1, 4 + ext $v1, $v0, 1, 3 + addu $v1, $a2 + lb $v1, 0($v1) + ext $t1, $v0, 5, 3 + addu $t1, $a2 + lb $t1, 0($t1) + ext $t2, $v0, 9, 3 + addu $t2, $a2 + lb $t2, 0($t2) + ext $t3, $v0, 17, 3 + addu $t3, $a2 + lb $t3, 0($t3) + ext $t4, $v0, 21, 3 + addu $t4, $a2 + lb $t4, 0($t4) + ext $t5, $v0, 25, 3 + addu $t5, $a2 + lb $t5, 0($t5) + ins $v1, $t1, 6, 5 + ins $v1, $t2, 11, 5 + ins $v1, $t3, 16, 5 + ins $v1, $t4, 22, 5 + ins $v1, $t5, 27, 5 + sw $v1, 0($a0) + addiu $t0, -1 + bnez $t0, dpc_gma_loop + addiu $a0, 4 + + jr $ra + nop + diff --git a/platform/psp/data/bg32.bin b/platform/psp/data/bg32.bin new file mode 100644 index 0000000..42f4473 Binary files /dev/null and b/platform/psp/data/bg32.bin differ diff --git a/platform/psp/data/bg40.bin b/platform/psp/data/bg40.bin new file mode 100644 index 0000000..b54a557 Binary files /dev/null and b/platform/psp/data/bg40.bin differ diff --git a/platform/psp/data/icon.png b/platform/psp/data/icon.png new file mode 100644 index 0000000..4bb5ac0 Binary files /dev/null and b/platform/psp/data/icon.png differ diff --git a/platform/psp/emu.c b/platform/psp/emu.c new file mode 100644 index 0000000..5c0cb57 --- /dev/null +++ b/platform/psp/emu.c @@ -0,0 +1,1067 @@ +/* + * PicoDrive + * (C) notaz, 2007,2008 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +#include +#include +#include // PATH_MAX + +#include +#include +#include +#include +#include + +#include "psp.h" +#include "menu.h" +#include "emu.h" +#include "mp3.h" +#include "asm_utils.h" +#include "../common/emu.h" +#include "../common/config.h" +#include "../common/lprintf.h" +#include +#include + +#define OSD_FPS_X 432 + +// additional pspaudio imports, credits to crazyc +int sceAudio_38553111(unsigned short samples, unsigned short freq, char unknown); // play with conversion? +int sceAudio_5C37C0AE(void); // end play? +int sceAudio_E0727056(int volume, void *buffer); // blocking output +int sceAudioOutput2GetRestSample(); + + +//unsigned char *Draw2FB = (unsigned char *)VRAM_CACHED_STUFF + 8; // +8 to be able to skip border with 1 quadword.. +int engineStateSuspend; + +#define PICO_PEN_ADJUST_X 4 +#define PICO_PEN_ADJUST_Y 2 +static int pico_pen_x = 320/2, pico_pen_y = 240/2; + +static void sound_init(void); +static void sound_deinit(void); +static void blit2(const char *fps, const char *notice, int lagging_behind); +static void clearArea(int full); + +int plat_get_root_dir(char *dst, int len) +{ + if (len > 0) *dst = 0; + return 0; +} + +static void osd_text(int x, const char *text, int is_active, int clear_all) +{ + unsigned short *screen = is_active ? psp_video_get_active_fb() : psp_screen; + int len = clear_all ? (480 / 2) : (strlen(text) * 8 / 2); + int *p, h; + void *tmp; + for (h = 0; h < 8; h++) { + p = (int *) (screen+x+512*(264+h)); + p = (int *) ((int)p & ~3); // align + memset32_uncached(p, 0, len); + } + if (is_active) { tmp = psp_screen; psp_screen = screen; } // nasty pointer tricks + emu_text_out16(x, 264, text); + if (is_active) psp_screen = tmp; +} + +void emu_msg_cb(const char *msg) +{ + osd_text(4, msg, 1, 1); + noticeMsgTime = sceKernelGetSystemTimeLow() - 2000000; + + /* assumption: emu_msg_cb gets called only when something slow is about to happen */ + reset_timing = 1; +} + +/* FIXME: move to plat */ +void emu_Init(void) +{ + sound_init(); +} + +void emu_Deinit(void) +{ + sound_deinit(); +} + +void pemu_prep_defconfig(void) +{ + defaultConfig.s_PsndRate = 22050; + defaultConfig.s_PicoCDBuffers = 64; + defaultConfig.CPUclock = 333; + defaultConfig.KeyBinds[ 4] = 1<<0; // SACB RLDU + defaultConfig.KeyBinds[ 6] = 1<<1; + defaultConfig.KeyBinds[ 7] = 1<<2; + defaultConfig.KeyBinds[ 5] = 1<<3; + defaultConfig.KeyBinds[14] = 1<<4; + defaultConfig.KeyBinds[13] = 1<<5; + defaultConfig.KeyBinds[15] = 1<<6; + defaultConfig.KeyBinds[ 3] = 1<<7; + defaultConfig.KeyBinds[12] = 1<<26; // switch rnd + defaultConfig.KeyBinds[ 8] = 1<<27; // save state + defaultConfig.KeyBinds[ 9] = 1<<28; // load state + defaultConfig.KeyBinds[28] = 1<<0; // num "buttons" + defaultConfig.KeyBinds[30] = 1<<1; + defaultConfig.KeyBinds[31] = 1<<2; + defaultConfig.KeyBinds[29] = 1<<3; + defaultConfig.scaling = 1; // bilinear filtering for psp + defaultConfig.scale = 1.20; // fullscreen + defaultConfig.hscale40 = 1.25; + defaultConfig.hscale32 = 1.56; +} + + +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); + +static void (*amips_clut_f)(unsigned short *dst, unsigned char *src, unsigned short *pal, int count) = NULL; + +struct Vertex +{ + short u,v; + short x,y,z; +}; + +static struct Vertex __attribute__((aligned(4))) g_vertices[2]; +static unsigned short __attribute__((aligned(16))) localPal[0x100]; +static int dynamic_palette = 0, need_pal_upload = 0, blit_16bit_mode = 0; +static int fbimg_offs = 0; + +static void set_scaling_params(void) +{ + int src_width, fbimg_width, fbimg_height, fbimg_xoffs, fbimg_yoffs, border_hack = 0; + g_vertices[0].x = g_vertices[0].y = + g_vertices[0].z = g_vertices[1].z = 0; + + fbimg_height = (int)(240.0 * currentConfig.scale + 0.5); + if (Pico.video.reg[12] & 1) { + fbimg_width = (int)(320.0 * currentConfig.scale * currentConfig.hscale40 + 0.5); + src_width = 320; + } else { + fbimg_width = (int)(256.0 * currentConfig.scale * currentConfig.hscale32 + 0.5); + src_width = 256; + } + + if (fbimg_width & 1) fbimg_width++; // make even + if (fbimg_height & 1) fbimg_height++; + + if (fbimg_width >= 480) { + g_vertices[0].u = (fbimg_width-480)/2; + g_vertices[1].u = src_width - (fbimg_width-480)/2 - 1; + fbimg_width = 480; + fbimg_xoffs = 0; + } else { + g_vertices[0].u = 0; + g_vertices[1].u = src_width; + fbimg_xoffs = 240 - fbimg_width/2; + } + if (fbimg_width > 320 && fbimg_width <= 480) border_hack = 1; + + if (fbimg_height >= 272) { + g_vertices[0].v = (fbimg_height-272)/2; + g_vertices[1].v = 240 - (fbimg_height-272)/2; + fbimg_height = 272; + fbimg_yoffs = 0; + } else { + g_vertices[0].v = 0; + g_vertices[1].v = 240; + fbimg_yoffs = 136 - fbimg_height/2; + } + + g_vertices[1].x = fbimg_width; + g_vertices[1].y = fbimg_height; + if (fbimg_xoffs < 0) fbimg_xoffs = 0; + if (fbimg_yoffs < 0) fbimg_yoffs = 0; + if (border_hack) { + g_vertices[0].u++; + g_vertices[0].x++; + g_vertices[1].u--; + g_vertices[1].x--; + } + fbimg_offs = (fbimg_yoffs*512 + fbimg_xoffs) * 2; // dst is always 16bit + + /* + lprintf("set_scaling_params:\n"); + lprintf("offs: %i, %i\n", fbimg_xoffs, fbimg_yoffs); + lprintf("xy0, xy1: %i, %i; %i, %i\n", g_vertices[0].x, g_vertices[0].y, g_vertices[1].x, g_vertices[1].y); + lprintf("uv0, uv1: %i, %i; %i, %i\n", g_vertices[0].u, g_vertices[0].v, g_vertices[1].u, g_vertices[1].v); + */ +} + +static void do_pal_update(int allow_sh, int allow_as) +{ + unsigned int *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); + do_pal_convert(localPal, Pico.cram, currentConfig.gamma, currentConfig.gamma2); + + Pico.m.dirtyPal = 0; + need_pal_upload = 1; + + if (allow_sh && (Pico.video.reg[0xC]&8)) // shadow/hilight? + { + // shadowed pixels + for (i = 0x3f/2; i >= 0; i--) + dpal[0x20|i] = dpal[0x60|i] = (dpal[i]>>1)&0x7bcf7bcf; + // hilighted pixels + for (i = 0x3f; i >= 0; i--) { + int t=localPal[i]&0xf79e;t+=0x4208; + if (t&0x20) t|=0x1e; + if (t&0x800) t|=0x780; + if (t&0x10000) t|=0xf000; + t&=0xf79e; + localPal[0x80|i]=(unsigned short)t; + } + localPal[0xe0] = 0; + localPal[0xf0] = 0x001f; + } +} + +static void do_slowmode_lines(int line_to) +{ + int line = 0, line_len = (Pico.video.reg[12]&1) ? 320 : 256; + unsigned short *dst = (unsigned short *)VRAM_STUFF + 512*240/2; + unsigned char *src = (unsigned char *)VRAM_CACHED_STUFF + 16; + if (!(Pico.video.reg[1]&8)) { line = 8; dst += 512*8; src += 512*8; } + + for (; line < line_to; line++, dst+=512, src+=512) + amips_clut_f(dst, src, localPal, line_len); +} + +static void EmuScanPrepare(void) +{ + Pico.est.HighCol = (unsigned char *)VRAM_CACHED_STUFF + 8; + if (!(Pico.video.reg[1]&8)) Pico.est.HighCol += 8*512; + + if (dynamic_palette > 0) + dynamic_palette--; + + if (Pico.m.dirtyPal) + do_pal_update(1, 1); + if (!(Pico.video.reg[0xC] & 8)) + amips_clut_f = amips_clut_6bit; + else amips_clut_f = amips_clut; +} + +static int EmuScanSlowBegin(unsigned int num) +{ + if (!dynamic_palette) + Pico.est.HighCol = (unsigned char *)VRAM_CACHED_STUFF + num * 512 + 8; + + return 0; +} + +static int EmuScanSlowEnd(unsigned int num) +{ + if (Pico.m.dirtyPal) { + if (!dynamic_palette) { + do_slowmode_lines(num); + dynamic_palette = 3; // last for 2 more frames + } + do_pal_update(1, 1); + } + + if (dynamic_palette) { + int line_len = (Pico.video.reg[12]&1) ? 320 : 256; + void *dst = (char *)VRAM_STUFF + 512*240 + 512*2*num; + amips_clut_f(dst, Pico.est.HighCol + 8, localPal, line_len); + } + + return 0; +} + +static void blitscreen_clut(void) +{ + int offs = fbimg_offs; + offs += (psp_screen == VRAM_FB0) ? VRAMOFFS_FB0 : VRAMOFFS_FB1; + + sceGuSync(0,0); // sync with prev + sceGuStart(GU_DIRECT, guCmdList); + sceGuDrawBuffer(GU_PSM_5650, (void *)offs, 512); // point to back buffer + + if (dynamic_palette) + { + if (!blit_16bit_mode) { // the current mode is not 16bit + sceGuTexMode(GU_PSM_5650, 0, 0, 0); + sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 512*240); + + blit_16bit_mode = 1; + } + } + else + { + if (blit_16bit_mode) { + sceGuClutMode(GU_PSM_5650,0,0xff,0); + sceGuTexMode(GU_PSM_T8,0,0,0); // 8-bit image + sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 16); + blit_16bit_mode = 0; + } + + if ((PicoIn.opt&0x10) && Pico.m.dirtyPal) + do_pal_update(0, 0); + + sceKernelDcacheWritebackAll(); + + if (need_pal_upload) { + need_pal_upload = 0; + sceGuClutLoad((256/8), localPal); // upload 32*8 entries (256) + } + } + +#if 1 + if (g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x) + { + struct Vertex* vertices; + int x; + + #define SLICE_WIDTH 32 + for (x = 0; x < g_vertices[1].x; x += SLICE_WIDTH) + { + // render sprite + vertices = (struct Vertex*)sceGuGetMemory(2 * sizeof(struct Vertex)); + memcpy(vertices, g_vertices, 2 * sizeof(struct Vertex)); + vertices[0].u = vertices[0].x = x; + vertices[1].u = vertices[1].x = x + SLICE_WIDTH; + sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,vertices); + } + // lprintf("listlen: %iB\n", sceGuCheckList()); // ~480 only + } + else +#endif + sceGuDrawArray(GU_SPRITES,GU_TEXTURE_16BIT|GU_VERTEX_16BIT|GU_TRANSFORM_2D,2,0,g_vertices); + + sceGuFinish(); +} + + +static void cd_leds(void) +{ + unsigned int reg, col_g, col_r, *p; + + reg = Pico_mcd->s68k_regs[0]; + + p = (unsigned int *)((short *)psp_screen + 512*2+4+2); + col_g = (reg & 2) ? 0x06000600 : 0; + col_r = (reg & 1) ? 0x00180018 : 0; + *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2; + *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; p += 512/2 - 12/2; + *p++ = col_g; *p++ = col_g; p+=2; *p++ = col_r; *p++ = col_r; +} + +static void draw_pico_ptr(void) +{ + unsigned char *p = (unsigned char *)VRAM_STUFF + 16; + + // only if pen enabled and for 8bit mode + if (pico_inp_mode == 0 || blit_16bit_mode) return; + + p += 512 * (pico_pen_y + PICO_PEN_ADJUST_Y); + p += pico_pen_x + PICO_PEN_ADJUST_X; + p[ -1] = 0xe0; p[ 0] = 0xf0; p[ 1] = 0xe0; + p[ 511] = 0xf0; p[ 512] = 0xf0; p[ 513] = 0xf0; + p[1023] = 0xe0; p[1024] = 0xf0; p[1025] = 0xe0; +} + + +#if 0 +static void dbg_text(void) +{ + int *p, h, len; + char text[128]; + + sprintf(text, "sl: %i, 16b: %i", g_vertices[0].u == 0 && g_vertices[1].u == g_vertices[1].x, blit_16bit_mode); + len = strlen(text) * 8 / 2; + for (h = 0; h < 8; h++) { + p = (int *) ((unsigned short *) psp_screen+2+512*(256+h)); + p = (int *) ((int)p & ~3); // align + memset32_uncached(p, 0, len); + } + emu_text_out16(2, 256, text); +} +#endif + +/* called after rendering is done, but frame emulation is not finished */ +void blit1(void) +{ + if (PicoIn.opt&0x10) + { + int i; + unsigned char *pd; + // clear top and bottom trash + for (pd = Pico.est.Draw2FB+8, i = 8; i > 0; i--, pd += 512) + memset32((int *)pd, 0xe0e0e0e0, 320/4); + for (pd = Pico.est.Draw2FB+512*232+8, i = 8; i > 0; i--, pd += 512) + memset32((int *)pd, 0xe0e0e0e0, 320/4); + } + + if (PicoIn.AHW & PAHW_PICO) + draw_pico_ptr(); + + blitscreen_clut(); +} + + +static void blit2(const char *fps, const char *notice, int lagging_behind) +{ + int vsync = 0, emu_opt = currentConfig.EmuOpt; + + if (notice || (emu_opt & 2)) { + if (notice) osd_text(4, notice, 0, 0); + if (emu_opt & 2) osd_text(OSD_FPS_X, fps, 0, 0); + } + + //dbg_text(); + + if ((emu_opt & 0x400) && (PicoIn.AHW & PAHW_MCD)) + cd_leds(); + + if (currentConfig.EmuOpt & 0x2000) { // want vsync + if (!(currentConfig.EmuOpt & 0x10000) || !lagging_behind) vsync = 1; + } + + psp_video_flip(vsync); +} + +// clears whole screen or just the notice area (in all buffers) +static void clearArea(int full) +{ + if (full) { + memset32_uncached(psp_screen, 0, 512*272*2/4); + psp_video_flip(0); + memset32_uncached(psp_screen, 0, 512*272*2/4); + memset32(VRAM_CACHED_STUFF, 0xe0e0e0e0, 512*240/4); + memset32((int *)VRAM_CACHED_STUFF+512*240/4, 0, 512*240*2/4); + } else { + void *fb = psp_video_get_active_fb(); + memset32_uncached((int *)((char *)psp_screen + 512*264*2), 0, 512*8*2/4); + memset32_uncached((int *)((char *)fb + 512*264*2), 0, 512*8*2/4); + } +} + +static void vidResetMode(void) +{ + // setup GU + sceGuSync(0,0); // sync with prev + sceGuStart(GU_DIRECT, guCmdList); + + sceGuClutMode(GU_PSM_5650,0,0xff,0); + sceGuTexMode(GU_PSM_T8,0,0,0); // 8-bit image + sceGuTexFunc(GU_TFX_REPLACE,GU_TCC_RGB); + if (currentConfig.scaling) + sceGuTexFilter(GU_LINEAR, GU_LINEAR); + else sceGuTexFilter(GU_NEAREST, GU_NEAREST); + sceGuTexScale(1.0f,1.0f); + sceGuTexOffset(0.0f,0.0f); + + sceGuTexImage(0,512,512,512,(char *)VRAM_STUFF + 16); + + // slow rend. + PicoDrawSetOutFormat(PDF_NONE, 0); + PicoDrawSetCallbacks(EmuScanSlowBegin, EmuScanSlowEnd); + + localPal[0xe0] = 0; + localPal[0xf0] = 0x001f; + Pico.m.dirtyPal = 1; + blit_16bit_mode = dynamic_palette = 0; + + sceGuFinish(); + set_scaling_params(); + sceGuSync(0,0); +} + +void plat_debug_cat(char *str) +{ + strcat(str, blit_16bit_mode ? "soft clut\n" : "hard clut\n"); +} + + +/* sound stuff */ +#define SOUND_BLOCK_SIZE_NTSC (1470*2) // 1024 // 1152 +#define SOUND_BLOCK_SIZE_PAL (1764*2) +#define SOUND_BLOCK_COUNT 8 + +static short __attribute__((aligned(4))) sndBuffer[SOUND_BLOCK_SIZE_PAL*SOUND_BLOCK_COUNT + 44100/50*2]; +static short *snd_playptr = NULL, *sndBuffer_endptr = NULL; +static int samples_made = 0, samples_done = 0, samples_block = 0; +static int sound_thread_exit = 0; +static SceUID sound_sem = -1; + +static void writeSound(int len); + +static int sound_thread(SceSize args, void *argp) +{ + int ret = 0; + + lprintf("sthr: started, priority %i\n", sceKernelGetThreadCurrentPriority()); + + while (!sound_thread_exit) + { + if (samples_made - samples_done < samples_block) { + // wait for data (use at least 2 blocks) + //lprintf("sthr: wait... (%i)\n", samples_made - samples_done); + while (samples_made - samples_done <= samples_block*2 && !sound_thread_exit) + ret = sceKernelWaitSema(sound_sem, 1, 0); + if (ret < 0) lprintf("sthr: sceKernelWaitSema: %i\n", ret); + continue; + } + + // lprintf("sthr: got data: %i\n", samples_made - samples_done); + + ret = sceAudio_E0727056(PSP_AUDIO_VOLUME_MAX, snd_playptr); + + samples_done += samples_block; + snd_playptr += samples_block; + if (snd_playptr >= sndBuffer_endptr) + snd_playptr = sndBuffer; + // 1.5 kernel returns 0, newer ones return # of samples queued + if (ret < 0) + lprintf("sthr: sceAudio_E0727056: %08x; pos %i/%i\n", ret, samples_done, samples_made); + + // shouln't happen, but just in case + if (samples_made - samples_done >= samples_block*3) { + //lprintf("sthr: block skip (%i)\n", samples_made - samples_done); + samples_done += samples_block; // skip + snd_playptr += samples_block; + } + + } + + lprintf("sthr: exit\n"); + sceKernelExitDeleteThread(0); + return 0; +} + +static void sound_init(void) +{ + SceUID thid; + int ret; + + sound_sem = sceKernelCreateSema("sndsem", 0, 0, 1, NULL); + if (sound_sem < 0) lprintf("sceKernelCreateSema() failed: %i\n", sound_sem); + + samples_made = samples_done = 0; + samples_block = SOUND_BLOCK_SIZE_NTSC; // make sure it goes to sema + sound_thread_exit = 0; + thid = sceKernelCreateThread("sndthread", sound_thread, 0x12, 0x10000, 0, NULL); + if (thid >= 0) + { + ret = sceKernelStartThread(thid, 0, 0); + if (ret < 0) lprintf("sound_init: sceKernelStartThread returned %08x\n", ret); + } + else + lprintf("sceKernelCreateThread failed: %i\n", thid); +} + +void pemu_sound_start(void) +{ + static int PsndRate_old = 0, PicoOpt_old = 0, pal_old = 0; + int ret, stereo; + + samples_made = samples_done = 0; + + if (PicoIn.sndRate != PsndRate_old || (PicoIn.opt&0x0b) != (PicoOpt_old&0x0b) || Pico.m.pal != pal_old) { + PsndRerate(Pico.m.frame_count ? 1 : 0); + } + stereo=(PicoIn.opt&8)>>3; + + samples_block = Pico.m.pal ? SOUND_BLOCK_SIZE_PAL : SOUND_BLOCK_SIZE_NTSC; + if (PicoIn.sndRate <= 22050) samples_block /= 2; + sndBuffer_endptr = &sndBuffer[samples_block*SOUND_BLOCK_COUNT]; + + lprintf("starting audio: %i, len: %i, stereo: %i, pal: %i, block samples: %i\n", + PicoIn.sndRate, Pico.snd.len, stereo, Pico.m.pal, samples_block); + + // while (sceAudioOutput2GetRestSample() > 0) psp_msleep(100); + // sceAudio_5C37C0AE(); + ret = sceAudio_38553111(samples_block/2, PicoIn.sndRate, 2); // seems to not need that stupid 64byte alignment + if (ret < 0) { + lprintf("sceAudio_38553111() failed: %i\n", ret); + emu_status_msg("sound init failed (%i), snd disabled", ret); + currentConfig.EmuOpt &= ~EOPT_EN_SOUND; + } else { + PicoIn.writeSound = writeSound; + memset32((int *)(void *)sndBuffer, 0, sizeof(sndBuffer)/4); + snd_playptr = sndBuffer_endptr - samples_block; + samples_made = samples_block; // send 1 empty block first.. + PicoIn.sndOut = sndBuffer; + PsndRate_old = PicoIn.sndRate; + PicoOpt_old = PicoIn.opt; + pal_old = Pico.m.pal; + } +} + +void pemu_sound_stop(void) +{ + int i; + if (samples_done == 0) + { + // if no data is written between sceAudio_38553111 and sceAudio_5C37C0AE calls, + // we get a deadlock on next sceAudio_38553111 call + // so this is yet another workaround: + memset32((int *)(void *)sndBuffer, 0, samples_block*4/4); + samples_made = samples_block * 3; + sceKernelSignalSema(sound_sem, 1); + } + sceKernelDelayThread(100*1000); + samples_made = samples_done = 0; + for (i = 0; sceAudioOutput2GetRestSample() > 0 && i < 16; i++) + psp_msleep(100); + sceAudio_5C37C0AE(); +} + +/* wait until we can write more sound */ +void pemu_sound_wait(void) +{ + // TODO: test this + while (!sound_thread_exit && samples_made - samples_done > samples_block * 4) + psp_msleep(10); +} + +static void sound_deinit(void) +{ + sound_thread_exit = 1; + sceKernelSignalSema(sound_sem, 1); + sceKernelDeleteSema(sound_sem); + sound_sem = -1; +} + +static void writeSound(int len) +{ + int ret; + + PicoIn.sndOut += len / 2; + /*if (PicoIn.sndOut > sndBuffer_endptr) { + memcpy32((int *)(void *)sndBuffer, (int *)endptr, (PicoIn.sndOut - endptr + 1) / 2); + PicoIn.sndOut = &sndBuffer[PicoIn.sndOut - endptr]; + lprintf("mov\n"); + } + else*/ + if (PicoIn.sndOut > sndBuffer_endptr) lprintf("snd oflow %i!\n", PicoIn.sndOut - sndBuffer_endptr); + if (PicoIn.sndOut >= sndBuffer_endptr) + PicoIn.sndOut = sndBuffer; + + // signal the snd thread + samples_made += len / 2; + if (samples_made - samples_done > samples_block*2) { + // lprintf("signal, %i/%i\n", samples_done, samples_made); + ret = sceKernelSignalSema(sound_sem, 1); + //if (ret < 0) lprintf("snd signal ret %08x\n", ret); + } +} + + +static void SkipFrame(void) +{ + PicoIn.skipFrame=1; + PicoFrame(); + PicoIn.skipFrame=0; +} + +void pemu_forced_frame(int no_scale, int do_emu) +{ + int po_old = PicoIn.opt; + int eo_old = currentConfig.EmuOpt; + + PicoIn.opt &= ~POPT_ALT_RENDERER; + PicoIn.opt |= POPT_ACC_SPRITES; + if (!no_scale) + PicoIn.opt |= POPT_EN_SOFTSCALE; + currentConfig.EmuOpt |= 0x80; + + vidResetMode(); + memset32(VRAM_CACHED_STUFF, 0xe0e0e0e0, 512*8/4); // borders + memset32((int *)VRAM_CACHED_STUFF + 512*232/4, 0xe0e0e0e0, 512*8/4); + memset32_uncached((int *)psp_screen + 512*264*2/4, 0, 512*8*2/4); + + PicoDrawSetOutFormat(PDF_NONE, 0); + PicoDrawSetCallbacks(EmuScanSlowBegin, EmuScanSlowEnd); + EmuScanPrepare(); + PicoFrameDrawOnly(); + blit1(); + sceGuSync(0,0); + + PicoIn.opt = po_old; + currentConfig.EmuOpt = eo_old; +} + + +static void RunEventsPico(unsigned int events, unsigned int keys) +{ + emu_RunEventsPico(events); + + if (pico_inp_mode != 0) + { + PicoIn.pad[0] &= ~0x0f; // release UDLR + if (keys & PBTN_UP) { pico_pen_y--; if (pico_pen_y < 8) pico_pen_y = 8; } + if (keys & PBTN_DOWN) { pico_pen_y++; if (pico_pen_y > 224-PICO_PEN_ADJUST_Y) pico_pen_y = 224-PICO_PEN_ADJUST_Y; } + if (keys & PBTN_LEFT) { pico_pen_x--; if (pico_pen_x < 0) pico_pen_x = 0; } + if (keys & PBTN_RIGHT) { + int lim = (Pico.video.reg[12]&1) ? 319 : 255; + pico_pen_x++; + if (pico_pen_x > lim-PICO_PEN_ADJUST_X) + pico_pen_x = lim-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 RunEvents(unsigned int which) +{ + if (which & 0x1800) // save or load (but not both) + { + int do_it = 1; + + if ( emu_check_save_file(state_slot) && + (( (which & 0x1000) && (currentConfig.EmuOpt & 0x800)) || // load + (!(which & 0x1000) && (currentConfig.EmuOpt & 0x200))) ) // save + { + int keys; + sceGuSync(0,0); + blit2("", (which & 0x1000) ? "LOAD STATE? (X=yes, O=no)" : "OVERWRITE SAVE? (X=yes, O=no)", 0); + while( !((keys = psp_pad_read(1)) & (PBTN_X|PBTN_CIRCLE)) ) + psp_msleep(50); + if (keys & PBTN_CIRCLE) do_it = 0; + while( ((keys = psp_pad_read(1)) & (PBTN_X|PBTN_CIRCLE)) ) // wait for release + psp_msleep(50); + clearArea(0); + } + + if (do_it) + { + osd_text(4, (which & 0x1000) ? "LOADING GAME" : "SAVING GAME", 1, 0); + PicoStateProgressCB = emu_msg_cb; + emu_save_load_game((which & 0x1000) >> 12, 0); + PicoStateProgressCB = NULL; + psp_msleep(0); + } + + reset_timing = 1; + } + if (which & 0x0400) // switch renderer + { + if (PicoIn.opt&0x10) { PicoIn.opt&=~0x10; currentConfig.EmuOpt |= 0x80; } + else { PicoIn.opt|= 0x10; currentConfig.EmuOpt &= ~0x80; } + + vidResetMode(); + + if (PicoIn.opt & POPT_ALT_RENDERER) + emu_status_msg("fast renderer"); + else if (currentConfig.EmuOpt&0x80) + emu_status_msg("accurate renderer"); + } + if (which & 0x0300) + { + if(which&0x0200) { + 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) ? "USED" : "FREE"); + } +} + +static void updateKeys(void) +{ + unsigned int keys, allActions[2] = { 0, 0 }, events; + static unsigned int prevEvents = 0; + int i; + + /* FIXME: port to input fw, merge with emu.c:emu_update_input() */ + keys = psp_pad_read(0); + if (keys & PSP_CTRL_HOME) + sceDisplayWaitVblankStart(); + + if (keys & PBTN_SELECT) + engineState = PGS_Menu; + + keys &= CONFIGURABLE_KEYS; + + PicoIn.pad[0] = allActions[0] & 0xfff; + PicoIn.pad[1] = allActions[1] & 0xfff; + + if (allActions[0] & 0x7000) emu_DoTurbo(&PicoIn.pad[0], allActions[0]); + if (allActions[1] & 0x7000) emu_DoTurbo(&PicoIn.pad[1], allActions[1]); + + events = (allActions[0] | allActions[1]) >> 16; + + if ((events ^ prevEvents) & 0x40) { + emu_set_fastforward(events & 0x40); + reset_timing = 1; + } + + events &= ~prevEvents; + + if (PicoIn.AHW == PAHW_PICO) + RunEventsPico(events, keys); + if (events) RunEvents(events); + if (movie_data) emu_updateMovie(); + + prevEvents = (allActions[0] | allActions[1]) >> 16; +} + + +static void simpleWait(unsigned int until) +{ + unsigned int tval; + int diff; + + tval = sceKernelGetSystemTimeLow(); + diff = (int)until - (int)tval; + if (diff >= 512 && diff < 100*1024) + sceKernelDelayThread(diff); +} + +void pemu_loop(void) +{ + static int mp3_init_done = 0; + char fpsbuff[24]; // fps count c string + unsigned int tval, tval_thissec = 0; // timing + int target_fps, target_frametime, lim_time, tval_diff, i, oldmodes = 0; + int pframes_done, pframes_shown; // "period" frames, used for sync + int frames_done, frames_shown, tval_fpsc = 0; // actual frames + char *notice = NULL; + + lprintf("entered emu_Loop()\n"); + + fpsbuff[0] = 0; + + if (currentConfig.CPUclock != psp_get_cpu_clock()) { + lprintf("setting cpu clock to %iMHz... ", currentConfig.CPUclock); + i = psp_set_cpu_clock(currentConfig.CPUclock); + lprintf(i ? "failed\n" : "done\n"); + currentConfig.CPUclock = psp_get_cpu_clock(); + } + + // make sure we are in correct mode + vidResetMode(); + clearArea(1); + Pico.m.dirtyPal = 1; + oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc; + + // pal/ntsc might have changed, reset related stuff + target_fps = Pico.m.pal ? 50 : 60; + target_frametime = Pico.m.pal ? (1000000<<8)/50 : (1000000<<8)/60+1; + reset_timing = 1; + + if (PicoIn.AHW & PAHW_MCD) { + // prepare CD buffer + PicoCDBufferInit(); + // mp3... + if (!mp3_init_done) { + i = mp3_init(); + mp3_init_done = 1; + if (i) { engineState = PGS_Menu; return; } + } + } + + // prepare sound stuff + PicoIn.sndOut = NULL; + if (currentConfig.EmuOpt & EOPT_EN_SOUND) + { + pemu_sound_start(); + } + + sceDisplayWaitVblankStart(); + pframes_shown = pframes_done = + frames_shown = frames_done = 0; + + tval_fpsc = sceKernelGetSystemTimeLow(); + + // loop? + while (engineState == PGS_Running) + { + int modes; + + tval = sceKernelGetSystemTimeLow(); + if (reset_timing || tval < tval_fpsc) { + //stdbg("timing reset"); + reset_timing = 0; + tval_thissec = tval; + pframes_shown = pframes_done = 0; + } + + // show notice message? + if (noticeMsgTime) { + static int noticeMsgSum; + if (tval - noticeMsgTime > 2000000) { // > 2.0 sec + noticeMsgTime = 0; + clearArea(0); + notice = 0; + } else { + int sum = noticeMsg[0]+noticeMsg[1]+noticeMsg[2]; + if (sum != noticeMsgSum) { clearArea(0); noticeMsgSum = sum; } + notice = noticeMsg; + } + } + + // check for mode changes + modes = ((Pico.video.reg[12]&1)<<2)|(Pico.video.reg[1]&8); + if (modes != oldmodes) { + oldmodes = modes; + clearArea(1); + set_scaling_params(); + } + + // second passed? + if (tval - tval_fpsc >= 1000000) + { + if (currentConfig.EmuOpt & 2) + sprintf(fpsbuff, "%02i/%02i ", frames_shown, frames_done); + frames_done = frames_shown = 0; + tval_fpsc += 1000000; + } + + if (tval - tval_thissec >= 1000000) + { + // missing 1 frame? + if (currentConfig.Frameskip < 0 && pframes_done < target_fps) { + SkipFrame(); pframes_done++; frames_done++; + } + + tval_thissec += 1000000; + + if (currentConfig.Frameskip < 0) { + pframes_done -= target_fps; if (pframes_done < 0) pframes_done = 0; + pframes_shown -= target_fps; if (pframes_shown < 0) pframes_shown = 0; + if (pframes_shown > pframes_done) pframes_shown = pframes_done; + } else { + pframes_done = pframes_shown = 0; + } + } +#ifdef PFRAMES + sprintf(fpsbuff, "%i", Pico.m.frame_count); +#endif + + lim_time = (pframes_done+1) * target_frametime; + if (currentConfig.Frameskip >= 0) // frameskip enabled + { + for (i = 0; i < currentConfig.Frameskip; i++) { + updateKeys(); + SkipFrame(); pframes_done++; frames_done++; + if (!(currentConfig.EmuOpt&0x40000)) { // do framelimitting if needed + int tval_diff; + tval = sceKernelGetSystemTimeLow(); + tval_diff = (int)(tval - tval_thissec) << 8; + if (tval_diff < lim_time) // we are too fast + simpleWait(tval + ((lim_time - tval_diff)>>8)); + } + lim_time += target_frametime; + } + } + else // auto frameskip + { + int tval_diff; + tval = sceKernelGetSystemTimeLow(); + tval_diff = (int)(tval - tval_thissec) << 8; + if (tval_diff > lim_time && (pframes_done/16 < pframes_shown)) + { + // no time left for this frame - skip + if (tval_diff - lim_time >= (300000<<8)) { + reset_timing = 1; + continue; + } + updateKeys(); + SkipFrame(); pframes_done++; frames_done++; + continue; + } + } + + updateKeys(); + + if (!(PicoIn.opt&0x10)) + EmuScanPrepare(); + + PicoFrame(); + + sceGuSync(0,0); + + // check time + tval = sceKernelGetSystemTimeLow(); + tval_diff = (int)(tval - tval_thissec) << 8; + + blit2(fpsbuff, notice, tval_diff > lim_time); + + if (currentConfig.Frameskip < 0 && tval_diff - lim_time >= (300000<<8)) { // slowdown detection + reset_timing = 1; + } + else if (!(currentConfig.EmuOpt&0x40000) || currentConfig.Frameskip < 0) + { + // sleep if we are still too fast + if (tval_diff < lim_time) + { + // we are too fast + simpleWait(tval + ((lim_time - tval_diff) >> 8)); + } + } + + pframes_done++; pframes_shown++; + frames_done++; frames_shown++; + } + + + emu_set_fastforward(0); + + if (PicoIn.AHW & PAHW_MCD) PicoCDBufferFree(); + + if (PicoIn.sndOut != NULL) { + pemu_sound_stop(); + PicoIn.sndOut = NULL; + } + + // save SRAM + if ((currentConfig.EmuOpt & 1) && SRam.changed) { + emu_msg_cb("Writing SRAM/BRAM.."); + emu_save_load_game(0, 1); + SRam.changed = 0; + } + + // clear fps counters and stuff + memset32_uncached((int *)psp_video_get_active_fb() + 512*264*2/4, 0, 512*8*2/4); +} + +void emu_HandleResume(void) +{ + if (!(PicoIn.AHW & PAHW_MCD)) return; + + // reopen first CD track + if (Pico_mcd->TOC.Tracks[0].F != NULL) + { + char *fname = rom_fname_reload; + int len = strlen(rom_fname_reload); + cue_data_t *cue_data = NULL; + + if (len > 4 && strcasecmp(fname + len - 4, ".cue") == 0) + { + cue_data = cue_parse(rom_fname_reload); + if (cue_data != NULL) + fname = cue_data->tracks[1].fname; + } + + lprintf("emu_HandleResume: reopen %s\n", fname); + pm_close(Pico_mcd->TOC.Tracks[0].F); + Pico_mcd->TOC.Tracks[0].F = pm_open(fname); + lprintf("reopen %s\n", Pico_mcd->TOC.Tracks[0].F != NULL ? "ok" : "failed"); + + if (cue_data != NULL) cue_destroy(cue_data); + } + + mp3_reopen_file(); + + if (!(Pico_mcd->s68k_regs[0x36] & 1) && (Pico_mcd->scd.Status_CDC & 1)) + cdda_start_play(); +} + diff --git a/platform/psp/emu.h b/platform/psp/emu.h new file mode 100644 index 0000000..4ef5383 --- /dev/null +++ b/platform/psp/emu.h @@ -0,0 +1,10 @@ + +extern int engineStateSuspend; + +void emu_HandleResume(void); + +void emu_msg_cb(const char *msg); + +// actually comes from Pico/Misc_amips.s +void memset32_uncached(int *dest, int c, int count); + diff --git a/platform/psp/main.c b/platform/psp/main.c new file mode 100644 index 0000000..989ce5c --- /dev/null +++ b/platform/psp/main.c @@ -0,0 +1,124 @@ +/* + * PicoDrive + * (C) notaz, 2007,2008 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +#include +#include "psp.h" +#include "emu.h" +#include "menu.h" +#include "mp3.h" +#include "../common/menu.h" +#include "../common/emu.h" +#include "../common/config.h" +#include "../common/lprintf.h" + +#ifdef GPROF +#include +#endif + +#ifdef GCOV +#include +#include + +void dummy(void) +{ + engineState = atoi(rom_fname_reload); + setbuf(NULL, NULL); + getenv(NULL); +} +#endif + +int pico_main(void) +{ + psp_init(); + + emu_prepareDefaultConfig(); + emu_ReadConfig(0, 0); + config_readlrom(PicoConfigFile); + + emu_Init(); + menu_init(); + // moved to emu_Loop(), after CPU clock change.. + //mp3_init(); + + engineState = PGS_Menu; + + for (;;) + { + switch (engineState) + { + case PGS_Menu: +#ifndef GPROF + menu_loop(); +#else + strcpy(rom_fname_reload, rom_fname_loaded); + engineState = PGS_ReloadRom; +#endif + break; + + case PGS_ReloadRom: + if (emu_reload_rom(rom_fname_reload)) { + engineState = PGS_Running; + if (mp3_last_error != 0) + engineState = PGS_Menu; // send to menu to display mp3 error + } else { + lprintf("PGS_ReloadRom == 0\n"); + engineState = PGS_Menu; + } + break; + + case PGS_Suspending: + while (engineState == PGS_Suspending) + psp_wait_suspend(); + break; + + case PGS_SuspendWake: + psp_unhandled_suspend = 0; + psp_resume_suspend(); + emu_HandleResume(); + engineState = engineStateSuspend; + break; + + case PGS_RestartRun: + engineState = PGS_Running; + + case PGS_Running: + if (psp_unhandled_suspend) { + psp_unhandled_suspend = 0; + psp_resume_suspend(); + emu_HandleResume(); + break; + } + pemu_loop(); +#ifdef GPROF + goto endloop; +#endif + break; + + case PGS_Quit: + goto endloop; + + default: + lprintf("engine got into unknown state (%i), exitting\n", engineState); + goto endloop; + } + } + + endloop: + + mp3_deinit(); + emu_Deinit(); +#ifdef GPROF + gprof_cleanup(); +#endif +#ifndef GCOV + psp_finish(); +#endif + + return 0; +} + diff --git a/platform/psp/menu.c b/platform/psp/menu.c new file mode 100644 index 0000000..ab022f9 --- /dev/null +++ b/platform/psp/menu.c @@ -0,0 +1,1789 @@ +/* + * PicoDrive + * (C) notaz, 2006-2008 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +// don't like to use loads of #ifdefs, so duplicating GP2X code +// horribly instead + +#include +#include +#include +#include +#include // PATH_MAX + +#include +#include +#include +#include + +#include "psp.h" +#include "emu.h" +#include "menu.h" +#include "mp3.h" +#include "../common/menu.h" +#include "../common/emu.h" +#include "../common/readpng.h" +#include "../common/lprintf.h" +#include "../common/input.h" +#include "version.h" + +#include +#include +#include + + +#define pspKeyUnkn "???" +const char * const keyNames[] = { + "SELECT", pspKeyUnkn, pspKeyUnkn, "START", "UP", "RIGHT", "DOWN", "LEFT", + "L", "R", pspKeyUnkn, pspKeyUnkn, "TRIANGLE", "CIRCLE", "X", "SQUARE", + "HOME", "HOLD", "WLAN_UP", "REMOTE", "VOLUP", "VOLDOWN", "SCREEN", "NOTE", + pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, pspKeyUnkn, "NUB UP", "NUB RIGHT", "NUB DOWN", "NUB LEFT" // fake +}; + +static unsigned short bg_buffer[480*272] __attribute__((aligned(16))); +#define menu_screen psp_screen + +void menu_darken_bg(void *dst, const void *src, int pixels, int darker); +static void menu_prepare_bg(int use_game_bg, int use_fg); + + +static unsigned int inp_prev = 0; + +void menu_draw_begin(void) +{ + // short *src = (short *)bg_buffer, *dst = (short *)menu_screen; + // int i; + + // for (i = 272; i >= 0; i--, dst += 512, src += 480) + // memcpy32((int *)dst, (int *)src, 480*2/4); + + sceGuSync(0,0); // sync with prev + sceGuStart(GU_DIRECT, guCmdList); + sceGuCopyImage(GU_PSM_5650, 0, 0, 480, 272, 480, bg_buffer, 0, 0, 512, menu_screen); + sceGuFinish(); + sceGuSync(0,0); +} + + +void menu_draw_end(void) +{ + psp_video_flip(1); +} + + +// --------- loading ROM screen ---------- + +static int lcdr_line = 0; + +static void load_progress_cb(int percent) +{ + int ln, len = percent * 480 / 100; + unsigned short *dst; + + //sceDisplayWaitVblankStart(); + + dst = (unsigned short *)menu_screen + 512*10*lcdr_line; + + if (len > 480) len = 480; + for (ln = 8; ln > 0; ln--, dst += 512) + memset(dst, 0xff, len*2); +} + +static void cdload_progress_cb(int percent) +{ + int ln, len = percent * 480 / 100; + unsigned short *dst; + + if (lcdr_line <= 2) { + lcdr_line++; + smalltext_out16(1, lcdr_line++ * 10, "Processing CD image / MP3s", 0xffff); + smalltext_out16_lim(1, lcdr_line++ * 10, romFileName, 0xffff, 80); + } + + dst = (unsigned short *)menu_screen + 512*10*lcdr_line; + + if (len > 480) len = 480; + for (ln = 8; ln > 0; ln--, dst += 512) + memset(dst, 0xff, len*2); +} + +void menu_romload_prepare(const char *rom_name) +{ + const char *p = rom_name + strlen(rom_name); + while (p > rom_name && *p != '/') p--; + + psp_video_switch_to_single(); + if (rom_loaded) menu_draw_begin(); + else memset32_uncached(psp_screen, 0, 512*272*2/4); + + smalltext_out16(1, 1, "Loading", 0xffff); + smalltext_out16_lim(1, 10, p, 0xffff, 80); + PicoCartLoadProgressCB = load_progress_cb; + PicoCDLoadProgressCB = cdload_progress_cb; + lcdr_line = 2; +} + +void menu_romload_end(void) +{ + PicoCartLoadProgressCB = PicoCDLoadProgressCB = NULL; + smalltext_out16(1, ++lcdr_line*10, "Starting emulation...", 0xffff); +} + +// -------------- ROM selector -------------- + +struct my_dirent +{ + unsigned int d_type; + char d_name[255]; +}; + +// bbbb bggg gggr rrrr +static unsigned short file2color(const char *fname) +{ + const char *ext = fname + strlen(fname) - 3; + static const char *rom_exts[] = { "zip", "bin", "smd", "gen", "iso", "cso", "cue" }; + static const char *other_exts[] = { "gmv", "pat" }; + int i; + + if (ext < fname) ext = fname; + for (i = 0; i < sizeof(rom_exts)/sizeof(rom_exts[0]); i++) + if (strcasecmp(ext, rom_exts[i]) == 0) return 0xfdf7; + for (i = 0; i < sizeof(other_exts)/sizeof(other_exts[0]); i++) + if (strcasecmp(ext, other_exts[i]) == 0) return 0xaff5; + return 0xffff; +} + +static void draw_dirlist(char *curdir, struct my_dirent **namelist, int n, int sel) +{ + int start, i, pos; + + start = 13 - sel; + n--; // exclude current dir (".") + + menu_draw_begin(); + + if (!rom_loaded) { +// menu_darken_bg(menu_screen, menu_screen, 321*240, 0); + } + + menu_darken_bg((char *)menu_screen + 512*129*2, (char *)menu_screen + 512*129*2, 512*10, 0); + + if (start - 2 >= 0) + smalltext_out16_lim(14, (start - 2)*10, curdir, 0xffff, 53-2); + for (i = 0; i < n; i++) { + pos = start + i; + if (pos < 0) continue; + if (pos > 26) break; + if (namelist[i+1]->d_type & FIO_S_IFDIR) { + smalltext_out16_lim(14, pos*10, "/", 0xd7ff, 1); + smalltext_out16_lim(14+6, pos*10, namelist[i+1]->d_name, 0xd7ff, 80-3); + } else { + unsigned short color = file2color(namelist[i+1]->d_name); + smalltext_out16_lim(14, pos*10, namelist[i+1]->d_name, color, 80-2); + } + } + text_out16(5, 130, ">"); + menu_draw_end(); +} + +static int scandir_cmp(const void *p1, const void *p2) +{ + struct my_dirent **d1 = (struct my_dirent **)p1, **d2 = (struct my_dirent **)p2; + if ((*d1)->d_type & (*d2)->d_type & FIO_S_IFDIR) + return strcasecmp((*d1)->d_name, (*d2)->d_name); + if ((*d1)->d_type & FIO_S_IFDIR) return -1; // put before + if ((*d2)->d_type & FIO_S_IFDIR) return 1; + return strcasecmp((*d1)->d_name, (*d2)->d_name); +} + +static char *filter_exts[] = { + ".mp3", ".srm", ".brm", "s.gz", ".mds", "bcfg", ".txt", ".htm", "html", + ".jpg", ".pbp" +}; + +static int scandir_filter(const struct my_dirent *ent) +{ + const char *p; + int i; + + if (ent == NULL || ent->d_name == NULL) return 0; + if (strlen(ent->d_name) < 5) return 1; + + p = ent->d_name + strlen(ent->d_name) - 4; + + for (i = 0; i < sizeof(filter_exts)/sizeof(filter_exts[0]); i++) + { + if (strcasecmp(p, filter_exts[i]) == 0) return 0; + } + + return 1; +} + +static int my_scandir(const char *dir, struct my_dirent ***namelist_out, + int(*filter)(const struct my_dirent *), + int(*compar)(const void *, const void *)) +{ + int ret = -1, dir_uid = -1, name_alloc = 4, name_count = 0; + struct my_dirent **namelist = NULL, *ent; + SceIoDirent sce_ent; + + namelist = malloc(sizeof(*namelist) * name_alloc); + if (namelist == NULL) { lprintf("%s:%i: OOM\n", __FILE__, __LINE__); goto fail; } + + // try to read first.. + dir_uid = sceIoDopen(dir); + if (dir_uid >= 0) + { + /* it is very important to clear SceIoDirent to be passed to sceIoDread(), */ + /* or else it may crash, probably misinterpreting something in it. */ + memset(&sce_ent, 0, sizeof(sce_ent)); + ret = sceIoDread(dir_uid, &sce_ent); + if (ret < 0) + { + lprintf("sceIoDread(\"%s\") failed with %i\n", dir, ret); + goto fail; + } + } + else + lprintf("sceIoDopen(\"%s\") failed with %i\n", dir, dir_uid); + + while (ret > 0) + { + ent = malloc(sizeof(*ent)); + if (ent == NULL) { lprintf("%s:%i: OOM\n", __FILE__, __LINE__); goto fail; } + ent->d_type = sce_ent.d_stat.st_mode; + strncpy(ent->d_name, sce_ent.d_name, sizeof(ent->d_name)); + ent->d_name[sizeof(ent->d_name)-1] = 0; + if (filter == NULL || filter(ent)) + namelist[name_count++] = ent; + else free(ent); + + if (name_count >= name_alloc) + { + void *tmp; + name_alloc *= 2; + tmp = realloc(namelist, sizeof(*namelist) * name_alloc); + if (tmp == NULL) { lprintf("%s:%i: OOM\n", __FILE__, __LINE__); goto fail; } + namelist = tmp; + } + + memset(&sce_ent, 0, sizeof(sce_ent)); + ret = sceIoDread(dir_uid, &sce_ent); + } + + // sort + if (compar != NULL && name_count > 3) qsort(&namelist[2], name_count - 2, sizeof(namelist[0]), compar); + + // all done. + ret = name_count; + *namelist_out = namelist; + goto end; + +fail: + if (namelist != NULL) + { + while (name_count--) + free(namelist[name_count]); + free(namelist); + } +end: + if (dir_uid >= 0) sceIoDclose(dir_uid); + return ret; +} + + +static SceIoStat cpstat; + +static char *romsel_loop(char *curr_path) +{ + struct my_dirent **namelist; + int n, iret, sel = 0; + unsigned long inp = 0; + char *ret = NULL, *fname = NULL; + + // is this a dir or a full path? + memset(&cpstat, 0, sizeof(cpstat)); + iret = sceIoGetstat(curr_path, &cpstat); + if (iret >= 0 && (cpstat.st_mode & FIO_S_IFDIR)); // dir + else if (iret >= 0 && (cpstat.st_mode & FIO_S_IFREG)) { // file + char *p; + for (p = curr_path + strlen(curr_path) - 1; p > curr_path && *p != '/'; p--); + if (p > curr_path) { + *p = 0; + fname = p+1; + } + else strcpy(curr_path, "ms0:/"); + } + else strcpy(curr_path, "ms0:/"); // something else + + n = my_scandir(curr_path, &namelist, scandir_filter, scandir_cmp); + if (n < 0) { + // try root.. + n = my_scandir("ms0:/", &namelist, scandir_filter, scandir_cmp); + if (n < 0) { + // oops, we failed + lprintf("scandir failed, dir: "); lprintf(curr_path); lprintf("\n"); + return NULL; + } + } + + // try to find sel + if (fname != NULL) { + int i; + for (i = 1; i < n; i++) { + if (strcmp(namelist[i]->d_name, fname) == 0) { + sel = i - 1; + break; + } + } + } + + for (;;) + { + draw_dirlist(curr_path, namelist, n, sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_L|PBTN_R|PBTN_X|PBTN_CIRCLE, 0); + if(inp & PBTN_UP ) { sel--; if (sel < 0) sel = n-2; } + if(inp & PBTN_DOWN) { sel++; if (sel > n-2) sel = 0; } + if(inp & PBTN_LEFT) { sel-=10; if (sel < 0) sel = 0; } + if(inp & PBTN_L) { sel-=24; if (sel < 0) sel = 0; } + if(inp & PBTN_RIGHT) { sel+=10; if (sel > n-2) sel = n-2; } + if(inp & PBTN_R) { sel+=24; if (sel > n-2) sel = n-2; } + if(inp & PBTN_CIRCLE) // enter dir/select + { + if (namelist[sel+1]->d_type & FIO_S_IFDIR) + { + int newlen = strlen(curr_path) + strlen(namelist[sel+1]->d_name) + 2; + char *p, *newdir = malloc(newlen); + if (strcmp(namelist[sel+1]->d_name, "..") == 0) { + char *start = curr_path; + p = start + strlen(start) - 1; + while (*p == '/' && p > start) p--; + while (*p != '/' && *p != ':' && p > start) p--; + if (p <= start || *p == ':') strcpy(newdir, "ms0:/"); + else { strncpy(newdir, start, p-start); newdir[p-start] = 0; } + } else { + strcpy(newdir, curr_path); + p = newdir + strlen(newdir) - 1; + while (*p == '/' && p >= newdir) *p-- = 0; + strcat(newdir, "/"); + strcat(newdir, namelist[sel+1]->d_name); + } + ret = romsel_loop(newdir); + free(newdir); + break; + } + else if (namelist[sel+1]->d_type & FIO_S_IFREG) + { + strcpy(romFileName, curr_path); + strcat(romFileName, "/"); + strcat(romFileName, namelist[sel+1]->d_name); + ret = romFileName; + break; + } + } + if(inp & PBTN_X) break; // cancel + } + + if (n > 0) { + while(n--) free(namelist[n]); + free(namelist); + } + + return ret; +} + +// ------------ patch/gg menu ------------ + +static void draw_patchlist(int sel) +{ + int start, i, pos, active; + + start = 13 - sel; + + menu_draw_begin(); + + for (i = 0; i < PicoPatchCount; i++) { + pos = start + i; + if (pos < 0) continue; + if (pos > 26) break; + active = PicoPatches[i].active; + smalltext_out16_lim(14, pos*10, active ? "ON " : "OFF", active ? 0xfff6 : 0xffff, 3); + smalltext_out16_lim(14+6*4, pos*10, PicoPatches[i].name, active ? 0xfff6 : 0xffff, 53-6); + } + pos = start + i; + if (pos < 27) smalltext_out16_lim(14, pos*10, "done", 0xffff, 4); + + text_out16(5, 130, ">"); + menu_draw_end(); +} + + +static void patches_menu_loop(void) +{ + int menu_sel = 0; + unsigned long inp = 0; + + for(;;) + { + draw_patchlist(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_L|PBTN_R|PBTN_X|PBTN_CIRCLE, 0); + if(inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = PicoPatchCount; } + if(inp & PBTN_DOWN) { menu_sel++; if (menu_sel > PicoPatchCount) menu_sel = 0; } + if(inp &(PBTN_LEFT|PBTN_L)) { menu_sel-=10; if (menu_sel < 0) menu_sel = 0; } + if(inp &(PBTN_RIGHT|PBTN_R)) { menu_sel+=10; if (menu_sel > PicoPatchCount) menu_sel = PicoPatchCount; } + if(inp & PBTN_CIRCLE) { // action + if (menu_sel < PicoPatchCount) + PicoPatches[menu_sel].active = !PicoPatches[menu_sel].active; + else return; + } + if(inp & PBTN_X) return; + } + +} + +// ------------ savestate loader ------------ + +static int state_slot_flags = 0; + +static void state_check_slots(void) +{ + int slot; + + state_slot_flags = 0; + + for (slot = 0; slot < 10; slot++) + { + if (emu_checkSaveFile(slot)) + { + state_slot_flags |= 1 << slot; + } + } +} + +static void *get_oldstate_for_preview(void) +{ + unsigned char *ptr = malloc(sizeof(Pico.vram) + sizeof(Pico.cram) + sizeof(Pico.vsram) + sizeof(Pico.video)); + if (ptr == NULL) return NULL; + + memcpy(ptr, Pico.vram, sizeof(Pico.vram)); + memcpy(ptr + sizeof(Pico.vram), Pico.cram, sizeof(Pico.cram)); + memcpy(ptr + sizeof(Pico.vram) + sizeof(Pico.cram), Pico.vsram, sizeof(Pico.vsram)); + memcpy(ptr + sizeof(Pico.vram) + sizeof(Pico.cram) + sizeof(Pico.vsram), &Pico.video, sizeof(Pico.video)); + return ptr; +} + +static void restore_oldstate(void *ptrx) +{ + unsigned char *ptr = ptrx; + memcpy(Pico.vram, ptr, sizeof(Pico.vram)); + memcpy(Pico.cram, ptr + sizeof(Pico.vram), sizeof(Pico.cram)); + memcpy(Pico.vsram, ptr + sizeof(Pico.vram) + sizeof(Pico.cram), sizeof(Pico.vsram)); + memcpy(&Pico.video,ptr + sizeof(Pico.vram) + sizeof(Pico.cram) + sizeof(Pico.vsram), sizeof(Pico.video)); + free(ptrx); +} + +static void draw_savestate_bg(int slot) +{ + void *file, *oldstate; + char *fname; + + fname = emu_GetSaveFName(1, 0, slot); + if (!fname) return; + + oldstate = get_oldstate_for_preview(); + if (oldstate == NULL) return; + + if (strcmp(fname + strlen(fname) - 3, ".gz") == 0) { + file = gzopen(fname, "rb"); + emu_setSaveStateCbs(1); + } else { + file = fopen(fname, "rb"); + emu_setSaveStateCbs(0); + } + + if (file) { + if (PicoIn.AHW & PAHW_MCD) { + PicoCdLoadStateGfx(file); + } else { + areaSeek(file, 0x10020, SEEK_SET); // skip header and RAM in state file + areaRead(Pico.vram, 1, sizeof(Pico.vram), file); + areaSeek(file, 0x2000, SEEK_CUR); + areaRead(Pico.cram, 1, sizeof(Pico.cram), file); + areaRead(Pico.vsram, 1, sizeof(Pico.vsram), file); + areaSeek(file, 0x221a0, SEEK_SET); + areaRead(&Pico.video, 1, sizeof(Pico.video), file); + } + areaClose(file); + } + + emu_forcedFrame(0); + menu_prepare_bg(1, 0); + + restore_oldstate(oldstate); +} + +static void draw_savestate_menu(int menu_sel, int is_loading) +{ + int tl_x = 80+25, tl_y = 16+60, y, i; + + if (state_slot_flags & (1 << menu_sel)) + draw_savestate_bg(menu_sel); + menu_draw_begin(); + + text_out16(tl_x, 16+30, is_loading ? "Load state" : "Save state"); + + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 108); + + /* draw all 10 slots */ + y = tl_y; + for (i = 0; i < 10; i++, y+=10) + { + text_out16(tl_x, y, "SLOT %i (%s)", i, (state_slot_flags & (1 << i)) ? "USED" : "free"); + } + text_out16(tl_x, y, "back"); + + menu_draw_end(); +} + +static int savestate_menu_loop(int is_loading) +{ + static int menu_sel = 10; + int menu_sel_max = 10; + unsigned long inp = 0; + + state_check_slots(); + + for(;;) + { + draw_savestate_menu(menu_sel, is_loading); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_X|PBTN_CIRCLE, 0); + if(inp & PBTN_UP ) { + do { + menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; + } while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading); + } + if(inp & PBTN_DOWN) { + do { + menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; + } while (!(state_slot_flags & (1 << menu_sel)) && menu_sel != menu_sel_max && is_loading); + } + if(inp & PBTN_CIRCLE) { // save/load + if (menu_sel < 10) { + state_slot = menu_sel; + PicoStateProgressCB = emu_msg_cb; /* also suitable for menu */ + if (emu_SaveLoadGame(is_loading, 0)) { + strcpy(menuErrorMsg, is_loading ? "Load failed" : "Save failed"); + return 1; + } + return 0; + } else return 1; + } + if(inp & PBTN_X) return 1; + } +} + +// -------------- key config -------------- + +static char *action_binds(int player_idx, int action_mask) +{ + static char strkeys[32*5]; + int i; + + strkeys[0] = 0; + for (i = 0; i < 32; i++) // i is key index + { + if (currentConfig.KeyBinds[i] & action_mask) + { + if (player_idx >= 0 && ((currentConfig.KeyBinds[i] >> 16) & 3) != player_idx) continue; + if (strkeys[0]) { + strcat(strkeys, i >= 28 ? ", " : " + "); // nub "buttons" don't create combos + strcat(strkeys, keyNames[i]); + break; + } + else strcpy(strkeys, keyNames[i]); + } + } + + return strkeys; +} + +static void unbind_action(int action) +{ + int i; + + for (i = 0; i < 32; i++) + currentConfig.KeyBinds[i] &= ~action; +} + +static int count_bound_keys(int action, int pl_idx) +{ + int i, keys = 0; + + for (i = 0; i < 32; i++) + { + if (pl_idx >= 0 && (currentConfig.KeyBinds[i]&0x30000) != (pl_idx<<16)) continue; + if (currentConfig.KeyBinds[i] & action) keys++; + } + + return keys; +} + +static void draw_key_config(const me_bind_action *opts, int opt_cnt, int player_idx, int sel) +{ + int x, y, tl_y = 16+20, i; + + menu_draw_begin(); + if (player_idx >= 0) { + text_out16(80+80, 16, "Player %i controls", player_idx + 1); + x = 80+80; + } else { + text_out16(80+80, 16, "Emulator controls"); + x = 80+40; + } + + menu_draw_selection(x - 16, tl_y + sel*10, (player_idx >= 0) ? 66 : 130); + + y = tl_y; + for (i = 0; i < opt_cnt; i++, y+=10) + text_out16(x, y, "%s : %s", opts[i].name, action_binds(player_idx, opts[i].mask)); + + text_out16(x, y, "Done"); + + if (sel < opt_cnt) { + text_out16(80+30, 220, "Press a button to bind/unbind"); + text_out16(80+30, 230, "Use SELECT to clear"); + text_out16(80+30, 240, "To bind UP/DOWN, hold SELECT"); + text_out16(80+30, 250, "Select \"Done\" to exit"); + } else { + text_out16(80+30, 230, "Use Options -> Save cfg"); + text_out16(80+30, 240, "to save controls"); + text_out16(80+30, 250, "Press X or O to exit"); + } + menu_draw_end(); +} + +static void key_config_loop(const me_bind_action *opts, int opt_cnt, int player_idx) +{ + int sel = 0, menu_sel_max = opt_cnt, prev_select = 0, i; + unsigned long inp = 0; + + for (;;) + { + draw_key_config(opts, opt_cnt, player_idx, sel); + inp = in_menu_wait(CONFIGURABLE_KEYS|PBTN_SELECT, 1); + if (!(inp & PBTN_SELECT)) { + prev_select = 0; + if(inp & PBTN_UP ) { sel--; if (sel < 0) sel = menu_sel_max; continue; } + if(inp & PBTN_DOWN) { sel++; if (sel > menu_sel_max) sel = 0; continue; } + } + if (sel >= opt_cnt) { + if (inp & (PBTN_X|PBTN_CIRCLE)) break; + else continue; + } + // if we are here, we want to bind/unbind something + if ((inp & PBTN_SELECT) && !prev_select) + unbind_action(opts[sel].mask); + prev_select = inp & PBTN_SELECT; + inp &= CONFIGURABLE_KEYS; + inp &= ~PBTN_SELECT; + for (i = 0; i < 32; i++) + if (inp & (1 << i)) { + if (count_bound_keys(opts[sel].mask, player_idx) >= 2) + currentConfig.KeyBinds[i] &= ~opts[sel].mask; // allow to unbind only + else currentConfig.KeyBinds[i] ^= opts[sel].mask; + if (player_idx >= 0 && (currentConfig.KeyBinds[i] & opts[sel].mask)) { + currentConfig.KeyBinds[i] &= ~(3 << 16); + currentConfig.KeyBinds[i] |= player_idx << 16; + } + } + } +} + +menu_entry ctrlopt_entries[] = +{ + { "Player 1", MB_NONE, MA_CTRL_PLAYER1, NULL, 0, 0, 0, 1, 0 }, + { "Player 2", MB_NONE, MA_CTRL_PLAYER2, NULL, 0, 0, 0, 1, 0 }, + { "Emulator controls", MB_NONE, MA_CTRL_EMU, NULL, 0, 0, 0, 1, 0 }, + { "6 button pad", MB_ONOFF, MA_OPT_6BUTTON_PAD, &PicoIn.opt, 0x020, 0, 0, 1, 1 }, + { "Turbo rate", MB_RANGE, MA_CTRL_TURBO_RATE, ¤tConfig.turbo_rate, 0, 1, 30, 1, 1 }, + { "Done", MB_NONE, MA_CTRL_DONE, NULL, 0, 0, 0, 1, 0 }, +}; + +#define CTRLOPT_ENTRY_COUNT (sizeof(ctrlopt_entries) / sizeof(ctrlopt_entries[0])) +const int ctrlopt_entry_count = CTRLOPT_ENTRY_COUNT; + +static void draw_kc_sel(int menu_sel) +{ + int tl_x = 80+25+40, tl_y = 16+60, y; + + y = tl_y; + menu_draw_begin(); + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 138); + + me_draw(ctrlopt_entries, ctrlopt_entry_count, tl_x, tl_y, NULL, NULL); + + menu_draw_end(); +} + + +// player2_flag, ?, ?, ?, ?, ?, ?, menu +// "NEXT SAVE SLOT", "PREV SAVE SLOT", "SWITCH RENDERER", "SAVE STATE", +// "LOAD STATE", "VOLUME UP", "VOLUME DOWN", "DONE" +me_bind_action emuctrl_actions[] = +{ + { "Load State ", 1<<28 }, + { "Save State ", 1<<27 }, + { "Prev Save Slot ", 1<<25 }, + { "Next Save Slot ", 1<<24 }, + { "Switch Renderer ", 1<<26 }, + { "Fast forward ", 1<<22 }, + { "Pico Next page ", 1<<21 }, + { "Pico Prev page ", 1<<20 }, + { "Pico Switch input", 1<<19 }, + { NULL, 0 } +}; + +static void kc_sel_loop(void) +{ + int menu_sel = 5, menu_sel_max = 5; + unsigned long inp = 0; + menu_id selected_id; + + while (1) + { + draw_kc_sel(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_X|PBTN_CIRCLE, 0); + selected_id = me_index2id(ctrlopt_entries, CTRLOPT_ENTRY_COUNT, menu_sel); + if (inp & (PBTN_LEFT|PBTN_RIGHT)) // multi choise + me_process(ctrlopt_entries, CTRLOPT_ENTRY_COUNT, selected_id, (inp&PBTN_RIGHT) ? 1 : 0); + if (inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + if (inp & PBTN_CIRCLE) { + int is_6button = PicoIn.opt & POPT_6BTN_PAD; + switch (selected_id) { + case MA_CTRL_PLAYER1: key_config_loop(me_ctrl_actions, is_6button ? 15 : 11, 0); return; + case MA_CTRL_PLAYER2: key_config_loop(me_ctrl_actions, is_6button ? 15 : 11, 1); return; + case MA_CTRL_EMU: key_config_loop(emuctrl_actions, + sizeof(emuctrl_actions)/sizeof(emuctrl_actions[0]) - 1, -1); return; + case MA_CTRL_DONE: if (!rom_loaded) emu_WriteConfig(0); return; + default: return; + } + } + if (inp & PBTN_X) return; + } +} + + +// --------- sega/mega cd options ---------- + +menu_entry cdopt_entries[] = +{ + { NULL, MB_NONE, MA_CDOPT_TESTBIOS_USA, NULL, 0, 0, 0, 1, 0 }, + { NULL, MB_NONE, MA_CDOPT_TESTBIOS_EUR, NULL, 0, 0, 0, 1, 0 }, + { NULL, MB_NONE, MA_CDOPT_TESTBIOS_JAP, NULL, 0, 0, 0, 1, 0 }, + { "CD LEDs", MB_ONOFF, MA_CDOPT_LEDS, ¤tConfig.EmuOpt, 0x0400, 0, 0, 1, 1 }, + { "CDDA audio", MB_ONOFF, MA_CDOPT_CDDA, &PicoIn.opt, 0x0800, 0, 0, 1, 1 }, + { "PCM audio", MB_ONOFF, MA_CDOPT_PCM, &PicoIn.opt, 0x0400, 0, 0, 1, 1 }, + { NULL, MB_NONE, MA_CDOPT_READAHEAD, NULL, 0, 0, 0, 1, 1 }, + { "SaveRAM cart", MB_ONOFF, MA_CDOPT_SAVERAM, &PicoIn.opt, 0x8000, 0, 0, 1, 1 }, + { "Scale/Rot. fx (slow)", MB_ONOFF, MA_CDOPT_SCALEROT_CHIP,&PicoIn.opt, 0x1000, 0, 0, 1, 1 }, + { "Better sync (slow)", MB_ONOFF, MA_CDOPT_BETTER_SYNC, &PicoIn.opt, 0x2000, 0, 0, 1, 1 }, + { "done", MB_NONE, MA_CDOPT_DONE, NULL, 0, 0, 0, 1, 0 }, +}; + +#define CDOPT_ENTRY_COUNT (sizeof(cdopt_entries) / sizeof(cdopt_entries[0])) +const int cdopt_entry_count = CDOPT_ENTRY_COUNT; + + +struct bios_names_t +{ + char us[32], eu[32], jp[32]; +}; + +static void menu_cdopt_cust_draw(const menu_entry *entry, int x, int y, void *param) +{ + struct bios_names_t *bios_names = param; + char ra_buff[16]; + + switch (entry->id) + { + case MA_CDOPT_TESTBIOS_USA: text_out16(x, y, "USA BIOS: %s", bios_names->us); break; + case MA_CDOPT_TESTBIOS_EUR: text_out16(x, y, "EUR BIOS: %s", bios_names->eu); break; + case MA_CDOPT_TESTBIOS_JAP: text_out16(x, y, "JAP BIOS: %s", bios_names->jp); break; + case MA_CDOPT_READAHEAD: + if (PicoCDBuffers > 1) sprintf(ra_buff, "%5iK", PicoCDBuffers * 2); + else strcpy(ra_buff, " OFF"); + text_out16(x, y, "ReadAhead buffer %s", ra_buff); + break; + default:break; + } +} + +static void draw_cd_menu_options(int menu_sel, struct bios_names_t *bios_names) +{ + int tl_x = 80+25, tl_y = 16+60; + menu_id selected_id; + char ra_buff[16]; + + if (PicoCDBuffers > 1) sprintf(ra_buff, "%5iK", PicoCDBuffers * 2); + else strcpy(ra_buff, " OFF"); + + menu_draw_begin(); + + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 246); + + me_draw(cdopt_entries, CDOPT_ENTRY_COUNT, tl_x, tl_y, menu_cdopt_cust_draw, bios_names); + + selected_id = me_index2id(cdopt_entries, CDOPT_ENTRY_COUNT, menu_sel); + if ((selected_id == MA_CDOPT_TESTBIOS_USA && strcmp(bios_names->us, "NOT FOUND")) || + (selected_id == MA_CDOPT_TESTBIOS_EUR && strcmp(bios_names->eu, "NOT FOUND")) || + (selected_id == MA_CDOPT_TESTBIOS_JAP && strcmp(bios_names->jp, "NOT FOUND"))) + text_out16(tl_x, 250, "Press start to test selected BIOS"); + + menu_draw_end(); +} + +static void cd_menu_loop_options(void) +{ + static int menu_sel = 0; + int menu_sel_max = 10; + unsigned long inp = 0; + struct bios_names_t bios_names; + menu_id selected_id; + char *bios, *p; + + if (emu_findBios(4, &bios)) { // US + for (p = bios+strlen(bios)-1; p > bios && *p != '/'; p--); + if (*p == '/') p++; + strncpy(bios_names.us, p, sizeof(bios_names.us)); bios_names.us[sizeof(bios_names.us)-1] = 0; + } else strcpy(bios_names.us, "NOT FOUND"); + + if (emu_findBios(8, &bios)) { // EU + for (p = bios+strlen(bios)-1; p > bios && *p != '/'; p--); + if (*p == '/') p++; + strncpy(bios_names.eu, p, sizeof(bios_names.eu)); bios_names.eu[sizeof(bios_names.eu)-1] = 0; + } else strcpy(bios_names.eu, "NOT FOUND"); + + if (emu_findBios(1, &bios)) { // JP + for (p = bios+strlen(bios)-1; p > bios && *p != '/'; p--); + if (*p == '/') p++; + strncpy(bios_names.jp, p, sizeof(bios_names.jp)); bios_names.jp[sizeof(bios_names.jp)-1] = 0; + } else strcpy(bios_names.jp, "NOT FOUND"); + + menuErrorMsg[0] = 0; + + for (;;) + { + draw_cd_menu_options(menu_sel, &bios_names); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_X|PBTN_CIRCLE|PBTN_START, 0); + if (inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + selected_id = me_index2id(cdopt_entries, CDOPT_ENTRY_COUNT, menu_sel); + if (inp & (PBTN_LEFT|PBTN_RIGHT)) { // multi choise + if (!me_process(cdopt_entries, CDOPT_ENTRY_COUNT, selected_id, (inp&PBTN_RIGHT) ? 1 : 0) && + selected_id == MA_CDOPT_READAHEAD) { + if (inp & PBTN_LEFT) { + PicoCDBuffers >>= 1; + if (PicoCDBuffers < 2) PicoCDBuffers = 0; + } else { + if (PicoCDBuffers < 2) PicoCDBuffers = 2; + else PicoCDBuffers <<= 1; + if (PicoCDBuffers > 8*1024) PicoCDBuffers = 8*1024; // 16M + } + } + } + if (inp & PBTN_CIRCLE) // toggleable options + if (!me_process(cdopt_entries, CDOPT_ENTRY_COUNT, selected_id, 1) && + selected_id == MA_CDOPT_DONE) { + return; + } + if (inp & PBTN_START) { + switch (selected_id) { // BIOS testers + case MA_CDOPT_TESTBIOS_USA: + if (emu_findBios(4, &bios)) { // test US + strcpy(romFileName, bios); + engineState = PGS_ReloadRom; + return; + } + break; + case MA_CDOPT_TESTBIOS_EUR: + if (emu_findBios(8, &bios)) { // test EU + strcpy(romFileName, bios); + engineState = PGS_ReloadRom; + return; + } + break; + case MA_CDOPT_TESTBIOS_JAP: + if (emu_findBios(1, &bios)) { // test JP + strcpy(romFileName, bios); + engineState = PGS_ReloadRom; + return; + } + break; + default: + break; + } + } + if (inp & PBTN_X) return; + } +} + +// --------- display options ---------- + +menu_entry opt3_entries[] = +{ + { NULL, MB_NONE, MA_OPT3_SCALE, NULL, 0, 0, 0, 1, 1 }, + { NULL, MB_NONE, MA_OPT3_HSCALE32, NULL, 0, 0, 0, 1, 1 }, + { NULL, MB_NONE, MA_OPT3_HSCALE40, NULL, 0, 0, 0, 1, 1 }, + { NULL, MB_ONOFF, MA_OPT3_FILTERING, ¤tConfig.scaling, 1, 0, 0, 1, 1 }, + { NULL, MB_RANGE, MA_OPT3_GAMMAA, ¤tConfig.gamma, 0, -4, 16, 1, 1 }, + { NULL, MB_RANGE, MA_OPT3_BLACKLVL, ¤tConfig.gamma2, 0, 0, 2, 1, 1 }, + { NULL, MB_NONE, MA_OPT3_VSYNC, NULL, 0, 0, 0, 1, 1 }, + { "Set to unscaled centered", MB_NONE, MA_OPT3_PRES_NOSCALE, NULL, 0, 0, 0, 1, 0 }, + { "Set to 4:3 scaled", MB_NONE, MA_OPT3_PRES_SCALE43, NULL, 0, 0, 0, 1, 0 }, + { "Set to fullscreen", MB_NONE, MA_OPT3_PRES_FULLSCR, NULL, 0, 0, 0, 1, 0 }, + { "done", MB_NONE, MA_OPT3_DONE, NULL, 0, 0, 0, 1, 0 }, +}; + +#define OPT3_ENTRY_COUNT (sizeof(opt3_entries) / sizeof(opt3_entries[0])) +const int opt3_entry_count = OPT3_ENTRY_COUNT; + + +static void menu_opt3_cust_draw(const menu_entry *entry, int x, int y, void *param) +{ + switch (entry->id) + { + case MA_OPT3_SCALE: + text_out16(x, y, "Scale factor: %.2f", currentConfig.scale); + break; + case MA_OPT3_HSCALE32: + text_out16(x, y, "Hor. scale (for low res. games): %.2f", currentConfig.hscale32); + break; + case MA_OPT3_HSCALE40: + text_out16(x, y, "Hor. scale (for hi res. games): %.2f", currentConfig.hscale40); + break; + case MA_OPT3_FILTERING: + text_out16(x, y, "Bilinear filtering %s", currentConfig.scaling?"ON":"OFF"); + break; + case MA_OPT3_GAMMAA: + text_out16(x, y, "Gamma adjustment %2i", currentConfig.gamma); + break; + case MA_OPT3_BLACKLVL: + text_out16(x, y, "Black level %2i", currentConfig.gamma2); + break; + case MA_OPT3_VSYNC: { + char *val = " never"; + if (currentConfig.EmuOpt & 0x2000) + val = (currentConfig.EmuOpt & 0x10000) ? "sometimes" : " always"; + text_out16(x, y, "Wait for vsync (slow) %s", val); + break; + } + default: break; + } +} + +static void menu_opt3_preview(int is_32col) +{ + void *oldstate = NULL; + + if (!rom_loaded || ((Pico.video.reg[12]&1)^1) != is_32col) + { + extern char bgdatac32_start[], bgdatac40_start[]; + extern int bgdatac32_size, bgdatac40_size; + void *bgdata = is_32col ? bgdatac32_start : bgdatac40_start; + unsigned long insize = is_32col ? bgdatac32_size : bgdatac40_size, outsize = 65856; + int ret; + ret = uncompress((Bytef *)bg_buffer, &outsize, bgdata, insize); + if (ret == 0) + { + if (rom_loaded) oldstate = get_oldstate_for_preview(); + memcpy(Pico.vram, bg_buffer, sizeof(Pico.vram)); + memcpy(Pico.cram, (char *)bg_buffer + 0x10000, 0x40*2); + memcpy(Pico.vsram, (char *)bg_buffer + 0x10080, 0x40*2); + memcpy(&Pico.video,(char *)bg_buffer + 0x10100, 0x40); + } + else + lprintf("uncompress returned %i\n", ret); + } + + memset32_uncached(psp_screen, 0, 512*272*2/4); + emu_forcedFrame(0); + menu_prepare_bg(1, 0); + + if (oldstate) restore_oldstate(oldstate); +} + +static void draw_dispmenu_options(int menu_sel) +{ + int tl_x = 80, tl_y = 16+50; + + menu_draw_begin(); + + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 316); + + me_draw(opt3_entries, OPT3_ENTRY_COUNT, tl_x, tl_y, menu_opt3_cust_draw, NULL); + + menu_draw_end(); +} + +static void dispmenu_loop_options(void) +{ + static int menu_sel = 0; + int menu_sel_max, is_32col = (Pico.video.reg[12]&1)^1; + unsigned long inp = 0; + menu_id selected_id; + + menu_sel_max = me_count_enabled(opt3_entries, OPT3_ENTRY_COUNT) - 1; + + for (;;) + { + draw_dispmenu_options(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_X|PBTN_CIRCLE, 0); + if (inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + selected_id = me_index2id(opt3_entries, OPT3_ENTRY_COUNT, menu_sel); + if (selected_id == MA_OPT3_HSCALE40 && is_32col) { is_32col = 0; menu_opt3_preview(is_32col); } + if (selected_id == MA_OPT3_HSCALE32 && !is_32col) { is_32col = 1; menu_opt3_preview(is_32col); } + + if (inp & (PBTN_LEFT|PBTN_RIGHT)) // multi choise + { + float *setting = NULL; + int tmp; + me_process(opt3_entries, OPT3_ENTRY_COUNT, selected_id, (inp&PBTN_RIGHT) ? 1 : 0); + switch (selected_id) { + case MA_OPT3_SCALE: setting = ¤tConfig.scale; break; + case MA_OPT3_HSCALE40: setting = ¤tConfig.hscale40; is_32col = 0; break; + case MA_OPT3_HSCALE32: setting = ¤tConfig.hscale32; is_32col = 1; break; + case MA_OPT3_FILTERING: + case MA_OPT3_GAMMAA: + case MA_OPT3_BLACKLVL: menu_opt3_preview(is_32col); break; + case MA_OPT3_VSYNC: + tmp = ((currentConfig.EmuOpt>>13)&1) | ((currentConfig.EmuOpt>>15)&2); + tmp = (inp & PBTN_LEFT) ? (tmp>>1) : ((tmp<<1)|1); + if (tmp > 3) tmp = 3; + currentConfig.EmuOpt &= ~0x12000; + currentConfig.EmuOpt |= ((tmp&2)<<15) | ((tmp&1)<<13); + break; + default: break; + } + if (setting != NULL) { + while ((inp = psp_pad_read(0)) & (PBTN_LEFT|PBTN_RIGHT)) { + *setting += (inp & PBTN_LEFT) ? -0.01 : 0.01; + if (*setting <= 0) *setting = 0.01; + menu_opt3_preview(is_32col); + draw_dispmenu_options(menu_sel); // will wait vsync + } + } + } + if (inp & PBTN_CIRCLE) { // toggleable options + me_process(opt3_entries, OPT3_ENTRY_COUNT, selected_id, 1); + switch (selected_id) { + case MA_OPT3_DONE: + return; + case MA_OPT3_PRES_NOSCALE: + currentConfig.scale = currentConfig.hscale40 = currentConfig.hscale32 = 1.0; + menu_opt3_preview(is_32col); + break; + case MA_OPT3_PRES_SCALE43: + currentConfig.scale = 1.20; + currentConfig.hscale40 = 1.00; + currentConfig.hscale32 = 1.25; + menu_opt3_preview(is_32col); + break; + case MA_OPT3_PRES_FULLSCR: + currentConfig.scale = 1.20; + currentConfig.hscale40 = 1.25; + currentConfig.hscale32 = 1.56; + menu_opt3_preview(is_32col); + break; + case MA_OPT3_FILTERING: + menu_opt3_preview(is_32col); + break; + default: break; + } + } + if (inp & PBTN_X) return; + } +} + + +// --------- advanced options ---------- + +menu_entry opt2_entries[] = +{ + { "Disable sprite limit", MB_ONOFF, MA_OPT2_NO_SPRITE_LIM, &PicoIn.opt, 0x40000, 0, 0, 1, 1 }, + { "Emulate Z80", MB_ONOFF, MA_OPT2_ENABLE_Z80, &PicoIn.opt, 0x00004, 0, 0, 1, 1 }, + { "Emulate YM2612 (FM)", MB_ONOFF, MA_OPT2_ENABLE_YM2612, &PicoIn.opt, 0x00001, 0, 0, 1, 1 }, + { "Emulate SN76496 (PSG)", MB_ONOFF, MA_OPT2_ENABLE_SN76496, &PicoIn.opt, 0x00002, 0, 0, 1, 1 }, + { "gzip savestates", MB_ONOFF, MA_OPT2_GZIP_STATES, ¤tConfig.EmuOpt, 0x00008, 0, 0, 1, 1 }, + { "Don't save last used ROM", MB_ONOFF, MA_OPT2_NO_LAST_ROM, ¤tConfig.EmuOpt, 0x00020, 0, 0, 1, 1 }, + { "Status line in main menu", MB_ONOFF, MA_OPT2_STATUS_LINE, ¤tConfig.EmuOpt, 0x20000, 0, 0, 1, 1 }, + { "Disable idle loop patching",MB_ONOFF, MA_OPT2_NO_IDLE_LOOPS, &PicoIn.opt, 0x80000, 0, 0, 1, 1 }, + { "Disable frame limiter", MB_ONOFF, MA_OPT2_NO_FRAME_LIMIT, ¤tConfig.EmuOpt, 0x40000, 0, 0, 1, 1 }, + { "done", MB_NONE, MA_OPT2_DONE, NULL, 0, 0, 0, 1, 0 }, +}; + +#define OPT2_ENTRY_COUNT (sizeof(opt2_entries) / sizeof(opt2_entries[0])) +const int opt2_entry_count = OPT2_ENTRY_COUNT; + + +static void draw_amenu_options(int menu_sel) +{ + int tl_x = 80+25, tl_y = 16+50; + + menu_draw_begin(); + + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 252); + + me_draw(opt2_entries, OPT2_ENTRY_COUNT, tl_x, tl_y, NULL, NULL); + + menu_draw_end(); +} + +static void amenu_loop_options(void) +{ + static int menu_sel = 0; + int menu_sel_max; + unsigned long inp = 0; + menu_id selected_id; + + menu_sel_max = me_count_enabled(opt2_entries, OPT2_ENTRY_COUNT) - 1; + + for(;;) + { + draw_amenu_options(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_X|PBTN_CIRCLE, 0); + if (inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + selected_id = me_index2id(opt2_entries, OPT2_ENTRY_COUNT, menu_sel); + if (inp & (PBTN_LEFT|PBTN_RIGHT)) { // multi choise + if (!me_process(opt2_entries, OPT2_ENTRY_COUNT, selected_id, (inp&PBTN_RIGHT) ? 1 : 0) && + selected_id == MA_OPT2_GAMMA) { + // TODO? + } + } + if (inp & PBTN_CIRCLE) { // toggleable options + if (!me_process(opt2_entries, OPT2_ENTRY_COUNT, selected_id, 1) && + selected_id == MA_OPT2_DONE) { + return; + } + } + if (inp & PBTN_X) return; + } +} + +// -------------- options -------------- + + +menu_entry opt_entries[] = +{ + { NULL, MB_NONE, MA_OPT_RENDERER, NULL, 0, 0, 0, 1, 1 }, + { "Accurate sprites", MB_ONOFF, MA_OPT_ACC_SPRITES, &PicoIn.opt, 0x080, 0, 0, 0, 1 }, + { "Show FPS", MB_ONOFF, MA_OPT_SHOW_FPS, ¤tConfig.EmuOpt, 0x0002, 0, 0, 1, 1 }, + { NULL, MB_RANGE, MA_OPT_FRAMESKIP, ¤tConfig.Frameskip, 0, -1, 16, 1, 1 }, + { "Enable sound", MB_ONOFF, MA_OPT_ENABLE_SOUND, ¤tConfig.EmuOpt, 0x0004, 0, 0, 1, 1 }, + { NULL, MB_NONE, MA_OPT_SOUND_QUALITY, NULL, 0, 0, 0, 1, 1 }, + { NULL, MB_NONE, MA_OPT_REGION, NULL, 0, 0, 0, 1, 1 }, + { "Use SRAM/BRAM savestates", MB_ONOFF, MA_OPT_SRAM_STATES, ¤tConfig.EmuOpt, 0x0001, 0, 0, 1, 1 }, + { NULL, MB_NONE, MA_OPT_CONFIRM_STATES,NULL, 0, 0, 0, 1, 1 }, + { "Save slot", MB_RANGE, MA_OPT_SAVE_SLOT, &state_slot, 0, 0, 9, 1, 1 }, + { NULL, MB_NONE, MA_OPT_CPU_CLOCKS, NULL, 0, 0, 0, 1, 1 }, + { "[Display options]", MB_NONE, MA_OPT_DISP_OPTS, NULL, 0, 0, 0, 1, 0 }, + { "[Sega/Mega CD options]", MB_NONE, MA_OPT_SCD_OPTS, NULL, 0, 0, 0, 1, 0 }, + { "[Advanced options]", MB_NONE, MA_OPT_ADV_OPTS, NULL, 0, 0, 0, 1, 0 }, + { NULL, MB_NONE, MA_OPT_SAVECFG, NULL, 0, 0, 0, 1, 0 }, + { "Save cfg for current game only",MB_NONE,MA_OPT_SAVECFG_GAME,NULL, 0, 0, 0, 1, 0 }, + { NULL, MB_NONE, MA_OPT_LOADCFG, NULL, 0, 0, 0, 1, 0 }, +}; + +#define OPT_ENTRY_COUNT (sizeof(opt_entries) / sizeof(opt_entries[0])) +const int opt_entry_count = OPT_ENTRY_COUNT; + + +static void menu_opt_cust_draw(const menu_entry *entry, int x, int y, void *param) +{ + char *str, str24[24]; + + switch (entry->id) + { + case MA_OPT_RENDERER: + if (PicoIn.opt & 0x10) + str = "fast"; + else if (currentConfig.EmuOpt & 0x80) + str = "accurate"; + else + str = " 8bit accurate"; // n/a + text_out16(x, y, "Renderer: %s", str); + break; + case MA_OPT_FRAMESKIP: + if (currentConfig.Frameskip < 0) + strcpy(str24, "Auto"); + else sprintf(str24, "%i", currentConfig.Frameskip); + text_out16(x, y, "Frameskip %s", str24); + break; + case MA_OPT_SOUND_QUALITY: + str = (PicoIn.opt&0x08)?"stereo":"mono"; + text_out16(x, y, "Sound Quality: %5iHz %s", PicoIn.sndRate, str); + break; + case MA_OPT_REGION: + text_out16(x, y, "Region: %s", me_region_name(PicoIn.regionOverride, PicoIn.autoRgnOrder)); + break; + case MA_OPT_CONFIRM_STATES: + switch ((currentConfig.EmuOpt >> 9) & 5) { + default: str = "OFF"; break; + case 1: str = "writes"; break; + case 4: str = "loads"; break; + case 5: str = "both"; break; + } + text_out16(x, y, "Confirm savestate %s", str); + break; + case MA_OPT_CPU_CLOCKS: + text_out16(x, y, "CPU/bus clock %3i/%3iMHz", currentConfig.CPUclock, currentConfig.CPUclock/2); + break; + case MA_OPT_SAVECFG: + str24[0] = 0; + if (config_slot != 0) sprintf(str24, " (profile: %i)", config_slot); + text_out16(x, y, "Save cfg as default%s", str24); + break; + case MA_OPT_LOADCFG: + text_out16(x, y, "Load cfg from profile %i", config_slot); + break; + default: + lprintf("%s: unimplemented (%i)\n", __FUNCTION__, entry->id); + break; + } +} + + +static void draw_menu_options(int menu_sel) +{ + int tl_x = 80+25, tl_y = 16+24; + + menu_draw_begin(); + + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 284); + + me_draw(opt_entries, OPT_ENTRY_COUNT, tl_x, tl_y, menu_opt_cust_draw, NULL); + + menu_draw_end(); +} + +static int sndrate_prevnext(int rate, int dir) +{ + int i, rates[] = { 11025, 22050, 44100 }; + + for (i = 0; i < 5; i++) + if (rates[i] == rate) break; + + i += dir ? 1 : -1; + if (i > 2) return dir ? 44100 : 22050; + if (i < 0) return dir ? 22050 : 11025; + return rates[i]; +} + +static void region_prevnext(int right) +{ + // jp_ntsc=1, jp_pal=2, usa=4, eu=8 + static int rgn_orders[] = { 0x148, 0x184, 0x814, 0x418, 0x841, 0x481 }; + int i; + if (right) { + if (!PicoIn.regionOverride) { + for (i = 0; i < 6; i++) + if (rgn_orders[i] == PicoIn.autoRgnOrder) break; + if (i < 5) PicoIn.autoRgnOrder = rgn_orders[i+1]; + else PicoIn.regionOverride=1; + } + else PicoIn.regionOverride<<=1; + if (PicoIn.regionOverride > 8) PicoIn.regionOverride = 8; + } else { + if (!PicoIn.regionOverride) { + for (i = 0; i < 6; i++) + if (rgn_orders[i] == PicoIn.autoRgnOrder) break; + if (i > 0) PicoIn.autoRgnOrder = rgn_orders[i-1]; + } + else PicoIn.regionOverride>>=1; + } +} + +static void menu_options_save(void) +{ + if (PicoIn.regionOverride) { + // force setting possibly changed.. + Pico.m.pal = (PicoIn.regionOverride == 2 || PicoIn.regionOverride == 8) ? 1 : 0; + } + if (!(PicoIn.opt & POPT_6BTN_PAD)) { + // unbind XYZ MODE, just in case + unbind_action(0xf00); + } +} + +static int menu_loop_options(void) +{ + static int menu_sel = 0; + int menu_sel_max, ret; + unsigned long inp = 0; + menu_id selected_id; + + me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_SAVECFG_GAME, rom_loaded); + me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_LOADCFG, config_slot != config_slot_current); + menu_sel_max = me_count_enabled(opt_entries, OPT_ENTRY_COUNT) - 1; + if (menu_sel > menu_sel_max) menu_sel = menu_sel_max; + + while (1) + { + draw_menu_options(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_LEFT|PBTN_RIGHT|PBTN_X|PBTN_CIRCLE, 0); + if (inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if (inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + selected_id = me_index2id(opt_entries, OPT_ENTRY_COUNT, menu_sel); + if (inp & (PBTN_LEFT|PBTN_RIGHT)) { // multi choise + if (!me_process(opt_entries, OPT_ENTRY_COUNT, selected_id, (inp&PBTN_RIGHT) ? 1 : 0)) { + switch (selected_id) { + case MA_OPT_RENDERER: + if ((PicoIn.opt & 0x10) || !(currentConfig.EmuOpt & 0x80)) { + PicoIn.opt &= ~0x10; + currentConfig.EmuOpt |= 0x80; + } else { + PicoIn.opt |= 0x10; + currentConfig.EmuOpt &= ~0x80; + } + break; + case MA_OPT_SOUND_QUALITY: + PicoIn.sndRate = sndrate_prevnext(PicoIn.sndRate, inp & PBTN_RIGHT); + break; + case MA_OPT_REGION: + region_prevnext(inp & PBTN_RIGHT); + break; + case MA_OPT_CONFIRM_STATES: { + int n = ((currentConfig.EmuOpt>>9)&1) | ((currentConfig.EmuOpt>>10)&2); + n += (inp & PBTN_LEFT) ? -1 : 1; + if (n < 0) n = 0; else if (n > 3) n = 3; + n |= n << 1; n &= ~2; + currentConfig.EmuOpt &= ~0xa00; + currentConfig.EmuOpt |= n << 9; + break; + } + case MA_OPT_SAVE_SLOT: + if (inp & PBTN_RIGHT) { + state_slot++; if (state_slot > 9) state_slot = 0; + } else {state_slot--; if (state_slot < 0) state_slot = 9; + } + break; + case MA_OPT_CPU_CLOCKS: + while ((inp = psp_pad_read(0)) & (PBTN_LEFT|PBTN_RIGHT)) { + currentConfig.CPUclock += (inp & PBTN_LEFT) ? -1 : 1; + if (currentConfig.CPUclock < 19) currentConfig.CPUclock = 19; + if (currentConfig.CPUclock > 333) currentConfig.CPUclock = 333; + draw_menu_options(menu_sel); // will wait vsync + } + break; + case MA_OPT_SAVECFG: + case MA_OPT_SAVECFG_GAME: + case MA_OPT_LOADCFG: + config_slot += (inp&PBTN_RIGHT) ? 1 : -1; + if (config_slot > 9) config_slot = 0; + if (config_slot < 0) config_slot = 9; + me_enable(opt_entries, OPT_ENTRY_COUNT, MA_OPT_LOADCFG, config_slot != config_slot_current); + menu_sel_max = me_count_enabled(opt_entries, OPT_ENTRY_COUNT) - 1; + if (menu_sel > menu_sel_max) menu_sel = menu_sel_max; + break; + default: + //lprintf("%s: something unknown selected (%i)\n", __FUNCTION__, selected_id); + break; + } + } + } + if (inp & PBTN_CIRCLE) { + if (!me_process(opt_entries, OPT_ENTRY_COUNT, selected_id, 1)) + { + switch (selected_id) + { + case MA_OPT_DISP_OPTS: + dispmenu_loop_options(); + break; + case MA_OPT_SCD_OPTS: + cd_menu_loop_options(); + if (engineState == PGS_ReloadRom) + return 0; // test BIOS + break; + case MA_OPT_ADV_OPTS: + amenu_loop_options(); + break; + case MA_OPT_SAVECFG: // done (update and write) + menu_options_save(); + if (emu_WriteConfig(0)) strcpy(menuErrorMsg, "config saved"); + else strcpy(menuErrorMsg, "failed to write config"); + return 1; + case MA_OPT_SAVECFG_GAME: // done (update and write for current game) + menu_options_save(); + if (emu_WriteConfig(1)) strcpy(menuErrorMsg, "config saved"); + else strcpy(menuErrorMsg, "failed to write config"); + return 1; + case MA_OPT_LOADCFG: + ret = emu_ReadConfig(1, 1); + if (!ret) ret = emu_ReadConfig(0, 1); + if (ret) strcpy(menuErrorMsg, "config loaded"); + else strcpy(menuErrorMsg, "failed to load config"); + return 1; + default: + //lprintf("%s: something unknown selected (%i)\n", __FUNCTION__, selected_id); + break; + } + } + } + if(inp & PBTN_X) { + menu_options_save(); + return 0; // done (update, no write) + } + } +} + +// -------------- credits -------------- + +static void draw_menu_credits(void) +{ + int tl_x = 80+15, tl_y = 16+64, y; + menu_draw_begin(); + + text_out16(tl_x, 16+20, "PicoDrive v" VERSION " (c) notaz, 2006-2008"); + + y = tl_y; + text_out16(tl_x, y, "Credits:"); + text_out16(tl_x, (y+=10), "fDave: base code of PicoDrive"); + text_out16(tl_x, (y+=10), "Chui: Fame/C"); + text_out16(tl_x, (y+=10), "NJ: CZ80"); + text_out16(tl_x, (y+=10), "MAME devs: YM2612 and SN76496 cores"); + text_out16(tl_x, (y+=10), "ps2dev.org people: PSP SDK/code"); + text_out16(tl_x, (y+=10), "ketchupgun: skin design"); + + text_out16(tl_x, (y+=20), "special thanks (for docs, ideas):"); + text_out16(tl_x, (y+=10), " Charles MacDonald, Haze,"); + text_out16(tl_x, (y+=10), " Stephane Dallongeville,"); + text_out16(tl_x, (y+=10), " Lordus, Exophase, Rokas,"); + text_out16(tl_x, (y+=10), " Nemesis, Tasco Deluxe"); + + menu_draw_end(); +} + + +// -------------- root menu -------------- + +menu_entry main_entries[] = +{ + { "Resume game", MB_NONE, MA_MAIN_RESUME_GAME, NULL, 0, 0, 0, 0 }, + { "Save State", MB_NONE, MA_MAIN_SAVE_STATE, NULL, 0, 0, 0, 0 }, + { "Load State", MB_NONE, MA_MAIN_LOAD_STATE, NULL, 0, 0, 0, 0 }, + { "Reset game", MB_NONE, MA_MAIN_RESET_GAME, NULL, 0, 0, 0, 0 }, + { "Load new ROM/ISO", MB_NONE, MA_MAIN_LOAD_ROM, NULL, 0, 0, 0, 1 }, + { "Change options", MB_NONE, MA_MAIN_OPTIONS, NULL, 0, 0, 0, 1 }, + { "Configure controls", MB_NONE, MA_MAIN_CONTROLS, NULL, 0, 0, 0, 1 }, + { "Credits", MB_NONE, MA_MAIN_CREDITS, NULL, 0, 0, 0, 1 }, + { "Patches / GameGenie",MB_NONE, MA_MAIN_PATCHES, NULL, 0, 0, 0, 0 }, + { "Exit", MB_NONE, MA_MAIN_EXIT, NULL, 0, 0, 0, 1 } +}; + +#define MAIN_ENTRY_COUNT (sizeof(main_entries) / sizeof(main_entries[0])) + +static void draw_menu_root(int menu_sel) +{ + const int tl_x = 86+70, tl_y = 16+70; + char *stat = NULL; + + menu_draw_begin(); + + if ((currentConfig.EmuOpt&0x20000) && (stat = psp_get_status_line())) + text_out16(287, 12, "%s", stat); + + text_out16(tl_x, 48, "PicoDrive v" VERSION); + + menu_draw_selection(tl_x - 16, tl_y + menu_sel*10, 146); + + me_draw(main_entries, MAIN_ENTRY_COUNT, tl_x, tl_y, NULL, NULL); + + // error + if (menuErrorMsg[0]) + text_out16(10, 252, menuErrorMsg); + menu_draw_end(); +} + + +static void menu_loop_root(void) +{ + static int menu_sel = 0; + int ret, menu_sel_max; + unsigned long inp = 0; + + me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_RESUME_GAME, rom_loaded); + me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_SAVE_STATE, rom_loaded); + me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_LOAD_STATE, rom_loaded); + me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_RESET_GAME, rom_loaded); + me_enable(main_entries, MAIN_ENTRY_COUNT, MA_MAIN_PATCHES, PicoPatches != NULL); + + menu_sel_max = me_count_enabled(main_entries, MAIN_ENTRY_COUNT) - 1; + if (menu_sel > menu_sel_max) menu_sel = menu_sel_max; + + // mp3 errors? + if (mp3_last_error != 0) { + if (mp3_last_error == -1) + sprintf(menuErrorMsg, "Unsupported mp3 format, use 44kHz stereo"); + else sprintf(menuErrorMsg, "mp3 init failed, code %08x", mp3_last_error); + mp3_last_error = 0; + } + + /* make sure action buttons are not pressed on entering menu */ + draw_menu_root(menu_sel); + + while (psp_pad_read(1) & (PBTN_X|PBTN_CIRCLE|PBTN_SELECT)) psp_msleep(50); + + for (;;) + { + draw_menu_root(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_X|PBTN_CIRCLE|PBTN_SELECT|PBTN_L|PBTN_R, 0); + if(inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if(inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + if((inp & (PBTN_L|PBTN_R)) == (PBTN_L|PBTN_R)) debug_menu_loop(); + if( inp & (PBTN_SELECT|PBTN_X)) { + if (rom_loaded) { + while (psp_pad_read(1) & (PBTN_SELECT|PBTN_X)) psp_msleep(50); // wait until released + engineState = PGS_Running; + break; + } + } + if(inp & PBTN_CIRCLE) { + menuErrorMsg[0] = 0; // clear error msg + switch (me_index2id(main_entries, MAIN_ENTRY_COUNT, menu_sel)) + { + case MA_MAIN_RESUME_GAME: + if (rom_loaded) { + while (psp_pad_read(1) & PBTN_CIRCLE) psp_msleep(50); + engineState = PGS_Running; + return; + } + break; + case MA_MAIN_SAVE_STATE: + if (rom_loaded) { + if(savestate_menu_loop(0)) + continue; + engineState = PGS_Running; + return; + } + break; + case MA_MAIN_LOAD_STATE: + if (rom_loaded) { + if(savestate_menu_loop(1)) + continue; + while (psp_pad_read(1) & PBTN_CIRCLE) psp_msleep(50); + engineState = PGS_Running; + return; + } + break; + case MA_MAIN_RESET_GAME: + if (rom_loaded) { + emu_ResetGame(); + while (psp_pad_read(1) & PBTN_CIRCLE) psp_msleep(50); + engineState = PGS_Running; + return; + } + break; + case MA_MAIN_LOAD_ROM: + { + char curr_path[PATH_MAX], *selfname; + FILE *tstf; + if ( (tstf = fopen(loadedRomFName, "rb")) ) + { + fclose(tstf); + strcpy(curr_path, loadedRomFName); + } + else + getcwd(curr_path, PATH_MAX); + selfname = romsel_loop(curr_path); + if (selfname) { + lprintf("selected file: %s\n", selfname); + engineState = PGS_ReloadRom; + return; + } + break; + } + case MA_MAIN_OPTIONS: + ret = menu_loop_options(); + if (ret == 1) continue; // status update + if (engineState == PGS_ReloadRom) + return; // BIOS test + break; + case MA_MAIN_CONTROLS: + kc_sel_loop(); + break; + case MA_MAIN_CREDITS: + draw_menu_credits(); + psp_msleep(500); + inp = 0; + while (!(inp & (PBTN_X|PBTN_CIRCLE))) + inp = in_menu_wait(PBTN_X|PBTN_CIRCLE, 0); + break; + case MA_MAIN_EXIT: + engineState = PGS_Quit; + return; + case MA_MAIN_PATCHES: + if (rom_loaded && PicoPatches) { + patches_menu_loop(); + PicoPatchApply(); + strcpy(menuErrorMsg, "Patches applied"); + continue; + } + break; + default: + lprintf("%s: something unknown selected\n", __FUNCTION__); + break; + } + } + } +} + +void menu_darken_bg(void *dst, const void *src, int pixels, int darker) +{ + unsigned int *dest = dst; + const unsigned int *srce = src; + pixels /= 2; + if (darker) + { + while (pixels--) + { + unsigned int p = *srce++; + *dest++ = ((p&0xf79ef79e)>>1) - ((p&0xc618c618)>>3); + } + } + else + { + while (pixels--) + { + unsigned int p = *srce++; + *dest++ = (p&0xf79ef79e)>>1; + } + } +} + +static void menu_prepare_bg(int use_game_bg, int use_fg) +{ + if (use_game_bg) + { + // darken the active framebuffer + unsigned short *dst = bg_buffer; + unsigned short *src = use_fg ? psp_video_get_active_fb() : psp_screen; + int i; + for (i = 272; i > 0; i--, dst += 480, src += 512) + menu_darken_bg(dst, src, 480, 1); + //memset32_uncached((int *)(bg_buffer + 480*264), 0, 480*8*2/4); + } + else + { + // should really only happen once, on startup.. + memset32_uncached((int *)(void *)bg_buffer, 0, sizeof(bg_buffer)/4); + readpng(bg_buffer, "skin/background.png", READPNG_BG); + } + sceKernelDcacheWritebackAll(); +} + +static void menu_gfx_prepare(void) +{ + menu_prepare_bg(rom_loaded, 1); + + menu_draw_begin(); + menu_draw_end(); +} + + +void menu_loop(void) +{ + menu_gfx_prepare(); + + menu_loop_root(); + + menuErrorMsg[0] = 0; +} + + +// --------- CD tray close menu ---------- + +static void draw_menu_tray(int menu_sel) +{ + int tl_x = 70, tl_y = 90, y; + + menu_draw_begin(); + + text_out16(tl_x, 20, "The unit is about to"); + text_out16(tl_x, 30, "close the CD tray."); + + y = tl_y; + text_out16(tl_x, y, "Load new CD image"); + text_out16(tl_x, (y+=10), "Insert nothing"); + + // draw cursor + text_out16(tl_x - 16, tl_y + menu_sel*10, ">"); + // error + if (menuErrorMsg[0]) text_out16(5, 226, menuErrorMsg); + menu_draw_end(); +} + + +int menu_loop_tray(void) +{ + int menu_sel = 0, menu_sel_max = 1; + unsigned long inp = 0; + char curr_path[PATH_MAX], *selfname; + FILE *tstf; + + menu_gfx_prepare(); + + if ( (tstf = fopen(loadedRomFName, "rb")) ) + { + fclose(tstf); + strcpy(curr_path, loadedRomFName); + } + else + { + getcwd(curr_path, PATH_MAX); + } + + /* make sure action buttons are not pressed on entering menu */ + draw_menu_tray(menu_sel); + while (psp_pad_read(1) & PBTN_CIRCLE) psp_msleep(50); + + for (;;) + { + draw_menu_tray(menu_sel); + inp = in_menu_wait(PBTN_UP|PBTN_DOWN|PBTN_CIRCLE, 0); + if(inp & PBTN_UP ) { menu_sel--; if (menu_sel < 0) menu_sel = menu_sel_max; } + if(inp & PBTN_DOWN) { menu_sel++; if (menu_sel > menu_sel_max) menu_sel = 0; } + if(inp & PBTN_CIRCLE) { + switch (menu_sel) { + case 0: // select image + selfname = romsel_loop(curr_path); + if (selfname) { + int ret = -1; + cd_img_type cd_type; + cd_type = emu_cdCheck(NULL, romFileName); + if (cd_type != CIT_NOT_CD) + ret = Insert_CD(romFileName, cd_type); + if (ret != 0) { + sprintf(menuErrorMsg, "Load failed, invalid CD image?"); + lprintf("%s\n", menuErrorMsg); + continue; + } + engineState = PGS_RestartRun; + return 1; + } + break; + case 1: // insert nothing + engineState = PGS_RestartRun; + return 0; + } + } + menuErrorMsg[0] = 0; // clear error msg + } +} + + diff --git a/platform/psp/menu.h b/platform/psp/menu.h new file mode 100644 index 0000000..d5aa608 --- /dev/null +++ b/platform/psp/menu.h @@ -0,0 +1,10 @@ + +void menu_loop(void); +int menu_loop_tray(void); +void menu_romload_prepare(const char *rom_name); +void menu_romload_end(void); + + +#define CONFIGURABLE_KEYS (PBTN_UP|PBTN_LEFT|PBTN_RIGHT|PBTN_DOWN|PBTN_L|PBTN_R|PBTN_TRIANGLE|PBTN_CIRCLE|PBTN_X|PBTN_SQUARE|PBTN_START| \ + PBTN_NUB_UP|PBTN_NUB_RIGHT|PBTN_NUB_DOWN|PBTN_NUB_LEFT|PBTN_NOTE) + diff --git a/platform/psp/mp3.c b/platform/psp/mp3.c new file mode 100644 index 0000000..1862439 --- /dev/null +++ b/platform/psp/mp3.c @@ -0,0 +1,495 @@ +/* + * PicoDrive + * (C) notaz, 2007,2008 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +#include +#include + +#include +#include +#include +#include + +#include "../../pico/pico_int.h" +#include "../../pico/sound/mix.h" +#include "../common/lprintf.h" + +int mp3_last_error = 0; + +static int initialized = 0; +static SceUID thread_job_sem = -1; +static SceUID thread_busy_sem = -1; +static int thread_exit = 0; + +// MPEG-1, layer 3 +static int bitrates[] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }; +//static int samplerates[] = { 44100, 48000, 32000, 0 }; + +#define MIN_INFRAME_SIZE 96 +#define IN_BUFFER_SIZE (2*1024) + +static unsigned long mp3_codec_struct[65] __attribute__((aligned(64))); + +static unsigned char mp3_src_buffer[2][IN_BUFFER_SIZE] __attribute__((aligned(64))); +static short mp3_mix_buffer[2][1152*2] __attribute__((aligned(64))); +static int working_buf = 0; + +static const char *mp3_fname = NULL; +static SceUID mp3_handle = -1; +static int mp3_src_pos = 0, mp3_src_size = 0; + +static int decode_thread(SceSize args, void *argp); + + +static void psp_sem_lock(SceUID sem) +{ + int ret = sceKernelWaitSema(sem, 1, 0); + if (ret < 0) lprintf("sceKernelWaitSema(%08x) failed with %08x\n", sem, ret); +} + +static void psp_sem_unlock(SceUID sem) +{ + int ret = sceKernelSignalSema(sem, 1); + if (ret < 0) lprintf("sceKernelSignalSema(%08x) failed with %08x\n", sem, ret); +} + +// only accepts MPEG-1, layer3 +static int find_sync_word(unsigned char *data, int len) +{ + int i; + for (i = 0; i < len-1; i++) + { + if ( data[i+0] != 0xff) continue; + if ((data[i+1] & 0xfe) == 0xfa) return i; + i++; + } + return -1; +} + +static int read_next_frame(int which_buffer) +{ + int i, bytes_read, frame_offset; + int bitrate, padding, frame_size = 0; + + for (i = 0; i < 32; i++) + { + bytes_read = sceIoRead(mp3_handle, mp3_src_buffer[which_buffer], sizeof(mp3_src_buffer[which_buffer])); + mp3_src_pos += bytes_read; + if (bytes_read < MIN_INFRAME_SIZE) { + mp3_src_pos = mp3_src_size; + return 0; // EOF/IO failure + } + frame_offset = find_sync_word(mp3_src_buffer[which_buffer], bytes_read); + if (frame_offset < 0) { + lprintf("missing syncword, foffs=%i\n", mp3_src_pos - bytes_read); + mp3_src_pos--; + sceIoLseek32(mp3_handle, mp3_src_pos, PSP_SEEK_SET); + continue; + } + if (bytes_read - frame_offset < 4) { + lprintf("syncword @ EOB, foffs=%i\n", mp3_src_pos - bytes_read); + mp3_src_pos--; + sceIoLseek32(mp3_handle, mp3_src_pos, PSP_SEEK_SET); + continue; + } + + bitrate = mp3_src_buffer[which_buffer][frame_offset+2] >> 4; + padding = (mp3_src_buffer[which_buffer][frame_offset+2] & 2) >> 1; + + frame_size = 144000*bitrates[bitrate]/44100 + padding; + if (frame_size <= 0) { + lprintf("bad frame, foffs=%i\n", mp3_src_pos - bytes_read); + continue; // bad frame + } + + if (bytes_read - frame_offset < frame_size) + { + lprintf("unfit, foffs=%i\n", mp3_src_pos - bytes_read); + mp3_src_pos -= bytes_read - frame_offset; + if (mp3_src_size - mp3_src_pos < frame_size) { + mp3_src_pos = mp3_src_size; + return 0; // EOF + } + sceIoLseek32(mp3_handle, mp3_src_pos, PSP_SEEK_SET); + continue; // didn't fit, re-read.. + } + + if (frame_offset) { + //lprintf("unaligned, foffs=%i, offs=%i\n", mp3_src_pos - bytes_read, frame_offset); + memmove(mp3_src_buffer[which_buffer], mp3_src_buffer[which_buffer] + frame_offset, frame_size); + } + + // align for next frame read + mp3_src_pos -= bytes_read - (frame_offset + frame_size); + sceIoLseek32(mp3_handle, mp3_src_pos, PSP_SEEK_SET); + + break; + } + + return frame_size > 0 ? frame_size : -1; +} + + +static SceUID load_start_module(const char *prxname) +{ + SceUID mod, mod1; + int status, ret; + + mod = pspSdkLoadStartModule(prxname, PSP_MEMORY_PARTITION_KERNEL); + if (mod < 0) { + lprintf("failed to load %s (%08x), trying kuKernelLoadModule\n", prxname, mod); + mod1 = kuKernelLoadModule(prxname, 0, NULL); + if (mod1 < 0) lprintf("kuKernelLoadModule failed with %08x\n", mod1); + else { + ret = sceKernelStartModule(mod1, 0, NULL, &status, 0); + if (ret < 0) lprintf("sceKernelStartModule failed with %08x\n", ret); + else mod = mod1; + } + } + return mod; +} + + +int mp3_init(void) +{ + SceUID thid, mod; + int ret; + + /* load modules */ + /* <= 1.5 (and probably some other, not sure which) fw need this to for audiocodec to work, + * so if it fails, assume we are just on new enough firmware and continue.. */ + load_start_module("flash0:/kd/me_for_vsh.prx"); + + if (sceKernelDevkitVersion() < 0x02070010) + mod = load_start_module("flash0:/kd/audiocodec.prx"); + else mod = load_start_module("flash0:/kd/avcodec.prx"); + if (mod < 0) { + ret = mod; + mod = load_start_module("flash0:/kd/audiocodec_260.prx"); // last chance.. + if (mod < 0) goto fail; + } + + /* audiocodec init */ + memset(mp3_codec_struct, 0, sizeof(mp3_codec_struct)); + ret = sceAudiocodecCheckNeedMem(mp3_codec_struct, 0x1002); + if (ret < 0) { + lprintf("sceAudiocodecCheckNeedMem failed with %08x\n", ret); + goto fail; + } + + ret = sceAudiocodecGetEDRAM(mp3_codec_struct, 0x1002); + if (ret < 0) { + lprintf("sceAudiocodecGetEDRAM failed with %08x\n", ret); + goto fail; + } + + ret = sceAudiocodecInit(mp3_codec_struct, 0x1002); + if (ret < 0) { + lprintf("sceAudiocodecInit failed with %08x\n", ret); + goto fail1; + } + + /* thread and stuff */ + thread_job_sem = sceKernelCreateSema("p_mp3job_sem", 0, 0, 1, NULL); + if (thread_job_sem < 0) { + lprintf("sceKernelCreateSema() failed: %08x\n", thread_job_sem); + ret = thread_job_sem; + goto fail1; + } + + thread_busy_sem = sceKernelCreateSema("p_mp3busy_sem", 0, 1, 1, NULL); + if (thread_busy_sem < 0) { + lprintf("sceKernelCreateSema() failed: %08x\n", thread_busy_sem); + ret = thread_busy_sem; + goto fail2; + } + + /* use slightly higher prio then main */ + thread_exit = 0; + thid = sceKernelCreateThread("mp3decode_thread", decode_thread, 30, 0x2000, 0, NULL); + if (thid < 0) { + lprintf("failed to create decode thread: %08x\n", thid); + ret = thid; + goto fail3; + } + ret = sceKernelStartThread(thid, 0, 0); + if (ret < 0) { + lprintf("failed to start decode thread: %08x\n", ret); + goto fail3; + } + + mp3_last_error = 0; + initialized = 1; + return 0; + +fail3: + sceKernelDeleteSema(thread_busy_sem); + thread_busy_sem = -1; +fail2: + sceKernelDeleteSema(thread_job_sem); + thread_job_sem = -1; +fail1: + sceAudiocodecReleaseEDRAM(mp3_codec_struct); +fail: + mp3_last_error = ret; + initialized = 0; + return 1; +} + +void mp3_deinit(void) +{ + lprintf("mp3_deinit, initialized=%i\n", initialized); + + if (!initialized) return; + thread_exit = 1; + psp_sem_lock(thread_busy_sem); + psp_sem_unlock(thread_busy_sem); + + sceKernelSignalSema(thread_job_sem, 1); + sceKernelDelayThread(100*1000); + + if (mp3_handle >= 0) sceIoClose(mp3_handle); + mp3_handle = -1; + mp3_fname = NULL; + + sceKernelDeleteSema(thread_busy_sem); + thread_busy_sem = -1; + sceKernelDeleteSema(thread_job_sem); + thread_job_sem = -1; + sceAudiocodecReleaseEDRAM(mp3_codec_struct); + initialized = 0; +} + +// may overflow stack? +static int decode_thread(SceSize args, void *argp) +{ + int ret, frame_size; + + lprintf("decode_thread started with id %08x, priority %i\n", + sceKernelGetThreadId(), sceKernelGetThreadCurrentPriority()); + + while (!thread_exit) + { + psp_sem_lock(thread_job_sem); + if (thread_exit) break; + + psp_sem_lock(thread_busy_sem); + //lprintf("{ job\n"); + + frame_size = read_next_frame(working_buf); + if (frame_size > 0) + { + mp3_codec_struct[6] = (unsigned long)mp3_src_buffer[working_buf]; + mp3_codec_struct[8] = (unsigned long)mp3_mix_buffer[working_buf]; + mp3_codec_struct[7] = mp3_codec_struct[10] = frame_size; + mp3_codec_struct[9] = 1152 * 4; + + ret = sceAudiocodecDecode(mp3_codec_struct, 0x1002); + if (ret < 0) lprintf("sceAudiocodecDecode failed with %08x\n", ret); + } + + //lprintf("} job\n"); + psp_sem_unlock(thread_busy_sem); + } + + lprintf("leaving decode thread\n"); + sceKernelExitDeleteThread(0); + return 0; +} + + +// might be called before initialization +int mp3_get_bitrate(void *f, int size) +{ + int ret, retval = -1, sample_rate, bitrate; + // filenames are stored instead handles in PSP, due to stupid max open file limit + char *fname = f; + + /* make sure thread is not busy.. */ + if (thread_busy_sem >= 0) + psp_sem_lock(thread_busy_sem); + + if (mp3_handle >= 0) sceIoClose(mp3_handle); + mp3_handle = sceIoOpen(fname, PSP_O_RDONLY, 0777); + if (mp3_handle < 0) { + lprintf("sceIoOpen(%s) failed\n", fname); + goto end; + } + + mp3_src_pos = 0; + ret = read_next_frame(0); + if (ret <= 0) { + lprintf("read_next_frame() failed (%s)\n", fname); + goto end; + } + sample_rate = (mp3_src_buffer[0][2] & 0x0c) >> 2; + bitrate = mp3_src_buffer[0][2] >> 4; + + if (sample_rate != 0) { + lprintf("unsupported samplerate (%s)\n", fname); + goto end; // only 44kHz supported.. + } + bitrate = bitrates[bitrate]; + if (bitrate == 0) { + lprintf("unsupported bitrate (%s)\n", fname); + goto end; + } + + /* looking good.. */ + retval = bitrate; +end: + if (mp3_handle >= 0) sceIoClose(mp3_handle); + mp3_handle = -1; + mp3_fname = NULL; + if (thread_busy_sem >= 0) + psp_sem_unlock(thread_busy_sem); + if (retval < 0) mp3_last_error = -1; // remember we had a problem.. + return retval; +} + + +static int mp3_job_started = 0, mp3_samples_ready = 0, mp3_buffer_offs = 0, mp3_play_bufsel = 0; + +void mp3_start_play(void *f, int pos) +{ + char *fname = f; + + if (!initialized) return; + + lprintf("mp3_start_play(%s) @ %i\n", fname, pos); + psp_sem_lock(thread_busy_sem); + + if (mp3_fname != fname || mp3_handle < 0) + { + if (mp3_handle >= 0) sceIoClose(mp3_handle); + mp3_handle = sceIoOpen(fname, PSP_O_RDONLY, 0777); + if (mp3_handle < 0) { + lprintf("sceIoOpen(%s) failed\n", fname); + psp_sem_unlock(thread_busy_sem); + return; + } + mp3_src_size = sceIoLseek32(mp3_handle, 0, PSP_SEEK_END); + mp3_fname = fname; + } + + // clear decoder state + sceAudiocodecInit(mp3_codec_struct, 0x1002); + + // seek.. + mp3_src_pos = (int) (((float)pos / 1023.0f) * (float)mp3_src_size); + sceIoLseek32(mp3_handle, mp3_src_pos, PSP_SEEK_SET); + lprintf("seek %i: %i/%i\n", pos, mp3_src_pos, mp3_src_size); + + mp3_job_started = 1; + mp3_samples_ready = mp3_buffer_offs = mp3_play_bufsel = 0; + working_buf = 0; + + /* send a request to decode first frame */ + psp_sem_unlock(thread_busy_sem); + psp_sem_unlock(thread_job_sem); + sceKernelDelayThread(1); // reschedule +} + + +void mp3_update(int *buffer, int length, int stereo) +{ + int length_mp3; + + // playback was started, track not ended + if (mp3_handle < 0 || mp3_src_pos >= mp3_src_size) return; + + length_mp3 = length; + if (PicoIn.sndRate == 22050) length_mp3 <<= 1; // mp3s are locked to 44100Hz stereo + else if (PicoIn.sndRate == 11025) length_mp3 <<= 2; // so make length 44100ish + + /* do we have to wait? */ + if (mp3_job_started && mp3_samples_ready < length_mp3) + { + psp_sem_lock(thread_busy_sem); + psp_sem_unlock(thread_busy_sem); + mp3_job_started = 0; + mp3_samples_ready += 1152; + } + + /* mix mp3 data, only stereo */ + if (mp3_samples_ready >= length_mp3) + { + int shr = 0; + void (*mix_samples)(int *dest_buf, short *mp3_buf, int count) = mix_16h_to_32; + if (PicoIn.sndRate == 22050) { mix_samples = mix_16h_to_32_s1; shr = 1; } + else if (PicoIn.sndRate == 11025) { mix_samples = mix_16h_to_32_s2; shr = 2; } + + if (1152 - mp3_buffer_offs >= length_mp3) { + mix_samples(buffer, mp3_mix_buffer[mp3_play_bufsel] + mp3_buffer_offs*2, length<<1); + + mp3_buffer_offs += length_mp3; + } else { + // collect samples from both buffers.. + int left = 1152 - mp3_buffer_offs; + if (mp3_play_bufsel == 0) + { + mix_samples(buffer, mp3_mix_buffer[0] + mp3_buffer_offs*2, length<<1); + mp3_buffer_offs = length_mp3 - left; + mp3_play_bufsel = 1; + } else { + mix_samples(buffer, mp3_mix_buffer[1] + mp3_buffer_offs*2, (left>>shr)<<1); + mp3_buffer_offs = length_mp3 - left; + mix_samples(buffer + ((left>>shr)<<1), + mp3_mix_buffer[0], (mp3_buffer_offs>>shr)<<1); + mp3_play_bufsel = 0; + } + } + mp3_samples_ready -= length_mp3; + } + + // ask to decode more if we already can + if (!mp3_job_started) + { + mp3_job_started = 1; + working_buf ^= 1; + + /* next job.. */ + psp_sem_lock(thread_busy_sem); // just in case + psp_sem_unlock(thread_busy_sem); + psp_sem_unlock(thread_job_sem); + sceKernelDelayThread(1); + } +} + + +int mp3_get_offset(void) // 0-1023 +{ + unsigned int offs1024 = 0; + int cdda_on; + + cdda_on = (PicoIn.AHW & PAHW_MCD) && (PicoIn.opt&0x800) && !(Pico_mcd->s68k_regs[0x36] & 1) && + (Pico_mcd->scd.Status_CDC & 1) && mp3_handle >= 0; + + if (cdda_on) { + offs1024 = mp3_src_pos << 7; + offs1024 /= mp3_src_size >> 3; + } + lprintf("offs1024=%u (%i/%i)\n", offs1024, mp3_src_pos, mp3_src_size); + + return offs1024; +} + + +void mp3_reopen_file(void) +{ + if (mp3_fname == NULL) return; + lprintf("mp3_reopen_file(%s)\n", mp3_fname); + + // try closing, just in case + if (mp3_handle >= 0) sceIoClose(mp3_handle); + + mp3_handle = sceIoOpen(mp3_fname, PSP_O_RDONLY, 0777); + if (mp3_handle >= 0) + sceIoLseek32(mp3_handle, mp3_src_pos, PSP_SEEK_SET); + lprintf("mp3_reopen_file %s\n", mp3_handle >= 0 ? "ok" : "failed"); +} + diff --git a/platform/psp/mp3.h b/platform/psp/mp3.h new file mode 100644 index 0000000..e2eab6a --- /dev/null +++ b/platform/psp/mp3.h @@ -0,0 +1,8 @@ + +// additional stuff for PSP mp3 decoder implementation +extern int mp3_last_error; + +int mp3_init(void); +void mp3_deinit(void); +void mp3_reopen_file(void); + diff --git a/platform/psp/psp.c b/platform/psp/psp.c new file mode 100644 index 0000000..8aad4c0 --- /dev/null +++ b/platform/psp/psp.c @@ -0,0 +1,374 @@ +/* + * PicoDrive + * (C) notaz, 2007,2008 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "psp.h" +#include "emu.h" +#include "../common/lprintf.h" +#include "version.h" + +extern int pico_main(void); + +#ifndef FW15 + +PSP_MODULE_INFO("PicoDrive", 0, 1, 51); +PSP_HEAP_SIZE_MAX(); + +int main() { return pico_main(); } /* just a wrapper */ + +#else + +PSP_MODULE_INFO("PicoDrive", 0x1000, 1, 51); +PSP_MAIN_THREAD_ATTR(0); + +int main() +{ + SceUID thid; + + /* this is the thing we need the kernel mode for */ + pspSdkInstallNoDeviceCheckPatch(); + + thid = sceKernelCreateThread("pico_main", (SceKernelThreadEntry) pico_main, 32, 0x2000, PSP_THREAD_ATTR_USER, NULL); + if (thid >= 0) + sceKernelStartThread(thid, 0, 0); +#ifndef GCOV + sceKernelExitDeleteThread(0); +#else + while (engineState != PGS_Quit) + sceKernelDelayThread(1024 * 1024); +#endif + + return 0; +} + +#endif + +int psp_unhandled_suspend = 0; + +unsigned int __attribute__((aligned(16))) guCmdList[GU_CMDLIST_SIZE]; + +void *psp_screen = VRAM_FB0; + +static int current_screen = 0; /* front bufer */ + +static SceUID main_thread_id = -1; + +#define ANALOG_DEADZONE 80 + +/* Exit callback */ +static int exit_callback(int arg1, int arg2, void *common) +{ + sceKernelExitGame(); + return 0; +} + +/* Power Callback */ +static int power_callback(int unknown, int pwrflags, void *common) +{ + lprintf("power_callback: flags: 0x%08X\n", pwrflags); + + /* check for power switch and suspending as one is manual and the other automatic */ + if (pwrflags & PSP_POWER_CB_POWER_SWITCH || pwrflags & PSP_POWER_CB_SUSPENDING || pwrflags & PSP_POWER_CB_STANDBY) + { + psp_unhandled_suspend = 1; + if (engineState != PGS_Suspending) + engineStateSuspend = engineState; + sceKernelDelayThread(100000); // ?? + } + else if (pwrflags & PSP_POWER_CB_RESUME_COMPLETE) + { + engineState = PGS_SuspendWake; + } + + //sceDisplayWaitVblankStart(); + return 0; +} + +/* Callback thread */ +static int callback_thread(SceSize args, void *argp) +{ + int cbid; + + lprintf("callback_thread started with id %08x, priority %i\n", + sceKernelGetThreadId(), sceKernelGetThreadCurrentPriority()); + + cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); + sceKernelRegisterExitCallback(cbid); + cbid = sceKernelCreateCallback("Power Callback", power_callback, NULL); + scePowerRegisterCallback(0, cbid); + + sceKernelSleepThreadCB(); + + return 0; +} + +void psp_init(void) +{ + SceUID thid; + char buff[128], *r; + + /* fw 1.5 sometimes returns 8002032c, although getcwd works */ + r = getcwd(buff, sizeof(buff)); + if (r) sceIoChdir(buff); + + main_thread_id = sceKernelGetThreadId(); + + lprintf("\n%s\n", "PicoDrive v" VERSION " " __DATE__ " " __TIME__); + lprintf("running on %08x kernel\n", sceKernelDevkitVersion()), + lprintf("entered psp_init, threadId %08x, priority %i\n", main_thread_id, + sceKernelGetThreadCurrentPriority()); + + thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, NULL); + if (thid >= 0) + { + sceKernelStartThread(thid, 0, 0); + } + + /* video */ + sceDisplaySetMode(0, 480, 272); + sceDisplaySetFrameBuf(VRAM_FB1, 512, PSP_DISPLAY_PIXEL_FORMAT_565, PSP_DISPLAY_SETBUF_NEXTFRAME); + current_screen = 1; + psp_screen = VRAM_FB0; + + /* gu */ + sceGuInit(); + + sceGuStart(GU_DIRECT, guCmdList); + sceGuDrawBuffer(GU_PSM_5650, (void *)VRAMOFFS_FB0, 512); + sceGuDispBuffer(480, 272, (void *)VRAMOFFS_FB1, 512); // don't care + sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT); + sceGuDepthBuffer((void *)VRAMOFFS_DEPTH, 512); + sceGuOffset(2048 - (480 / 2), 2048 - (272 / 2)); + sceGuViewport(2048, 2048, 480, 272); + sceGuDepthRange(0xc350, 0x2710); + sceGuScissor(0, 0, 480, 272); + sceGuEnable(GU_SCISSOR_TEST); + + sceGuDepthMask(0xffff); + sceGuDisable(GU_DEPTH_TEST); + + sceGuFrontFace(GU_CW); + sceGuEnable(GU_TEXTURE_2D); + sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB); + sceGuAmbientColor(0xffffffff); + sceGuColor(0xffffffff); + sceGuFinish(); + sceGuSync(0, 0); + + sceDisplayWaitVblankStart(); + sceGuDisplay(GU_TRUE); + + + /* input */ + sceCtrlSetSamplingCycle(0); + sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG); +} + +void psp_finish(void) +{ + lprintf("psp_finish..\n"); + sceGuTerm(); + + //sceKernelSleepThread(); + sceKernelExitGame(); +} + +void psp_video_flip(int wait_vsync) +{ + if (wait_vsync) sceDisplayWaitVblankStart(); + sceDisplaySetFrameBuf(psp_screen, 512, PSP_DISPLAY_PIXEL_FORMAT_565, + wait_vsync ? PSP_DISPLAY_SETBUF_IMMEDIATE : PSP_DISPLAY_SETBUF_NEXTFRAME); + current_screen ^= 1; + psp_screen = current_screen ? VRAM_FB0 : VRAM_FB1; +} + +void *psp_video_get_active_fb(void) +{ + return current_screen ? VRAM_FB1 : VRAM_FB0; +} + +void psp_video_switch_to_single(void) +{ + psp_screen = VRAM_FB0; + sceDisplaySetFrameBuf(psp_screen, 512, PSP_DISPLAY_PIXEL_FORMAT_565, PSP_DISPLAY_SETBUF_NEXTFRAME); + current_screen = 0; +} + +void psp_msleep(int ms) +{ + sceKernelDelayThread(ms * 1000); +} + +unsigned int psp_pad_read(int blocking) +{ + unsigned int buttons; + SceCtrlData pad; + if (blocking) + sceCtrlReadBufferPositive(&pad, 1); + else sceCtrlPeekBufferPositive(&pad, 1); + buttons = pad.Buttons; + + // analog.. + buttons &= ~(PBTN_NUB_UP|PBTN_NUB_DOWN|PBTN_NUB_LEFT|PBTN_NUB_RIGHT); + if (pad.Lx < 128 - ANALOG_DEADZONE) buttons |= PBTN_NUB_LEFT; + if (pad.Lx > 128 + ANALOG_DEADZONE) buttons |= PBTN_NUB_RIGHT; + if (pad.Ly < 128 - ANALOG_DEADZONE) buttons |= PBTN_NUB_UP; + if (pad.Ly > 128 + ANALOG_DEADZONE) buttons |= PBTN_NUB_DOWN; + + return buttons; +} + +int psp_get_cpu_clock(void) +{ + return scePowerGetCpuClockFrequencyInt(); +} + +int psp_set_cpu_clock(int clock) +{ + int ret = scePowerSetClockFrequency(clock, clock, clock/2); + if (ret != 0) lprintf("failed to set clock: %i\n", ret); + + return ret; +} + +char *psp_get_status_line(void) +{ + static char buff[64]; + int ret, bat_percent, bat_time; + pspTime time; + + ret = sceRtcGetCurrentClockLocalTime(&time); + bat_percent = scePowerGetBatteryLifePercent(); + bat_time = scePowerGetBatteryLifeTime(); + if (ret < 0 || bat_percent < 0 || bat_time < 0) return NULL; + + snprintf(buff, sizeof(buff), "%02i:%02i bat: %3i%%", time.hour, time.minutes, bat_percent); + if (!scePowerIsPowerOnline()) + snprintf(buff+strlen(buff), sizeof(buff)-strlen(buff), " (%i:%02i)", bat_time/60, bat_time%60); + return buff; +} + +void psp_wait_suspend(void) +{ + // probably should do something smarter here? + sceDisplayWaitVblankStart(); +} + +void psp_resume_suspend(void) +{ + // for some reason file IO doesn't seem to work + // after resume for some period of time, at least on 1.5 + SceUID fd; + int i; + for (i = 0; i < 30; i++) { + fd = sceIoOpen("EBOOT.PBP", PSP_O_RDONLY, 0777); + if (fd >= 0) break; + sceKernelDelayThread(100 * 1024); + } + if (fd >= 0) sceIoClose(fd); + sceDisplayWaitVblankStart(); + if (i < 30) + lprintf("io resumed after %i tries\n", i); + else { + lprintf("io resume failed with %08x\n", fd); + sceKernelDelayThread(500 * 1024); + } +} + +/* alt logging */ +#define LOG_FILE "log.txt" + +#ifndef LPRINTF_STDIO +typedef struct _log_entry +{ + char buff[256]; + struct _log_entry *next; +} log_entry; + +static log_entry *le_root = NULL; +#endif + +/* strange: if this function leaks memory (before psp_init() call?), + * resume after suspend breaks on 3.90 */ +void lprintf(const char *fmt, ...) +{ + va_list vl; + +#ifdef LPRINTF_STDIO + va_start(vl, fmt); + vprintf(fmt, vl); + va_end(vl); +#else + static SceUID logfd = -1; + static int msg_count = 0; + char buff[256]; + log_entry *le, *le1; + + if (logfd == -2) return; // disabled + + va_start(vl, fmt); + vsnprintf(buff, sizeof(buff), fmt, vl); + va_end(vl); + + // note: this is still unsafe code + if (main_thread_id != sceKernelGetThreadId()) + { + le = malloc(sizeof(*le)); + if (le == NULL) return; + le->next = NULL; + strcpy(le->buff, buff); + if (le_root == NULL) le_root = le; + else { + for (le1 = le_root; le1->next != NULL; le1 = le1->next); + le1->next = le; + } + return; + } + + logfd = sceIoOpen(LOG_FILE, PSP_O_WRONLY|PSP_O_APPEND, 0777); + if (logfd < 0) { + if (msg_count == 0) logfd = -2; + return; + } + + if (le_root != NULL) + { + le1 = le_root; + le_root = NULL; + sceKernelDelayThread(1000); + while (le1 != NULL) { + le = le1; + le1 = le->next; + sceIoWrite(logfd, le->buff, strlen(le->buff)); + free(le); + msg_count++; + } + } + + sceIoWrite(logfd, buff, strlen(buff)); + msg_count++; + + // make sure it gets flushed + sceIoClose(logfd); + logfd = -1; +#endif +} + + diff --git a/platform/psp/psp.h b/platform/psp/psp.h new file mode 100644 index 0000000..7067f05 --- /dev/null +++ b/platform/psp/psp.h @@ -0,0 +1,73 @@ +/* + * PicoDrive + * (C) notaz, 2007,2008 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ + +#include + +void psp_init(void); +void psp_finish(void); + +void psp_msleep(int ms); + +// vram usage map: +// 000000-044000 fb0 +// 044000-088000 fb1 +// 088000-0cc000 depth (?) +// 0cc000-126000 emu draw buffers: 512*240 + 512*240*2 + +#define VRAMOFFS_FB0 0x00000000 +#define VRAMOFFS_FB1 0x00044000 +#define VRAMOFFS_DEPTH 0x00088000 +#define VRAMOFFS_STUFF 0x000cc000 + +#define VRAM_FB0 ((void *) (0x44000000+VRAMOFFS_FB0)) +#define VRAM_FB1 ((void *) (0x44000000+VRAMOFFS_FB1)) +#define VRAM_STUFF ((void *) (0x44000000+VRAMOFFS_STUFF)) + +#define VRAM_CACHED_STUFF ((void *) (0x04000000+VRAMOFFS_STUFF)) + +#define GU_CMDLIST_SIZE (16*1024) + +extern unsigned int guCmdList[GU_CMDLIST_SIZE]; +extern int psp_unhandled_suspend; + +void *psp_video_get_active_fb(void); +void psp_video_switch_to_single(void); +void psp_video_flip(int wait_vsync); +extern void *psp_screen; + +unsigned int psp_pad_read(int blocking); + +int psp_get_cpu_clock(void); +int psp_set_cpu_clock(int clock); + +char *psp_get_status_line(void); + +void psp_wait_suspend(void); +void psp_resume_suspend(void); + +/* shorter btn names */ +#define PBTN_UP PSP_CTRL_UP +#define PBTN_LEFT PSP_CTRL_LEFT +#define PBTN_RIGHT PSP_CTRL_RIGHT +#define PBTN_DOWN PSP_CTRL_DOWN +#define PBTN_L PSP_CTRL_LTRIGGER +#define PBTN_R PSP_CTRL_RTRIGGER +#define PBTN_TRIANGLE PSP_CTRL_TRIANGLE +#define PBTN_CIRCLE PSP_CTRL_CIRCLE +#define PBTN_X PSP_CTRL_CROSS +#define PBTN_SQUARE PSP_CTRL_SQUARE +#define PBTN_SELECT PSP_CTRL_SELECT +#define PBTN_START PSP_CTRL_START +#define PBTN_NOTE PSP_CTRL_NOTE // doesn't seem to work? + +/* fake 'nub' btns */ +#define PBTN_NUB_UP (1 << 28) +#define PBTN_NUB_RIGHT (1 << 29) +#define PBTN_NUB_DOWN (1 << 30) +#define PBTN_NUB_LEFT (1 << 31) + diff --git a/platform/psp/skin/background.png b/platform/psp/skin/background.png new file mode 100644 index 0000000..735ab64 Binary files /dev/null and b/platform/psp/skin/background.png differ diff --git a/platform/psp/skin/font.png b/platform/psp/skin/font.png new file mode 100644 index 0000000..c526a08 Binary files /dev/null and b/platform/psp/skin/font.png differ diff --git a/platform/psp/skin/readme.txt b/platform/psp/skin/readme.txt new file mode 100644 index 0000000..dd83963 --- /dev/null +++ b/platform/psp/skin/readme.txt @@ -0,0 +1,8 @@ +The skin images can be customized, but there are several limitations: + +background.png - must be 320x240 image with 24bit RGB colors. +font.png - must be 128x160 8bit grayscale image. +selector.png - must be 8x10 8bit grayscale image. + +Font and selector colors can be changed by editing skin.txt. + diff --git a/platform/psp/skin/selector.png b/platform/psp/skin/selector.png new file mode 100644 index 0000000..5062cc2 Binary files /dev/null and b/platform/psp/skin/selector.png differ diff --git a/platform/psp/skin/skin.txt b/platform/psp/skin/skin.txt new file mode 100644 index 0000000..c2baeb2 --- /dev/null +++ b/platform/psp/skin/skin.txt @@ -0,0 +1,4 @@ +// html-style hex color codes, ex. ff0000 is red, 0000ff is blue, etc. +text_color=ffffff +selection_color=c00000 + diff --git a/platform/win32/Makefile b/platform/win32/Makefile new file mode 100644 index 0000000..b2f02e2 --- /dev/null +++ b/platform/win32/Makefile @@ -0,0 +1,72 @@ +# settings +CROSS=i586-mingw32msvc- + +#use_musashi = 1 +use_fame = 1 +use_cz80 = 1 +use_sh2drc = 1 +#use_sh2mame = 1 + +-include Makefile.local + +ARCH ?= x86 +CC = $(CROSS)gcc +CXX = $(CROSS)g++ +LD = $(CROSS)ld +STRIP = $(CROSS)strip + +DEFINES = _UNZIP_SUPPORT IN_VK +CFLAGS += -O2 -Wall -falign-functions=2 -ffast-math +CFLAGS += -I../.. -I. -I../../zlib/ -Idirectx/include/ +LDFLAGS += -mwindows -L. -Ldirectx/lib/ -lgdi32 -lcomdlg32 -lddraw -ldsound -ldxguid + +# frontend +OBJS += main.o plat.o direct.o dsnd.o in_vk.o + +# common +OBJS += platform/common/emu.o platform/common/menu.o \ + platform/common/config.o platform/common/fonts.o platform/common/readpng.o \ + platform/common/input.o + +OBJS += pico/carthw/svp/compiler.o +OBJS += pico/sound/mix.o +# zlib +OBJS += zlib/gzio.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o \ + zlib/deflate.o zlib/crc32.o zlib/adler32.o zlib/zutil.o zlib/compress.o zlib/uncompr.o +# unzip +OBJS += unzip/unzip.o unzip/unzip_stream.o + +CFLAGS += $(addprefix -D,$(DEFINES)) +CXXFLAGS = $(CFLAGS) + +vpath %.c = ../.. + +DIRS += zlib unzip + +TARGET = PicoDrive.exe +all: mkdirs $(TARGET) + +include ../common/common.mak +include ../common/revision.mak + +clean: tidy + @$(RM) $(TARGET) +tidy: + $(RM) $(OBJS) $(TARGET).map + rm -rf $(DIRS) + +$(TARGET) : $(OBJS) + @echo ">>>" $@ + $(CC) $(CFLAGS) $^ $(LDFLAGS) -lm -lpng -Wl,-Map=$(TARGET).map -o $@ + $(STRIP) $@ + +# ----------- release ----------- +ifneq ($(findstring rel,$(MAKECMDGOALS)),) +ifeq ($(VER),) +$(error need VER) +endif +endif + +rel: $(TARGET) readme.txt carthw.cfg + zip -9 -j ../../PicoDrive_win32_$(VER).zip $^ + diff --git a/platform/win32/in_vk.c b/platform/win32/in_vk.c new file mode 100644 index 0000000..44e1fde --- /dev/null +++ b/platform/win32/in_vk.c @@ -0,0 +1,277 @@ +#define RC_INVOKED // we only need defines +#include +#undef RC_INVOKED +#include + +#include "../common/input.h" +#include "../common/emu.h" // array_size +#include "in_vk.h" + +#define IN_VK_PREFIX "vk:" +#define IN_VK_NKEYS 0x100 + +static const char * const in_vk_prefix = IN_VK_PREFIX; +static const char * const in_vk_keys[IN_VK_NKEYS] = { + [0 ... (IN_VK_NKEYS - 1)] = NULL, + [VK_LBUTTON] = "LBUTTON", [VK_RBUTTON] = "RBUTTON", + [VK_TAB] = "TAB", [VK_RETURN] = "RETURN", + [VK_SHIFT] = "SHIFT", [VK_CONTROL] = "CONTROL", + [VK_LEFT] = "LEFT", [VK_UP] = "UP", + [VK_RIGHT] = "RIGHT", [VK_DOWN] = "DOWN", + [VK_SPACE] = "SPACE", +}; + +// additional player12 keys +int in_vk_add_pl12; + +// up to 4, keyboards rarely allow more +static int in_vk_keys_down[4]; + +/* +#define VK_END 35 +#define VK_HOME 36 +#define VK_INSERT 45 +#define VK_DELETE 46 +#define VK_NUMPAD0 0x60 +#define VK_NUMPAD1 0x61 +#define VK_NUMPAD2 0x62 +#define VK_NUMPAD3 0x63 +#define VK_NUMPAD4 0x64 +#define VK_NUMPAD5 0x65 +#define VK_NUMPAD6 0x66 +#define VK_NUMPAD7 0x67 +#define VK_NUMPAD8 0x68 +#define VK_NUMPAD9 0x69 +#define VK_MULTIPLY 0x6A +#define VK_ADD 0x6B +#define VK_SEPARATOR 0x6C +#define VK_SUBTRACT 0x6D +#define VK_DECIMAL 0x6E +#define VK_DIVIDE 0x6F +#define VK_F1 0x70 +#define VK_F2 0x71 +#define VK_F3 0x72 +#define VK_F4 0x73 +#define VK_F5 0x74 +#define VK_F6 0x75 +#define VK_F7 0x76 +#define VK_F8 0x77 +#define VK_F9 0x78 +#define VK_F10 0x79 +#define VK_F11 0x7A +#define VK_F12 0x7B +*/ + +static void in_vk_probe(void) +{ + memset(in_vk_keys_down, 0, sizeof(in_vk_keys_down)); + in_register(IN_VK_PREFIX "vk", IN_DRVID_VK, -1, (void *)1, IN_VK_NKEYS, NULL, 0); +} + +static int in_vk_get_bind_count(void) +{ + return IN_VK_NKEYS; +} + +/* ORs result with pressed buttons */ +int in_vk_update(void *drv_data, const int *binds, int *result) +{ + int i, t, k; + + for (i = 0; i < array_size(in_vk_keys_down); i++) { + k = in_vk_keys_down[i]; + if (!k) + continue; + + for (t = 0; t < IN_BINDTYPE_COUNT; t++) + result[t] |= binds[IN_BIND_OFFS(k, t)]; + } + + result[IN_BINDTYPE_PLAYER12] |= in_vk_add_pl12; + + return 0; +} + +void in_vk_keydown(int kc) +{ + int i; + + // search + for (i = 0; i < array_size(in_vk_keys_down); i++) + if (in_vk_keys_down[i] == kc) + return; + + // do + for (i = 0; i < array_size(in_vk_keys_down); i++) { + if (in_vk_keys_down[i] == 0) { + in_vk_keys_down[i] = kc; + return; + } + } +} + +void in_vk_keyup(int kc) +{ + int i; + for (i = 0; i < array_size(in_vk_keys_down); i++) + if (in_vk_keys_down[i] == kc) + in_vk_keys_down[i] = 0; +} + +static int in_vk_update_keycode(void *data, int *is_down) +{ + return 0; +} + +static const struct { + short key; + short pbtn; +} key_pbtn_map[] = +{ + { VK_UP, PBTN_UP }, + { VK_DOWN, PBTN_DOWN }, + { VK_LEFT, PBTN_LEFT }, + { VK_RIGHT, PBTN_RIGHT }, + { VK_RETURN, PBTN_MOK }, +/* + { BTN_X, PBTN_MBACK }, + { BTN_A, PBTN_MA2 }, + { BTN_Y, PBTN_MA3 }, + { BTN_L, PBTN_L }, + { BTN_R, PBTN_R }, + { BTN_SELECT, PBTN_MENU }, +*/ +}; + +#define KEY_PBTN_MAP_SIZE (sizeof(key_pbtn_map) / sizeof(key_pbtn_map[0])) + +static int in_vk_menu_translate(void *drv_data, int keycode) +{ + int i; + if (keycode < 0) + { + /* menu -> kc */ + keycode = -keycode; + for (i = 0; i < KEY_PBTN_MAP_SIZE; i++) + if (key_pbtn_map[i].pbtn == keycode) + return key_pbtn_map[i].key; + } + else + { + for (i = 0; i < KEY_PBTN_MAP_SIZE; i++) + if (key_pbtn_map[i].key == keycode) + return key_pbtn_map[i].pbtn; + } + + return 0; +} + +static int in_vk_get_key_code(const char *key_name) +{ + int i; + + if (key_name[1] == 0 && 'A' <= key_name[0] && key_name[0] <= 'Z') + return key_name[0]; + + for (i = 0; i < IN_VK_NKEYS; i++) { + const char *k = in_vk_keys[i]; + if (k != NULL && strcasecmp(k, key_name) == 0) + return i; + } + + return -1; +} + +static const char *in_vk_get_key_name(int keycode) +{ + const char *name = NULL; + static char buff[4]; + + if ('A' <= keycode && keycode < 'Z') { + buff[0] = keycode; + buff[1] = 0; + return buff; + } + + if (0 <= keycode && keycode < IN_VK_NKEYS) + name = in_vk_keys[keycode]; + if (name == NULL) + name = "Unkn"; + + return name; +} + +static const struct { + short code; + char btype; + char bit; +} in_vk_def_binds[] = +{ + /* MXYZ SACB RLDU */ + { VK_UP, IN_BINDTYPE_PLAYER12, 0 }, + { VK_DOWN, IN_BINDTYPE_PLAYER12, 1 }, + { VK_LEFT, IN_BINDTYPE_PLAYER12, 2 }, + { VK_RIGHT, IN_BINDTYPE_PLAYER12, 3 }, + { 'S', IN_BINDTYPE_PLAYER12, 4 }, /* B */ + { 'D', IN_BINDTYPE_PLAYER12, 5 }, /* C */ + { 'A', IN_BINDTYPE_PLAYER12, 6 }, /* A */ + { VK_RETURN, IN_BINDTYPE_PLAYER12, 7 }, + { 'E', IN_BINDTYPE_PLAYER12, 8 }, /* Z */ + { 'W', IN_BINDTYPE_PLAYER12, 9 }, /* Y */ + { 'Q', IN_BINDTYPE_PLAYER12,10 }, /* X */ + { 'R', IN_BINDTYPE_PLAYER12,11 }, /* M */ +/* + { BTN_SELECT, IN_BINDTYPE_EMU, PEVB_MENU }, +// { BTN_Y, IN_BINDTYPE_EMU, PEVB_SWITCH_RND }, + { BTN_L, IN_BINDTYPE_EMU, PEVB_STATE_SAVE }, + { BTN_R, IN_BINDTYPE_EMU, PEVB_STATE_LOAD }, + { BTN_VOL_UP, IN_BINDTYPE_EMU, PEVB_VOL_UP }, + { BTN_VOL_DOWN, IN_BINDTYPE_EMU, PEVB_VOL_DOWN }, +*/ +}; + +#define DEF_BIND_COUNT (sizeof(in_vk_def_binds) / sizeof(in_vk_def_binds[0])) + +static void in_vk_get_def_binds(int *binds) +{ + int i; + + for (i = 0; i < DEF_BIND_COUNT; i++) + binds[IN_BIND_OFFS(in_vk_def_binds[i].code, in_vk_def_binds[i].btype)] = + 1 << in_vk_def_binds[i].bit; +} + +/* remove binds of missing keys, count remaining ones */ +static int in_vk_clean_binds(void *drv_data, int *binds, int *def_binds) +{ + int i, count = 0; + + for (i = 0; i < IN_VK_NKEYS; i++) { + int t, offs; + for (t = 0; t < IN_BINDTYPE_COUNT; t++) { + offs = IN_BIND_OFFS(i, t); + if (strcmp(in_vk_get_key_name(i), "Unkn") == 0) + binds[offs] = def_binds[offs] = 0; + if (binds[offs]) + count++; + } + } + + return count; +} + +void in_vk_init(void *vdrv) +{ + in_drv_t *drv = vdrv; + + drv->prefix = in_vk_prefix; + drv->probe = in_vk_probe; + drv->get_bind_count = in_vk_get_bind_count; + drv->get_def_binds = in_vk_get_def_binds; + drv->clean_binds = in_vk_clean_binds; + drv->menu_translate = in_vk_menu_translate; + drv->get_key_code = in_vk_get_key_code; + drv->get_key_name = in_vk_get_key_name; + drv->update_keycode = in_vk_update_keycode; +} + diff --git a/platform/win32/in_vk.h b/platform/win32/in_vk.h new file mode 100644 index 0000000..8bc7e98 --- /dev/null +++ b/platform/win32/in_vk.h @@ -0,0 +1,16 @@ +#ifdef IN_VK + +void in_vk_init(void *vdrv); +int in_vk_update(void *drv_data, const int *binds, int *result); + +void in_vk_keydown(int kc); +void in_vk_keyup(int kc); + +extern int in_vk_add_pl12; + +#else + +#define in_vk_init(x) +#define in_vk_update(a,b,c) 0 + +#endif diff --git a/platform/win32/main.c b/platform/win32/main.c new file mode 100644 index 0000000..eb55e0e --- /dev/null +++ b/platform/win32/main.c @@ -0,0 +1,635 @@ +#include +#include +#include + +#include "../../pico/pico.h" +#include "../common/readpng.h" +#include "../common/config.h" +#include "../common/lprintf.h" +#include "../common/emu.h" +#include "../common/menu.h" +#include "../common/input.h" +#include "../common/plat.h" +#include "version.h" +#include "direct.h" +#include "in_vk.h" + +char *romname=NULL; +HWND FrameWnd=NULL; +RECT FrameRectMy; +RECT EmuScreenRect = { 0, 0, 320, 224 }; +int lock_to_1_1 = 1; +static HWND PicoSwWnd=NULL, PicoPadWnd=NULL; + +static HMENU mmain = 0, mdisplay = 0, mpicohw = 0; +static HBITMAP ppad_bmp = 0; +static HBITMAP ppage_bmps[7] = { 0, }; +static char rom_name[0x20*3+1]; +static int main_wnd_as_pad = 0; + +static HANDLE loop_enter_event, loop_end_event; + +void error(char *text) +{ + MessageBox(FrameWnd, text, "Error", 0); +} + +static void UpdateRect(void) +{ + WINDOWINFO wi; + memset(&wi, 0, sizeof(wi)); + wi.cbSize = sizeof(wi); + GetWindowInfo(FrameWnd, &wi); + FrameRectMy = wi.rcClient; +} + +static int extract_rom_name(char *dest, const unsigned char *src, int len) +{ + char *p = dest, s_old = 0x20; + int i; + + for (i = len - 1; i >= 0; i--) + { + if (src[i^1] != ' ') break; + } + len = i + 1; + + for (i = 0; i < len; i++) + { + unsigned char s = src[i^1]; + if (s == 0x20 && s_old == 0x20) continue; + else if (s >= 0x20 && s < 0x7f && s != '%') + { + *p++ = s; + } + else + { + sprintf(p, "%%%02x", s); + p += 3; + } + s_old = s; + } + *p = 0; + + return p - dest; +} + +static void check_name_alias(const char *afname) +{ + char buff[256], *var, *val; + FILE *f; + int ret; + + f = fopen(afname, "r"); + if (f == NULL) return; + + while (1) + { + ret = config_get_var_val(f, buff, sizeof(buff), &var, &val); + if (ret == 0) break; + if (ret == -1) continue; + + if (strcmp(rom_name, var) == 0) { + lprintf("rom aliased: \"%s\" -> \"%s\"\n", rom_name, val); + strncpy(rom_name, val, sizeof(rom_name)); + break; + } + } + fclose(f); +} + +static HBITMAP png2hb(const char *fname, int is_480) +{ + BITMAPINFOHEADER bih; + HBITMAP bmp; + void *bmem; + int ret; + + bmem = calloc(1, is_480 ? 480*240*3 : 320*240*3); + if (bmem == NULL) return NULL; + ret = readpng(bmem, fname, READPNG_24, is_480 ? 480 : 320, 240); + if (ret != 0) { + free(bmem); + return NULL; + } + + memset(&bih, 0, sizeof(bih)); + bih.biSize = sizeof(bih); + bih.biWidth = is_480 ? 480 : 320; + bih.biHeight = -240; + bih.biPlanes = 1; + bih.biBitCount = 24; + bih.biCompression = BI_RGB; + bmp = CreateDIBitmap(GetDC(FrameWnd), &bih, CBM_INIT, bmem, (BITMAPINFO *)&bih, 0); + if (bmp == NULL) + lprintf("CreateDIBitmap failed with %i", GetLastError()); + + free(bmem); + return bmp; +} + +static void PrepareForROM(void) +{ + unsigned char *rom_data = NULL; + int i, ret, show = PicoIn.AHW & PAHW_PICO; + + PicoGetInternal(PI_ROM, (pint_ret_t *) &rom_data); + EnableMenuItem(mmain, 2, MF_BYPOSITION|(show ? MF_ENABLED : MF_GRAYED)); + ShowWindow(PicoPadWnd, show ? SW_SHOWNA : SW_HIDE); + ShowWindow(PicoSwWnd, show ? SW_SHOWNA : SW_HIDE); + CheckMenuItem(mpicohw, 1210, show ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(mpicohw, 1211, show ? MF_CHECKED : MF_UNCHECKED); + PostMessage(FrameWnd, WM_COMMAND, 1220 + PicoPicohw.page, 0); + DrawMenuBar(FrameWnd); + InvalidateRect(PicoSwWnd, NULL, 1); + + PicoPicohw.pen_pos[0] = + PicoPicohw.pen_pos[1] = 0x8000; + in_vk_add_pl12 = 0; + + ret = extract_rom_name(rom_name, rom_data + 0x150, 0x20); + if (ret == 0) + extract_rom_name(rom_name, rom_data + 0x130, 0x20); + + if (show) + { + char path[MAX_PATH], *p; + GetModuleFileName(NULL, path, sizeof(path) - 32); + p = strrchr(path, '\\'); + if (p == NULL) p = path; + else p++; + if (ppad_bmp == NULL) { + strcpy(p, "pico\\pad.png"); + ppad_bmp = png2hb(path, 0); + } + + strcpy(p, "pico\\alias.txt"); + check_name_alias(path); + + for (i = 0; i < 7; i++) { + if (ppage_bmps[i] != NULL) DeleteObject(ppage_bmps[i]); + sprintf(p, "pico\\%s_%i.png", rom_name, i); + ppage_bmps[i] = png2hb(path, 1); + } + // games usually don't have page 6, so just duplicate page 5. + if (ppage_bmps[6] == NULL && ppage_bmps[5] != NULL) { + sprintf(p, "pico\\%s_5.png", rom_name); + ppage_bmps[6] = png2hb(path, 1); + } + } +} + +static void LoadROM(const char *cmdpath) +{ + char rompath[MAX_PATH]; + int ret; + + if (cmdpath != NULL && strlen(cmdpath)) { + strcpy(rompath, cmdpath + (cmdpath[0] == '\"' ? 1 : 0)); + if (rompath[strlen(rompath)-1] == '\"') + rompath[strlen(rompath)-1] = 0; + } + else { + OPENFILENAME of; ZeroMemory(&of, sizeof(of)); + rompath[sizeof(rompath) - 1] = 0; + strncpy(rompath, rom_fname_loaded, sizeof(rompath) - 1); + of.lStructSize = sizeof(of); + of.lpstrFilter = "ROMs, CD images\0*.smd;*.bin;*.gen;*.zip;*.32x;*.sms;*.iso;*.cso;*.cue\0" + "whatever\0*.*\0"; + of.lpstrFile = rompath; + of.nMaxFile = MAX_PATH; + of.Flags = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; + of.hwndOwner = FrameWnd; + if (!GetOpenFileName(&of)) + return; + } + + if (engineState == PGS_Running) { + engineState = PGS_Paused; + WaitForSingleObject(loop_end_event, 5000); + } + + ret = emu_reload_rom(rompath); + if (ret == 0) { + extern char menu_error_msg[]; // HACK.. + error(menu_error_msg); + return; + } + + PrepareForROM(); + engineState = PGS_Running; + SetEvent(loop_enter_event); +} + +static const int rect_widths[4] = { 320, 256, 640, 512 }; +static const int rect_heights[4] = { 224, 224, 448, 448 }; + +// Window proc for the frame window: +static LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam) +{ + POINT pt; + RECT rc; + int i; + switch (msg) + { + case WM_CLOSE: + PostQuitMessage(0); + return 0; + case WM_DESTROY: + FrameWnd = NULL; // Blank the handle + break; + case WM_SIZE: + case WM_MOVE: + case WM_SIZING: + UpdateRect(); + if (lock_to_1_1 && FrameRectMy.right - FrameRectMy.left != 0 && + (FrameRectMy.right - FrameRectMy.left != EmuScreenRect.right - EmuScreenRect.left || + FrameRectMy.bottom - FrameRectMy.top != EmuScreenRect.bottom - EmuScreenRect.top)) { + lock_to_1_1 = 0; + CheckMenuItem(mdisplay, 1104, MF_UNCHECKED); + } + break; + case WM_COMMAND: + switch (LOWORD(wparam)) + { + case 1000: + LoadROM(NULL); + break; + case 1001: + emu_reset_game(); + return 0; + case 1002: + PostQuitMessage(0); + return 0; + case 1100: + case 1101: + case 1102: + case 1103: +// LoopWait=1; // another sync hack +// for (i = 0; !LoopWaiting && i < 10; i++) Sleep(10); + FrameRectMy.right = FrameRectMy.left + rect_widths[wparam&3]; + FrameRectMy.bottom = FrameRectMy.top + rect_heights[wparam&3]; + AdjustWindowRect(&FrameRectMy, WS_OVERLAPPEDWINDOW, 1); + MoveWindow(hwnd, FrameRectMy.left, FrameRectMy.top, + FrameRectMy.right-FrameRectMy.left, FrameRectMy.bottom-FrameRectMy.top, 1); + UpdateRect(); + lock_to_1_1 = 0; + CheckMenuItem(mdisplay, 1104, MF_UNCHECKED); +// if (rom_loaded) LoopWait=0; + return 0; + case 1104: + lock_to_1_1 = !lock_to_1_1; + CheckMenuItem(mdisplay, 1104, lock_to_1_1 ? MF_CHECKED : MF_UNCHECKED); + /* FALLTHROUGH */ + case 2000: // EmuScreenRect/FrameRectMy sync request + if (!lock_to_1_1) + return 0; + FrameRectMy.right = FrameRectMy.left + (EmuScreenRect.right - EmuScreenRect.left); + FrameRectMy.bottom = FrameRectMy.top + (EmuScreenRect.bottom - EmuScreenRect.top); + AdjustWindowRect(&FrameRectMy, WS_OVERLAPPEDWINDOW, 1); + MoveWindow(hwnd, FrameRectMy.left, FrameRectMy.top, + FrameRectMy.right-FrameRectMy.left, FrameRectMy.bottom-FrameRectMy.top, 1); + UpdateRect(); + return 0; + case 1210: + case 1211: + i = IsWindowVisible((LOWORD(wparam)&1) ? PicoPadWnd : PicoSwWnd); + i = !i; + ShowWindow((LOWORD(wparam)&1) ? PicoPadWnd : PicoSwWnd, i ? SW_SHOWNA : SW_HIDE); + CheckMenuItem(mpicohw, LOWORD(wparam), i ? MF_CHECKED : MF_UNCHECKED); + return 0; + case 1212: + main_wnd_as_pad = !main_wnd_as_pad; + CheckMenuItem(mpicohw, 1212, main_wnd_as_pad ? MF_CHECKED : MF_UNCHECKED); + return 0; + case 1220: + case 1221: + case 1222: + case 1223: + case 1224: + case 1225: + case 1226: + PicoPicohw.page = LOWORD(wparam) % 10; + for (i = 0; i < 7; i++) + CheckMenuItem(mpicohw, 1220 + i, MF_UNCHECKED); + CheckMenuItem(mpicohw, 1220 + PicoPicohw.page, MF_CHECKED); + InvalidateRect(PicoSwWnd, NULL, 1); + return 0; + case 1300: + MessageBox(FrameWnd, plat_get_credits(), "About", 0); + return 0; + } + break; + case WM_TIMER: + GetCursorPos(&pt); + GetWindowRect(PicoSwWnd, &rc); + if (PtInRect(&rc, pt)) break; + GetWindowRect(PicoPadWnd, &rc); + if (PtInRect(&rc, pt)) break; + PicoPicohw.pen_pos[0] |= 0x8000; + PicoPicohw.pen_pos[1] |= 0x8000; + in_vk_add_pl12 = 0; + break; + case WM_LBUTTONDOWN: in_vk_add_pl12 |= 0x20; return 0; + case WM_LBUTTONUP: in_vk_add_pl12 &= ~0x20; return 0; + case WM_MOUSEMOVE: + if (!main_wnd_as_pad) break; + PicoPicohw.pen_pos[0] = 0x03c + (320 * LOWORD(lparam) / (FrameRectMy.right - FrameRectMy.left)); + PicoPicohw.pen_pos[1] = 0x1fc + (232 * HIWORD(lparam) / (FrameRectMy.bottom - FrameRectMy.top)); + SetTimer(FrameWnd, 100, 1000, NULL); + break; + case WM_KEYDOWN: + if (wparam == VK_TAB) { + emu_reset_game(); + break; + } + if (wparam == VK_ESCAPE) { + LoadROM(NULL); + break; + } + in_vk_keydown(wparam); + break; + case WM_KEYUP: + in_vk_keyup(wparam); + break; + } + + return DefWindowProc(hwnd,msg,wparam,lparam); +} + +static LRESULT CALLBACK PicoSwWndProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam) +{ + PAINTSTRUCT ps; + HDC hdc, hdc2; + + switch (msg) + { + case WM_DESTROY: PicoSwWnd=NULL; break; + case WM_LBUTTONDOWN: in_vk_add_pl12 |= 0x20; return 0; + case WM_LBUTTONUP: in_vk_add_pl12 &= ~0x20; return 0; + case WM_MOUSEMOVE: + if (HIWORD(lparam) < 0x20) break; + PicoPicohw.pen_pos[0] = 0x03c + LOWORD(lparam) * 2/3; + PicoPicohw.pen_pos[1] = 0x2f8 + HIWORD(lparam) - 0x20; + SetTimer(FrameWnd, 100, 1000, NULL); + break; + case WM_KEYDOWN: in_vk_keydown(wparam); break; + case WM_KEYUP: in_vk_keyup(wparam); break; + case WM_PAINT: + hdc = BeginPaint(hwnd, &ps); + if (ppage_bmps[PicoPicohw.page] == NULL) + { + SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT)); + SetTextColor(hdc, RGB(255, 255, 255)); + SetBkColor(hdc, RGB(0, 0, 0)); + TextOut(hdc, 2, 2, "missing PNGs for", 16); + TextOut(hdc, 2, 18, rom_name, strlen(rom_name)); + } + else + { + hdc2 = CreateCompatibleDC(GetDC(FrameWnd)); + SelectObject(hdc2, ppage_bmps[PicoPicohw.page]); + BitBlt(hdc, 0, 0, 480, 240, hdc2, 0, 0, SRCCOPY); + DeleteDC(hdc2); + } + EndPaint(hwnd, &ps); + return 0; + case WM_CLOSE: + ShowWindow(hwnd, SW_HIDE); + CheckMenuItem(mpicohw, 1210, MF_UNCHECKED); + return 0; + } + + return DefWindowProc(hwnd,msg,wparam,lparam); +} + +static LRESULT CALLBACK PicoPadWndProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam) +{ + PAINTSTRUCT ps; + HDC hdc, hdc2; + + switch (msg) + { + case WM_DESTROY: PicoPadWnd=NULL; break; + case WM_LBUTTONDOWN: in_vk_add_pl12 |= 0x20; return 0; + case WM_LBUTTONUP: in_vk_add_pl12 &= ~0x20; return 0; + case WM_MOUSEMOVE: + PicoPicohw.pen_pos[0] = 0x03c + LOWORD(lparam); + PicoPicohw.pen_pos[1] = 0x1fc + HIWORD(lparam); + SetTimer(FrameWnd, 100, 1000, NULL); + break; + case WM_KEYDOWN: in_vk_keydown(wparam); break; + case WM_KEYUP: in_vk_keyup(wparam); break; + case WM_PAINT: + if (ppad_bmp == NULL) break; + hdc = BeginPaint(hwnd, &ps); + hdc2 = CreateCompatibleDC(GetDC(FrameWnd)); + SelectObject(hdc2, ppad_bmp); + BitBlt(hdc, 0, 0, 320, 240, hdc2, 0, 0, SRCCOPY); + EndPaint(hwnd, &ps); + DeleteDC(hdc2); + return 0; + case WM_CLOSE: + ShowWindow(hwnd, SW_HIDE); + CheckMenuItem(mpicohw, 1211, MF_UNCHECKED); + return 0; + } + + return DefWindowProc(hwnd,msg,wparam,lparam); +} + + +static int FrameInit() +{ + WNDCLASS wc; + RECT rect={0,0,0,0}; + HMENU mfile; + int style=0; + int left=0,top=0,width=0,height=0; + + memset(&wc,0,sizeof(wc)); + + // Register the window class: + wc.lpfnWndProc=WndProc; + wc.hInstance=GetModuleHandle(NULL); + wc.hCursor=LoadCursor(NULL,IDC_ARROW); + wc.hbrBackground=CreateSolidBrush(0); + wc.lpszClassName="PicoMainFrame"; + RegisterClass(&wc); + + wc.lpszClassName="PicoSwWnd"; + wc.lpfnWndProc=PicoSwWndProc; + RegisterClass(&wc); + + wc.lpszClassName="PicoPadWnd"; + wc.lpfnWndProc=PicoPadWndProc; + RegisterClass(&wc); + + rect.right =320; + rect.bottom=224; + + // Adjust size of windows based on borders: + style=WS_OVERLAPPEDWINDOW; + AdjustWindowRect(&rect,style,1); + width =rect.right-rect.left; + height=rect.bottom-rect.top; + + // Place window in the centre of the screen: + SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0); + left=rect.left+rect.right; + top=rect.top+rect.bottom; + + left-=width; left>>=1; + top-=height; top>>=1; + + // Create menu: + mfile = CreateMenu(); + InsertMenu(mfile, -1, MF_BYPOSITION|MF_STRING, 1000, "&Load ROM"); + InsertMenu(mfile, -1, MF_BYPOSITION|MF_STRING, 1001, "&Reset"); + InsertMenu(mfile, -1, MF_BYPOSITION|MF_STRING, 1002, "E&xit"); + mdisplay = CreateMenu(); + InsertMenu(mdisplay, -1, MF_BYPOSITION|MF_STRING, 1100, "320x224"); + InsertMenu(mdisplay, -1, MF_BYPOSITION|MF_STRING, 1101, "256x224"); + InsertMenu(mdisplay, -1, MF_BYPOSITION|MF_STRING, 1102, "640x448"); + InsertMenu(mdisplay, -1, MF_BYPOSITION|MF_STRING, 1103, "512x448"); + InsertMenu(mdisplay, -1, MF_BYPOSITION|MF_STRING, 1104, "Lock to 1:1"); + mpicohw = CreateMenu(); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1210, "Show &Storyware"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1211, "Show &Drawing pad"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1212, "&Main window as pad"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_SEPARATOR, 0, NULL); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1220, "Title page (&0)"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1221, "Page &1"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1222, "Page &2"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1223, "Page &3"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1224, "Page &4"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1225, "Page &5"); + InsertMenu(mpicohw, -1, MF_BYPOSITION|MF_STRING, 1226, "Page &6"); + mmain = CreateMenu(); + InsertMenu(mmain, -1, MF_BYPOSITION|MF_STRING|MF_POPUP, (UINT_PTR) mfile, "&File"); + InsertMenu(mmain, -1, MF_BYPOSITION|MF_STRING|MF_POPUP, (UINT_PTR) mdisplay, "&Display"); + InsertMenu(mmain, -1, MF_BYPOSITION|MF_STRING|MF_POPUP, (UINT_PTR) mpicohw, "&Pico"); + EnableMenuItem(mmain, 2, MF_BYPOSITION|MF_GRAYED); +// InsertMenu(mmain, -1, MF_BYPOSITION|MF_STRING|MF_POPUP, 1200, "&Config"); + InsertMenu(mmain, -1, MF_BYPOSITION|MF_STRING, 1300, "&About"); + + // Create the window: + FrameWnd=CreateWindow("PicoMainFrame","PicoDrive " VERSION,style|WS_VISIBLE, + left,top,width,height,NULL,mmain,NULL,NULL); + + CheckMenuItem(mdisplay, 1104, lock_to_1_1 ? MF_CHECKED : MF_UNCHECKED); + ShowWindow(FrameWnd, SW_NORMAL); + UpdateWindow(FrameWnd); + UpdateRect(); + + // create Pico windows + style = WS_OVERLAPPED|WS_CAPTION|WS_BORDER|WS_SYSMENU; + rect.left=rect.top=0; + rect.right =320; + rect.bottom=224; + + AdjustWindowRect(&rect,style,1); + width =rect.right-rect.left; + height=rect.bottom-rect.top; + + left += 326; + PicoSwWnd=CreateWindow("PicoSwWnd","Storyware",style, + left,top,width+160,height,FrameWnd,NULL,NULL,NULL); + + top += 266; + PicoPadWnd=CreateWindow("PicoPadWnd","Drawing Pad",style, + left,top,width,height,FrameWnd,NULL,NULL,NULL); + + return 0; +} + +// -------------------- + +static DWORD WINAPI work_thread(void *x) +{ + while (engineState != PGS_Quit) { + WaitForSingleObject(loop_enter_event, INFINITE); + if (engineState != PGS_Running) + continue; + + printf("loop..\n"); + emu_loop(); + SetEvent(loop_end_event); + } + + return 0; +} + +// XXX: use main.c +void xxinit(void) +{ + /* in_init() must go before config, config accesses in_ fwk */ + in_init(); + emu_prep_defconfig(); + emu_read_config(NULL, 0); + config_readlrom(PicoConfigFile); + + plat_init(); + in_probe(); + + emu_init(); + menu_init(); +} + + +int WINAPI WinMain(HINSTANCE p1, HINSTANCE p2, LPSTR cmdline, int p4) +{ + MSG msg; + DWORD tid = 0; + HANDLE thread; + int ret; + + xxinit(); + FrameInit(); + ret = DirectInit(); + if (ret) + goto end0; + + loop_enter_event = CreateEvent(NULL, 0, 0, NULL); + if (loop_enter_event == NULL) + goto end0; + + loop_end_event = CreateEvent(NULL, 0, 0, NULL); + if (loop_end_event == NULL) + goto end0; + + thread = CreateThread(NULL, 0, work_thread, NULL, 0, &tid); + if (thread == NULL) + goto end0; + + LoadROM(cmdline); + + // Main window loop: + for (;;) + { + GetMessage(&msg,NULL,0,0); + if (msg.message==WM_QUIT) break; + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Signal thread to quit and wait for it to exit: + if (engineState == PGS_Running) { + engineState = PGS_Quit; + WaitForSingleObject(loop_end_event, 5000); + } + CloseHandle(thread); thread=NULL; + + emu_write_config(0); + emu_finish(); + //plat_finish(); + +end0: + DirectExit(); + DestroyWindow(FrameWnd); + +// _CrtDumpMemoryLeaks(); + return 0; +} + diff --git a/platform/win32/main.h b/platform/win32/main.h new file mode 100644 index 0000000..7186b52 --- /dev/null +++ b/platform/win32/main.h @@ -0,0 +1,11 @@ +#ifdef __cplusplus +extern "C" { +#endif + +extern HWND FrameWnd; +extern RECT FrameRectMy; +extern RECT EmuScreenRect; + +#ifdef __cplusplus +} +#endif diff --git a/platform/win32/plat.c b/platform/win32/plat.c new file mode 100644 index 0000000..54af829 --- /dev/null +++ b/platform/win32/plat.c @@ -0,0 +1,253 @@ +/* + * PicoDrive + * (C) notaz, 2009,2010 + * + * This work is licensed under the terms of MAME license. + * See COPYING file in the top-level directory. + */ +#include +#include + +#include "../common/lprintf.h" +#include "../common/plat.h" +#include "../common/emu.h" +#include "../../pico/pico.h" +#include "version.h" +#include "direct.h" +#include "dsnd.h" +#include "main.h" + +static unsigned short screen_buff[320 * 240]; +const char *renderer_names[] = { NULL }; +const char *renderer_names32x[] = { NULL }; + +void plat_init(void) +{ + g_screen_ptr = (void *)screen_buff; +} + +int plat_is_dir(const char *path) +{ + return (GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0; +} + +unsigned int plat_get_ticks_ms(void) +{ + return GetTickCount(); +} + +unsigned int plat_get_ticks_us(void) +{ + // XXX: maybe performance counters? + return GetTickCount() * 1000; +} + +void plat_wait_till_us(unsigned int us) +{ + int msdiff = (int)(us - plat_get_ticks_us()) / 1000; + if (msdiff > 6) + Sleep(msdiff - 6); + while (plat_get_ticks_us() < us) + ; +} + +void plat_sleep_ms(int ms) +{ + Sleep(ms); +} + +int plat_wait_event(int *fds_hnds, int count, int timeout_ms) +{ + return -1; +} + +void pemu_prep_defconfig(void) +{ + memset(&defaultConfig, 0, sizeof(defaultConfig)); + defaultConfig.s_PicoCDBuffers = 0; + defaultConfig.Frameskip = 0; +} + +void pemu_validate_config(void) +{ +} + +void pemu_loop_prep(void) +{ + PicoDrawSetOutFormat(PDF_RGB555, 1); + PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2); + pemu_sound_start(); +} + +void pemu_loop_end(void) +{ + pemu_sound_stop(); +} + +void pemu_forced_frame(int no_scale, int do_emu) +{ +} + +void pemu_finalize_frame(const char *fps, const char *notice_msg) +{ +} + +void plat_video_flip(void) +{ + DirectScreen(g_screen_ptr); + DirectPresent(); +} + +void plat_video_wait_vsync(void) +{ +} + +void plat_video_toggle_renderer(int change, int is_menu) +{ + // this will auto-select SMS/32X renderers + PicoDrawSetOutFormat(PDF_RGB555, 1); +} + +void emu_video_mode_change(int start_line, int line_count, int is_32cols) +{ + EmuScreenRect.left = is_32cols ? 32 : 0; + EmuScreenRect.right = is_32cols ? 256+32 : 320; + EmuScreenRect.top = start_line; + EmuScreenRect.bottom = start_line + line_count; + + PostMessage(FrameWnd, WM_COMMAND, 0x20000 | 2000, 0); +} + +static int sndbuff[2*44100/50/2 + 4]; + +static void update_sound(int len) +{ + /* avoid writing audio when lagging behind to prevent audio lag */ + if (PicoIn.skipFrame != 2) + DSoundUpdate(sndbuff, (currentConfig.EmuOpt & EOPT_NO_FRMLIMIT) ? 0 : 1); +} + +void pemu_sound_start(void) +{ + int ret; + + PicoIn.sndOut = NULL; + currentConfig.EmuOpt &= ~EOPT_EXT_FRMLIMIT; + + // prepare sound stuff + if (currentConfig.EmuOpt & EOPT_EN_SOUND) + { + PsndRerate(0); + + ret = DSoundInit(FrameWnd, PicoIn.sndRate, (PicoIn.opt & POPT_EN_STEREO) ? 1 : 0, Pico.snd.len); + if (ret != 0) { + lprintf("dsound init failed\n"); + return; + } + + PicoIn.sndOut = (void *)sndbuff; + PicoIn.writeSound = update_sound; + currentConfig.EmuOpt |= EOPT_EXT_FRMLIMIT; + } +} + +void pemu_sound_stop(void) +{ + DSoundExit(); +} + +void pemu_sound_wait(void) +{ +} + +int plat_get_root_dir(char *dst, int len) +{ + int ml; + + ml = GetModuleFileName(NULL, dst, len); + while (ml > 0 && dst[ml] != '\\') + ml--; + if (ml != 0) + ml++; + + dst[ml] = 0; + return ml; +} + +void plat_status_msg_busy_first(const char *msg) +{ +} + +void plat_status_msg_busy_next(const char *msg) +{ +} + +void plat_status_msg_clear(void) +{ +} + +void plat_video_menu_enter(int is_rom_loaded) +{ +} + +void plat_video_menu_begin(void) +{ +} + +void plat_video_menu_end(void) +{ +} + +void plat_update_volume(int has_changed, int is_up) +{ +} + +const char *plat_get_credits(void) +{ + return "PicoDrive v" VERSION " minibeta (c) notaz, 2006-2009\n\n" + "Credits:\n" + "fDave: base code of PicoDrive\n" + "Chui: Fame/C\n" + "NJ: CZ80\n" + "MAME devs: YM2612, SN76496 and SH2 cores\n" + "Stéphane Dallongeville: base of Fame/C (C68K), CZ80\n\n" + "Special thanks (ideas, valuable information and stuff):\n" + "Charles MacDonald, Eke, Exophase, Haze, Lordus, Nemesis,\n" + "Pierpaolo Prazzoli, Rokas, Steve Snake, Tasco Deluxe.\n"; +} + +void plat_debug_cat(char *str) +{ +} + +// required by pico +int mp3_get_bitrate(void *f, int size) +{ + return 128; +} + +void mp3_start_play(void *f, int pos) +{ +} + +void mp3_update(int *buffer, int length, int stereo) +{ +} + +// other +void lprintf(const char *fmt, ...) +{ + char buf[512]; + va_list val; + + va_start(val, fmt); + vsnprintf(buf, sizeof(buf), fmt, val); + va_end(val); + OutputDebugString(buf); + printf("%s", buf); +} + +// fake +int alphasort() { return 0; } +int scandir() { return 0; } + diff --git a/platform/win32/readme.txt b/platform/win32/readme.txt new file mode 100644 index 0000000..4069486 --- /dev/null +++ b/platform/win32/readme.txt @@ -0,0 +1,70 @@ + +About +----- + +This is a quick windows port of PicoDrive, a Megadrive / Genesis emulator for +handheld devices. It was originally coded having ARM CPU based devices in mind +(most work was done on GP2X version), but there is also a PSP port. + +The reason I'm sometimes doing windows versions is to show certain emulation +possibilities, first release was to demonstrate SVP emulation (Virtua Racing), +later Pico toy and X-Men 32X prototype. It is not to compete with other +emulators like Kega Fusion and the likes. + +For more info, visit http://notaz.gp2x.de/svp.php + + +Releases +-------- + +1.70 - preliminary 32X emulation, runs X-Men proto. +1.45a - Few bugfixes and additions. +1.45 - Added preliminary Sega Pico emulation. +1.40b - Perspective fix thanks to Pierpaolo Prazzoli's info. +1.40a - Tasco Deluxe's dithering fix. +1.40 - first release. + + +Controls +-------- + +These are currently hardcoded, keyboard only: + +PC Gen/MD Sega Pico +-------+-----------+--------- +Enter: Start +A: A +S: B red button +D: C pen push +Q,W,E: X,Y,Z +TAB: (reset) +Esc: (load ROM) +Arrows: D-pad + +It is possible to change some things in config.cfg (it is created on exit), +but possibilities are limited. + + +Credits +------- + +Vast majority of code written by notaz (notasasatgmailcom). + +A lot of work on making SVP emulation happen was done by Tasco Deluxe, my +stuff is a continuation of his. Pierpaolo Prazzoli's information and his +SSP1610 disassembler in MAME code helped a lot too. + +The original PicoDrive was written by fDave from finalburn.com + +This PicoDrive version uses bits and pieces of from other projects: + +68k: FAME/C core, by Chui and Stéphane Dallongeville (as C68K). +z80: CZ80 by Stéphane Dallongeville and modified by NJ. +YM2612, SN76496 and SH2 cores: MAME devs. + +Special thanks (ideas, valuable information and stuff): +Charles MacDonald, Eke, Exophase, Haze, Lordus, Nemesis, +Pierpaolo Prazzoli, Rokas, Steve Snake, Tasco Deluxe. + +Greets to all the sceners and emu authors out there! +