all repos — mgba @ f6755a6e1b7b0cf2b944cd8ca842746f11d6bf82

mGBA Game Boy Advance Emulator

src/platform/psp2/psp2-context.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "psp2-context.h"
  7
  8#include <mgba/core/blip_buf.h>
  9#include <mgba/core/core.h>
 10
 11#ifdef M_CORE_GBA
 12#include <mgba/internal/gba/gba.h>
 13#endif
 14#ifdef M_CORE_GB
 15#include <mgba/internal/gb/gb.h>
 16#endif
 17
 18#include "feature/gui/gui-runner.h"
 19#include <mgba/internal/gba/input.h>
 20
 21#include <mgba-util/memory.h>
 22#include <mgba-util/circle-buffer.h>
 23#include <mgba-util/math.h>
 24#include <mgba-util/threading.h>
 25#include <mgba-util/vfs.h>
 26#include <mgba-util/platform/psp2/sce-vfs.h>
 27
 28#include <psp2/appmgr.h>
 29#include <psp2/audioout.h>
 30#include <psp2/camera.h>
 31#include <psp2/ctrl.h>
 32#include <psp2/display.h>
 33#include <psp2/gxm.h>
 34#include <psp2/kernel/sysmem.h>
 35#include <psp2/motion.h>
 36
 37#include <vita2d.h>
 38
 39#define RUMBLE_PWM 8
 40#define CDRAM_ALIGN 0x40000
 41
 42mLOG_DECLARE_CATEGORY(GUI_PSP2);
 43mLOG_DEFINE_CATEGORY(GUI_PSP2, "Vita", "gui.psp2");
 44
 45static enum ScreenMode {
 46	SM_BACKDROP,
 47	SM_PLAIN,
 48	SM_FULL,
 49	SM_ASPECT,
 50	SM_MAX
 51} screenMode;
 52
 53static void* outputBuffer;
 54static vita2d_texture* tex;
 55static vita2d_texture* oldTex;
 56static vita2d_texture* screenshot;
 57static Thread audioThread;
 58static bool interframeBlending = false;
 59static bool sgbCrop = false;
 60
 61static struct mSceRotationSource {
 62	struct mRotationSource d;
 63	struct SceMotionSensorState state;
 64} rotation;
 65
 66static struct mSceRumble {
 67	struct mRumble d;
 68	struct CircleBuffer history;
 69	int current;
 70} rumble;
 71
 72static struct mSceImageSource {
 73	struct mImageSource d;
 74	SceUID memblock;
 75	void* buffer;
 76	unsigned cam;
 77	size_t bufferOffset;
 78} camera;
 79
 80static struct mAVStream stream;
 81
 82bool frameLimiter = true;
 83
 84extern const uint8_t _binary_backdrop_png_start[];
 85static vita2d_texture* backdrop = 0;
 86
 87#define PSP2_SAMPLES 512
 88#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 16)
 89
 90static struct mPSP2AudioContext {
 91	struct GBAStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE];
 92	size_t writeOffset;
 93	size_t readOffset;
 94	size_t samples;
 95	Mutex mutex;
 96	Condition cond;
 97	bool running;
 98} audioContext;
 99
