/* Copyright (c) 2013-2015 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/. */ #define asm __asm__ #include #include #include #include #include #include #include #include #include #include "feature/gui/gui-runner.h" #include #include #include #include #include #include #include #include #include #include #define GCN1_INPUT 0x47434E31 #define GCN2_INPUT 0x47434E32 #define WIIMOTE_INPUT 0x5749494D #define CLASSIC_INPUT 0x57494943 #define TEX_W 256 #define TEX_H 224 #define ANALOG_DEADZONE 0x30 static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) { mInputBindKey(map, binding, __builtin_ctz(nativeKey), key); } static enum ScreenMode { SM_PA, SM_SF, SM_MAX } screenMode = SM_PA; static enum FilterMode { FM_NEAREST, FM_LINEAR_1x, FM_LINEAR_2x, FM_MAX } filterMode = FM_NEAREST; static enum VideoMode { VM_AUTODETECT, VM_480i, VM_480p, VM_240p, // TODO: PAL support VM_MAX } videoMode = VM_AUTODETECT; #define SAMPLES 1024 #define GUI_SCALE 1.35f #define GUI_SCALE_240p 2.0f static void _retraceCallback(u32 count); static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right); static void _audioDMA(void); static void _setRumble(struct mRumble* rumble, int enable); static void _sampleRotation(struct mRotationSource* source); static int32_t _readTiltX(struct mRotationSource* source); static int32_t _readTiltY(struct mRotationSource* source); static int32_t _readGyroZ(struct mRotationSource* source); static void _drawStart(void); static void _drawEnd(void); static uint32_t _pollInput(const struct mInputMap*); static enum GUICursorState _pollCursor(unsigned* x, unsigned* y); static void _guiPrepare(void); static void _setup(struct mGUIRunner* runner); static void _gameLoaded(struct mGUIRunner* runner); static void _gameUnloaded(struct mGUIRunner* runner); static void _unpaused(struct mGUIRunner* runner); static void _prepareForFrame(struct mGUIRunner* runner); static void _drawFrame(struct mGUIRunner* runner, bool faded); static uint16_t _pollGameInput(struct mGUIRunner* runner); static void _setFrameLimiter(struct mGUIRunner* runner, bool limit); static void _incrementScreenMode(struct mGUIRunner* runner); static s8 WPAD_StickX(u8 chan, u8 right); static s8 WPAD_StickY(u8 chan, u8 right); static void* outputBuffer; static struct mAVStream stream; static struct mRumble rumble; static struct mRotationSource rotation; static GXRModeObj* vmode; static float wAdjust; static float hAdjust; static float wStretch = 0.9f; static float hStretch = 0.9f; static float guiScale = GUI_SCALE; static Mtx model, view, modelview; static uint16_t* texmem; static GXTexObj tex; static uint16_t* rescaleTexmem; static GXTexObj rescaleTex; static uint16_t* interframeTexmem; static GXTexObj interframeTex; static bool sgbCrop = false; static int32_t tiltX; static int32_t tiltY; static int32_t gyroZ; static uint32_t retraceCount; static uint32_t referenceRetraceCount; static bool frameLimiter = true; static int scaleFactor; static unsigned corew, coreh; static bool interframeBlending = true; uint32_t* romBuffer; size_t romBufferSize; static void* framebuffer[2] = { 0, 0 }; static int whichFb = 0; static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32))); static volatile size_t audioBufferSize = 0; static volatile int currentAudioBuffer = 0; static double audioSampleRate = 60.0 / 1.001; static struct GUIFont* font; static void reconfigureScreen(struct mGUIRunner* runner) { if (runner) { unsigned mode; if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) { videoMode = mode; } } wAdjust = 1.f; hAdjust = 1.f; guiScale = GUI_SCALE; audioSampleRate = 60.0 / 1.001; s32 signalMode = CONF_GetVideo(); switch (videoMode) { case VM_AUTODETECT: default: vmode = VIDEO_GetPreferredMode(0); break; case VM_480i: switch (signalMode) { case CONF_VIDEO_NTSC: vmode = &TVNtsc480IntDf; break; case CONF_VIDEO_MPAL: vmode = &TVMpal480IntDf; break; case CONF_VIDEO_PAL: vmode = &TVEurgb60Hz480IntDf; break; } break; case VM_480p: switch (signalMode) { case CONF_VIDEO_NTSC: vmode = &TVNtsc480Prog; break; case CONF_VIDEO_MPAL: vmode = &TVMpal480Prog; break; case CONF_VIDEO_PAL: vmode = &TVEurgb60Hz480Prog; break; } break; case VM_240p: switch (signalMode) { case CONF_VIDEO_NTSC: vmode = &TVNtsc240Ds; break; case CONF_VIDEO_MPAL: vmode = &TVMpal240Ds; break; case CONF_VIDEO_PAL: vmode = &TVEurgb60Hz240Ds; break; } wAdjust = 0.5f; audioSampleRate = 90.0 / 1.50436; guiScale = GUI_SCALE_240p; break; } vmode->viWidth = 704; vmode->viXOrigin = 8; VIDEO_SetBlack(true); VIDEO_Configure(vmode); free(framebuffer[0]); free(framebuffer[1]); framebuffer[0] = SYS_AllocateFramebuffer(vmode); framebuffer[1] = SYS_AllocateFramebuffer(vmode); VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[0]), COLOR_BLACK); VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[1]), COLOR_BLACK); VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb])); VIDEO_Flush(); VIDEO_WaitVSync(); if (vmode->viTVMode & VI_NON_INTERLACE) { VIDEO_WaitVSync(); } GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1); f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight); u32 xfbHeight = GX_SetDispCopyYScale(yscale); GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth); GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight); GX_SetDispCopyDst(vmode->fbWidth, xfbHeight); GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter); GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE)); if (runner) { runner->params.width = vmode->fbWidth * guiScale * wAdjust; runner->params.height = vmode->efbHeight * guiScale * hAdjust; if (runner->core) { double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1); blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio); blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio); } } } int main(int argc, char* argv[]) { VIDEO_Init(); VIDEO_SetBlack(true); VIDEO_Flush(); VIDEO_WaitVSync(); PAD_Init(); WPAD_Init(); WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR); AUDIO_Init(0); AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ); AUDIO_RegisterDMACallback(_audioDMA); memset(audioBuffer, 0, sizeof(audioBuffer)); #ifdef FIXED_ROM_BUFFER romBufferSize = SIZE_CART0; romBuffer = SYS_GetArena2Lo(); SYS_SetArena2Lo((void*)((intptr_t) romBuffer + SIZE_CART0)); #endif #if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5) #error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5 #endif GXColor bg = { 0, 0, 0, 0xFF }; void* fifo = memalign(32, 0x40000); memset(fifo, 0, 0x40000); GX_Init(fifo, 0x40000); GX_SetCopyClear(bg, 0x00FFFFFF); GX_SetCullMode(GX_CULL_NONE); GX_SetDispCopyGamma(GX_GM_1_0); GX_ClearVtxDesc(); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); GX_SetNumTevStages(1); GX_SetNumChans(1); GX_SetNumTexGens(1); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); GX_SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR0A0); GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE); GX_SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_DIVIDE_2, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ONE, GX_CC_CPREV); GX_SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV); GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GX_InvVtxCache(); GX_InvalidateTexAll(); guVector cam = { 0.0f, 0.0f, 0.0f }; guVector up = { 0.0f, 1.0f, 0.0f }; guVector look = { 0.0f, 0.0f, -1.0f }; guLookAt(view, &cam, &up, &look); guMtxIdentity(model); guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f); guMtxConcat(view, model, modelview); GX_LoadPosMtxImm(modelview, GX_PNMTX0); texmem = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL); GX_InitTexObj(&tex, texmem, TEX_W, TEX_H, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE); interframeTexmem = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL); GX_InitTexObj(&interframeTex, interframeTexmem, TEX_W, TEX_H, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE); rescaleTexmem = memalign(32, TEX_W * TEX_H * 4 * BYTES_PER_PIXEL); GX_InitTexObj(&rescaleTex, rescaleTexmem, TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE); GX_InitTexObjFilterMode(&rescaleTex, GX_LINEAR, GX_LINEAR); VIDEO_SetPostRetraceCallback(_retraceCallback); font = GUIFontCreate(); fatInitDefault(); rumble.setRumble = _setRumble; rotation.sample = _sampleRotation; rotation.readTiltX = _readTiltX; rotation.readTiltY = _readTiltY; rotation.readGyroZ = _readGyroZ; stream.videoDimensionsChanged = NULL; stream.postVideoFrame = NULL; stream.postAudioFrame = NULL; stream.postAudioBuffer = _postAudioBuffer; struct mGUIRunner runner = { .params = { 640, 480, font, "", _drawStart, _drawEnd, _pollInput, _pollCursor, 0, _guiPrepare, 0, }, .keySources = (struct GUIInputKeys[]) { { .name = "GameCube Input (1)", .id = GCN1_INPUT, .keyNames = (const char*[]) { "D-Pad Left", "D-Pad Right", "D-Pad Down", "D-Pad Up", "Z", "R", "L", 0, "A", "B", "X", "Y", "Start" }, .nKeys = 13 }, { .name = "GameCube Input (2)", .id = GCN2_INPUT, .keyNames = (const char*[]) { "D-Pad Left", "D-Pad Right", "D-Pad Down", "D-Pad Up", "Z", "R", "L", 0, "A", "B", "X", "Y", "Start" }, .nKeys = 13 }, { .name = "Wii Remote Input", .id = WIIMOTE_INPUT, .keyNames = (const char*[]) { "2", "1", "B", "A", "-", 0, 0, "\1\xE", "Left", "Right", "Down", "Up", "+", 0, 0, 0, "Z", "C", }, .nKeys = 18 }, { .name = "Classic Controller Input", .id = CLASSIC_INPUT, .keyNames = (const char*[]) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Up", "Left", "ZR", "X", "A", "Y", "B", "ZL", 0, "R", "+", "\1\xE", "-", "L", "Down", "Right", }, .nKeys = 32 }, { .id = 0 } }, .configExtra = (struct GUIMenuItem[]) { { .title = "Video mode", .data = "videoMode", .submenu = 0, .state = 0, .validStates = (const char*[]) { "Autodetect (recommended)", "480i", "480p", "240p", }, .nStates = 4 }, { .title = "Screen mode", .data = "screenMode", .submenu = 0, .state = 0, .validStates = (const char*[]) { "Pixel-Accurate", "Stretched", }, .nStates = 2 }, { .title = "Filtering", .data = "filter", .submenu = 0, .state = 0, .validStates = (const char*[]) { "Pixelated", "Bilinear (smoother)", "Bilinear (pixelated)", }, .nStates = 3 }, { .title = "Horizontal stretch", .data = "stretchWidth", .submenu = 0, .state = 7, .validStates = (const char*[]) { "1/2x", "0.6x", "2/3x", "0.7x", "3/4x", "0.8x", "0.9x", "1.0x" }, .stateMappings = (const struct GUIVariant[]) { GUI_V_F(0.5f), GUI_V_F(0.6f), GUI_V_F(2.f / 3.f), GUI_V_F(0.7f), GUI_V_F(0.75f), GUI_V_F(0.8f), GUI_V_F(0.9f), GUI_V_F(1.0f), }, .nStates = 8 }, { .title = "Vertical stretch", .data = "stretchHeight", .submenu = 0, .state = 6, .validStates = (const char*[]) { "1/2x", "0.6x", "2/3x", "0.7x", "3/4x", "0.8x", "0.9x", "1.0x" }, .stateMappings = (const struct GUIVariant[]) { GUI_V_F(0.5f), GUI_V_F(0.6f), GUI_V_F(2.f / 3.f), GUI_V_F(0.7f), GUI_V_F(0.75f), GUI_V_F(0.8f), GUI_V_F(0.9f), GUI_V_F(1.0f), }, .nStates = 8 }, }, .nConfigExtra = 5, .setup = _setup, .teardown = 0, .gameLoaded = _gameLoaded, .gameUnloaded = _gameUnloaded, .prepareForFrame = _prepareForFrame, .drawFrame = _drawFrame, .paused = _gameUnloaded, .unpaused = _unpaused, .incrementScreenMode = _incrementScreenMode, .setFrameLimiter = _setFrameLimiter, .pollGameInput = _pollGameInput }; mGUIInit(&runner, "wii"); reconfigureScreen(&runner); // Make sure screen is properly initialized by drawing a blank frame _drawStart(); _drawEnd(); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_A, GUI_INPUT_SELECT); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_B, GUI_INPUT_BACK); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_TRIGGER_Z, GUI_INPUT_CANCEL); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_UP, GUI_INPUT_UP); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_DOWN, GUI_INPUT_DOWN); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_LEFT, GUI_INPUT_LEFT); _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GUI_INPUT_RIGHT); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GUI_INPUT_SELECT); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GUI_INPUT_BACK); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_HOME, GUI_INPUT_CANCEL); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GUI_INPUT_UP); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GUI_INPUT_DOWN); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GUI_INPUT_LEFT); _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GUI_INPUT_RIGHT); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GUI_INPUT_SELECT); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GUI_INPUT_BACK); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_HOME, GUI_INPUT_CANCEL); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GUI_INPUT_UP); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GUI_INPUT_DOWN); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GUI_INPUT_LEFT); _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GUI_INPUT_RIGHT); float stretch = 0; if (mCoreConfigGetFloatValue(&runner.config, "stretchWidth", &stretch)) { wStretch = fminf(1.0f, fmaxf(0.5f, stretch)); } if (mCoreConfigGetFloatValue(&runner.config, "stretchHeight", &stretch)) { hStretch = fminf(1.0f, fmaxf(0.5f, stretch)); } if (argc > 1) { size_t i; for (i = 0; runner.keySources[i].id; ++i) { mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config)); } mGUIRun(&runner, argv[1]); } else { mGUIRunloop(&runner); } VIDEO_SetBlack(true); VIDEO_Flush(); VIDEO_WaitVSync(); mGUIDeinit(&runner); free(fifo); free(texmem); free(rescaleTexmem); free(interframeTexmem); free(outputBuffer); GUIFontDestroy(font); free(framebuffer[0]); free(framebuffer[1]); return 0; } static void _audioDMA(void) { if (!audioBufferSize) { return; } DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample)); AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample)); currentAudioBuffer = (currentAudioBuffer + 1) % 3; audioBufferSize = 0; } static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { UNUSED(stream); int available = blip_samples_avail(left); if (available + audioBufferSize > SAMPLES) { available = SAMPLES - audioBufferSize; } available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes if (available > 0) { // These appear to be reversed for AUDIO_InitDMA blip_read_samples(left, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true); blip_read_samples(right, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true); audioBufferSize += available; } if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) { _audioDMA(); AUDIO_StartDMA(); } } static void _drawStart(void) { VIDEO_SetBlack(false); u32 level = 0; _CPU_ISR_Disable(level); if (referenceRetraceCount > retraceCount) { if (frameLimiter) { VIDEO_WaitVSync(); } referenceRetraceCount = retraceCount; } _CPU_ISR_Restore(level); GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GX_SetColorUpdate(GX_TRUE); GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1); } static void _drawEnd(void) { GX_CopyDisp(framebuffer[whichFb], GX_TRUE); GX_DrawDone(); VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb])); VIDEO_Flush(); whichFb = !whichFb; u32 level = 0; _CPU_ISR_Disable(level); ++referenceRetraceCount; _CPU_ISR_Restore(level); } static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) { UNUSED(runner); frameLimiter = limit; } static uint32_t _pollInput(const struct mInputMap* map) { PAD_ScanPads(); u16 padkeys = PAD_ButtonsHeld(0); WPAD_ScanPads(); u32 wiiPad = WPAD_ButtonsHeld(0); u32 ext = 0; WPAD_Probe(0, &ext); int keys = 0; keys |= mInputMapKeyBits(map, GCN1_INPUT, padkeys, 0); keys |= mInputMapKeyBits(map, GCN2_INPUT, padkeys, 0); keys |= mInputMapKeyBits(map, WIIMOTE_INPUT, wiiPad, 0); if (ext == WPAD_EXP_CLASSIC) { keys |= mInputMapKeyBits(map, CLASSIC_INPUT, wiiPad, 0); } int x = PAD_StickX(0); int y = PAD_StickY(0); int w_x = WPAD_StickX(0, 0); int w_y = WPAD_StickY(0, 0); if (x < -ANALOG_DEADZONE || w_x < -ANALOG_DEADZONE) { keys |= 1 << GUI_INPUT_LEFT; } if (x > ANALOG_DEADZONE || w_x > ANALOG_DEADZONE) { keys |= 1 << GUI_INPUT_RIGHT; } if (y < -ANALOG_DEADZONE || w_y < -ANALOG_DEADZONE) { keys |= 1 << GUI_INPUT_DOWN; } if (y > ANALOG_DEADZONE || w_y > ANALOG_DEADZONE) { keys |= 1 << GUI_INPUT_UP; } return keys; } static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) { ir_t ir; WPAD_IR(0, &ir); if (!ir.smooth_valid) { return GUI_CURSOR_NOT_PRESENT; } *x = ir.sx; *y = ir.sy; WPAD_ScanPads(); u32 wiiPad = WPAD_ButtonsHeld(0); if (wiiPad & WPAD_BUTTON_A) { return GUI_CURSOR_DOWN; } return GUI_CURSOR_UP; } void _reproj(int w, int h) { Mtx44 proj; int top = (vmode->efbHeight * hAdjust - h) / 2; int left = (vmode->fbWidth * wAdjust - w) / 2; guOrtho(proj, -top, top + h, -left, left + w, 0, 300); GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); } void _reproj2(int w, int h) { Mtx44 proj; int top = h * (1.0 - hStretch) / 2; int left = w * (1.0 - wStretch) / 2; guOrtho(proj, -top, h + top, -left, w + left, 0, 300); GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); } void _guiPrepare(void) { GX_SetNumTevStages(1); _reproj2(vmode->fbWidth * guiScale * wAdjust, vmode->efbHeight * guiScale * hAdjust); } void _setup(struct mGUIRunner* runner) { runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble); runner->core->setAVStream(runner->core, &stream); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT); _mapKey(&runner->core->inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L); _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L); _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R); struct mInputAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, ANALOG_DEADZONE, -ANALOG_DEADZONE }; mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 0, &desc); mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, &desc); desc = (struct mInputAxis) { GBA_KEY_UP, GBA_KEY_DOWN, ANALOG_DEADZONE, -ANALOG_DEADZONE }; mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 1, &desc); mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, &desc); outputBuffer = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL); runner->core->setVideoBuffer(runner->core, outputBuffer, TEX_W); runner->core->setAudioBufferSize(runner->core, SAMPLES); double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1); blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio); blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio); frameLimiter = true; } void _gameUnloaded(struct mGUIRunner* runner) { UNUSED(runner); AUDIO_StopDMA(); frameLimiter = true; VIDEO_SetBlack(true); VIDEO_Flush(); VIDEO_WaitVSync(); } void _gameLoaded(struct mGUIRunner* runner) { reconfigureScreen(runner); if (runner->core->platform(runner->core) == PLATFORM_GBA && ((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) { int i; for (i = 0; i < 6; ++i) { u32 result = WPAD_SetMotionPlus(0, 1); if (result == WPAD_ERR_NONE) { break; } sleep(1); } } memset(texmem, 0, TEX_W * TEX_H * BYTES_PER_PIXEL); memset(interframeTexmem, 0, TEX_W * TEX_H * BYTES_PER_PIXEL); _unpaused(runner); } void _unpaused(struct mGUIRunner* runner) { u32 level = 0; VIDEO_WaitVSync(); _CPU_ISR_Disable(level); referenceRetraceCount = retraceCount; _CPU_ISR_Restore(level); unsigned mode; if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) { if (mode != videoMode) { reconfigureScreen(runner); } } if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) { screenMode = mode; } if (mCoreConfigGetUIntValue(&runner->config, "filter", &mode) && mode < FM_MAX) { filterMode = mode; switch (mode) { case FM_NEAREST: case FM_LINEAR_2x: default: GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR); break; case FM_LINEAR_1x: GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR); break; } } int fakeBool; if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) { interframeBlending = fakeBool; } if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) { sgbCrop = fakeBool; } float stretch; if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) { wStretch = fminf(1.0f, fmaxf(0.5f, stretch)); } if (mCoreConfigGetFloatValue(&runner->config, "stretchHeight", &stretch)) { hStretch = fminf(1.0f, fmaxf(0.5f, stretch)); } } void _prepareForFrame(struct mGUIRunner* runner) { if (interframeBlending) { memcpy(interframeTexmem, texmem, TEX_W * TEX_H * BYTES_PER_PIXEL); } } void _drawFrame(struct mGUIRunner* runner, bool faded) { runner->core->desiredVideoDimensions(runner->core, &corew, &coreh); uint32_t color = 0xFFFFFF3F; if (!faded) { color |= 0xC0; } size_t x, y; uint64_t* texdest = (uint64_t*) texmem; uint64_t* texsrc = (uint64_t*) outputBuffer; for (y = 0; y < coreh; y += 4) { for (x = 0; x < corew >> 2; ++x) { texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64]; texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64]; texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64]; texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64]; } } DCFlushRange(texdest, TEX_W * TEX_H * BYTES_PER_PIXEL); if (interframeBlending) { DCFlushRange(interframeTexmem, TEX_W * TEX_H * BYTES_PER_PIXEL); } if (faded || interframeBlending) { GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP); } else { GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP); } GX_InvalidateTexAll(); if (interframeBlending) { GX_LoadTexObj(&interframeTex, GX_TEXMAP0); GX_LoadTexObj(&tex, GX_TEXMAP1); GX_SetNumTevStages(2); } else { GX_LoadTexObj(&tex, GX_TEXMAP0); GX_SetNumTevStages(1); } GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); s16 vertWidth = corew; s16 vertHeight = coreh; if (filterMode == FM_LINEAR_2x) { Mtx44 proj; guOrtho(proj, 0, vmode->efbHeight, 0, vmode->fbWidth, 0, 300); GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position2s16(0, TEX_H * 2); GX_Color1u32(0xFFFFFFFF); GX_TexCoord2f32(0, 1); GX_Position2s16(TEX_W * 2, TEX_H * 2); GX_Color1u32(0xFFFFFFFF); GX_TexCoord2f32(1, 1); GX_Position2s16(TEX_W * 2, 0); GX_Color1u32(0xFFFFFFFF); GX_TexCoord2f32(1, 0); GX_Position2s16(0, 0); GX_Color1u32(0xFFFFFFFF); GX_TexCoord2f32(0, 0); GX_End(); GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2); GX_SetTexCopyDst(TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_FALSE); GX_CopyTex(rescaleTexmem, GX_TRUE); GX_LoadTexObj(&rescaleTex, GX_TEXMAP0); GX_SetNumTevStages(1); if (!faded) { GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP); } } if (screenMode == SM_PA) { unsigned factorWidth = corew; unsigned factorHeight = coreh; if (sgbCrop && factorWidth == 256 && factorHeight == 224) { factorWidth = GB_VIDEO_HORIZONTAL_PIXELS; factorHeight = GB_VIDEO_VERTICAL_PIXELS; } int hfactor = (vmode->fbWidth * wStretch) / (factorWidth * wAdjust); int vfactor = (vmode->efbHeight * hStretch) / (factorHeight * hAdjust); if (hfactor > vfactor) { scaleFactor = vfactor; } else { scaleFactor = hfactor; } vertWidth *= scaleFactor; vertHeight *= scaleFactor; _reproj(corew * scaleFactor, coreh * scaleFactor); } else { _reproj2(corew, coreh); } GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position2s16(0, vertHeight); GX_Color1u32(color); GX_TexCoord2f32(0, coreh / (float) TEX_H); GX_Position2s16(vertWidth, vertHeight); GX_Color1u32(color); GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H); GX_Position2s16(vertWidth, 0); GX_Color1u32(color); GX_TexCoord2f32(corew / (float) TEX_W, 0); GX_Position2s16(0, 0); GX_Color1u32(color); GX_TexCoord2f32(0, 0); GX_End(); } uint16_t _pollGameInput(struct mGUIRunner* runner) { UNUSED(runner); PAD_ScanPads(); u16 padkeys = PAD_ButtonsHeld(0); WPAD_ScanPads(); u32 wiiPad = WPAD_ButtonsHeld(0); u32 ext = 0; WPAD_Probe(0, &ext); uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0); keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0); keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0); enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0)); if (angles != GBA_KEY_NONE) { keys |= 1 << angles; } angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0)); if (angles != GBA_KEY_NONE) { keys |= 1 << angles; } if (ext == WPAD_EXP_CLASSIC) { keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0); angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0)); if (angles != GBA_KEY_NONE) { keys |= 1 << angles; } angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0)); if (angles != GBA_KEY_NONE) { keys |= 1 << angles; } } return keys; } void _incrementScreenMode(struct mGUIRunner* runner) { UNUSED(runner); int mode = screenMode | (filterMode << 1); ++mode; screenMode = mode % SM_MAX; filterMode = (mode >> 1) % FM_MAX; mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode); mCoreConfigSetUIntValue(&runner->config, "filter", filterMode); switch (filterMode) { case FM_NEAREST: case FM_LINEAR_2x: default: GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR); break; case FM_LINEAR_1x: GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR); break; } } void _setRumble(struct mRumble* rumble, int enable) { UNUSED(rumble); WPAD_Rumble(0, enable); if (enable) { PAD_ControlMotor(0, PAD_MOTOR_RUMBLE); } else { PAD_ControlMotor(0, PAD_MOTOR_STOP); } } void _sampleRotation(struct mRotationSource* source) { UNUSED(source); vec3w_t accel; WPAD_Accel(0, &accel); // These are swapped tiltX = (0x1EA - accel.y) << 22; tiltY = (0x1EA - accel.x) << 22; // This doesn't seem to work at all with -TR remotes struct expansion_t exp; WPAD_Expansion(0, &exp); if (exp.type != EXP_MOTION_PLUS) { return; } gyroZ = exp.mp.rz - 0x1FA0; gyroZ <<= 18; } 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 s8 WPAD_StickX(u8 chan, u8 right) { struct expansion_t exp; WPAD_Expansion(chan, &exp); struct joystick_t* js = NULL; switch (exp.type) { case WPAD_EXP_NUNCHUK: case WPAD_EXP_GUITARHERO3: if (right == 0) { js = &exp.nunchuk.js; } break; case WPAD_EXP_CLASSIC: if (right == 0) { js = &exp.classic.ljs; } else { js = &exp.classic.rjs; } break; default: break; } if (!js) { return 0; } int centered = (int) js->pos.x - (int) js->center.x; int range = (int) js->max.x - (int) js->min.x; int value = (centered * 0xFF) / range; if (value > 0x7F) { return 0x7F; } if (value < -0x80) { return -0x80; } return value; } static s8 WPAD_StickY(u8 chan, u8 right) { struct expansion_t exp; WPAD_Expansion(chan, &exp); struct joystick_t* js = NULL; switch (exp.type) { case WPAD_EXP_NUNCHUK: case WPAD_EXP_GUITARHERO3: if (right == 0) { js = &exp.nunchuk.js; } break; case WPAD_EXP_CLASSIC: if (right == 0) { js = &exp.classic.ljs; } else { js = &exp.classic.rjs; } break; default: break; } if (!js) { return 0; } int centered = (int) js->pos.y - (int) js->center.y; int range = (int) js->max.y - (int) js->min.y; int value = (centered * 0xFF) / range; if (value > 0x7F) { return 0x7F; } if (value < -0x80) { return -0x80; } return value; } void _retraceCallback(u32 count) { u32 level = 0; _CPU_ISR_Disable(level); retraceCount = count; _CPU_ISR_Restore(level); }