1 /* mpv media player libretro core
2  * Copyright (C) 2018 Mahyar Koshkouei
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <math.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #ifdef HAVE_LOCALE
25 #include <locale.h>
26 #endif
27 
28 #include <mpv/client.h>
29 #include <mpv/render_gl.h>
30 
31 #include <libretro.h>
32 #ifdef RARCH_INTERNAL
33 #include "internal_cores.h"
34 #define CORE_PREFIX(s) libretro_mpv_##s
35 #else
36 #define CORE_PREFIX(s) s
37 #endif
38 
39 #include "version.h"
40 
41 static struct retro_hw_render_callback hw_render;
42 
43 static struct retro_log_callback logging;
44 static retro_log_printf_t log_cb;
45 
46 static retro_video_refresh_t CORE_PREFIX(video_cb);
47 static retro_audio_sample_t CORE_PREFIX(audio_cb);
48 static retro_audio_sample_batch_t CORE_PREFIX(audio_batch_cb);
49 static retro_environment_t CORE_PREFIX(environ_cb);
50 static retro_input_poll_t CORE_PREFIX(input_poll_cb);
51 static retro_input_state_t CORE_PREFIX(input_state_cb);
52 
53 static mpv_handle *mpv;
54 static mpv_render_context *mpv_gl;
55 
56 /* Save the current playback time for context changes */
57 static int64_t playback_time = 0;
58 
59 /* filepath required globaly as mpv is reopened on context change */
60 static char *filepath = NULL;
61 
62 static volatile int frame_queue = 0;
63 
on_mpv_redraw(void * cb_ctx)64 void on_mpv_redraw(void *cb_ctx)
65 {
66 	frame_queue++;
67 }
68 
fallback_log(enum retro_log_level level,const char * fmt,...)69 static void fallback_log(enum retro_log_level level, const char *fmt, ...)
70 {
71 	(void)level;
72 	va_list va;
73 	va_start(va, fmt);
74 	vfprintf(stderr, fmt, va);
75 	va_end(va);
76 }
77 
78 /**
79  * Process various events triggered by mpv, such as printing log messages.
80  *
81  * \param event_block	Wait until the mpv triggers specified event. Should be
82  *						NULL if no wait is required.
83  */
process_mpv_events(mpv_event_id event_block)84 static void process_mpv_events(mpv_event_id event_block)
85 {
86 	do
87 	{
88 		mpv_event *mp_event = mpv_wait_event(mpv, 0);
89 		if(event_block == MPV_EVENT_NONE &&
90 				mp_event->event_id == MPV_EVENT_NONE)
91 			break;
92 
93 		if(mp_event->event_id == event_block)
94 			event_block = MPV_EVENT_NONE;
95 
96 		if(mp_event->event_id == MPV_EVENT_LOG_MESSAGE)
97 		{
98 			struct mpv_event_log_message *msg =
99 				(struct mpv_event_log_message *)mp_event->data;
100 			log_cb(RETRO_LOG_INFO, "mpv: [%s] %s: %s",
101 					msg->prefix, msg->level, msg->text);
102 		}
103 		else if(mp_event->event_id == MPV_EVENT_END_FILE)
104 		{
105 			struct mpv_event_end_file *eof =
106 				(struct mpv_event_end_file *)mp_event->data;
107 
108 			if(eof->reason == MPV_END_FILE_REASON_EOF)
109 				CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SHUTDOWN, NULL);
110 #if 0
111 			/* The following could be done instead if the file was not
112 			 * closed once the end was reached - allowing the user to seek
113 			 * back without reopening the file.
114 			 */
115 			struct retro_message ra_msg = {
116 				"Finished playing file", 60 * 5, /* 5 seconds */
117 			};
118 
119 			CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE, &ra_msg);RETRO_ENVIRONMENT_SHUTDOWN
120 #endif
121 		}
122 		else if(mp_event->event_id == MPV_EVENT_NONE)
123 			continue;
124 		else
125 		{
126 			log_cb(RETRO_LOG_INFO, "mpv: %s\n",
127 					mpv_event_name(mp_event->event_id));
128 		}
129 	}
130 	while(1);
131 }
132 
get_proc_address_mpv(void * fn_ctx,const char * name)133 static void *get_proc_address_mpv(void *fn_ctx, const char *name)
134 {
135 	/* The "ISO C forbids conversion of function pointer to object pointer
136 	 * type" warning is suppressed.
137 	 */
138 #pragma GCC diagnostic push
139 #pragma GCC diagnostic ignored "-Wpedantic"
140 	void *proc_addr = (void *) hw_render.get_proc_address(name);
141 #pragma GCC diagnostic pop
142 
143 	if(proc_addr == NULL)
144 		log_cb(RETRO_LOG_ERROR, "Failure obtaining %s proc address\n", name);
145 
146 	return proc_addr;
147 }
148 
CORE_PREFIX(retro_init)149 void CORE_PREFIX(retro_init)(void)
150 {
151 	if(mpv_client_api_version() != MPV_CLIENT_API_VERSION)
152 	{
153 		log_cb(RETRO_LOG_WARN, "libmpv version mismatch. Please update or "
154 				"recompile mpv-libretro after updating libmpv.");
155 	}
156 
157 	return;
158 }
159 
CORE_PREFIX(retro_deinit)160 void CORE_PREFIX(retro_deinit)(void)
161 {}
162 
CORE_PREFIX(retro_api_version)163 unsigned CORE_PREFIX(retro_api_version)(void)
164 {
165 	return RETRO_API_VERSION;
166 }
167 
CORE_PREFIX(retro_set_controller_port_device)168 void CORE_PREFIX(retro_set_controller_port_device)(unsigned port, unsigned device)
169 {
170 	log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port);
171 	return;
172 }
173 
CORE_PREFIX(retro_get_system_info)174 void CORE_PREFIX(retro_get_system_info)(struct retro_system_info *info)
175 {
176 	memset(info, 0, sizeof(*info));
177 	info->library_name     = "mpv";
178 	info->library_version  = LIBRETRO_MPV_VERSION;
179 	info->need_fullpath    = true;	/* Allow MPV to load the file on its own */
180 	info->valid_extensions = "264|265|302|669|722|3g2|3gp|aa|aa3|aac|abc|ac3|"
181 		"acm|adf|adp|ads|adx|aea|afc|aix|al|amf|ams|ans|ape|apl|aqt|art|asc|"
182 		"ast|avc|avi|avr|avs|bcstm|bfstm|bin|bit|bmv|brstm|cdata|cdg|cdxl|cgi|"
183 		"cif|daud|dbm|dif|diz|dmf|dsm|dss|dtk|dts|dtshd|dv|eac3|fap|far|flac|"
184 		"flm|flv|fsb|g722|g723_1|g729|genh|gsm|h261|h264|h265|h26l|hevc|ice|"
185 		"idf|idx|ircam|it|itgz|itr|itz|ivr|j2k|lvf|m2a|m3u8|m4a|m4s|m4v|mac|"
186 		"mdgz|mdl|mdr|mdz|med|mid|mj2|mjpeg|mjpg|mk3d|mka|mks|mkv|mlp|mod|mov|"
187 		"mp2|mp3|mp4|mpa|mpc|mpeg|mpegts|mpg|mpl2|mpo|msf|mt2|mtaf|mtm|musx|"
188 		"mvi|mxg|nfo|nist|nut|oga|ogg|ogv|okt|oma|omg|paf|pjs|psm|ptm|pvf|qcif|"
189 		"rco|rgb|rsd|rso|rt|s3gz|s3m|s3r|s3z|sami|sb|sbg|scc|sdr2|sds|sdx|sf|"
190 		"shn|sln|smi|son|sph|ss2|stl|stm|str|sub|sup|svag|sw|tak|tco|thd|ts|"
191 		"tta|txt|ub|ul|ult|umx|uw|v|v210|vag|vb|vc1|viv|vob|vpk|vqe|vqf|vql|vt|"
192 		"vtt|wav|wsd|xl|xm|xmgz|xmr|xmv|xmz|xvag|y4m|yop|yuv|yuv10";
193 }
194 
CORE_PREFIX(retro_get_system_av_info)195 void CORE_PREFIX(retro_get_system_av_info)(struct retro_system_av_info *info)
196 {
197 	float sampling_rate = 48000.0f;
198 
199 #if 0
200 	struct retro_variable var = { .key = "test_aspect" };
201 	CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE, &var);
202 
203 	var.key = "test_samplerate";
204 
205 	if(CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
206 		sampling_rate = strtof(var.value, NULL);
207 #endif
208 	info->timing = (struct retro_system_timing) {
209 		.fps = 60.0,
210 		.sample_rate = sampling_rate,
211 	};
212 
213 	/* We don't know the dimensions of the video yet, so we set some good
214 	 * defaults in the meantime.
215 	 */
216 	info->geometry = (struct retro_game_geometry) {
217 		.base_width   = 256,
218 		.base_height  = 144,
219 		.max_width    = 1920,
220 		.max_height   = 1080,
221 		.aspect_ratio = -1,
222 	};
223 }
224 
CORE_PREFIX(retro_set_environment)225 void CORE_PREFIX(retro_set_environment)(retro_environment_t cb)
226 {
227 	CORE_PREFIX(environ_cb) = cb;
228 
229 	static const struct retro_variable vars[] = {
230 #if 0
231 		{ "test_samplerate", "Sample Rate; 48000" },
232 		{ "test_opt0", "Test option #0; false|true" },
233 		{ "test_opt1", "Test option #1; 0" },
234 		{ "test_opt2", "Test option #2; 0|1|foo|3" },
235 #endif
236 		{ NULL, NULL },
237 	};
238 
239 	cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)vars);
240 
241 	if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging))
242 		log_cb = logging.log;
243 	else
244 		log_cb = fallback_log;
245 }
246 
context_reset(void)247 static void context_reset(void)
248 {
249 	const char *cmd[] = {"loadfile", filepath, NULL};
250 	int ret;
251 
252 #ifdef HAVE_LOCALE
253 	setlocale(LC_NUMERIC, "C");
254 #endif
255 
256 	mpv = mpv_create();
257 
258 	if(!mpv)
259 	{
260 		log_cb(RETRO_LOG_ERROR, "failed creating context\n");
261 		exit(EXIT_FAILURE);
262 	}
263 
264 	if((ret = mpv_initialize(mpv)) < 0)
265 	{
266 		log_cb(RETRO_LOG_ERROR, "mpv init failed: %s\n", mpv_error_string(ret));
267 		exit(EXIT_FAILURE);
268 	}
269 
270 	if((ret = mpv_request_log_messages(mpv, "v")) < 0)
271 	{
272 		log_cb(RETRO_LOG_ERROR, "mpv logging failed: %s\n",
273 				mpv_error_string(ret));
274 	}
275 
276 	mpv_render_param params[] = {
277 		{MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL},
278 		{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &(mpv_opengl_init_params){
279 			.get_proc_address = get_proc_address_mpv,
280 		}},
281 		{0}
282 	};
283 
284 	if((ret = mpv_render_context_create(&mpv_gl, mpv, params)) < 0)
285 	{
286 		log_cb(RETRO_LOG_ERROR, "failed to initialize mpv GL context: %s\n",
287 				mpv_error_string(ret));
288 		goto err;
289 	}
290 
291 	mpv_render_context_set_update_callback(mpv_gl, on_mpv_redraw, NULL);
292 
293 	mpv_set_option_string(mpv, "ao", "libmpv");
294 
295 	/* Attempt to enable hardware acceleration. MPV will fallback to software
296 	 * decoding on failure.
297 	 */
298 	if((ret = mpv_set_option_string(mpv, "hwdec", "auto")) < 0)
299 	{
300 		log_cb(RETRO_LOG_ERROR, "failed to set hwdec option: %s\n",
301 				mpv_error_string(ret));
302 	}
303 
304 	if((ret = mpv_command(mpv, cmd)) != 0)
305 	{
306 		log_cb(RETRO_LOG_ERROR, "mpv_command failed to load input file: %s\n",
307 				mpv_error_string(ret));
308 		goto err;
309 	}
310 
311 	/* TODO #2: Check for the highest samplerate in audio stream, and use that.
312 	 * Fall back to 48kHz otherwise.
313 	 * We currently force the audio to be sampled at 48KHz.
314 	 */
315 	mpv_set_option_string(mpv, "af", "format=s16:48000:stereo");
316 	//mpv_set_option_string(mpv, "opengl-swapinterval", "0");
317 
318 	/* Process any events whilst we wait for playback to begin. */
319 	process_mpv_events(MPV_EVENT_NONE);
320 
321 	/* Keep trying until mpv accepts the property. This is done to seek to the
322 	 * point in the file after the previous context was destroyed. If no
323 	 * context was destroyed previously, the file seeks to 0.
324 	 *
325 	 * This also seems to fix some black screen issues.
326 	 */
327 	if(playback_time != 0)
328 	{
329 		process_mpv_events(MPV_EVENT_PLAYBACK_RESTART);
330 		while(mpv_set_property(mpv,
331 					"playback-time", MPV_FORMAT_INT64, &playback_time) < 0)
332 		{}
333 	}
334 
335 	/* The following works best when vsync is switched off in Retroarch. */
336 	//mpv_set_option_string(mpv, "video-sync", "display-resample");
337 	//mpv_set_option_string(mpv, "display-fps", "60");
338 
339 	log_cb(RETRO_LOG_INFO, "Context reset.\n");
340 
341 	return;
342 
343 err:
344 	/* Print mpv logs to see why mpv failed. */
345 	process_mpv_events(MPV_EVENT_NONE);
346 	exit(EXIT_FAILURE);
347 }
348 
context_destroy(void)349 static void context_destroy(void)
350 {
351 	mpv_get_property(mpv, "playback-time", MPV_FORMAT_INT64, &playback_time);
352 	mpv_render_context_free(mpv_gl);
353 	mpv_terminate_destroy(mpv);
354 	log_cb(RETRO_LOG_INFO, "Context destroyed.\n");
355 }
356 
retro_init_hw_context(void)357 static bool retro_init_hw_context(void)
358 {
359 #if defined(HAVE_OPENGLES)
360 	hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
361 #else
362 	hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
363 #endif
364 	hw_render.context_reset = context_reset;
365 	hw_render.context_destroy = context_destroy;
366 
367 	if (!CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
368 		return false;
369 
370 	return true;
371 }
372 
CORE_PREFIX(retro_set_audio_sample)373 void CORE_PREFIX(retro_set_audio_sample)(retro_audio_sample_t cb)
374 {
375 	CORE_PREFIX(audio_cb) = cb;
376 }
377 
CORE_PREFIX(retro_set_audio_sample_batch)378 void CORE_PREFIX(retro_set_audio_sample_batch)(retro_audio_sample_batch_t cb)
379 {
380 	CORE_PREFIX(audio_batch_cb) = cb;
381 }
382 
CORE_PREFIX(retro_set_input_poll)383 void CORE_PREFIX(retro_set_input_poll)(retro_input_poll_t cb)
384 {
385 	CORE_PREFIX(input_poll_cb) = cb;
386 }
387 
CORE_PREFIX(retro_set_input_state)388 void CORE_PREFIX(retro_set_input_state)(retro_input_state_t cb)
389 {
390 	CORE_PREFIX(input_state_cb) = cb;
391 }
392 
CORE_PREFIX(retro_set_video_refresh)393 void CORE_PREFIX(retro_set_video_refresh)(retro_video_refresh_t cb)
394 {
395 	CORE_PREFIX(video_cb) = cb;
396 }
397 
CORE_PREFIX(retro_reset)398 void CORE_PREFIX(retro_reset)(void)
399 {
400 }
401 
audio_callback(double fps)402 static void audio_callback(double fps)
403 {
404 	/* Obtain len samples to reduce lag. */
405 	int len = 48000 / (int)floor(fps);
406     const unsigned int buff_len = 1024;
407     uint8_t buff[buff_len];
408 
409     if(len < 4)
410         len = 4;
411 
412     do
413 	{
414         len = len - (len % 4);
415 
416         int ret = mpv_audio_callback(mpv, &buff,
417                 len > buff_len ? buff_len : len);
418 
419         if(ret < 0)
420         {
421 #if 0
422             log_cb(RETRO_LOG_ERROR, "mpv encountered an error in audio "
423                     "callback: %d.\n", ret);
424 #endif
425             return;
426         }
427 
428         /* mpv may refuse to buffer audio if the first video frame has not
429          * displayed yet. */
430         if(ret == 0)
431             return;
432 
433 		len -= ret;
434 
435 		CORE_PREFIX(audio_batch_cb)((const int16_t*)buff, ret);
436 	}
437 	while(len > 4);
438 }
439 
retropad_update_input(void)440 static void retropad_update_input(void)
441 {
442 	struct Input
443 	{
444 		unsigned int l : 1;
445 		unsigned int r : 1;
446 		unsigned int a : 1;
447 	};
448 	struct Input current;
449 	static struct Input last;
450 
451 	CORE_PREFIX(input_poll_cb)();
452 
453 	/* Commands that are called on rising edge only */
454 
455 	/* A ternary operator is used since input_state_cb returns an int16_t, but
456 	 * we only care about whether the button is on or off which is why we store
457 	 * the value in a single bit for each button.
458 	 *
459 	 * Unsure if saving the memory is worth the extra checks, costing CPU time,
460 	 * but both are incredibly miniscule anyway.
461 	 */
462 	current.l = CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD,
463 			0, RETRO_DEVICE_ID_JOYPAD_L) != 0 ? 1 : 0;
464 	current.r = CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD,
465 			0, RETRO_DEVICE_ID_JOYPAD_R) != 0 ? 1 : 0;
466 	current.a = CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD,
467 			0, RETRO_DEVICE_ID_JOYPAD_A) != 0 ? 1 : 0;
468 
469 	if(current.l == 1 && last.l == 0)
470 		mpv_command_string(mpv, "cycle audio");
471 
472 	if(current.r == 1 && last.r == 0)
473 		mpv_command_string(mpv, "cycle sub");
474 
475 	if(current.a == 1 && last.a == 0)
476 		mpv_command_string(mpv, "cycle pause");
477 
478 	/* TODO #3: Press and hold commands with small delay */
479 	if (CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD, 0,
480 			RETRO_DEVICE_ID_JOYPAD_LEFT))
481 		mpv_command_string(mpv, "seek -5");
482 
483 	if (CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD, 0,
484 			RETRO_DEVICE_ID_JOYPAD_RIGHT))
485 		mpv_command_string(mpv, "seek 5");
486 
487 	if (CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD, 0,
488 			RETRO_DEVICE_ID_JOYPAD_UP))
489 		mpv_command_string(mpv, "seek 60");
490 
491 	if (CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD, 0,
492 			RETRO_DEVICE_ID_JOYPAD_DOWN))
493 		mpv_command_string(mpv, "seek -60");
494 
495 	/* Press and hold commands */
496 	if (CORE_PREFIX(input_state_cb)(0, RETRO_DEVICE_JOYPAD, 0,
497 			RETRO_DEVICE_ID_JOYPAD_X))
498 		mpv_command_string(mpv, "show-progress");
499 
500 	/* Instead of copying the structs as though they were a union, we assign
501 	 * each variable one-by-one to avoid endian issues.
502 	 */
503 	last.l = current.l;
504 	last.r = current.r;
505 	last.a = current.a;
506 }
507 
CORE_PREFIX(retro_run)508 void CORE_PREFIX(retro_run)(void)
509 {
510 	/* We only need to update the base video size once, and we do it here since
511 	 * the input file is not processed during the first
512 	 * retro_get_system_av_info() call.
513 	 */
514 	static bool updated_video_dimensions = false;
515 	static int64_t width = 0, height = 0;
516 	static double container_fps = 30.0f;
517 
518 	if(updated_video_dimensions == false)
519 	{
520 		mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &width);
521 		mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &height);
522 		mpv_get_property(mpv, "container-fps", MPV_FORMAT_DOUBLE, &container_fps);
523 
524 		/* We don't know the dimensions of the video when
525 		 * retro_get_system_av_info is called, so we have to set it here for
526 		 * the correct aspect ratio.
527 		 * This is not a temporary change
528 		 */
529 		struct retro_game_geometry geometry = {
530 			.base_width   = width,
531 			.base_height  = height,
532 			/* max_width and max_height are ignored */
533 			.max_width    = width,
534 			.max_height   = height,
535 			/* Aspect ratio calculated automatically from base dimensions */
536 			.aspect_ratio = -1,
537 		};
538 
539 		log_cb(RETRO_LOG_INFO, "Setting fps to %f\n", container_fps);
540 
541 		struct retro_system_timing timing = {
542 			.fps = container_fps,
543 			.sample_rate = 48000.0f,
544 		};
545 
546 		struct retro_system_av_info av_info = {
547 			.geometry = geometry,
548 			.timing = timing,
549 		};
550 
551 		if(width > 0 && height > 0)
552 			CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av_info);
553 
554 		updated_video_dimensions = true;
555 	}
556 
557 	retropad_update_input();
558 
559 	/* TODO #2: Implement an audio callback feature in to libmpv */
560 	audio_callback(container_fps);
561 
562 	process_mpv_events(MPV_EVENT_NONE);
563 
564 #if 1
565 	if(frame_queue > 0)
566 	{
567 		mpv_render_param params[] = {
568 			{MPV_RENDER_PARAM_OPENGL_FBO, &(mpv_opengl_fbo){
569 				.fbo = hw_render.get_current_framebuffer(),
570 				.w = width,
571 				.h = height,
572 			}},
573 			{0}
574 		};
575 		mpv_render_context_render(mpv_gl, params);
576 		CORE_PREFIX(video_cb)(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0);
577 		frame_queue--;
578 	}
579 	else
580 		CORE_PREFIX(video_cb)(NULL, width, height, 0);
581 #else
582 	mpv_opengl_cb_draw(mpv_gl, hw_render.get_current_framebuffer(), width, height);
583 	CORE_PREFIX(video_cb)(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0);
584 #endif
585 
586 	return;
587 }
588 
589 /* No save-state support */
CORE_PREFIX(retro_serialize_size)590 size_t CORE_PREFIX(retro_serialize_size)(void)
591 {
592 	return 0;
593 }
594 
CORE_PREFIX(retro_serialize)595 bool CORE_PREFIX(retro_serialize)(void *data_, size_t size)
596 {
597 	return true;
598 }
599 
CORE_PREFIX(retro_unserialize)600 bool CORE_PREFIX(retro_unserialize)(const void *data_, size_t size)
601 {
602 	return true;
603 }
604 
CORE_PREFIX(retro_load_game)605 bool CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
606 {
607 	/* Supported on most systems. */
608 	enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
609 	struct retro_input_descriptor desc[] = {
610 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A,  "Pause/Play" },
611 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X,  "Show Progress" },
612 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT,  "Seek -5 seconds" },
613 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP,    "Seek +60 seconds" },
614 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN,  "Seek -60 seconds" },
615 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Seek +5 seconds" },
616 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L,     "Cycle Audio Track" },
617 		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Cycle Subtitle Track" },
618 
619 		{ 0 },
620 	};
621 
622 	if(info->path == NULL)
623 		return false;
624 
625 	/* Copy the file path to a global variable as we need it in context_reset()
626 	 * where mpv is initialised.
627 	 */
628 	if((filepath = malloc(strlen(info->path)+1)) == NULL)
629 	{
630 		log_cb(RETRO_LOG_ERROR, "Unable to allocate memory for filepath\n");
631 		return false;
632 	}
633 
634 	strcpy(filepath,info->path);
635 
636 	CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
637 
638 	/* Not bothered if this fails. Assuming the default is selected anyway. */
639 	if(CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt) == false)
640 	{
641 		log_cb(RETRO_LOG_ERROR, "XRGB8888 is not supported.\n");
642 
643 		/* Try RGB565 */
644 		fmt = RETRO_PIXEL_FORMAT_RGB565;
645 		CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt);
646 	}
647 
648 	if(retro_init_hw_context() == false)
649 	{
650 		log_cb(RETRO_LOG_ERROR, "HW Context could not be initialized\n");
651 		return false;
652 	}
653 
654 	return true;
655 }
656 
CORE_PREFIX(retro_load_game_special)657 bool CORE_PREFIX(retro_load_game_special)(unsigned type, const struct retro_game_info *info,
658 		size_t num)
659 {
660 	return false;
661 }
662 
CORE_PREFIX(retro_unload_game)663 void CORE_PREFIX(retro_unload_game)(void)
664 {
665 	free(filepath);
666 	filepath = NULL;
667 }
668 
CORE_PREFIX(retro_get_region)669 unsigned CORE_PREFIX(retro_get_region)(void)
670 {
671 	return RETRO_REGION_NTSC;
672 }
673 
CORE_PREFIX(retro_get_memory_data)674 void *CORE_PREFIX(retro_get_memory_data)(unsigned id)
675 {
676 	return NULL;
677 }
678 
CORE_PREFIX(retro_get_memory_size)679 size_t CORE_PREFIX(retro_get_memory_size)(unsigned id)
680 {
681 	return 0;
682 }
683 
CORE_PREFIX(retro_cheat_reset)684 void CORE_PREFIX(retro_cheat_reset)(void)
685 {}
686 
CORE_PREFIX(retro_cheat_set)687 void CORE_PREFIX(retro_cheat_set)(unsigned index, bool enabled, const char *code)
688 {
689    (void)index;
690    (void)enabled;
691    (void)code;
692 }
693