100void mPSP2MapKey(struct mInputMap* map, int pspKey, int key) {
101	mInputBindKey(map, PSP2_INPUT, __builtin_ctz(pspKey), key);
102}
103
104static THREAD_ENTRY _audioThread(void* context) {
105	struct mPSP2AudioContext* audio = (struct mPSP2AudioContext*) context;
106	uint32_t zeroBuffer[PSP2_SAMPLES] = {0};
107	void* buffer = zeroBuffer;
108	int audioPort = sceAudioOutOpenPort(SCE_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, SCE_AUDIO_OUT_MODE_STEREO);
109	while (audio->running) {
110		MutexLock(&audio->mutex);
111		if (buffer != zeroBuffer) {
112			// Can only happen in successive iterations
113			audio->samples -= PSP2_SAMPLES;
114			ConditionWake(&audio->cond);
115		}
116		if (audio->samples >= PSP2_SAMPLES) {
117			buffer = &audio->buffer[audio->readOffset];
118			audio->readOffset += PSP2_SAMPLES;
119			if (audio->readOffset >= PSP2_AUDIO_BUFFER_SIZE) {
120				audio->readOffset = 0;
121			}
122			// Don't mark samples as read until the next loop iteration to prevent
123			// writing to the buffer while being read (see above)
124		} else {
125			buffer = zeroBuffer;
126		}
127		MutexUnlock(&audio->mutex);
128
129		sceAudioOutOutput(audioPort, buffer);
130	}
131	sceAudioOutReleasePort(audioPort);
132	return 0;
133}
134
135static void _sampleRotation(struct mRotationSource* source) {
136	struct mSceRotationSource* rotation = (struct mSceRotationSource*) source;
137	sceMotionGetSensorState(&rotation->state, 1);
138}
139
140static int32_t _readTiltX(struct mRotationSource* source) {
141	struct mSceRotationSource* rotation = (struct mSceRotationSource*) source;
142	return rotation->state.accelerometer.x * 0x30000000;
143}
144
145static int32_t _readTiltY(struct mRotationSource* source) {
146	struct mSceRotationSource* rotation = (struct mSceRotationSource*) source;
147	return rotation->state.accelerometer.y * -0x30000000;
148}
149
150static int32_t _readGyroZ(struct mRotationSource* source) {
151	struct mSceRotationSource* rotation = (struct mSceRotationSource*) source;
152	return rotation->state.gyro.z * -0x10000000;
153}
154
155static void _setRumble(struct mRumble* source, int enable) {
156	struct mSceRumble* rumble = (struct mSceRumble*) source;
157	rumble->current += enable;
158	if (CircleBufferSize(&rumble->history) == RUMBLE_PWM) {
159		int8_t oldLevel;
160		CircleBufferRead8(&rumble->history, &oldLevel);
161		rumble->current -= oldLevel;
162	}
163	CircleBufferWrite8(&rumble->history, enable);
164	int small = (rumble->current << 21) / 65793;
165	int big = ((rumble->current * rumble->current) << 18) / 65793;
166	struct SceCtrlActuator state = {
167		small,
168		big
169	};
170	sceCtrlSetActuator(1, &state);
171}
172
173static void _resetCamera(struct mSceImageSource* imageSource) {
174	if (!imageSource->cam) {
175		return;
176	}
177
178	sceCameraOpen(imageSource->cam - 1, &(SceCameraInfo) {
179		.size = sizeof(SceCameraInfo),
180		.format = 5, // SCE_CAMERA_FORMAT_ABGR
181		.resolution = SCE_CAMERA_RESOLUTION_176_144,
182		.framerate = SCE_CAMERA_FRAMERATE_30_FPS,
183		.sizeIBase = 176 * 144 * 4,
184		.pitch = 0,
185		.pIBase = imageSource->buffer,
186	});
187	sceCameraStart(imageSource->cam - 1);
188}
189
190static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
191	UNUSED(colorFormats);
192	struct mSceImageSource* imageSource = (struct mSceImageSource*) source;
193
194	if (!imageSource->buffer) {
195		imageSource->memblock = sceKernelAllocMemBlock("camera", SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, CDRAM_ALIGN, NULL);
196		sceKernelGetMemBlockBase(imageSource->memblock, &imageSource->buffer);
197	}
198
199	if (!imageSource->cam) {
200		return;
201	}
202
203	_resetCamera(imageSource);
204	imageSource->bufferOffset = (176 - w) / 2 + (144 - h) * 176 / 2;
205
206	SceCameraRead read = {
207		sizeof(SceCameraRead),
208		1
209	};
210	sceCameraRead(imageSource->cam - 1, &read);
211}
212
213static void _stopRequestImage(struct mImageSource* source) {
214	struct mSceImageSource* imageSource = (struct mSceImageSource*) source;
215	if (imageSource->cam) {
216		sceCameraStop(imageSource->cam - 1);
217		sceCameraClose(imageSource->cam - 1);
218	}
219	sceKernelFreeMemBlock(imageSource->memblock);
220	imageSource->buffer = NULL;
221}
222
223
224static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
225	struct mSceImageSource* imageSource = (struct mSceImageSource*) source;
226
227	if (!imageSource->cam) {
228		memset(imageSource->buffer, 0, 176 * 144 * 4);
229		*buffer = (uint32_t*) imageSource->buffer;
230		*stride = 176;
231		*colorFormat = mCOLOR_XBGR8;
232		return;
233	}
234
235	*buffer = (uint32_t*) imageSource->buffer + imageSource->bufferOffset;
236	*stride = 176;
237	*colorFormat = mCOLOR_XBGR8;
238
239	SceCameraRead read = {
240		sizeof(SceCameraRead),
241		1
242	};
243	sceCameraRead(imageSource->cam - 1, &read);
244}
245
246static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
247	UNUSED(stream);
248	MutexLock(&audioContext.mutex);
249	while (audioContext.samples + PSP2_SAMPLES >= PSP2_AUDIO_BUFFER_SIZE) {
250		if (!frameLimiter) {
251			blip_clear(left);
252			blip_clear(right);
253			MutexUnlock(&audioContext.mutex);
254			return;
255		}
256		ConditionWait(&audioContext.cond, &audioContext.mutex);
257	}
258	struct GBAStereoSample* samples = &audioContext.buffer[audioContext.writeOffset];
259	blip_read_samples(left, &samples[0].left, PSP2_SAMPLES, true);
260	blip_read_samples(right, &samples[0].right, PSP2_SAMPLES, true);
261	audioContext.samples += PSP2_SAMPLES;
262	audioContext.writeOffset += PSP2_SAMPLES;
263	if (audioContext.writeOffset >= PSP2_AUDIO_BUFFER_SIZE) {
264		audioContext.writeOffset = 0;
265	}
266	MutexUnlock(&audioContext.mutex);
267}
268
269uint16_t mPSP2PollInput(struct mGUIRunner* runner) {
270	SceCtrlData pad;
271	sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
272
273	int activeKeys = mInputMapKeyBits(&runner->core->inputMap, PSP2_INPUT, pad.buttons, 0);
274	int angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 0, pad.ly);
275	if (angles != GBA_KEY_NONE) {
276		activeKeys |= 1 << angles;
277	}
278	angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 1, pad.lx);
279	if (angles != GBA_KEY_NONE) {
280		activeKeys |= 1 << angles;
281	}
282	angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 2, pad.ry);
283	if (angles != GBA_KEY_NONE) {
284		activeKeys |= 1 << angles;
285	}
286	angles = mInputMapAxis(&runner->core->inputMap, PSP2_INPUT, 3, pad.rx);
287	if (angles != GBA_KEY_NONE) {
288		activeKeys |= 1 << angles;
289	}
290	return activeKeys;
291}
292
293void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit) {
294	UNUSED(runner);
295	if (!frameLimiter && limit) {
296		MutexLock(&audioContext.mutex);
297		while (audioContext.samples) {
298			ConditionWait(&audioContext.cond, &audioContext.mutex);
299		}
300		MutexUnlock(&audioContext.mutex);
301	}
302	frameLimiter = limit;
303}
304
305void mPSP2Setup(struct mGUIRunner* runner) {
306	mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
307	mCoreLoadForeignConfig(runner->core, &runner->config);
308
309	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CROSS, GBA_KEY_A);
310	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CIRCLE, GBA_KEY_B);
311	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_START, GBA_KEY_START);
312	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_SELECT, GBA_KEY_SELECT);
313	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_UP, GBA_KEY_UP);
314	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN);
315	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT);
316	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT);
317	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, GBA_KEY_L);
318	mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, GBA_KEY_R);
319
320	struct mInputAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 };
321	mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);
322	desc = (struct mInputAxis) { GBA_KEY_RIGHT, GBA_KEY_LEFT, 192, 64 };
323	mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc);
324
325	unsigned width, height;
326	runner->core->desiredVideoDimensions(runner->core, &width, &height);
327	tex = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);
328	oldTex = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);
329	screenshot = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);
330
331	outputBuffer = anonymousMemoryMap(256 * toPow2(height) * 4);
332	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
333	runner->core->setAudioBufferSize(runner->core, PSP2_SAMPLES);
334
335	rotation.d.sample = _sampleRotation;
336	rotation.d.readTiltX = _readTiltX;
337	rotation.d.readTiltY = _readTiltY;
338	rotation.d.readGyroZ = _readGyroZ;
339	runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
340
341	rumble.d.setRumble = _setRumble;
342	CircleBufferInit(&rumble.history, RUMBLE_PWM);
343	runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
344
345	camera.d.startRequestImage = _startRequestImage;
346	camera.d.stopRequestImage = _stopRequestImage;
347	camera.d.requestImage = _requestImage;
348	camera.buffer = NULL;
349	camera.cam = 1;
350	runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d);
351
352
353	stream.videoDimensionsChanged = NULL;
354	stream.postAudioFrame = NULL;
355	stream.postAudioBuffer = _postAudioBuffer;
356	stream.postVideoFrame = NULL;
357	runner->core->setAVStream(runner->core, &stream);
358
359	frameLimiter = true;
360	backdrop = vita2d_load_PNG_buffer(_binary_backdrop_png_start);
361
362	unsigned mode;
363	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
364		screenMode = mode;
365	}
366	if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
367		camera.cam = mode;
368	}
369	int fakeBool;
370	if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
371		sgbCrop = fakeBool;
372	}
373}
374
375void mPSP2LoadROM(struct mGUIRunner* runner) {
376	float rate = 60.0f / 1.001f;
377	sceDisplayGetRefreshRate(&rate);
378	double ratio = GBAAudioCalculateRatio(1, rate, 1);
379	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
380	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
381
382	switch (runner->core->platform(runner->core)) {
383#ifdef M_CORE_GBA
384	case PLATFORM_GBA:
385		if (((struct GBA*) runner->core->board)->memory.hw.devices & (HW_TILT | HW_GYRO)) {
386			sceMotionStartSampling();
387		}
388		break;
389#endif
390#ifdef M_CORE_GB
391	case PLATFORM_GB:
392		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
393			sceMotionStartSampling();
394		}
395		break;
396#endif
397	default:
398		break;
399	}
400
401	int fakeBool;
402	if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
403		interframeBlending = fakeBool;
404	}
405
406	// Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R
407	if (!sceKernelIsPSVitaTV()) {
408		int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2));
409		if (key >= 0) {
410			mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key);
411		}
412		key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2));
413		if (key >= 0) {
414			mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key);
415		}
416	}
417
418	MutexInit(&audioContext.mutex);
419	ConditionInit(&audioContext.cond);
420	memset(audioContext.buffer, 0, sizeof(audioContext.buffer));
421	audioContext.readOffset = 0;
422	audioContext.writeOffset = 0;
423	audioContext.running = true;
424	ThreadCreate(&audioThread, _audioThread, &audioContext);
425}
426
427
428void mPSP2UnloadROM(struct mGUIRunner* runner) {
429	switch (runner->core->platform(runner->core)) {
430#ifdef M_CORE_GBA
431	case PLATFORM_GBA:
432		if (((struct GBA*) runner->core->board)->memory.hw.devices & (HW_TILT | HW_GYRO)) {
433			sceMotionStopSampling();
434		}
435		break;
436#endif
437#ifdef M_CORE_GB
438	case PLATFORM_GB:
439		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
440			sceMotionStopSampling();
441		}
442		break;
443#endif
444	default:
445		break;
446	}
447	audioContext.running = false;
448	ThreadJoin(&audioThread);
449}
450
451void mPSP2Paused(struct mGUIRunner* runner) {
452	UNUSED(runner);
453	struct SceCtrlActuator state = {
454		0,
455		0
456	};
457	sceCtrlSetActuator(1, &state);
458	frameLimiter = true;
459}
460
461void mPSP2Unpaused(struct mGUIRunner* runner) {
462	unsigned mode;
463	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode != screenMode) {
464		screenMode = mode;
465	}
466
467	if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
468		if (mode != camera.cam) {
469			if (camera.buffer) {
470				sceCameraStop(camera.cam - 1);
471				sceCameraClose(camera.cam - 1);
472			}
473			camera.cam = mode;
474			if (camera.buffer) {
475				_resetCamera(&camera);
476			}
477		}
478	}
479
480	int fakeBool;
481	if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
482		interframeBlending = fakeBool;
483	}
484
485	if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
486		sgbCrop = fakeBool;
487	}
488}
489
490void mPSP2Teardown(struct mGUIRunner* runner) {
491	UNUSED(runner);
492	CircleBufferDeinit(&rumble.history);
493	vita2d_free_texture(tex);
494	vita2d_free_texture(oldTex);
495	vita2d_free_texture(screenshot);
496	mappedMemoryFree(outputBuffer, 256 * 256 * 4);
497	frameLimiter = true;
498}
499
500void _drawTex(vita2d_texture* t, unsigned width, unsigned height, bool faded, bool interframe) {
501	unsigned w = width;
502	unsigned h = height;
503	// Get greatest common divisor
504	while (w != 0) {
505		int temp = h % w;
506		h = w;
507		w = temp;
508	}
509	int gcd = h;
510	int aspectw = width / gcd;
511	int aspecth = height / gcd;
512	float scalex;
513	float scaley;
514
515	unsigned tint = 0x1FFFFFFF;
516	if (!faded) {
517		if (interframe) {
518			tint |= 0x60000000;
519		} else {
520			tint |= 0xE0000000;
521		}
522	} else if (!interframe) {
523		tint |= 0x20000000;
524	}
525
526	switch (screenMode) {
527	case SM_BACKDROP:
528	default:
529		vita2d_draw_texture_tint(backdrop, 0, 0, tint);
530		// Fall through
531	case SM_PLAIN:
532		if (sgbCrop && width == 256 && height == 224) {
533			w = 768;
534			h = 672;
535			scalex = 3;
536			scaley = 3;
537			break;
538		}
539		w = 960 / width;
540		h = 544 / height;
541		if (w * height > 544) {
542			scalex = h;
543			w = width * h;
544			h = height * h;
545		} else {
546			scalex = w;
547			w = width * w;
548			h = height * w;
549		}
550		scaley = scalex;
551		break;
552	case SM_ASPECT:
553		if (sgbCrop && width == 256 && height == 224) {
554			w = 967;
555			h = 846;
556			scalex = 34.0f / 9.0f;
557			scaley = scalex;
558			break;
559		}
560		w = 960 / aspectw;
561		h = 544 / aspecth;
562		if (w * aspecth > 544) {
563			w = aspectw * h;
564			h = aspecth * h;
565		} else {
566			w = aspectw * w;
567			h = aspecth * w;
568		}
569		scalex = w / (float) width;
570		scaley = scalex;
571		break;
572	case SM_FULL:
573		w = 960;
574		h = 544;
575		scalex = 960.0f / width;
576		scaley = 544.0f / height;
577		break;
578	}
579	vita2d_draw_texture_tint_part_scale(t,
580	                                    (960.0f - w) / 2.0f, (544.0f - h) / 2.0f,
581	                                    0, 0, width, height,
582	                                    scalex, scaley,
583	                                    tint);
584}
585
586void mPSP2Draw(struct mGUIRunner* runner, bool faded) {
587	unsigned width, height;
588	runner->core->desiredVideoDimensions(runner->core, &width, &height);
589	void* texpixels = vita2d_texture_get_datap(tex);
590	if (interframeBlending) {
591		void* oldTexpixels = vita2d_texture_get_datap(oldTex);
592		memcpy(oldTexpixels, texpixels, 256 * height * 4);
593		_drawTex(oldTex, width, height, faded, false);
594	}
595	memcpy(texpixels, outputBuffer, 256 * height * 4);
596	_drawTex(tex, width, height, faded, interframeBlending);
597}
598
599void mPSP2DrawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, unsigned width, unsigned height, bool faded) {
600	UNUSED(runner);
601	uint32_t* texpixels = vita2d_texture_get_datap(screenshot);
602	unsigned y;
603	for (y = 0; y < height; ++y) {
604		memcpy(&texpixels[256 * y], &pixels[width * y], width * 4);
605	}
606	_drawTex(screenshot, width, height, faded, false);
607}
608
609void mPSP2IncrementScreenMode(struct mGUIRunner* runner) {
610	screenMode = (screenMode + 1) % SM_MAX;
611	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
612}
613
614bool mPSP2SystemPoll(struct mGUIRunner* runner) {
615	SceAppMgrSystemEvent event;
616	if (sceAppMgrReceiveSystemEvent(&event) < 0) {
617		return true;
618	}
619	if (event.systemEvent == SCE_APPMGR_SYSTEMEVENT_ON_RESUME) {
620		mLOG(GUI_PSP2, INFO, "Suspend detected, reloading save");
621		mCoreAutoloadSave(runner->core);
622	}
623	return true;
624}
625
626__attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) {
627	printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line);
628	exit(1);
629}