(PS3) We shouldn't compile in zlib code in a core period -
[picodrive.git] / platform / libretro / libretro.c
1 /*
2  * libretro core glue for PicoDrive
3  * (C) notaz, 2013
4  *
5  * This work is licensed under the terms of MAME license.
6  * See COPYING file in the top-level directory.
7  */
8
9 #define _GNU_SOURCE 1 // mremap
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <string.h>
13 #ifndef _WIN32
14 #ifndef NO_MMAP
15 #include <sys/mman.h>
16 #endif
17 #else
18 #include <io.h>
19 #include <windows.h>
20 #include <sys/types.h>
21 #endif
22 #include <errno.h>
23 #ifdef __MACH__
24 #include <libkern/OSCacheControl.h>
25 #endif
26
27 #include <pico/pico_int.h>
28 #include <pico/state.h>
29 #include "../common/input_pico.h"
30 #include "../common/version.h"
31 #include "libretro.h"
32
33 static retro_log_printf_t log_cb;
34 static retro_video_refresh_t video_cb;
35 static retro_input_poll_t input_poll_cb;
36 static retro_input_state_t input_state_cb;
37 static retro_environment_t environ_cb;
38 static retro_audio_sample_batch_t audio_batch_cb;
39
40 #define VOUT_MAX_WIDTH 320
41 #define VOUT_MAX_HEIGHT 240
42 static void *vout_buf;
43 static int vout_width, vout_height, vout_offset;
44
45 static short __attribute__((aligned(4))) sndBuffer[2*44100/50];
46
47 static void snd_write(int len);
48
49 #ifdef _WIN32
50 #define SLASH '\\'
51 #else
52 #define SLASH '/'
53 #endif
54
55 /* functions called by the core */
56
57 void cache_flush_d_inval_i(void *start, void *end)
58 {
59 #ifdef __arm__
60 #if defined(__BLACKBERRY_QNX__)
61         msync(start, end - start, MS_SYNC | MS_CACHE_ONLY | MS_INVALIDATE_ICACHE);
62 #elif defined(__MACH__)
63         size_t len = (char *)end - (char *)start;
64         sys_dcache_flush(start, len);
65         sys_icache_invalidate(start, len);
66 #else
67         __clear_cache(start, end);
68 #endif
69 #endif
70 }
71
72 #ifdef _WIN32
73 /* mmap() replacement for Windows
74  *
75  * Author: Mike Frysinger <vapier@gentoo.org>
76  * Placed into the public domain
77  */
78
79 /* References:
80  * CreateFileMapping: http://msdn.microsoft.com/en-us/library/aa366537(VS.85).aspx
81  * CloseHandle:       http://msdn.microsoft.com/en-us/library/ms724211(VS.85).aspx
82  * MapViewOfFile:     http://msdn.microsoft.com/en-us/library/aa366761(VS.85).aspx
83  * UnmapViewOfFile:   http://msdn.microsoft.com/en-us/library/aa366882(VS.85).aspx
84  */
85
86 #define PROT_READ     0x1
87 #define PROT_WRITE    0x2
88 /* This flag is only available in WinXP+ */
89 #ifdef FILE_MAP_EXECUTE
90 #define PROT_EXEC     0x4
91 #else
92 #define PROT_EXEC        0x0
93 #define FILE_MAP_EXECUTE 0
94 #endif
95
96 #define MAP_SHARED    0x01
97 #define MAP_PRIVATE   0x02
98 #define MAP_ANONYMOUS 0x20
99 #define MAP_ANON      MAP_ANONYMOUS
100 #define MAP_FAILED    ((void *) -1)
101
102 #ifdef __USE_FILE_OFFSET64
103 # define DWORD_HI(x) (x >> 32)
104 # define DWORD_LO(x) ((x) & 0xffffffff)
105 #else
106 # define DWORD_HI(x) (0)
107 # define DWORD_LO(x) (x)
108 #endif
109
110 static void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
111 {
112         if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))
113                 return MAP_FAILED;
114         if (fd == -1) {
115                 if (!(flags & MAP_ANON) || offset)
116                         return MAP_FAILED;
117         } else if (flags & MAP_ANON)
118                 return MAP_FAILED;
119
120         DWORD flProtect;
121         if (prot & PROT_WRITE) {
122                 if (prot & PROT_EXEC)
123                         flProtect = PAGE_EXECUTE_READWRITE;
124                 else
125                         flProtect = PAGE_READWRITE;
126         } else if (prot & PROT_EXEC) {
127                 if (prot & PROT_READ)
128                         flProtect = PAGE_EXECUTE_READ;
129                 else if (prot & PROT_EXEC)
130                         flProtect = PAGE_EXECUTE;
131         } else
132                 flProtect = PAGE_READONLY;
133
134         off_t end = length + offset;
135         HANDLE mmap_fd, h;
136         if (fd == -1)
137                 mmap_fd = INVALID_HANDLE_VALUE;
138         else
139                 mmap_fd = (HANDLE)_get_osfhandle(fd);
140         h = CreateFileMapping(mmap_fd, NULL, flProtect, DWORD_HI(end), DWORD_LO(end), NULL);
141         if (h == NULL)
142                 return MAP_FAILED;
143
144         DWORD dwDesiredAccess;
145         if (prot & PROT_WRITE)
146                 dwDesiredAccess = FILE_MAP_WRITE;
147         else
148                 dwDesiredAccess = FILE_MAP_READ;
149         if (prot & PROT_EXEC)
150                 dwDesiredAccess |= FILE_MAP_EXECUTE;
151         if (flags & MAP_PRIVATE)
152                 dwDesiredAccess |= FILE_MAP_COPY;
153         void *ret = MapViewOfFile(h, dwDesiredAccess, DWORD_HI(offset), DWORD_LO(offset), length);
154         if (ret == NULL) {
155                 CloseHandle(h);
156                 ret = MAP_FAILED;
157         }
158         return ret;
159 }
160
161 static void munmap(void *addr, size_t length)
162 {
163         UnmapViewOfFile(addr);
164         /* ruh-ro, we leaked handle from CreateFileMapping() ... */
165 }
166 #elif defined(NO_MMAP)
167 #define PROT_EXEC   0x04
168 #define MAP_FAILED 0
169 #define PROT_READ 0
170 #define PROT_WRITE 0
171 #define MAP_PRIVATE 0
172 #define MAP_ANONYMOUS 0
173
174 void* mmap(void *desired_addr, size_t len, int mmap_prot, int mmap_flags, int fildes, size_t off)
175 {
176    return malloc(len);
177 }
178
179 void munmap(void *base_addr, size_t len)
180 {
181    free(base_addr);
182 }
183
184 int mprotect(void *addr, size_t len, int prot)
185 {
186    /* stub - not really needed at this point since this codepath has no dynarecs */
187    return 0;
188 }
189
190 #endif
191
192 #ifndef MAP_ANONYMOUS
193 #define MAP_ANONYMOUS MAP_ANON
194 #endif
195
196 void *plat_mmap(unsigned long addr, size_t size, int need_exec, int is_fixed)
197 {
198    int flags = MAP_PRIVATE | MAP_ANONYMOUS;
199    void *req, *ret;
200
201    req = (void *)addr;
202    ret = mmap(req, size, PROT_READ | PROT_WRITE, flags, -1, 0);
203    if (ret == MAP_FAILED) {
204       if (log_cb)
205          log_cb(RETRO_LOG_ERROR, "mmap(%08lx, %zd) failed: %d\n", addr, size, errno);
206       return NULL;
207    }
208
209    if (addr != 0 && ret != (void *)addr) {
210       if (log_cb)
211          log_cb(RETRO_LOG_WARN, "warning: wanted to map @%08lx, got %p\n",
212                addr, ret);
213
214       if (is_fixed) {
215          munmap(ret, size);
216          return NULL;
217       }
218    }
219
220         return ret;
221 }
222
223 void *plat_mremap(void *ptr, size_t oldsize, size_t newsize)
224 {
225 #ifdef __linux__
226         void *ret = mremap(ptr, oldsize, newsize, 0);
227         if (ret == MAP_FAILED)
228                 return NULL;
229
230         return ret;
231 #else
232         void *tmp, *ret;
233         size_t preserve_size;
234         
235         preserve_size = oldsize;
236         if (preserve_size > newsize)
237                 preserve_size = newsize;
238         tmp = malloc(preserve_size);
239         if (tmp == NULL)
240                 return NULL;
241         memcpy(tmp, ptr, preserve_size);
242
243         munmap(ptr, oldsize);
244         ret = mmap(ptr, newsize, PROT_READ | PROT_WRITE,
245                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
246         if (ret == MAP_FAILED) {
247                 free(tmp);
248                 return NULL;
249         }
250         memcpy(ret, tmp, preserve_size);
251         free(tmp);
252         return ret;
253 #endif
254 }
255
256 void plat_munmap(void *ptr, size_t size)
257 {
258         if (ptr != NULL)
259                 munmap(ptr, size);
260 }
261
262 int plat_mem_set_exec(void *ptr, size_t size)
263 {
264 #ifdef _WIN32
265    int ret = VirtualProtect(ptr,size,PAGE_EXECUTE_READWRITE,0);
266    if (ret == 0 && log_cb)
267       log_cb(RETRO_LOG_ERROR, "mprotect(%p, %zd) failed: %d\n", ptr, size, 0);
268 #else
269    int ret = mprotect(ptr, size, PROT_READ | PROT_WRITE | PROT_EXEC);
270    if (ret != 0 && log_cb)
271       log_cb(RETRO_LOG_ERROR, "mprotect(%p, %zd) failed: %d\n", ptr, size, errno);
272 #endif
273         return ret;
274 }
275
276 void emu_video_mode_change(int start_line, int line_count, int is_32cols)
277 {
278         memset(vout_buf, 0, 320 * 240 * 2);
279         vout_width = is_32cols ? 256 : 320;
280         PicoDrawSetOutBuf(vout_buf, vout_width * 2);
281
282         vout_height = line_count;
283         vout_offset = vout_width * start_line;
284 }
285
286 void emu_32x_startup(void)
287 {
288 }
289
290 void lprintf(const char *fmt, ...)
291 {
292    char buffer[256];
293    va_list ap;
294    va_start(ap, fmt);
295    vsprintf(buffer, fmt, ap);
296    /* TODO - add 'level' param for warning/error messages? */
297    if (log_cb)
298       log_cb(RETRO_LOG_INFO, "%s\n", fmt);
299 }
300
301 /* libretro */
302 void retro_set_environment(retro_environment_t cb)
303 {
304         static const struct retro_variable vars[] = {
305                 //{ "region", "Region; Auto|NTSC|PAL" },
306                 { "picodrive_input1", "Input device 1; 3 button pad|6 button pad|None" },
307                 { "picodrive_input2", "Input device 2; 3 button pad|6 button pad|None" },
308                 { "picodrive_sprlim", "No sprite limit; disabled|enabled" },
309                 { "picodrive_ramcart", "MegaCD RAM cart; disabled|enabled" },
310 #ifdef DRC_SH2
311                 { "picodrive_drc", "Dynamic recompilers; enabled|disabled" },
312 #endif
313                 { NULL, NULL },
314         };
315
316         environ_cb = cb;
317
318         cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars);
319 }
320
321 void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
322 void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
323 void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { audio_batch_cb = cb; }
324 void retro_set_input_poll(retro_input_poll_t cb) { input_poll_cb = cb; }
325 void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; }
326
327 unsigned retro_api_version(void)
328 {
329         return RETRO_API_VERSION;
330 }
331
332 void retro_set_controller_port_device(unsigned port, unsigned device)
333 {
334 }
335
336 void retro_get_system_info(struct retro_system_info *info)
337 {
338         memset(info, 0, sizeof(*info));
339         info->library_name = "PicoDrive";
340         info->library_version = VERSION;
341         info->valid_extensions = "bin|gen|smd|md|32x|cue|iso|sms";
342         info->need_fullpath = true;
343 }
344
345 void retro_get_system_av_info(struct retro_system_av_info *info)
346 {
347         memset(info, 0, sizeof(*info));
348         info->timing.fps            = Pico.m.pal ? 50 : 60;
349         info->timing.sample_rate    = 44100;
350         info->geometry.base_width   = 320;
351         info->geometry.base_height  = vout_height;
352         info->geometry.max_width    = VOUT_MAX_WIDTH;
353         info->geometry.max_height   = VOUT_MAX_HEIGHT;
354         info->geometry.aspect_ratio = 0.0f;
355 }
356
357 /* savestates */
358 struct savestate_state {
359         const char *load_buf;
360         char *save_buf;
361         size_t size;
362         size_t pos;
363 };
364
365 size_t state_read(void *p, size_t size, size_t nmemb, void *file)
366 {
367         struct savestate_state *state = file;
368         size_t bsize = size * nmemb;
369
370         if (state->pos + bsize > state->size) {
371       if (log_cb)
372          log_cb(RETRO_LOG_ERROR, "savestate error: %u/%u\n",
373                state->pos + bsize, state->size);
374                 bsize = state->size - state->pos;
375                 if ((int)bsize <= 0)
376                         return 0;
377         }
378
379         memcpy(p, state->load_buf + state->pos, bsize);
380         state->pos += bsize;
381         return bsize;
382 }
383
384 size_t state_write(void *p, size_t size, size_t nmemb, void *file)
385 {
386         struct savestate_state *state = file;
387         size_t bsize = size * nmemb;
388
389         if (state->pos + bsize > state->size) {
390       if (log_cb)
391          log_cb(RETRO_LOG_ERROR, "savestate error: %u/%u\n",
392                state->pos + bsize, state->size);
393                 bsize = state->size - state->pos;
394                 if ((int)bsize <= 0)
395                         return 0;
396         }
397
398         memcpy(state->save_buf + state->pos, p, bsize);
399         state->pos += bsize;
400         return bsize;
401 }
402
403 size_t state_skip(void *p, size_t size, size_t nmemb, void *file)
404 {
405         struct savestate_state *state = file;
406         size_t bsize = size * nmemb;
407
408         state->pos += bsize;
409         return bsize;
410 }
411
412 size_t state_eof(void *file)
413 {
414         struct savestate_state *state = file;
415
416         return state->pos >= state->size;
417 }
418
419 int state_fseek(void *file, long offset, int whence)
420 {
421         struct savestate_state *state = file;
422
423         switch (whence) {
424         case SEEK_SET:
425                 state->pos = offset;
426                 break;
427         case SEEK_CUR:
428                 state->pos += offset;
429                 break;
430         case SEEK_END:
431                 state->pos = state->size + offset;
432                 break;
433         }
434         return (int)state->pos;
435 }
436
437 /* savestate sizes vary wildly depending if cd/32x or
438  * carthw is active, so run the whole thing to get size */
439 size_t retro_serialize_size(void) 
440
441         struct savestate_state state = { 0, };
442         int ret;
443
444         ret = PicoStateFP(&state, 1, NULL, state_skip, NULL, state_fseek);
445         if (ret != 0)
446                 return 0;
447
448         return state.pos;
449 }
450
451 bool retro_serialize(void *data, size_t size)
452
453         struct savestate_state state = { 0, };
454         int ret;
455
456         state.save_buf = data;
457         state.size = size;
458         state.pos = 0;
459
460         ret = PicoStateFP(&state, 1, NULL, state_write,
461                 NULL, state_fseek);
462         return ret == 0;
463 }
464
465 bool retro_unserialize(const void *data, size_t size)
466 {
467         struct savestate_state state = { 0, };
468         int ret;
469
470         state.load_buf = data;
471         state.size = size;
472         state.pos = 0;
473
474         ret = PicoStateFP(&state, 0, state_read, NULL,
475                 state_eof, state_fseek);
476         return ret == 0;
477 }
478
479 /* cheats - TODO */
480 void retro_cheat_reset(void)
481 {
482 }
483
484 void retro_cheat_set(unsigned index, bool enabled, const char *code)
485 {
486 }
487
488 /* multidisk support */
489 static bool disk_ejected;
490 static unsigned int disk_current_index;
491 static unsigned int disk_count;
492 static struct disks_state {
493         char *fname;
494 } disks[8];
495
496 static bool disk_set_eject_state(bool ejected)
497 {
498         // TODO?
499         disk_ejected = ejected;
500         return true;
501 }
502
503 static bool disk_get_eject_state(void)
504 {
505         return disk_ejected;
506 }
507
508 static unsigned int disk_get_image_index(void)
509 {
510         return disk_current_index;
511 }
512
513 static bool disk_set_image_index(unsigned int index)
514 {
515         enum cd_img_type cd_type;
516         int ret;
517
518         if (index >= sizeof(disks) / sizeof(disks[0]))
519                 return false;
520
521         if (disks[index].fname == NULL) {
522       if (log_cb)
523          log_cb(RETRO_LOG_ERROR, "missing disk #%u\n", index);
524
525                 // RetroArch specifies "no disk" with index == count,
526                 // so don't fail here..
527                 disk_current_index = index;
528                 return true;
529         }
530
531    if (log_cb)
532       log_cb(RETRO_LOG_INFO, "switching to disk %u: \"%s\"\n", index,
533             disks[index].fname);
534
535         ret = -1;
536         cd_type = PicoCdCheck(disks[index].fname, NULL);
537         if (cd_type != CIT_NOT_CD)
538                 ret = cdd_load(disks[index].fname, cd_type);
539         if (ret != 0) {
540       if (log_cb)
541          log_cb(RETRO_LOG_ERROR, "Load failed, invalid CD image?\n");
542                 return 0;
543         }
544
545         disk_current_index = index;
546         return true;
547 }
548
549 static unsigned int disk_get_num_images(void)
550 {
551         return disk_count;
552 }
553
554 static bool disk_replace_image_index(unsigned index,
555         const struct retro_game_info *info)
556 {
557         bool ret = true;
558
559         if (index >= sizeof(disks) / sizeof(disks[0]))
560                 return false;
561
562         if (disks[index].fname != NULL)
563                 free(disks[index].fname);
564         disks[index].fname = NULL;
565
566         if (info != NULL) {
567                 disks[index].fname = strdup(info->path);
568                 if (index == disk_current_index)
569                         ret = disk_set_image_index(index);
570         }
571
572         return ret;
573 }
574
575 static bool disk_add_image_index(void)
576 {
577         if (disk_count >= sizeof(disks) / sizeof(disks[0]))
578                 return false;
579
580         disk_count++;
581         return true;
582 }
583
584 static struct retro_disk_control_callback disk_control = {
585         .set_eject_state = disk_set_eject_state,
586         .get_eject_state = disk_get_eject_state,
587         .get_image_index = disk_get_image_index,
588         .set_image_index = disk_set_image_index,
589         .get_num_images = disk_get_num_images,
590         .replace_image_index = disk_replace_image_index,
591         .add_image_index = disk_add_image_index,
592 };
593
594 static void disk_tray_open(void)
595 {
596    if (log_cb)
597       log_cb(RETRO_LOG_INFO, "cd tray open\n");
598         disk_ejected = 1;
599 }
600
601 static void disk_tray_close(void)
602 {
603    if (log_cb)
604       log_cb(RETRO_LOG_INFO, "cd tray close\n");
605         disk_ejected = 0;
606 }
607
608
609 static const char * const biosfiles_us[] = {
610         "us_scd2_9306", "SegaCDBIOS9303", "us_scd1_9210", "bios_CD_U"
611 };
612 static const char * const biosfiles_eu[] = {
613         "eu_mcd2_9306", "eu_mcd2_9303", "eu_mcd1_9210", "bios_CD_E"
614 };
615 static const char * const biosfiles_jp[] = {
616         "jp_mcd2_921222", "jp_mcd1_9112", "jp_mcd1_9111", "bios_CD_J"
617 };
618
619 static void make_system_path(char *buf, size_t buf_size,
620         const char *name, const char *ext)
621 {
622         const char *dir = NULL;
623
624         if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) {
625                 snprintf(buf, buf_size, "%s%c%s%s", dir, SLASH, name, ext);
626         }
627         else {
628                 snprintf(buf, buf_size, "%s%s", name, ext);
629         }
630 }
631
632 static const char *find_bios(int *region, const char *cd_fname)
633 {
634         const char * const *files;
635         static char path[256];
636         int i, count;
637         FILE *f = NULL;
638
639         if (*region == 4) { // US
640                 files = biosfiles_us;
641                 count = sizeof(biosfiles_us) / sizeof(char *);
642         } else if (*region == 8) { // EU
643                 files = biosfiles_eu;
644                 count = sizeof(biosfiles_eu) / sizeof(char *);
645         } else if (*region == 1 || *region == 2) {
646                 files = biosfiles_jp;
647                 count = sizeof(biosfiles_jp) / sizeof(char *);
648         } else {
649                 return NULL;
650         }
651
652         for (i = 0; i < count; i++)
653         {
654                 make_system_path(path, sizeof(path), files[i], ".bin");
655                 f = fopen(path, "rb");
656                 if (f != NULL)
657                         break;
658
659                 make_system_path(path, sizeof(path), files[i], ".zip");
660                 f = fopen(path, "rb");
661                 if (f != NULL)
662                         break;
663         }
664
665         if (f != NULL) {
666       if (log_cb)
667          log_cb(RETRO_LOG_INFO, "using bios: %s\n", path);
668                 fclose(f);
669                 return path;
670         }
671
672         return NULL;
673 }
674
675 bool retro_load_game(const struct retro_game_info *info)
676 {
677         enum media_type_e media_type;
678         static char carthw_path[256];
679         size_t i;
680
681         enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_RGB565;
682         if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) {
683       if (log_cb)
684          log_cb(RETRO_LOG_ERROR, "RGB565 support required, sorry\n");
685                 return false;
686         }
687
688         if (info == NULL || info->path == NULL) {
689       if (log_cb)
690          log_cb(RETRO_LOG_ERROR, "info->path required\n");
691                 return false;
692         }
693
694         for (i = 0; i < sizeof(disks) / sizeof(disks[0]); i++) {
695                 if (disks[i].fname != NULL) {
696                         free(disks[i].fname);
697                         disks[i].fname = NULL;
698                 }
699         }
700
701         disk_current_index = 0;
702         disk_count = 1;
703         disks[0].fname = strdup(info->path);
704
705         make_system_path(carthw_path, sizeof(carthw_path), "carthw", ".cfg");
706
707         media_type = PicoLoadMedia(info->path, carthw_path,
708                         find_bios, NULL);
709
710         switch (media_type) {
711         case PM_BAD_DETECT:
712       if (log_cb)
713          log_cb(RETRO_LOG_ERROR, "Failed to detect ROM/CD image type.\n");
714                 return false;
715         case PM_BAD_CD:
716       if (log_cb)
717          log_cb(RETRO_LOG_ERROR, "Invalid CD image\n");
718                 return false;
719         case PM_BAD_CD_NO_BIOS:
720       if (log_cb)
721          log_cb(RETRO_LOG_ERROR, "Missing BIOS\n");
722                 return false;
723         case PM_ERROR:
724       if (log_cb)
725          log_cb(RETRO_LOG_ERROR, "Load error\n");
726                 return false;
727         default:
728                 break;
729         }
730
731         PicoLoopPrepare();
732
733         PicoWriteSound = snd_write;
734         memset(sndBuffer, 0, sizeof(sndBuffer));
735         PsndOut = sndBuffer;
736         PsndRerate(0);
737
738         return true;
739 }
740
741 bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info)
742 {
743         return false;
744 }
745
746 void retro_unload_game(void) 
747 {
748 }
749
750 unsigned retro_get_region(void)
751 {
752         return Pico.m.pal ? RETRO_REGION_PAL : RETRO_REGION_NTSC;
753 }
754
755 void *retro_get_memory_data(unsigned id)
756 {
757         if (id != RETRO_MEMORY_SAVE_RAM)
758                 return NULL;
759
760         if (PicoAHW & PAHW_MCD)
761                 return Pico_mcd->bram;
762         else
763                 return SRam.data;
764 }
765
766 size_t retro_get_memory_size(unsigned id)
767 {
768         unsigned int i;
769         int sum;
770
771         if (id != RETRO_MEMORY_SAVE_RAM)
772                 return 0;
773
774         if (PicoAHW & PAHW_MCD)
775                 // bram
776                 return 0x2000;
777
778         if (Pico.m.frame_count == 0)
779                 return SRam.size;
780
781         // if game doesn't write to sram, don't report it to
782         // libretro so that RA doesn't write out zeroed .srm
783         for (i = 0, sum = 0; i < SRam.size; i++)
784                 sum |= SRam.data[i];
785
786         return (sum != 0) ? SRam.size : 0;
787 }
788
789 void retro_reset(void)
790 {
791         PicoReset();
792 }
793
794 static const unsigned short retro_pico_map[] = {
795         [RETRO_DEVICE_ID_JOYPAD_B]      = 1 << GBTN_B,
796         [RETRO_DEVICE_ID_JOYPAD_Y]      = 1 << GBTN_A,
797         [RETRO_DEVICE_ID_JOYPAD_SELECT] = 1 << GBTN_MODE,
798         [RETRO_DEVICE_ID_JOYPAD_START]  = 1 << GBTN_START,
799         [RETRO_DEVICE_ID_JOYPAD_UP]     = 1 << GBTN_UP,
800         [RETRO_DEVICE_ID_JOYPAD_DOWN]   = 1 << GBTN_DOWN,
801         [RETRO_DEVICE_ID_JOYPAD_LEFT]   = 1 << GBTN_LEFT,
802         [RETRO_DEVICE_ID_JOYPAD_RIGHT]  = 1 << GBTN_RIGHT,
803         [RETRO_DEVICE_ID_JOYPAD_A]      = 1 << GBTN_C,
804         [RETRO_DEVICE_ID_JOYPAD_X]      = 1 << GBTN_Y,
805         [RETRO_DEVICE_ID_JOYPAD_L]      = 1 << GBTN_X,
806         [RETRO_DEVICE_ID_JOYPAD_R]      = 1 << GBTN_Z,
807 };
808 #define RETRO_PICO_MAP_LEN (sizeof(retro_pico_map) / sizeof(retro_pico_map[0]))
809
810 static void snd_write(int len)
811 {
812         audio_batch_cb(PsndOut, len / 4);
813 }
814
815 static enum input_device input_name_to_val(const char *name)
816 {
817         if (strcmp(name, "3 button pad") == 0)
818                 return PICO_INPUT_PAD_3BTN;
819         if (strcmp(name, "6 button pad") == 0)
820                 return PICO_INPUT_PAD_6BTN;
821         if (strcmp(name, "None") == 0)
822                 return PICO_INPUT_NOTHING;
823
824    if (log_cb)
825       log_cb(RETRO_LOG_WARN, "invalid picodrive_input: '%s'\n", name);
826         return PICO_INPUT_PAD_3BTN;
827 }
828
829 static void update_variables(void)
830 {
831         struct retro_variable var;
832
833         var.value = NULL;
834         var.key = "picodrive_input1";
835         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
836                 PicoSetInputDevice(0, input_name_to_val(var.value));
837
838         var.value = NULL;
839         var.key = "picodrive_input2";
840         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
841                 PicoSetInputDevice(1, input_name_to_val(var.value));
842
843         var.value = NULL;
844         var.key = "picodrive_sprlim";
845         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
846                 if (strcmp(var.value, "enabled") == 0)
847                         PicoOpt |= POPT_DIS_SPRITE_LIM;
848                 else
849                         PicoOpt &= ~POPT_DIS_SPRITE_LIM;
850         }
851
852         var.value = NULL;
853         var.key = "picodrive_ramcart";
854         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
855                 if (strcmp(var.value, "enabled") == 0)
856                         PicoOpt |= POPT_EN_MCD_RAMCART;
857                 else
858                         PicoOpt &= ~POPT_EN_MCD_RAMCART;
859         }
860
861 #ifdef DRC_SH2
862         var.value = NULL;
863         var.key = "picodrive_drc";
864         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
865                 if (strcmp(var.value, "enabled") == 0)
866                         PicoOpt |= POPT_EN_DRC;
867                 else
868                         PicoOpt &= ~POPT_EN_DRC;
869         }
870 #endif
871 }
872
873 void retro_run(void) 
874 {
875         bool updated = false;
876         int pad, i;
877
878         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
879                 update_variables();
880
881         input_poll_cb();
882
883         PicoPad[0] = PicoPad[1] = 0;
884         for (pad = 0; pad < 2; pad++)
885                 for (i = 0; i < RETRO_PICO_MAP_LEN; i++)
886                         if (input_state_cb(pad, RETRO_DEVICE_JOYPAD, 0, i))
887                                 PicoPad[pad] |= retro_pico_map[i];
888
889         PicoFrame();
890
891         video_cb((short *)vout_buf + vout_offset,
892                 vout_width, vout_height, vout_width * 2);
893 }
894
895 void retro_init(void)
896 {
897    struct retro_log_callback log;
898         int level;
899
900         level = 0;
901         environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
902
903    if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
904       log_cb = log.log;
905    else
906       log_cb = NULL;
907
908         environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &disk_control);
909
910         PicoOpt = POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80
911                 | POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_MCD_GFX
912                 | POPT_EN_32X|POPT_EN_PWM
913                 | POPT_ACC_SPRITES|POPT_DIS_32C_BORDER;
914 #ifdef __arm__
915         PicoOpt |= POPT_EN_DRC;
916 #endif
917         PsndRate = 44100;
918         PicoAutoRgnOrder = 0x184; // US, EU, JP
919
920         vout_width = 320;
921         vout_height = 240;
922         vout_buf = malloc(VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2);
923
924         PicoInit();
925         PicoDrawSetOutFormat(PDF_RGB555, 0);
926         PicoDrawSetOutBuf(vout_buf, vout_width * 2);
927
928         //PicoMessage = plat_status_msg_busy_next;
929         PicoMCDopenTray = disk_tray_open;
930         PicoMCDcloseTray = disk_tray_close;
931
932         update_variables();
933 }
934
935 void retro_deinit(void)
936 {
937         PicoExit();
938 }