1120 lines
31 KiB
C
1120 lines
31 KiB
C
/* Copyright (c) 2013-2018 Jeffrey Pfau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#include "feature/gui/gui-runner.h"
|
|
#include <mgba/core/core.h>
|
|
#include <mgba/internal/gb/video.h>
|
|
#include <mgba/internal/gba/audio.h>
|
|
#include <mgba/internal/gba/input.h>
|
|
#include <mgba-util/gui.h>
|
|
#include <mgba-util/gui/font.h>
|
|
#include <mgba-util/gui/menu.h>
|
|
#include <mgba-util/vfs.h>
|
|
|
|
#include <malloc.h>
|
|
#include <switch.h>
|
|
#include <EGL/egl.h>
|
|
#include <GLES3/gl3.h>
|
|
#include <GLES3/gl31.h>
|
|
|
|
#define AUTO_INPUT 0x4E585031
|
|
#define SAMPLES 0x400
|
|
#define N_BUFFERS 6
|
|
#define ANALOG_DEADZONE 0x4000
|
|
|
|
TimeType __nx_time_type = TimeType_UserSystemClock;
|
|
|
|
static EGLDisplay s_display;
|
|
static EGLContext s_context;
|
|
static EGLSurface s_surface;
|
|
|
|
static const GLfloat _offsets[] = {
|
|
0.f, 0.f,
|
|
1.f, 0.f,
|
|
1.f, 1.f,
|
|
0.f, 1.f,
|
|
};
|
|
|
|
static const GLchar* const _gles2Header =
|
|
"#version 100\n"
|
|
"precision mediump float;\n";
|
|
|
|
static const char* const _vertexShader =
|
|
"attribute vec2 offset;\n"
|
|
"uniform vec2 dims;\n"
|
|
"uniform vec2 insize;\n"
|
|
"varying vec2 texCoord;\n"
|
|
|
|
"void main() {\n"
|
|
" vec2 ratio = insize;\n"
|
|
" vec2 scaledOffset = offset * dims;\n"
|
|
" gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n"
|
|
" texCoord = offset * ratio;\n"
|
|
"}";
|
|
|
|
static const char* const _fragmentShader =
|
|
"varying vec2 texCoord;\n"
|
|
"uniform sampler2D tex;\n"
|
|
"uniform vec4 color;\n"
|
|
|
|
"void main() {\n"
|
|
" vec4 texColor = vec4(texture2D(tex, texCoord).rgb, 1.0);\n"
|
|
" texColor *= color;\n"
|
|
" gl_FragColor = texColor;\n"
|
|
"}";
|
|
|
|
static GLuint program;
|
|
static GLuint vbo;
|
|
static GLuint vao;
|
|
static GLuint pbo;
|
|
static GLuint copyFbo;
|
|
static GLuint texLocation;
|
|
static GLuint dimsLocation;
|
|
static GLuint insizeLocation;
|
|
static GLuint colorLocation;
|
|
static GLuint tex;
|
|
static GLuint oldTex;
|
|
static GLuint screenshotTex;
|
|
|
|
static struct GUIFont* font;
|
|
static mColor* frameBuffer;
|
|
static struct mAVStream stream;
|
|
static struct mSwitchRumble {
|
|
struct mRumbleIntegrator d;
|
|
HidVibrationValue value;
|
|
} rumble;
|
|
static struct mRotationSource rotation = {0};
|
|
static AudioDriver audrenDriver;
|
|
static AudioDriverWaveBuf audrvBuffer[N_BUFFERS];
|
|
static struct mStereoSample* audioBuffer[N_BUFFERS];
|
|
static double fpsRatio = 1;
|
|
static bool frameLimiter = true;
|
|
static unsigned framecount = 0;
|
|
static unsigned framecap = 10;
|
|
static PadState pad;
|
|
static HidSixAxisSensorHandle sixaxisHandles[4];
|
|
static HidVibrationDeviceHandle vibrationDeviceHandles[4];
|
|
static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f };
|
|
static bool usePbo = true;
|
|
static u8 vmode;
|
|
static u32 vwidth;
|
|
static u32 vheight;
|
|
static bool interframeBlending = false;
|
|
static bool sgbCrop = false;
|
|
static bool useLightSensor = true;
|
|
static struct mGUIRunnerLux lightSensor;
|
|
static float gyroZ = 0;
|
|
static float tiltX = 0;
|
|
static float tiltY = 0;
|
|
|
|
static enum ScreenMode {
|
|
SM_PA,
|
|
SM_AF,
|
|
SM_SF,
|
|
SM_MAX
|
|
} screenMode = SM_PA;
|
|
|
|
static enum FilterMode {
|
|
FM_NEAREST,
|
|
FM_LINEAR,
|
|
FM_MAX
|
|
} filterMode = FM_NEAREST;
|
|
|
|
static bool eglInit() {
|
|
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if (!s_display) {
|
|
goto _fail0;
|
|
}
|
|
|
|
eglInitialize(s_display, NULL, NULL);
|
|
|
|
EGLConfig config;
|
|
EGLint numConfigs;
|
|
static const EGLint attributeList[] = {
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_NONE
|
|
};
|
|
eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
|
|
if (!numConfigs) {
|
|
goto _fail1;
|
|
}
|
|
|
|
s_surface = eglCreateWindowSurface(s_display, config, nwindowGetDefault(), NULL);
|
|
if (!s_surface) {
|
|
goto _fail1;
|
|
}
|
|
|
|
EGLint contextAttributeList[] = {
|
|
EGL_CONTEXT_MAJOR_VERSION, 3,
|
|
EGL_CONTEXT_MINOR_VERSION, 1,
|
|
EGL_NONE
|
|
};
|
|
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
|
|
if (!s_context) {
|
|
goto _fail2;
|
|
}
|
|
|
|
eglMakeCurrent(s_display, s_surface, s_surface, s_context);
|
|
return true;
|
|
|
|
_fail2:
|
|
eglDestroySurface(s_display, s_surface);
|
|
s_surface = NULL;
|
|
_fail1:
|
|
eglTerminate(s_display);
|
|
s_display = NULL;
|
|
_fail0:
|
|
return false;
|
|
}
|
|
|
|
static void eglDeinit() {
|
|
if (s_display) {
|
|
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
if (s_context) {
|
|
eglDestroyContext(s_display, s_context);
|
|
}
|
|
if (s_surface) {
|
|
eglDestroySurface(s_display, s_surface);
|
|
}
|
|
eglTerminate(s_display);
|
|
}
|
|
}
|
|
|
|
static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, int key) {
|
|
mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
|
|
}
|
|
|
|
static void _drawStart(void) {
|
|
glClearColor(0.f, 0.f, 0.f, 1.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
static void _drawEnd(void) {
|
|
if (frameLimiter || framecount >= framecap) {
|
|
eglSwapBuffers(s_display, s_surface);
|
|
framecount = 0;
|
|
}
|
|
}
|
|
|
|
static uint32_t _pollInput(const struct mInputMap* map) {
|
|
int keys = 0;
|
|
padUpdate(&pad);
|
|
u32 padkeys = padGetButtons(&pad);
|
|
keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
|
|
|
|
HidAnalogStickState jspos = padGetStickPos(&pad, 0);
|
|
|
|
int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_StickLLeft));
|
|
int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_StickLRight));
|
|
int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_StickLUp));
|
|
int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_StickLDown));
|
|
|
|
if (l == -1) {
|
|
l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_Left));
|
|
}
|
|
if (r == -1) {
|
|
r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_Right));
|
|
}
|
|
if (u == -1) {
|
|
u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_Up));
|
|
}
|
|
if (d == -1) {
|
|
d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(HidNpadButton_Down));
|
|
}
|
|
|
|
if (jspos.x < -ANALOG_DEADZONE && l != -1) {
|
|
keys |= 1 << l;
|
|
}
|
|
if (jspos.x > ANALOG_DEADZONE && r != -1) {
|
|
keys |= 1 << r;
|
|
}
|
|
if (jspos.y < -ANALOG_DEADZONE && d != -1) {
|
|
keys |= 1 << d;
|
|
}
|
|
if (jspos.y > ANALOG_DEADZONE && u != -1) {
|
|
keys |= 1 << u;
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
|
|
HidTouchScreenState state = {0};
|
|
hidGetTouchScreenStates(&state, 1);
|
|
if (state.count < 1) {
|
|
return GUI_CURSOR_NOT_PRESENT;
|
|
}
|
|
*x = state.touches[0].x;
|
|
*y = state.touches[0].y;
|
|
return GUI_CURSOR_DOWN;
|
|
}
|
|
|
|
static void _updateRenderer(struct mGUIRunner* runner, bool gl) {
|
|
if (gl) {
|
|
runner->core->setVideoGLTex(runner->core, tex);
|
|
usePbo = false;
|
|
} else {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
|
|
usePbo = true;
|
|
}
|
|
}
|
|
|
|
static void _resetSampleRate(u32 samplerate) {
|
|
if (!samplerate) {
|
|
samplerate = 32768;
|
|
}
|
|
audrvVoiceInit(&audrenDriver, 0, 2, PcmFormat_Int16, samplerate / fpsRatio);
|
|
audrvVoiceSetDestinationMix(&audrenDriver, 0, AUDREN_FINAL_MIX_ID);
|
|
audrvVoiceSetMixFactor(&audrenDriver, 0, 1.0f, 0, 0);
|
|
audrvVoiceSetMixFactor(&audrenDriver, 0, 0.0f, 0, 1);
|
|
audrvVoiceSetMixFactor(&audrenDriver, 0, 0.0f, 1, 0);
|
|
audrvVoiceSetMixFactor(&audrenDriver, 0, 1.0f, 1, 1);
|
|
audrvUpdate(&audrenDriver);
|
|
}
|
|
|
|
static void _setup(struct mGUIRunner* runner) {
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_A, GBA_KEY_A);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_B, GBA_KEY_B);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_Plus, GBA_KEY_START);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_Minus, GBA_KEY_SELECT);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_Up, GBA_KEY_UP);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_Down, GBA_KEY_DOWN);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_Left, GBA_KEY_LEFT);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_Right, GBA_KEY_RIGHT);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_L, GBA_KEY_L);
|
|
_mapKey(&runner->core->inputMap, AUTO_INPUT, HidNpadButton_R, GBA_KEY_R);
|
|
|
|
int fakeBool = false;
|
|
if (runner->core->supportsFeature(runner->core, mCORE_FEATURE_OPENGL)) {
|
|
mCoreConfigGetIntValue(&runner->config, "hwaccelVideo", &fakeBool);
|
|
}
|
|
_updateRenderer(runner, fakeBool);
|
|
|
|
mRumbleIntegratorReset(&rumble.d);
|
|
runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d.d);
|
|
runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
|
|
runner->core->setAVStream(runner->core, &stream);
|
|
|
|
if (runner->core->platform(runner->core) == mPLATFORM_GBA && useLightSensor) {
|
|
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
|
|
}
|
|
|
|
unsigned mode;
|
|
if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
|
|
screenMode = mode;
|
|
}
|
|
if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
|
|
filterMode = mode;
|
|
}
|
|
|
|
runner->core->setAudioBufferSize(runner->core, SAMPLES);
|
|
|
|
u32 samplerate = runner->core->audioSampleRate(runner->core);
|
|
fpsRatio = mCoreCalculateFramerateRatio(runner->core, 60.0);
|
|
_resetSampleRate(samplerate);
|
|
}
|
|
|
|
static void _gameLoaded(struct mGUIRunner* runner) {
|
|
mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
|
|
|
|
unsigned mode;
|
|
if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
|
|
screenMode = mode;
|
|
}
|
|
if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
|
|
filterMode = mode;
|
|
}
|
|
|
|
int fakeBool;
|
|
if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
|
|
interframeBlending = fakeBool;
|
|
}
|
|
if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
|
|
sgbCrop = fakeBool;
|
|
}
|
|
if (mCoreConfigGetIntValue(&runner->config, "useLightSensor", &fakeBool)) {
|
|
if (useLightSensor != fakeBool) {
|
|
useLightSensor = fakeBool;
|
|
|
|
if (runner->core->platform(runner->core) == mPLATFORM_GBA) {
|
|
if (useLightSensor) {
|
|
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &lightSensor.d);
|
|
} else {
|
|
runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (runner->core->supportsFeature(runner->core, mCORE_FEATURE_OPENGL)) {
|
|
if (mCoreConfigGetIntValue(&runner->config, "hwaccelVideo", &fakeBool) && fakeBool == usePbo) {
|
|
_updateRenderer(runner, fakeBool);
|
|
runner->core->reloadConfigOption(runner->core, "hwaccelVideo", &runner->config);
|
|
}
|
|
|
|
unsigned scale;
|
|
if (mCoreConfigGetUIntValue(&runner->config, "videoScale", &scale)) {
|
|
runner->core->reloadConfigOption(runner->core, "videoScale", &runner->config);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _gameUnloaded(struct mGUIRunner* runner) {
|
|
UNUSED(runner);
|
|
HidVibrationValue values[4];
|
|
memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
|
|
memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
|
|
memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
|
|
memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
|
|
hidSendVibrationValues(vibrationDeviceHandles, values, 4);
|
|
audrvVoiceStop(&audrenDriver, 0);
|
|
}
|
|
|
|
static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded, bool blendTop) {
|
|
glViewport(0, 1080 - vheight, vwidth, vheight);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterMode == FM_LINEAR ? GL_LINEAR : GL_NEAREST);
|
|
|
|
glUseProgram(program);
|
|
glBindVertexArray(vao);
|
|
float inwidth = width;
|
|
float inheight = height;
|
|
if (sgbCrop && width == 256 && height == 224) {
|
|
inwidth = GB_VIDEO_HORIZONTAL_PIXELS;
|
|
inheight = GB_VIDEO_VERTICAL_PIXELS;
|
|
}
|
|
float aspectX = inwidth / vwidth;
|
|
float aspectY = inheight / vheight;
|
|
float max = 1.f;
|
|
switch (screenMode) {
|
|
case SM_MAX: // This should never get hit, so just fall through to silence warning
|
|
case SM_PA:
|
|
if (aspectX > aspectY) {
|
|
max = floor(1.f / aspectX);
|
|
} else {
|
|
max = floor(1.f / aspectY);
|
|
}
|
|
if (max >= 1.f) {
|
|
break;
|
|
}
|
|
// Fall through
|
|
case SM_AF:
|
|
if (aspectX > aspectY) {
|
|
max = 1.f / aspectX;
|
|
} else {
|
|
max = 1.f / aspectY;
|
|
}
|
|
break;
|
|
case SM_SF:
|
|
aspectX = 1.f;
|
|
aspectY = 1.f;
|
|
break;
|
|
}
|
|
|
|
if (screenMode != SM_SF) {
|
|
aspectX = width / (float) vwidth;
|
|
aspectY = height / (float) vheight;
|
|
}
|
|
|
|
aspectX *= max;
|
|
aspectY *= max;
|
|
|
|
glUniform1i(texLocation, 0);
|
|
glUniform2f(dimsLocation, aspectX, aspectY);
|
|
if (usePbo) {
|
|
glUniform2f(insizeLocation, width / 256.f, height / 256.f);
|
|
} else {
|
|
glUniform2f(insizeLocation, 1, 1);
|
|
}
|
|
if (!faded) {
|
|
glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, blendTop ? 0.5f : 1.0f);
|
|
} else {
|
|
glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, blendTop ? 0.4f : 0.8f);
|
|
}
|
|
|
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
|
|
|
glBindVertexArray(0);
|
|
glUseProgram(0);
|
|
glDisable(GL_BLEND);
|
|
glViewport(0, 1080 - runner->params.height, runner->params.width, runner->params.height);
|
|
}
|
|
|
|
static void _prepareForFrame(struct mGUIRunner* runner) {
|
|
if (interframeBlending) {
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
|
|
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
int width, height;
|
|
int format;
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
|
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
|
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
|
|
glBindTexture(GL_TEXTURE_2D, oldTex);
|
|
glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0, width, height, 0);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
if (usePbo) {
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
|
|
frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
|
|
if (frameBuffer) {
|
|
runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
|
|
}
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
}
|
|
|
|
static void _drawFrame(struct mGUIRunner* runner, bool faded) {
|
|
++framecount;
|
|
if (!frameLimiter && framecount < framecap) {
|
|
return;
|
|
}
|
|
|
|
unsigned width, height;
|
|
runner->core->currentVideoSize(runner->core, &width, &height);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
if (usePbo) {
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
|
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
} else if (!interframeBlending) {
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
}
|
|
|
|
if (interframeBlending) {
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, oldTex);
|
|
_drawTex(runner, width, height, faded, false);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
_drawTex(runner, width, height, faded, true);
|
|
} else {
|
|
glDisable(GL_BLEND);
|
|
|
|
_drawTex(runner, width, height, faded, false);
|
|
}
|
|
|
|
HidVibrationValue values[4];
|
|
memcpy(&values[0], &rumble.value, sizeof(rumble.value));
|
|
memcpy(&values[1], &rumble.value, sizeof(rumble.value));
|
|
memcpy(&values[2], &rumble.value, sizeof(rumble.value));
|
|
memcpy(&values[3], &rumble.value, sizeof(rumble.value));
|
|
hidSendVibrationValues(vibrationDeviceHandles, values, 4);
|
|
}
|
|
|
|
static void _drawScreenshot(struct mGUIRunner* runner, const mColor* pixels, unsigned width, unsigned height, bool faded) {
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, screenshotTex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
|
|
runner->core->currentVideoSize(runner->core, &width, &height);
|
|
glDisable(GL_BLEND);
|
|
bool wasPbo = usePbo;
|
|
usePbo = false;
|
|
_drawTex(runner, width, height, faded, false);
|
|
usePbo = wasPbo;
|
|
}
|
|
|
|
static uint16_t _pollGameInput(struct mGUIRunner* runner) {
|
|
return _pollInput(&runner->core->inputMap);
|
|
}
|
|
|
|
static void _incrementScreenMode(struct mGUIRunner* runner) {
|
|
UNUSED(runner);
|
|
screenMode = (screenMode + 1) % SM_MAX;
|
|
mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
|
|
}
|
|
|
|
static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
|
|
UNUSED(runner);
|
|
if (!frameLimiter && limit) {
|
|
audrenWaitFrame();
|
|
}
|
|
frameLimiter = limit;
|
|
eglSwapInterval(s_surface, limit);
|
|
}
|
|
|
|
static bool _running(struct mGUIRunner* runner) {
|
|
UNUSED(runner);
|
|
u8 newMode = appletGetOperationMode();
|
|
if (newMode != vmode) {
|
|
if (newMode == AppletOperationMode_Console) {
|
|
vwidth = 1920;
|
|
vheight = 1080;
|
|
} else {
|
|
vwidth = 1280;
|
|
vheight = 720;
|
|
}
|
|
nwindowSetCrop(nwindowGetDefault(), 0, 0, vwidth, vheight);
|
|
vmode = newMode;
|
|
}
|
|
|
|
return appletMainLoop();
|
|
}
|
|
|
|
static void _audioRateChanged(struct mAVStream* stream, uint32_t samplerate) {
|
|
UNUSED(stream);
|
|
_resetSampleRate(samplerate);
|
|
}
|
|
|
|
static void _postAudioBuffer(struct mAVStream* stream, struct mAudioBuffer* buffer) {
|
|
UNUSED(stream);
|
|
int i;
|
|
while (true) {
|
|
audrvUpdate(&audrenDriver);
|
|
for (i = 0; i < N_BUFFERS; ++i) {
|
|
if (audrvBuffer[i].state == AudioDriverWaveBufState_Free || audrvBuffer[i].state == AudioDriverWaveBufState_Done) {
|
|
break;
|
|
}
|
|
}
|
|
if (i < N_BUFFERS) {
|
|
break;
|
|
}
|
|
if (!frameLimiter) {
|
|
mAudioBufferClear(buffer);
|
|
return;
|
|
}
|
|
audrenWaitFrame();
|
|
}
|
|
struct mStereoSample* samples = audioBuffer[i];
|
|
mAudioBufferRead(buffer, (int16_t*) samples, SAMPLES);
|
|
armDCacheFlush(samples, SAMPLES * sizeof(struct mStereoSample));
|
|
audrvVoiceAddWaveBuf(&audrenDriver, 0, &audrvBuffer[i]);
|
|
|
|
if (!audrvVoiceIsPlaying(&audrenDriver, 0)) {
|
|
audrvVoiceStart(&audrenDriver, 0);
|
|
}
|
|
audrvUpdate(&audrenDriver);
|
|
}
|
|
|
|
void _setRumble(struct mRumbleIntegrator* rumble, float level) {
|
|
struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
|
|
sr->value.amp_low = level;
|
|
sr->value.amp_high = level;
|
|
}
|
|
|
|
void _sampleRotation(struct mRotationSource* source) {
|
|
UNUSED(source);
|
|
HidSixAxisSensorState sixaxis = {0};
|
|
u64 styles = padGetStyleSet(&pad);
|
|
if (styles & HidNpadStyleTag_NpadHandheld) {
|
|
hidGetSixAxisSensorStates(sixaxisHandles[3], &sixaxis, 1);
|
|
} else if (styles & HidNpadStyleTag_NpadFullKey) {
|
|
hidGetSixAxisSensorStates(sixaxisHandles[2], &sixaxis, 1);
|
|
} else if (styles & HidNpadStyleTag_NpadJoyDual) {
|
|
u64 attrib = padGetAttributes(&pad);
|
|
if (attrib & HidNpadAttribute_IsLeftConnected) {
|
|
hidGetSixAxisSensorStates(sixaxisHandles[0], &sixaxis, 1);
|
|
} else if (attrib & HidNpadAttribute_IsRightConnected) {
|
|
hidGetSixAxisSensorStates(sixaxisHandles[1], &sixaxis, 1);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
tiltX = sixaxis.acceleration.x * 3e8f;
|
|
tiltY = sixaxis.acceleration.y * -3e8f;
|
|
gyroZ = sixaxis.angular_velocity.z * -5.5e8f;
|
|
}
|
|
|
|
int32_t _readTiltX(struct mRotationSource* source) {
|
|
UNUSED(source);
|
|
return tiltX;
|
|
}
|
|
|
|
int32_t _readTiltY(struct mRotationSource* source) {
|
|
UNUSED(source);
|
|
return tiltY;
|
|
}
|
|
|
|
int32_t _readGyroZ(struct mRotationSource* source) {
|
|
UNUSED(source);
|
|
return gyroZ;
|
|
}
|
|
|
|
static void _lightSensorSample(struct GBALuminanceSource* lux) {
|
|
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
|
|
float luxLevel = 0;
|
|
appletGetCurrentIlluminance(&luxLevel);
|
|
runnerLux->luxLevel = cbrtf(luxLevel) * 8;
|
|
}
|
|
|
|
static uint8_t _lightSensorRead(struct GBALuminanceSource* lux) {
|
|
struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
|
|
return 0xFF - runnerLux->luxLevel;
|
|
}
|
|
|
|
static int _batteryState(void) {
|
|
u32 charge;
|
|
int state = 0;
|
|
if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
|
|
state = charge | BATTERY_PERCENTAGE_VALID;
|
|
} else {
|
|
return BATTERY_NOT_PRESENT;
|
|
}
|
|
PsmChargerType type;
|
|
if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
|
|
state |= BATTERY_CHARGING;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
static void _guiPrepare(void) {
|
|
glViewport(0, 1080 - vheight, vwidth, vheight);
|
|
}
|
|
|
|
static void _guiFinish(void) {
|
|
GUIFontDrawSubmit(font);
|
|
}
|
|
|
|
static enum GUIKeyboardStatus _keyboardRun(struct GUIKeyboardParams* keyboard) {
|
|
SwkbdConfig swkbd;
|
|
swkbdCreate(&swkbd, 0);
|
|
swkbdConfigMakePresetDefault(&swkbd);
|
|
swkbdConfigSetStringLenMax(&swkbd, keyboard->maxLen);
|
|
swkbdConfigSetInitialText(&swkbd, keyboard->result);
|
|
swkbdConfigSetHeaderText(&swkbd, keyboard->title);
|
|
swkbdConfigSetReturnButtonFlag(&swkbd, keyboard->multiline);
|
|
|
|
Result rc = swkbdShow(&swkbd, keyboard->result, sizeof(keyboard->result));
|
|
swkbdClose(&swkbd);
|
|
if (R_SUCCEEDED(rc)) {
|
|
return GUI_KEYBOARD_DONE;
|
|
} else {
|
|
return GUI_KEYBOARD_CANCEL;
|
|
}
|
|
}
|
|
|
|
static void glInit(void) {
|
|
glViewport(0, 1080 - vheight, vwidth, vheight);
|
|
glClearColor(0.f, 0.f, 0.f, 1.f);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glGenTextures(1, &tex);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glGenTextures(1, &oldTex);
|
|
glBindTexture(GL_TEXTURE_2D, oldTex);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glGenTextures(1, &screenshotTex);
|
|
glBindTexture(GL_TEXTURE_2D, screenshotTex);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glGenBuffers(1, &pbo);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
|
|
frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
glGenFramebuffers(1, ©Fbo);
|
|
glBindTexture(GL_TEXTURE_2D, tex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
glBindTexture(GL_TEXTURE_2D, oldTex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo);
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
|
|
program = glCreateProgram();
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
const GLchar* shaderBuffer[2];
|
|
|
|
shaderBuffer[0] = _gles2Header;
|
|
|
|
shaderBuffer[1] = _vertexShader;
|
|
glShaderSource(vertexShader, 2, shaderBuffer, NULL);
|
|
|
|
shaderBuffer[1] = _fragmentShader;
|
|
glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
|
|
|
|
glAttachShader(program, vertexShader);
|
|
glAttachShader(program, fragmentShader);
|
|
|
|
glCompileShader(fragmentShader);
|
|
|
|
GLint success;
|
|
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
|
|
if (!success) {
|
|
GLchar msg[512];
|
|
glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
|
|
puts(msg);
|
|
}
|
|
|
|
glCompileShader(vertexShader);
|
|
|
|
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
|
|
if (!success) {
|
|
GLchar msg[512];
|
|
glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
|
|
puts(msg);
|
|
}
|
|
glLinkProgram(program);
|
|
|
|
glDeleteShader(vertexShader);
|
|
glDeleteShader(fragmentShader);
|
|
|
|
texLocation = glGetUniformLocation(program, "tex");
|
|
colorLocation = glGetUniformLocation(program, "color");
|
|
dimsLocation = glGetUniformLocation(program, "dims");
|
|
insizeLocation = glGetUniformLocation(program, "insize");
|
|
GLuint offsetLocation = glGetAttribLocation(program, "offset");
|
|
|
|
glGenBuffers(1, &vbo);
|
|
glGenVertexArrays(1, &vao);
|
|
glBindVertexArray(vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
|
|
glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
glEnableVertexAttribArray(offsetLocation);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
static void glDeinit(void) {
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
|
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
glDeleteBuffers(1, &pbo);
|
|
|
|
glDeleteFramebuffers(1, ©Fbo);
|
|
glDeleteTextures(1, &tex);
|
|
glDeleteTextures(1, &oldTex);
|
|
glDeleteTextures(1, &screenshotTex);
|
|
glDeleteBuffers(1, &vbo);
|
|
glDeleteProgram(program);
|
|
glDeleteVertexArrays(1, &vao);
|
|
}
|
|
|
|
static void hidSetup(void) {
|
|
hidInitializeTouchScreen();
|
|
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
|
padInitializeDefault(&pad);
|
|
|
|
mRumbleIntegratorInit(&rumble.d);
|
|
rumble.d.setRumble = _setRumble;
|
|
rumble.value.freq_low = 120.0;
|
|
rumble.value.freq_high = 180.0;
|
|
hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld);
|
|
hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, HidNpadIdType_No1, HidNpadStyleTag_NpadJoyDual);
|
|
|
|
hidGetSixAxisSensorHandles(&sixaxisHandles[0], 2, HidNpadIdType_No1, HidNpadStyleTag_NpadJoyDual);
|
|
hidGetSixAxisSensorHandles(&sixaxisHandles[2], 1, HidNpadIdType_No1, HidNpadStyleTag_NpadFullKey);
|
|
hidGetSixAxisSensorHandles(&sixaxisHandles[3], 1, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld);
|
|
hidStartSixAxisSensor(sixaxisHandles[0]);
|
|
hidStartSixAxisSensor(sixaxisHandles[1]);
|
|
hidStartSixAxisSensor(sixaxisHandles[2]);
|
|
hidStartSixAxisSensor(sixaxisHandles[3]);
|
|
rotation.readTiltX = _readTiltX;
|
|
rotation.readTiltY = _readTiltY;
|
|
rotation.readGyroZ = _readGyroZ;
|
|
rotation.sample = _sampleRotation;
|
|
|
|
lightSensor.d.readLuminance = _lightSensorRead;
|
|
lightSensor.d.sample = _lightSensorSample;
|
|
}
|
|
|
|
static void hidTeardown(void) {
|
|
hidStopSixAxisSensor(sixaxisHandles[0]);
|
|
hidStopSixAxisSensor(sixaxisHandles[1]);
|
|
hidStopSixAxisSensor(sixaxisHandles[2]);
|
|
hidStopSixAxisSensor(sixaxisHandles[3]);
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
NWindow* window = nwindowGetDefault();
|
|
nwindowSetDimensions(window, 1920, 1080);
|
|
|
|
socketInitializeDefault();
|
|
nxlinkStdio();
|
|
eglInit();
|
|
romfsInit();
|
|
psmInitialize();
|
|
|
|
const AudioRendererConfig audren = {
|
|
.output_rate = AudioRendererOutputRate_48kHz,
|
|
.num_voices = 24,
|
|
.num_effects = 0,
|
|
.num_sinks = 1,
|
|
.num_mix_objs = 1,
|
|
.num_mix_buffers = 2,
|
|
};
|
|
const u8 channels[] = { 0, 1 };
|
|
audrenInitialize(&audren);
|
|
audrvCreate(&audrenDriver, &audren, 2);
|
|
struct mStereoSample* buffers = memalign(AUDREN_MEMPOOL_ALIGNMENT, SAMPLES * N_BUFFERS * sizeof(struct mStereoSample));
|
|
int mempool = audrvMemPoolAdd(&audrenDriver, buffers, SAMPLES * N_BUFFERS * sizeof(struct mStereoSample));
|
|
audrvMemPoolAttach(&audrenDriver, mempool);
|
|
audrvDeviceSinkAdd(&audrenDriver, AUDREN_DEFAULT_DEVICE_NAME, 2, channels);
|
|
audrvUpdate(&audrenDriver);
|
|
audrenStartAudioRenderer();
|
|
|
|
font = GUIFontCreate();
|
|
|
|
vmode = appletGetOperationMode();
|
|
if (vmode == AppletOperationMode_Console) {
|
|
vwidth = 1920;
|
|
vheight = 1080;
|
|
} else {
|
|
vwidth = 1280;
|
|
vheight = 720;
|
|
}
|
|
nwindowSetCrop(window, 0, 0, vwidth, vheight);
|
|
|
|
glInit();
|
|
hidSetup();
|
|
|
|
stream.videoDimensionsChanged = NULL;
|
|
stream.postVideoFrame = NULL;
|
|
stream.postAudioFrame = NULL;
|
|
stream.postAudioBuffer = _postAudioBuffer;
|
|
stream.audioRateChanged = _audioRateChanged;
|
|
|
|
size_t i;
|
|
for (i = 0; i < N_BUFFERS; ++i) {
|
|
audrvBuffer[i].data_raw = buffers;
|
|
audrvBuffer[i].size = SAMPLES * N_BUFFERS * sizeof(struct mStereoSample);
|
|
audrvBuffer[i].start_sample_offset = SAMPLES * i;
|
|
audrvBuffer[i].end_sample_offset = SAMPLES * (i + 1);
|
|
audioBuffer[i] = &buffers[SAMPLES * i];
|
|
}
|
|
|
|
bool illuminanceAvailable = false;
|
|
appletIsIlluminanceAvailable(&illuminanceAvailable);
|
|
|
|
struct mGUIRunner runner = {
|
|
.params = {
|
|
1280, 720,
|
|
font, "/",
|
|
_drawStart, _drawEnd,
|
|
_pollInput, _pollCursor,
|
|
_batteryState,
|
|
_guiPrepare, _guiFinish,
|
|
_keyboardRun,
|
|
},
|
|
.keySources = (struct GUIInputKeys[]) {
|
|
{
|
|
.name = "Controller Input",
|
|
.id = AUTO_INPUT,
|
|
.keyNames = (const char*[]) {
|
|
"A",
|
|
"B",
|
|
"X",
|
|
"Y",
|
|
"L Stick",
|
|
"R Stick",
|
|
"L",
|
|
"R",
|
|
"ZL",
|
|
"ZR",
|
|
"+",
|
|
"-",
|
|
"Left",
|
|
"Up",
|
|
"Right",
|
|
"Down",
|
|
"L Left",
|
|
"L Up",
|
|
"L Right",
|
|
"L Down",
|
|
"R Left",
|
|
"R Up",
|
|
"R Right",
|
|
"R Down",
|
|
"SL",
|
|
"SR"
|
|
},
|
|
.nKeys = 26
|
|
},
|
|
{ .id = 0 }
|
|
},
|
|
.configExtra = (struct GUIMenuItem[]) {
|
|
{
|
|
.title = "Screen mode",
|
|
.data = GUI_V_S("screenMode"),
|
|
.submenu = 0,
|
|
.state = SM_PA,
|
|
.validStates = (const char*[]) {
|
|
"Pixel-Accurate",
|
|
"Aspect-Ratio Fit",
|
|
"Stretched",
|
|
},
|
|
.nStates = 3
|
|
},
|
|
{
|
|
.title = "Fast forward cap",
|
|
.data = GUI_V_S("fastForwardCap"),
|
|
.submenu = 0,
|
|
.state = 7,
|
|
.validStates = (const char*[]) {
|
|
"2", "3", "4", "5", "6", "7", "8", "9",
|
|
"10", "11", "12", "13", "14", "15",
|
|
"20", "30"
|
|
},
|
|
.stateMappings = (const struct GUIVariant[]) {
|
|
GUI_V_U(2),
|
|
GUI_V_U(3),
|
|
GUI_V_U(4),
|
|
GUI_V_U(5),
|
|
GUI_V_U(6),
|
|
GUI_V_U(7),
|
|
GUI_V_U(8),
|
|
GUI_V_U(9),
|
|
GUI_V_U(10),
|
|
GUI_V_U(11),
|
|
GUI_V_U(12),
|
|
GUI_V_U(13),
|
|
GUI_V_U(14),
|
|
GUI_V_U(15),
|
|
GUI_V_U(20),
|
|
GUI_V_U(30),
|
|
},
|
|
.nStates = 16
|
|
},
|
|
{
|
|
.title = "Filtering",
|
|
.data = GUI_V_S("filterMode"),
|
|
.submenu = 0,
|
|
.state = FM_NEAREST,
|
|
.validStates = (const char*[]) {
|
|
"None",
|
|
"Bilinear",
|
|
},
|
|
.nStates = 2
|
|
},
|
|
{
|
|
.title = "GPU-accelerated renderer",
|
|
.data = GUI_V_S("hwaccelVideo"),
|
|
.submenu = 0,
|
|
.state = FM_NEAREST,
|
|
.validStates = (const char*[]) {
|
|
"Off",
|
|
"On",
|
|
},
|
|
.nStates = 2
|
|
},
|
|
{
|
|
.title = "Hi-res scaling (requires GPU rendering)",
|
|
.data = GUI_V_S("videoScale"),
|
|
.submenu = 0,
|
|
.state = 0,
|
|
.validStates = (const char*[]) {
|
|
"1x",
|
|
"2x",
|
|
"3x",
|
|
"4x",
|
|
"5x",
|
|
"6x",
|
|
},
|
|
.stateMappings = (const struct GUIVariant[]) {
|
|
GUI_V_U(1),
|
|
GUI_V_U(2),
|
|
GUI_V_U(3),
|
|
GUI_V_U(4),
|
|
GUI_V_U(5),
|
|
GUI_V_U(6),
|
|
},
|
|
.nStates = 6
|
|
},
|
|
{
|
|
.title = "Use built-in brightness sensor for Boktai",
|
|
.data = GUI_V_S("useLightSensor"),
|
|
.submenu = 0,
|
|
.state = illuminanceAvailable,
|
|
.validStates = (const char*[]) {
|
|
"Off",
|
|
"On",
|
|
},
|
|
.nStates = 2
|
|
},
|
|
},
|
|
.nConfigExtra = 6,
|
|
.setup = _setup,
|
|
.teardown = NULL,
|
|
.gameLoaded = _gameLoaded,
|
|
.gameUnloaded = _gameUnloaded,
|
|
.prepareForFrame = _prepareForFrame,
|
|
.drawFrame = _drawFrame,
|
|
.drawScreenshot = _drawScreenshot,
|
|
.paused = _gameUnloaded,
|
|
.unpaused = _gameLoaded,
|
|
.incrementScreenMode = _incrementScreenMode,
|
|
.setFrameLimiter = _setFrameLimiter,
|
|
.pollGameInput = _pollGameInput,
|
|
.running = _running
|
|
};
|
|
mGUIInit(&runner, "switch");
|
|
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_A, GUI_INPUT_SELECT);
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_B, GUI_INPUT_BACK);
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_X, GUI_INPUT_CANCEL);
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_Up, GUI_INPUT_UP);
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_Down, GUI_INPUT_DOWN);
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_Left, GUI_INPUT_LEFT);
|
|
_mapKey(&runner.params.keyMap, AUTO_INPUT, HidNpadButton_Right, GUI_INPUT_RIGHT);
|
|
|
|
if (argc > 0) {
|
|
struct VFile* vf = VFileOpen("romfs:/fileassoc.cfg.in", O_RDONLY);
|
|
if (vf) {
|
|
size_t size = vf->size(vf);
|
|
const char* arg0 = strchr(argv[0], '/');
|
|
char* buffer[2] = {
|
|
calloc(size + 1, 1),
|
|
malloc(size + strlen(arg0) + 1)
|
|
};
|
|
vf->read(vf, buffer[0], vf->size(vf));
|
|
vf->close(vf);
|
|
snprintf(buffer[1], size + strlen(arg0), buffer[0], arg0);
|
|
mkdir("sdmc:/config/nx-hbmenu/fileassoc", 0755);
|
|
vf = VFileOpen("sdmc:/config/nx-hbmenu/fileassoc/mgba.cfg", O_CREAT | O_TRUNC | O_WRONLY);
|
|
if (vf) {
|
|
vf->write(vf, buffer[1], strlen(buffer[1]));
|
|
vf->close(vf);
|
|
}
|
|
free(buffer[0]);
|
|
free(buffer[1]);
|
|
}
|
|
}
|
|
|
|
if (argc > 1) {
|
|
mGUILoadInputMaps(&runner);
|
|
mGUIRun(&runner, argv[1]);
|
|
} else {
|
|
mGUIRunloop(&runner);
|
|
}
|
|
|
|
mGUIDeinit(&runner);
|
|
|
|
audrvClose(&audrenDriver);
|
|
audrenExit();
|
|
GUIFontDestroy(font);
|
|
|
|
glDeinit();
|
|
hidTeardown();
|
|
|
|
psmExit();
|
|
audoutExit();
|
|
romfsExit();
|
|
eglDeinit();
|
|
socketExit();
|
|
return 0;
|
|
}
|