initial libretro port
[picodrive.git] / platform / libretro.c
CommitLineData
2446536b 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#include <sys/mman.h>
14#ifdef __MACH__
15#include <libkern/OSCacheControl.h>
16#endif
17
18#include <pico/pico_int.h>
19#include "common/input_pico.h"
20#include "common/version.h"
21#include "libretro.h"
22
23static retro_video_refresh_t video_cb;
24static retro_input_poll_t input_poll_cb;
25static retro_input_state_t input_state_cb;
26static retro_environment_t environ_cb;
27static retro_audio_sample_batch_t audio_batch_cb;
28
29static FILE *emu_log;
30
31#define VOUT_MAX_WIDTH 320
32#define VOUT_MAX_HEIGHT 240
33static void *vout_buf;
34static int vout_width, vout_height;
35
36static short __attribute__((aligned(4))) sndBuffer[2*44100/50];
37
38// FIXME: these 2 shouldn't be here
39static unsigned char PicoDraw2FB_[(8+320) * (8+240+8)];
40unsigned char *PicoDraw2FB = PicoDraw2FB_;
41
42static void snd_write(int len);
43
44#ifdef _WIN32
45#define SLASH '\\'
46#else
47#define SLASH '/'
48#endif
49
50/* functions called by the core */
51
52void cache_flush_d_inval_i(void *start, void *end)
53{
54#ifdef __arm__
55#if defined(__BLACKBERRY_QNX__)
56 msync(start, end - start, MS_SYNC | MS_CACHE_ONLY | MS_INVALIDATE_ICACHE);
57#elif defined(__MACH__)
58 size_t len = (char *)end - (char *)start;
59 sys_dcache_flush(start, len);
60 sys_icache_invalidate(start, len);
61#else
62 __clear_cache(start, end);
63#endif
64#endif
65}
66
67void *plat_mmap(unsigned long addr, size_t size, int need_exec, int is_fixed)
68{
69 int flags = MAP_PRIVATE | MAP_ANONYMOUS;
70 void *req, *ret;
71
72 req = (void *)addr;
73 ret = mmap(req, size, PROT_READ | PROT_WRITE, flags, -1, 0);
74 if (ret == MAP_FAILED)
75 return NULL;
76
77 if (addr != 0 && ret != (void *)addr) {
78 lprintf("warning: wanted to map @%08lx, got %p\n",
79 addr, ret);
80
81 if (is_fixed) {
82 munmap(ret, size);
83 return NULL;
84 }
85 }
86
87 return ret;
88}
89
90void *plat_mremap(void *ptr, size_t oldsize, size_t newsize)
91{
92 void *ret = mremap(ptr, oldsize, newsize, 0);
93 if (ret == MAP_FAILED)
94 return NULL;
95
96 return ret;
97}
98
99void plat_munmap(void *ptr, size_t size)
100{
101 if (ptr != NULL)
102 munmap(ptr, size);
103}
104
105void emu_video_mode_change(int start_line, int line_count, int is_32cols)
106{
107 memset(vout_buf, 0, 320 * 240 * 2);
108 vout_width = is_32cols ? 256 : 320;
109 PicoDrawSetOutBuf(vout_buf, vout_width * 2);
110}
111
112void emu_32x_startup(void)
113{
114 PicoDrawSetOutFormat(PDF_RGB555, 1);
115}
116
117#ifndef ANDROID
118
119void lprintf(const char *fmt, ...)
120{
121 va_list list;
122
123 va_start(list, fmt);
124 fprintf(emu_log, "PicoDrive: ");
125 vfprintf(emu_log, fmt, list);
126 va_end(list);
127 fflush(emu_log);
128}
129
130#else
131
132#include <android/log.h>
133
134void lprintf(const char *fmt, ...)
135{
136 va_list list;
137
138 va_start(list, fmt);
139 __android_log_vprint(ANDROID_LOG_INFO, "PicoDrive", fmt, list);
140 va_end(list);
141}
142
143#endif
144
145/* libretro */
146void retro_set_environment(retro_environment_t cb)
147{
148 static const struct retro_variable vars[] = {
149 //{ "region", "Region; Auto|NTSC|PAL" },
150 { NULL, NULL },
151 };
152
153 environ_cb = cb;
154
155 cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars);
156}
157
158void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
159void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
160void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { audio_batch_cb = cb; }
161void retro_set_input_poll(retro_input_poll_t cb) { input_poll_cb = cb; }
162void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; }
163
164unsigned retro_api_version(void)
165{
166 return RETRO_API_VERSION;
167}
168
169void retro_set_controller_port_device(unsigned port, unsigned device)
170{
171}
172
173void retro_get_system_info(struct retro_system_info *info)
174{
175 memset(info, 0, sizeof(*info));
176 info->library_name = "PicoDrive";
177 info->library_version = VERSION;
178 info->valid_extensions = "bin|gen|smd|32x|cue|iso|sms";
179 info->need_fullpath = true;
180}
181
182void retro_get_system_av_info(struct retro_system_av_info *info)
183{
184 memset(info, 0, sizeof(*info));
185 info->timing.fps = Pico.m.pal ? 50 : 60;
186 info->timing.sample_rate = 44100;
187 info->geometry.base_width = 320;
188 info->geometry.base_height = 240;
189 info->geometry.max_width = VOUT_MAX_WIDTH;
190 info->geometry.max_height = VOUT_MAX_HEIGHT;
191 info->geometry.aspect_ratio = 4.0 / 3.0;
192}
193
194/* savestates - TODO */
195size_t retro_serialize_size(void)
196{
197 return 0;
198}
199
200bool retro_serialize(void *data, size_t size)
201{
202 return false;
203}
204
205bool retro_unserialize(const void *data, size_t size)
206{
207 return false;
208}
209
210/* cheats - TODO */
211void retro_cheat_reset(void)
212{
213}
214
215void retro_cheat_set(unsigned index, bool enabled, const char *code)
216{
217}
218
219/* multidisk support */
220static bool disk_ejected;
221static unsigned int disk_current_index;
222static unsigned int disk_count;
223static struct disks_state {
224 char *fname;
225} disks[8];
226
227static bool disk_set_eject_state(bool ejected)
228{
229 // TODO?
230 disk_ejected = ejected;
231 return true;
232}
233
234static bool disk_get_eject_state(void)
235{
236 return disk_ejected;
237}
238
239static unsigned int disk_get_image_index(void)
240{
241 return disk_current_index;
242}
243
244static bool disk_set_image_index(unsigned int index)
245{
246 cd_img_type cd_type;
247 int ret;
248
249 if (index >= sizeof(disks) / sizeof(disks[0]))
250 return false;
251
252 if (disks[index].fname == NULL) {
253 lprintf("missing disk #%u\n", index);
254
255 // RetroArch specifies "no disk" with index == count,
256 // so don't fail here..
257 disk_current_index = index;
258 return true;
259 }
260
261 lprintf("switching to disk %u: \"%s\"\n", index,
262 disks[index].fname);
263
264 ret = -1;
265 cd_type = PicoCdCheck(disks[index].fname, NULL);
266 if (cd_type != CIT_NOT_CD)
267 ret = Insert_CD(disks[index].fname, cd_type);
268 if (ret != 0) {
269 lprintf("Load failed, invalid CD image?\n");
270 return 0;
271 }
272
273 disk_current_index = index;
274 return true;
275}
276
277static unsigned int disk_get_num_images(void)
278{
279 return disk_count;
280}
281
282static bool disk_replace_image_index(unsigned index,
283 const struct retro_game_info *info)
284{
285 bool ret = true;
286
287 if (index >= sizeof(disks) / sizeof(disks[0]))
288 return false;
289
290 if (disks[index].fname != NULL)
291 free(disks[index].fname);
292 disks[index].fname = NULL;
293
294 if (info != NULL) {
295 disks[index].fname = strdup(info->path);
296 if (index == disk_current_index)
297 ret = disk_set_image_index(index);
298 }
299
300 return ret;
301}
302
303static bool disk_add_image_index(void)
304{
305 if (disk_count >= sizeof(disks) / sizeof(disks[0]))
306 return false;
307
308 disk_count++;
309 return true;
310}
311
312static struct retro_disk_control_callback disk_control = {
313 .set_eject_state = disk_set_eject_state,
314 .get_eject_state = disk_get_eject_state,
315 .get_image_index = disk_get_image_index,
316 .set_image_index = disk_set_image_index,
317 .get_num_images = disk_get_num_images,
318 .replace_image_index = disk_replace_image_index,
319 .add_image_index = disk_add_image_index,
320};
321
322static void disk_tray_open(void)
323{
324 lprintf("cd tray open\n");
325 disk_ejected = 1;
326}
327
328static void disk_tray_close(void)
329{
330 lprintf("cd tray close\n");
331 disk_ejected = 0;
332}
333
334
335static const char * const biosfiles_us[] = {
336 "us_scd1_9210", "us_scd2_9306", "SegaCDBIOS9303", "bios_CD_U"
337};
338static const char * const biosfiles_eu[] = {
339 "eu_mcd1_9210", "eu_mcd2_9306", "eu_mcd2_9303", "bios_CD_E"
340};
341static const char * const biosfiles_jp[] = {
342 "jp_mcd1_9112", "jp_mcd1_9111", "bios_CD_J"
343};
344
345static void make_system_path(char *buf, size_t buf_size,
346 const char *name, const char *ext)
347{
348 const char *dir = NULL;
349
350 if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) {
351 snprintf(buf, buf_size, "%s%c%s%s", dir, SLASH, name, ext);
352 }
353 else {
354 snprintf(buf, buf_size, "%s%s", name, ext);
355 }
356}
357
358static const char *find_bios(int *region, const char *cd_fname)
359{
360 const char * const *files;
361 static char path[256];
362 int i, count;
363 FILE *f = NULL;
364
365 if (*region == 4) { // US
366 files = biosfiles_us;
367 count = sizeof(biosfiles_us) / sizeof(char *);
368 } else if (*region == 8) { // EU
369 files = biosfiles_eu;
370 count = sizeof(biosfiles_eu) / sizeof(char *);
371 } else if (*region == 1 || *region == 2) {
372 files = biosfiles_jp;
373 count = sizeof(biosfiles_jp) / sizeof(char *);
374 } else {
375 return NULL;
376 }
377
378 for (i = 0; i < count; i++)
379 {
380 make_system_path(path, sizeof(path), files[i], ".bin");
381 f = fopen(path, "rb");
382 if (f != NULL)
383 break;
384
385 make_system_path(path, sizeof(path), files[i], ".zip");
386 f = fopen(path, "rb");
387 if (f != NULL)
388 break;
389 }
390
391 if (f != NULL) {
392 lprintf("using bios: %s\n", path);
393 fclose(f);
394 return path;
395 }
396
397 return NULL;
398}
399
400bool retro_load_game(const struct retro_game_info *info)
401{
402 enum media_type_e media_type;
403 static char carthw_path[256];
404 size_t i;
405
406 enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_RGB565;
407 if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) {
408 lprintf("RGB565 suppot required, sorry\n");
409 return false;
410 }
411
412 if (info == NULL || info->path == NULL) {
413 lprintf("info->path required\n");
414 return false;
415 }
416
417 for (i = 0; i < sizeof(disks) / sizeof(disks[0]); i++) {
418 if (disks[i].fname != NULL) {
419 free(disks[i].fname);
420 disks[i].fname = NULL;
421 }
422 }
423
424 disk_current_index = 0;
425 disk_count = 1;
426 disks[0].fname = strdup(info->path);
427
428 make_system_path(carthw_path, sizeof(carthw_path), "carthw", ".cfg");
429
430 media_type = PicoLoadMedia(info->path, carthw_path,
431 find_bios, NULL);
432
433 switch (media_type) {
434 case PM_BAD_DETECT:
435 lprintf("Failed to detect ROM/CD image type.\n");
436 return false;
437 case PM_BAD_CD:
438 lprintf("Invalid CD image\n");
439 return false;
440 case PM_BAD_CD_NO_BIOS:
441 lprintf("Missing BIOS\n");
442 return false;
443 case PM_ERROR:
444 lprintf("Load error\n");
445 return false;
446 default:
447 break;
448 }
449
450 PicoLoopPrepare();
451
452 PicoWriteSound = snd_write;
453 memset(sndBuffer, 0, sizeof(sndBuffer));
454 PsndOut = sndBuffer;
455 PsndRerate(1);
456
457 return true;
458}
459
460bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info)
461{
462 return false;
463}
464
465void retro_unload_game(void)
466{
467}
468
469unsigned retro_get_region(void)
470{
471 return Pico.m.pal ? RETRO_REGION_PAL : RETRO_REGION_NTSC;
472}
473
474void *retro_get_memory_data(unsigned id)
475{
476 if (id != RETRO_MEMORY_SAVE_RAM)
477 return NULL;
478
479 if (PicoAHW & PAHW_MCD)
480 return Pico_mcd->bram;
481 else
482 return SRam.data;
483}
484
485size_t retro_get_memory_size(unsigned id)
486{
487 if (id != RETRO_MEMORY_SAVE_RAM)
488 return 0;
489
490 if (PicoAHW & PAHW_MCD)
491 // bram
492 return 0x2000;
493 else
494 return SRam.size;
495}
496
497void retro_reset(void)
498{
499 PicoReset();
500}
501
502static const unsigned short retro_pico_map[] = {
503 [RETRO_DEVICE_ID_JOYPAD_B] = 1 << GBTN_B,
504 [RETRO_DEVICE_ID_JOYPAD_Y] = 1 << GBTN_A,
505 [RETRO_DEVICE_ID_JOYPAD_SELECT] = 1 << GBTN_MODE,
506 [RETRO_DEVICE_ID_JOYPAD_START] = 1 << GBTN_START,
507 [RETRO_DEVICE_ID_JOYPAD_UP] = 1 << GBTN_UP,
508 [RETRO_DEVICE_ID_JOYPAD_DOWN] = 1 << GBTN_DOWN,
509 [RETRO_DEVICE_ID_JOYPAD_LEFT] = 1 << GBTN_LEFT,
510 [RETRO_DEVICE_ID_JOYPAD_RIGHT] = 1 << GBTN_RIGHT,
511 [RETRO_DEVICE_ID_JOYPAD_A] = 1 << GBTN_C,
512 [RETRO_DEVICE_ID_JOYPAD_X] = 1 << GBTN_Y,
513 [RETRO_DEVICE_ID_JOYPAD_L] = 1 << GBTN_X,
514 [RETRO_DEVICE_ID_JOYPAD_R] = 1 << GBTN_Z,
515};
516#define RETRO_PICO_MAP_LEN (sizeof(retro_pico_map) / sizeof(retro_pico_map[0]))
517
518static void snd_write(int len)
519{
520 audio_batch_cb(PsndOut, len / 4);
521}
522
523void retro_run(void)
524{
525 bool updated = false;
526 int pad, i;
527
528 if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
529 ; //update_variables(true);
530
531 input_poll_cb();
532
533 PicoPad[0] = PicoPad[1] = 0;
534 for (pad = 0; pad < 2; pad++)
535 for (i = 0; i < RETRO_PICO_MAP_LEN; i++)
536 if (input_state_cb(pad, RETRO_DEVICE_JOYPAD, 0, i))
537 PicoPad[pad] |= retro_pico_map[i];
538
539 PicoFrame();
540
541 video_cb(vout_buf, vout_width, vout_height, vout_width * 2);
542}
543
544void retro_init(void)
545{
546 int level;
547
548#ifdef IOS
549 emu_log = fopen("/User/Documents/PicoDrive.log", "w");
550 if (emu_log == NULL)
551 emu_log = fopen("PicoDrive.log", "w");
552 if (emu_log == NULL)
553#endif
554 emu_log = stdout;
555
556 level = 0;
557 environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
558
559 environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &disk_control);
560
561 PicoOpt = POPT_EN_STEREO|POPT_EN_FM|POPT_EN_PSG|POPT_EN_Z80
562 | POPT_EN_MCD_PCM|POPT_EN_MCD_CDDA|POPT_EN_SVP_DRC
563 | POPT_ACC_SPRITES|POPT_EN_32X|POPT_EN_PWM
564 | POPT_DIS_32C_BORDER;
565 PsndRate = 44100;
566 PicoAutoRgnOrder = 0x184; // US, EU, JP
567 PicoCDBuffers = 0;
568
569 p32x_msh2_multiplier = MSH2_MULTI_DEFAULT;
570 p32x_ssh2_multiplier = SSH2_MULTI_DEFAULT;
571
572 vout_width = 320;
573 vout_height = 240;
574 vout_buf = malloc(VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2);
575
576 PicoInit();
577 PicoDrawSetOutFormat(PDF_RGB555, 1);
578 PicoDrawSetOutBuf(vout_buf, vout_width * 2);
579
580 //PicoMessage = plat_status_msg_busy_next;
581 PicoMCDopenTray = disk_tray_open;
582 PicoMCDcloseTray = disk_tray_close;
583}
584
585void retro_deinit(void)
586{
587 PicoExit();
588}