diff --git a/CHANGES b/CHANGES index 37fb3e7c3..6dcfef142 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,10 @@ -0.4.0: (Future) +0.5.0: (Future) +Bugfixes: + - VFS: Fix reading 7z archives without rewinding first +Misc: + - GBA: Slightly optimize GBAProcessEvents + +0.4.0: (2016-02-02) Features: - Officially supported ports for the Nintendo 3DS, Wii, and PlayStation Vita - I/O viewer @@ -15,45 +21,59 @@ Features: - Libretro: Settings for using BIOS and skipping intro - Libretro: Customizable idle loop removal - Implemented cycle counting for sprite rendering + - Cleaner, unified settings window + - Added a setting for pausing when the emulator is not in focus + - Customizable paths for save games, save states, screenshots and patches + - Controller hotplugging + - Ability to store save games and active cheats within savestates Bugfixes: - - Util: Fix PowerPC PNG read/write pixel order - - VFS: Fix VFileReadline and remove _vfdReadline - - GBA Memory: Fix DMA register writing behavior - - GBA BIOS: Fix misaligned CpuSet - ARM7: Fix sign of unaligned LDRSH + - ARM7: Fix decoding of some ARM ALU instructions with shifters + - Debugger: Fix watchpoints in gdb - GBA: Fix warnings when creating and loading savestates - - GBA Memory: Fix DMAs triggering two cycles early - - GBA Hardware: Fix GPIO on big endian - - Util: Fix excessive memory allocation when decoding a PNG - GBA: Fix Iridion II savetype + - GBA BIOS: Fix misaligned CpuSet + - GBA Cheats: Fix cheats setting the Action Replay version + - GBA Hardware: Fix GPIO on big endian + - GBA Memory: Fix DMA register writing behavior + - GBA Memory: Fix DMAs triggering two cycles early - Libretro: Fix aspect ratio + - Qt: Fix some potential crashes with the gamepad mapping + - Qt: Fix keys being mapped incorrectly when loading configuration file + - Util: Fix PowerPC PNG read/write pixel order + - Util: Fix excessive memory allocation when decoding a PNG + - VFS: Fix VFileReadline and remove _vfdReadline Misc: - - Qt: Window size command line options are now supported - - Qt: Increase usability of key mapper - - GBA Memory: Use a dynamically sized mask for ROM memory - - ARM7: Combine shifter-immediate and shifter-register functions to reduce binary size - - SDL: Support fullscreen in SDL 1.2 - - Libretro: Use anonymous memory mappers for large blocks of memory - - Qt: Add 'Apply' button to settings window - - GBA Video: Remove lastHblank, as it is implied - - GBA Config: Add "override" layer for better one-time configuration - - SDL: Allow GBASDLAudio to be used without a thread context - All: Improved PowerPC support - All: Fix some undefined behavior warnings - - Util: Use VFile for configuration - - GBA Memory: Implement several unimplemented memory access types + - ARM7: Combine shifter-immediate and shifter-register functions to reduce binary size + - Debugger: Convert breakpoints and watchpoints from linked-lists to vectors - GBA: Implement bad I/O register loading + - GBA: Allow jumping to OAM and palette RAM + - GBA BIOS: Finish implementing RegisterRamReset + - GBA Config: Add "override" layer for better one-time configuration + - GBA Input: Consolidate GBA_KEY_NONE and GBA_NO_MAPPING + - GBA Memory: Use a dynamically sized mask for ROM memory + - GBA Memory: Implement several unimplemented memory access types - GBA Memory: Add GBAView* functions for viewing memory directly without bus issues - - Util: Add MutexTryLock + - GBA RR: Starting from savestate now embeds the savegame + - GBA RR: Add preliminary SRAM support for VBM loading + - GBA RR: Add support for resets in movies + - GBA Video: Remove lastHblank, as it is implied + - Libretro: Use anonymous memory mappers for large blocks of memory + - Libretro: Add install target for libretro core + - Qt: Window size command line options are now supported + - Qt: Increase usability of key mapper + - Qt: Add 'Apply' button to settings window - Qt: Gray out "Skip BIOS intro" while "Use BIOS file" is unchecked - Qt: Allow use of modifier keys as input - Qt: Optimize log viewer - - GBA RR: Starting from savestate now embeds the savegame - - Libretro: Add install target for libretro core - - 3DS: Update to new ctrulib API - - GBA RR: Add preliminary SRAM support for VBM loading - - GBA RR: Add support for resets in movies - - GBA Input: Consolidate GBA_KEY_NONE and GBA_NO_MAPPING + - Qt: Added button for breaking into the GDB debugger + - Qt: Add box for showing duration of rewind + - SDL: Support fullscreen in SDL 1.2 + - SDL: Allow GBASDLAudio to be used without a thread context + - Util: Use VFile for configuration + - Util: Add MutexTryLock 0.3.2: (2015-12-16) Bugfixes: diff --git a/README.md b/README.md index cb040fde9..dfc0affaf 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,6 @@ Footnotes - OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5)) - Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9)) -- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141)) [2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. diff --git a/res/shaders/pixelate.shader/manifest.ini b/res/shaders/pixelate.shader/manifest.ini new file mode 100644 index 000000000..b2c55560a --- /dev/null +++ b/res/shaders/pixelate.shader/manifest.ini @@ -0,0 +1,9 @@ +[shader] +name=Pixelate +author=endrift +description=Only scale up the screen at an integer ratio +passes=1 + +[pass.0] +blend=1 +integerScaling=1 diff --git a/res/shaders/xbr.shader/manifest.ini b/res/shaders/xbr.shader/manifest.ini new file mode 100644 index 000000000..a60401aa2 --- /dev/null +++ b/res/shaders/xbr.shader/manifest.ini @@ -0,0 +1,38 @@ +[shader] +name=xBR +author=Hyllian +description=xBR upsampling filter +passes=1 + +[pass.0] +integerScaling=1 +vertexShader=xbr.vs +fragmentShader=xbr.fs + +[pass.0.uniform.XBR_Y_WEIGHT] +type=float +default=48 +readableName=Y Weight +min=0 +max=100 + +[pass.0.uniform.XBR_EQ_THRESHOLD] +type=float +readableName=Eq Threshold +default=10.0 +min=0.0 +max=50.0 + +[pass.0.uniform.XBR_EQ_THRESHOLD2] +type=float +readableName=Eq Threshold2 +default=2.0 +min=0.0 +max=4.0 + +[pass.0.uniform.XBR_LV2_COEFFICIENT] +type=float +readableName=Lv2 Coefficient +default=2.0 +min=1.0 +max=3.0 diff --git a/res/shaders/xbr.shader/xbr.fs b/res/shaders/xbr.shader/xbr.fs new file mode 100644 index 000000000..69c0faaad --- /dev/null +++ b/res/shaders/xbr.shader/xbr.fs @@ -0,0 +1,251 @@ +/* + Hyllian's xBR-lv3 Shader + + Copyright (C) 2011-2015 Hyllian - sergiogdb@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + + Incorporates some of the ideas from SABR shader. Thanks to Joshua Street. +*/ + +uniform float XBR_Y_WEIGHT; +uniform float XBR_EQ_THRESHOLD; +uniform float XBR_EQ_THRESHOLD2; +uniform float XBR_LV2_COEFFICIENT; + +const mat3 yuv = mat3(0.299, 0.587, 0.114, -0.169, -0.331, 0.499, 0.499, -0.418, -0.0813); +const vec4 delta = vec4(0.4, 0.4, 0.4, 0.4); + +vec4 df(vec4 A, vec4 B) +{ + return vec4(abs(A-B)); +} + +float c_df(vec3 c1, vec3 c2) { + vec3 df = abs(c1 - c2); + return df.r + df.g + df.b; +} + +bvec4 eq(vec4 A, vec4 B) +{ + return lessThan(df(A, B), vec4(XBR_EQ_THRESHOLD)); +} + +bvec4 eq2(vec4 A, vec4 B) +{ + return lessThan(df(A, B), vec4(XBR_EQ_THRESHOLD2)); +} + +bvec4 and(bvec4 A, bvec4 B) +{ + return bvec4(A.x && B.x, A.y && B.y, A.z && B.z, A.w && B.w); +} + +bvec4 or(bvec4 A, bvec4 B) +{ + return bvec4(A.x || B.x, A.y || B.y, A.z || B.z, A.w || B.w); +} + +vec4 weighted_distance(vec4 a, vec4 b, vec4 c, vec4 d, vec4 e, vec4 f, vec4 g, vec4 h) +{ + return (df(a,b) + df(a,c) + df(d,e) + df(d,f) + 4.0*df(g,h)); +} + +// GLSL shader autogenerated by cg2glsl.py. +#if __VERSION__ >= 130 +#define varying in +#define COMPAT_TEXTURE texture +out vec4 FragColor; +#else +#define FragColor gl_FragColor +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif +uniform sampler2D tex; +varying vec2 texCoord; +varying vec4 TEX1; +varying vec4 TEX2; +varying vec4 TEX3; +varying vec4 TEX4; +varying vec4 TEX5; +varying vec4 TEX6; +varying vec4 TEX7; + +const vec2 TextureSize = vec2(240.0, 160.0); + +void main() +{ + bvec4 edr, edr_left, edr_up, edr3_left, edr3_up, px; // px = pixel, edr = edge detection rule + bvec4 interp_restriction_lv1, interp_restriction_lv2_left, interp_restriction_lv2_up; + bvec4 interp_restriction_lv3_left, interp_restriction_lv3_up; + bvec4 nc, nc30, nc60, nc45, nc15, nc75; // new_color + vec4 fx, fx_left, fx_up, finalfx, fx3_left, fx3_up; // inequations of straight lines. + vec3 res1, res2, pix1, pix2; + float blend1, blend2; + + vec2 fp = fract(texCoord * TextureSize); + + vec3 A1 = COMPAT_TEXTURE(tex, TEX1.xw).rgb; + vec3 B1 = COMPAT_TEXTURE(tex, TEX1.yw).rgb; + vec3 C1 = COMPAT_TEXTURE(tex, TEX1.zw).rgb; + + vec3 A = COMPAT_TEXTURE(tex, TEX2.xw).rgb; + vec3 B = COMPAT_TEXTURE(tex, TEX2.yw).rgb; + vec3 C = COMPAT_TEXTURE(tex, TEX2.zw).rgb; + + vec3 D = COMPAT_TEXTURE(tex, TEX3.xw).rgb; + vec3 E = COMPAT_TEXTURE(tex, TEX3.yw).rgb; + vec3 F = COMPAT_TEXTURE(tex, TEX3.zw).rgb; + + vec3 G = COMPAT_TEXTURE(tex, TEX4.xw).rgb; + vec3 H = COMPAT_TEXTURE(tex, TEX4.yw).rgb; + vec3 I = COMPAT_TEXTURE(tex, TEX4.zw).rgb; + + vec3 G5 = COMPAT_TEXTURE(tex, TEX5.xw).rgb; + vec3 H5 = COMPAT_TEXTURE(tex, TEX5.yw).rgb; + vec3 I5 = COMPAT_TEXTURE(tex, TEX5.zw).rgb; + + vec3 A0 = COMPAT_TEXTURE(tex, TEX6.xy).rgb; + vec3 D0 = COMPAT_TEXTURE(tex, TEX6.xz).rgb; + vec3 G0 = COMPAT_TEXTURE(tex, TEX6.xw).rgb; + + vec3 C4 = COMPAT_TEXTURE(tex, TEX7.xy).rgb; + vec3 F4 = COMPAT_TEXTURE(tex, TEX7.xz).rgb; + vec3 I4 = COMPAT_TEXTURE(tex, TEX7.xw).rgb; + + vec4 b = transpose(mat4x3(B, D, H, F)) * (XBR_Y_WEIGHT * yuv[0]); + vec4 c = transpose(mat4x3(C, A, G, I)) * (XBR_Y_WEIGHT * yuv[0]); + vec4 e = transpose(mat4x3(E, E, E, E)) * (XBR_Y_WEIGHT * yuv[0]); + vec4 d = b.yzwx; + vec4 f = b.wxyz; + vec4 g = c.zwxy; + vec4 h = b.zwxy; + vec4 i = c.wxyz; + + vec4 i4 = transpose(mat4x3(I4, C1, A0, G5)) * (XBR_Y_WEIGHT*yuv[0]); + vec4 i5 = transpose(mat4x3(I5, C4, A1, G0)) * (XBR_Y_WEIGHT*yuv[0]); + vec4 h5 = transpose(mat4x3(H5, F4, B1, D0)) * (XBR_Y_WEIGHT*yuv[0]); + vec4 f4 = h5.yzwx; + + vec4 c1 = i4.yzwx; + vec4 g0 = i5.wxyz; + vec4 b1 = h5.zwxy; + vec4 d0 = h5.wxyz; + + vec4 Ao = vec4( 1.0, -1.0, -1.0, 1.0 ); + vec4 Bo = vec4( 1.0, 1.0, -1.0,-1.0 ); + vec4 Co = vec4( 1.5, 0.5, -0.5, 0.5 ); + vec4 Ax = vec4( 1.0, -1.0, -1.0, 1.0 ); + vec4 Bx = vec4( 0.5, 2.0, -0.5,-2.0 ); + vec4 Cx = vec4( 1.0, 1.0, -0.5, 0.0 ); + vec4 Ay = vec4( 1.0, -1.0, -1.0, 1.0 ); + vec4 By = vec4( 2.0, 0.5, -2.0,-0.5 ); + vec4 Cy = vec4( 2.0, 0.0, -1.0, 0.5 ); + + vec4 Az = vec4( 6.0, -2.0, -6.0, 2.0 ); + vec4 Bz = vec4( 2.0, 6.0, -2.0, -6.0 ); + vec4 Cz = vec4( 5.0, 3.0, -3.0, -1.0 ); + vec4 Aw = vec4( 2.0, -6.0, -2.0, 6.0 ); + vec4 Bw = vec4( 6.0, 2.0, -6.0,-2.0 ); + vec4 Cw = vec4( 5.0, -1.0, -3.0, 3.0 ); + + fx = (Ao*fp.y+Bo*fp.x); + fx_left = (Ax*fp.y+Bx*fp.x); + fx_up = (Ay*fp.y+By*fp.x); + fx3_left= (Az*fp.y+Bz*fp.x); + fx3_up = (Aw*fp.y+Bw*fp.x); + + // It uses CORNER_C if none of the others are defined. +#ifdef CORNER_A + interp_restriction_lv1 = and(notEqual(e, f), notEqual(e, h)); +#elif defined(CORNER_B) + interp_restriction_lv1 = ((e!=f) && (e!=h) && ( !eq(f,b) && !eq(h,d) || eq(e,i) && !eq(f,i4) && !eq(h,i5) || eq(e,g) || eq(e,c) ) ); +#elif defined(CORNER_D) + interp_restriction_lv1 = ((e!=f) && (e!=h) && ( !eq(f,b) && !eq(h,d) || eq(e,i) && !eq(f,i4) && !eq(h,i5) || eq(e,g) || eq(e,c) ) && (f!=f4 && f!=i || h!=h5 && h!=i || h!=g || f!=c || eq(b,c1) && eq(d,g0))); +#else + interp_restriction_lv1 = and(and(notEqual(e, f), notEqual(e, h)), + or(or(and(not(eq(f,b)), not(eq(f,c))), + and(not(eq(h,d)), not(eq(h,g)))), + or(and(eq(e,i), or(and(not(eq(f,f4)), not(eq(f,i4))), + and(not(eq(h,h5)), not(eq(h,i5))))), + or(eq(e,g), eq(e,c))))); +#endif + + interp_restriction_lv2_left = and(notEqual(e, g), notEqual(d, g)); + interp_restriction_lv2_up = and(notEqual(e, c), notEqual(b, c)); + interp_restriction_lv3_left = and(eq2(g,g0), not(eq2(d0,g0))); + interp_restriction_lv3_up = and(eq2(c,c1), not(eq2(b1,c1))); + + vec4 fx45 = smoothstep(Co - delta, Co + delta, fx); + vec4 fx30 = smoothstep(Cx - delta, Cx + delta, fx_left); + vec4 fx60 = smoothstep(Cy - delta, Cy + delta, fx_up); + vec4 fx15 = smoothstep(Cz - delta, Cz + delta, fx3_left); + vec4 fx75 = smoothstep(Cw - delta, Cw + delta, fx3_up); + + edr = and(lessThan(weighted_distance( e, c, g, i, h5, f4, h, f), weighted_distance( h, d, i5, f, i4, b, e, i)), interp_restriction_lv1); + edr_left = and(lessThanEqual((XBR_LV2_COEFFICIENT*df(f,g)), df(h,c)), interp_restriction_lv2_left); + edr_up = and(greaterThanEqual(df(f,g), (XBR_LV2_COEFFICIENT*df(h,c))), interp_restriction_lv2_up); + edr3_left = interp_restriction_lv3_left; + edr3_up = interp_restriction_lv3_up; + + nc45 = and(edr, bvec4(fx45)); + nc30 = and(edr, and(edr_left, bvec4(fx30))); + nc60 = and(edr, and(edr_up, bvec4(fx60))); + nc15 = and(and(edr, edr_left), and(edr3_left, bvec4(fx15))); + nc75 = and(and(edr, edr_up), and(edr3_up, bvec4(fx75))); + + px = lessThanEqual(df(e, f), df(e, h)); + + nc = bvec4(nc75.x || nc15.x || nc30.x || nc60.x || nc45.x, nc75.y || nc15.y || nc30.y || nc60.y || nc45.y, nc75.z || nc15.z || nc30.z || nc60.z || nc45.z, nc75.w || nc15.w || nc30.w || nc60.w || nc45.w); + + vec4 final45 = vec4(nc45) * fx45; + vec4 final30 = vec4(nc30) * fx30; + vec4 final60 = vec4(nc60) * fx60; + vec4 final15 = vec4(nc15) * fx15; + vec4 final75 = vec4(nc75) * fx75; + + vec4 maximo = max(max(max(final15, final75),max(final30, final60)), final45); + + if (nc.x) {pix1 = px.x ? F : H; blend1 = maximo.x;} + else if (nc.y) {pix1 = px.y ? B : F; blend1 = maximo.y;} + else if (nc.z) {pix1 = px.z ? D : B; blend1 = maximo.z;} + else if (nc.w) {pix1 = px.w ? H : D; blend1 = maximo.w;} + + if (nc.w) {pix2 = px.w ? H : D; blend2 = maximo.w;} + else if (nc.z) {pix2 = px.z ? D : B; blend2 = maximo.z;} + else if (nc.y) {pix2 = px.y ? B : F; blend2 = maximo.y;} + else if (nc.x) {pix2 = px.x ? F : H; blend2 = maximo.x;} + + res1 = mix(E, pix1, blend1); + res2 = mix(E, pix2, blend2); + vec3 res = mix(res1, res2, step(c_df(E, res1), c_df(E, res2))); + + FragColor = vec4(res, 1.0); +} diff --git a/res/shaders/xbr.shader/xbr.vs b/res/shaders/xbr.shader/xbr.vs new file mode 100644 index 000000000..d59a970ef --- /dev/null +++ b/res/shaders/xbr.shader/xbr.vs @@ -0,0 +1,60 @@ +/* + Hyllian's xBR-lv3 Shader + + Copyright (C) 2011-2015 Hyllian - sergiogdb@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + Incorporates some of the ideas from SABR shader. Thanks to Joshua Street. +*/ + +varying vec2 texCoord; +varying vec4 TEX1; +varying vec4 TEX2; +varying vec4 TEX3; +varying vec4 TEX4; +varying vec4 TEX5; +varying vec4 TEX6; +varying vec4 TEX7; +attribute vec4 position; + +/* VERTEX_SHADER */ +void main() +{ + gl_Position = position; + + vec2 ps = vec2(1.0/240.0, 1.0/160.0); + float dx = ps.x; + float dy = ps.y; + + // A1 B1 C1 + // A0 A B C C4 + // D0 D E F F4 + // G0 G H I I4 + // G5 H5 I5 + + texCoord = (position.st + vec2(1.0, 1.0)) * vec2(0.5, 0.5); + TEX1 = texCoord.xxxy + vec4( -dx, 0, dx,-2.0*dy); // A1 B1 C1 + TEX2 = texCoord.xxxy + vec4( -dx, 0, dx, -dy); // A B C + TEX3 = texCoord.xxxy + vec4( -dx, 0, dx, 0); // D E F + TEX4 = texCoord.xxxy + vec4( -dx, 0, dx, dy); // G H I + TEX5 = texCoord.xxxy + vec4( -dx, 0, dx, 2.0*dy); // G5 H5 I5 + TEX6 = texCoord.xyyy + vec4(-2.0*dx,-dy, 0, dy); // A0 D0 G0 + TEX7 = texCoord.xyyy + vec4( 2.0*dx,-dy, 0, dy); // C4 F4 I4 +} diff --git a/src/arm/decoder-arm.c b/src/arm/decoder-arm.c index 9a5cb7b57..e65f8aecd 100644 --- a/src/arm/decoder-arm.c +++ b/src/arm/decoder-arm.c @@ -11,9 +11,9 @@ #define ADDR_MODE_1_SHIFT(OP) \ info->op3.reg = opcode & 0x0000000F; \ + info->op3.shifterOp = ARM_SHIFT_ ## OP; \ info->operandFormat |= ARM_OPERAND_REGISTER_3; \ if (opcode & 0x00000010) { \ - info->op3.shifterOp = ARM_SHIFT_ ## OP; \ info->op3.shifterReg = (opcode >> 8) & 0xF; \ ++info->iCycles; \ info->operandFormat |= ARM_OPERAND_SHIFT_REGISTER_3; \ @@ -101,11 +101,13 @@ info->affectsCPSR = S; \ SHIFTER; \ if (SKIPPED == 1) { \ - info->operandFormat >>= 8; \ info->op1 = info->op2; \ info->op2 = info->op3; \ + info->operandFormat >>= 8; \ } else if (SKIPPED == 2) { \ - info->operandFormat &= ~ARM_OPERAND_2; \ + info->op2 = info->op3; \ + info->operandFormat |= info->operandFormat >> 8; \ + info->operandFormat &= ~ARM_OPERAND_3; \ } \ if (info->op1.reg == ARM_PC) { \ info->branchType = ARM_BRANCH_INDIRECT; \ diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index c3f0c9de1..1a07e1768 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -536,7 +536,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } uint32_t address = dv->intValue; - ARMDebuggerSetWatchpoint(&debugger->d, address); + ARMDebuggerSetWatchpoint(&debugger->d, address, WATCHPOINT_RW); // TODO: ro/wo } static void _breakIntoDefault(int signal) { @@ -802,7 +802,11 @@ static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason break; case DEBUGGER_ENTER_WATCHPOINT: if (info) { - printf("Hit watchpoint at 0x%08X: (old value = 0x%08X)\n", info->address, info->oldValue); + if (info->accessType & WATCHPOINT_WRITE) { + printf("Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->newValue, info->oldValue); + } else { + printf("Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->oldValue); + } } else { printf("Hit watchpoint\n"); } diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index 14e7da62a..cccfaf3b9 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -12,10 +12,14 @@ const uint32_t ARM_DEBUGGER_ID = 0xDEADBEEF; -static struct DebugBreakpoint* _lookupBreakpoint(struct DebugBreakpoint* breakpoints, uint32_t address) { - for (; breakpoints; breakpoints = breakpoints->next) { - if (breakpoints->address == address) { - return breakpoints; +DEFINE_VECTOR(DebugBreakpointList, struct DebugBreakpoint); +DEFINE_VECTOR(DebugWatchpointList, struct DebugWatchpoint); + +static struct DebugBreakpoint* _lookupBreakpoint(struct DebugBreakpointList* breakpoints, uint32_t address) { + size_t i; + for (i = 0; i < DebugBreakpointListSize(breakpoints); ++i) { + if (DebugBreakpointListGetPointer(breakpoints, i)->address == address) { + return DebugBreakpointListGetPointer(breakpoints, i); } } return 0; @@ -29,7 +33,7 @@ static void _checkBreakpoints(struct ARMDebugger* debugger) { } else { instructionLength = WORD_SIZE_THUMB; } - struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength); + struct DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength); if (!breakpoint) { return; } @@ -52,11 +56,11 @@ void ARMDebuggerInit(struct ARMCore* cpu, struct ARMComponent* component) { struct ARMDebugger* debugger = (struct ARMDebugger*) component; debugger->cpu = cpu; debugger->state = DEBUGGER_RUNNING; - debugger->breakpoints = 0; - debugger->swBreakpoints = 0; debugger->originalMemory = cpu->memory; - debugger->watchpoints = 0; debugger->currentBreakpoint = 0; + DebugBreakpointListInit(&debugger->breakpoints, 0); + DebugBreakpointListInit(&debugger->swBreakpoints, 0); + DebugWatchpointListInit(&debugger->watchpoints, 0); if (debugger->init) { debugger->init(debugger); } @@ -65,12 +69,15 @@ void ARMDebuggerInit(struct ARMCore* cpu, struct ARMComponent* component) { void ARMDebuggerDeinit(struct ARMComponent* component) { struct ARMDebugger* debugger = (struct ARMDebugger*) component; debugger->deinit(debugger); + DebugBreakpointListDeinit(&debugger->breakpoints); + DebugBreakpointListDeinit(&debugger->swBreakpoints); + DebugWatchpointListDeinit(&debugger->watchpoints); } void ARMDebuggerRun(struct ARMDebugger* debugger) { switch (debugger->state) { case DEBUGGER_RUNNING: - if (!debugger->breakpoints && !debugger->watchpoints) { + if (!DebugBreakpointListSize(&debugger->breakpoints) && !DebugWatchpointListSize(&debugger->watchpoints)) { ARMRunLoop(debugger->cpu); } else { ARMRun(debugger->cpu); @@ -105,7 +112,7 @@ void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason rea struct ARMCore* cpu = debugger->cpu; cpu->nextEvent = cpu->cycles; if (reason == DEBUGGER_ENTER_BREAKPOINT) { - struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu)); + struct DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->swBreakpoints, _ARMPCAddress(cpu)); debugger->currentBreakpoint = breakpoint; if (breakpoint && breakpoint->isSw) { info->address = breakpoint->address; @@ -122,11 +129,9 @@ void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason rea } void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address) { - struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint)); + struct DebugBreakpoint* breakpoint = DebugBreakpointListAppend(&debugger->breakpoints); breakpoint->address = address; - breakpoint->next = debugger->breakpoints; breakpoint->isSw = false; - debugger->breakpoints = breakpoint; } bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode) { @@ -135,56 +140,44 @@ bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t add return false; } - struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint)); + struct DebugBreakpoint* breakpoint = DebugBreakpointListAppend(&debugger->swBreakpoints); breakpoint->address = address; - breakpoint->next = debugger->swBreakpoints; breakpoint->isSw = true; breakpoint->sw.opcode = opcode; breakpoint->sw.mode = mode; - debugger->swBreakpoints = breakpoint; return true; } void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) { - struct DebugBreakpoint** previous = &debugger->breakpoints; - struct DebugBreakpoint* breakpoint; - struct DebugBreakpoint** next; - while ((breakpoint = *previous)) { - next = &breakpoint->next; - if (breakpoint->address == address) { - *previous = *next; - free(breakpoint); - continue; + struct DebugBreakpointList* breakpoints = &debugger->breakpoints; + size_t i; + for (i = 0; i < DebugBreakpointListSize(breakpoints); ++i) { + if (DebugBreakpointListGetPointer(breakpoints, i)->address == address) { + DebugBreakpointListShift(breakpoints, i, 1); } - previous = next; } + } -void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address) { - if (!debugger->watchpoints) { +void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address, enum WatchpointType type) { + if (!DebugWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerInstallMemoryShim(debugger); } - struct DebugWatchpoint* watchpoint = malloc(sizeof(struct DebugWatchpoint)); + struct DebugWatchpoint* watchpoint = DebugWatchpointListAppend(&debugger->watchpoints); watchpoint->address = address; - watchpoint->next = debugger->watchpoints; - debugger->watchpoints = watchpoint; + watchpoint->type = type; } void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) { - struct DebugWatchpoint** previous = &debugger->watchpoints; - struct DebugWatchpoint* watchpoint; - struct DebugWatchpoint** next; - while ((watchpoint = *previous)) { - next = &watchpoint->next; - if (watchpoint->address == address) { - *previous = *next; - free(watchpoint); - continue; + struct DebugWatchpointList* watchpoints = &debugger->watchpoints; + size_t i; + for (i = 0; i < DebugWatchpointListSize(watchpoints); ++i) { + if (DebugWatchpointListGetPointer(watchpoints, i)->address == address) { + DebugWatchpointListShift(watchpoints, i, 1); } - previous = next; } - if (!debugger->watchpoints) { + if (!DebugWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerRemoveMemoryShim(debugger); } } diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h index 5abfc4cc7..7f54b4f7c 100644 --- a/src/debugger/debugger.h +++ b/src/debugger/debugger.h @@ -8,7 +8,8 @@ #include "util/common.h" -#include "arm.h" +#include "arm/arm.h" +#include "util/vector.h" extern const uint32_t ARM_DEBUGGER_ID; @@ -20,7 +21,6 @@ enum DebuggerState { }; struct DebugBreakpoint { - struct DebugBreakpoint* next; uint32_t address; bool isSw; struct { @@ -32,15 +32,17 @@ struct DebugBreakpoint { enum WatchpointType { WATCHPOINT_WRITE = 1, WATCHPOINT_READ = 2, - WATCHPOINT_RW = 3 + WATCHPOINT_RW = WATCHPOINT_WRITE | WATCHPOINT_READ }; struct DebugWatchpoint { - struct DebugWatchpoint* next; uint32_t address; enum WatchpointType type; }; +DECLARE_VECTOR(DebugBreakpointList, struct DebugBreakpoint); +DECLARE_VECTOR(DebugWatchpointList, struct DebugWatchpoint); + enum DebuggerEntryReason { DEBUGGER_ENTER_MANUAL, DEBUGGER_ENTER_ATTACHED, @@ -54,7 +56,9 @@ struct DebuggerEntryInfo { union { struct { uint32_t oldValue; + uint32_t newValue; enum WatchpointType watchType; + enum WatchpointType accessType; }; struct { @@ -75,9 +79,9 @@ struct ARMDebugger { enum DebuggerState state; struct ARMCore* cpu; - struct DebugBreakpoint* breakpoints; - struct DebugBreakpoint* swBreakpoints; - struct DebugWatchpoint* watchpoints; + struct DebugBreakpointList breakpoints; + struct DebugBreakpointList swBreakpoints; + struct DebugWatchpointList watchpoints; struct ARMMemory originalMemory; struct DebugBreakpoint* currentBreakpoint; @@ -101,7 +105,7 @@ void ARMDebuggerEnter(struct ARMDebugger*, enum DebuggerEntryReason, struct Debu void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address); bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode); void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address); -void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address); +void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address, enum WatchpointType type); void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address); #endif diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 09f021805..f2d2542d1 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -40,13 +40,19 @@ static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReas snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT); break; case DEBUGGER_ENTER_BREAKPOINT: - snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); // TODO: Use hwbreak/swbreak if gdb supports it break; - case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address + case DEBUGGER_ENTER_WATCHPOINT: if (info) { const char* type = 0; switch (info->watchType) { case WATCHPOINT_WRITE: + if (info->newValue == info->oldValue) { + if (stub->d.state == DEBUGGER_PAUSED) { + stub->d.state = DEBUGGER_RUNNING; + } + return; + } type = "watch"; break; case WATCHPOINT_READ: @@ -56,7 +62,7 @@ static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReas type = "awatch"; break; } - snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08X", SIGTRAP, type, info->address); + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08x;", SIGTRAP, type, info->address); } else { snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); } @@ -317,9 +323,17 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { _sendMessage(stub); break; case '2': + ARMDebuggerSetWatchpoint(&stub->d, address, WATCHPOINT_WRITE); + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); + break; case '3': + ARMDebuggerSetWatchpoint(&stub->d, address, WATCHPOINT_READ); + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); + break; case '4': - ARMDebuggerSetWatchpoint(&stub->d, address); + ARMDebuggerSetWatchpoint(&stub->d, address, WATCHPOINT_RW); strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); _sendMessage(stub); break; diff --git a/src/debugger/memory-debugger.c b/src/debugger/memory-debugger.c index 69d402d64..750e0be04 100644 --- a/src/debugger/memory-debugger.c +++ b/src/debugger/memory-debugger.c @@ -11,7 +11,7 @@ #include -static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width); +static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, enum WatchpointType type, uint32_t newValue, int width); #define FIND_DEBUGGER(DEBUGGER, CPU) \ { \ @@ -32,18 +32,29 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } -#define CREATE_WATCHPOINT_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \ +#define CREATE_WATCHPOINT_READ_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \ static RETURN ARMDebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ struct DebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WIDTH)) { \ + if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_READ, 0, WIDTH)) { \ ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \ } \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } -#define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME) \ +#define CREATE_WATCHPOINT_WRITE_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \ + static RETURN ARMDebuggerShim_ ## NAME TYPES { \ + struct ARMDebugger* debugger; \ + FIND_DEBUGGER(debugger, cpu); \ + struct DebuggerEntryInfo info; \ + if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_WRITE, value, WIDTH)) { \ + ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \ + } \ + return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ + } + +#define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME, ACCESS_TYPE) \ static uint32_t ARMDebuggerShim_ ## NAME (struct ARMCore* cpu, uint32_t address, int mask, enum LSMDirection direction, int* cycleCounter) { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ @@ -60,28 +71,30 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st unsigned i; \ for (i = 0; i < popcount; ++i) { \ struct DebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, base + 4 * i, &info, 4)) { \ + if (_checkWatchpoints(debugger, base + 4 * i, &info, ACCESS_TYPE, 0, 4)) { \ ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \ } \ } \ return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \ } -CREATE_WATCHPOINT_SHIM(load32, 4, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(load16, 2, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(load8, 1, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(store32, 4, void, (struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter), address, value, cycleCounter) -CREATE_WATCHPOINT_SHIM(store16, 2, void, (struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter), address, value, cycleCounter) -CREATE_WATCHPOINT_SHIM(store8, 1, void, (struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter), address, value, cycleCounter) -CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple) -CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple) +CREATE_WATCHPOINT_READ_SHIM(load32, 4, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) +CREATE_WATCHPOINT_READ_SHIM(load16, 2, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) +CREATE_WATCHPOINT_READ_SHIM(load8, 1, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) +CREATE_WATCHPOINT_WRITE_SHIM(store32, 4, void, (struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter), address, value, cycleCounter) +CREATE_WATCHPOINT_WRITE_SHIM(store16, 2, void, (struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter), address, value, cycleCounter) +CREATE_WATCHPOINT_WRITE_SHIM(store8, 1, void, (struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter), address, value, cycleCounter) +CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple, WATCHPOINT_READ) +CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple, WATCHPOINT_WRITE) CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address) -static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width) { +static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, enum WatchpointType type, uint32_t newValue, int width) { --width; - struct DebugWatchpoint* watchpoints; - for (watchpoints = debugger->watchpoints; watchpoints; watchpoints = watchpoints->next) { - if (!((watchpoints->address ^ address) & ~width)) { + struct DebugWatchpoint* watchpoint; + size_t i; + for (i = 0; i < DebugWatchpointListSize(&debugger->watchpoints); ++i) { + watchpoint = DebugWatchpointListGetPointer(&debugger->watchpoints, i); + if (!((watchpoint->address ^ address) & ~width) && watchpoint->type & type) { switch (width + 1) { case 1: info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); @@ -93,8 +106,10 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0); break; } + info->newValue = newValue; info->address = address; - info->watchType = watchpoints->type; + info->watchType = watchpoint->type; + info->accessType = type; return true; } } diff --git a/src/gba/bios.c b/src/gba/bios.c index 018f8e22c..1e959d3a6 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -71,10 +71,96 @@ static void _RegisterRamReset(struct GBA* gba) { cpu->memory.store32(cpu, BASE_IO | REG_JOY_TRANS_LO, 0, 0); } if (registers & 0x40) { - GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on Audio unimplemented"); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND1CNT_LO, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND1CNT_HI, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND1CNT_X, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND2CNT_LO, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND2CNT_HI, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND3CNT_LO, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND3CNT_HI, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND3CNT_X, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND4CNT_LO, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUND4CNT_HI, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUNDCNT_LO, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUNDCNT_HI, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUNDCNT_X, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SOUNDBIAS, 0x200, 0); + memset(gba->audio.ch3.wavedata, 0, sizeof(gba->audio.ch3.wavedata)); } if (registers & 0x80) { - GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on IO unimplemented"); + cpu->memory.store16(cpu, BASE_IO | 0x00, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x04, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x06, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x08, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x0A, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x0C, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x0E, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x10, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x12, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x14, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x16, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x18, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x1A, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x1C, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x1E, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG2PA, 0x100, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG2PB, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG2PC, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG2PD, 0x100, 0); + cpu->memory.store32(cpu, BASE_IO | 0x28, 0, 0); + cpu->memory.store32(cpu, BASE_IO | 0x2C, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG3PA, 0x100, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG3PB, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG3PC, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_BG3PD, 0x100, 0); + cpu->memory.store32(cpu, BASE_IO | 0x38, 0, 0); + cpu->memory.store32(cpu, BASE_IO | 0x3C, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x40, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x42, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x44, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x46, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x48, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x4A, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x4C, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x50, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x52, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x54, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xB0, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xB2, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xB4, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xB6, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xB8, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xBA, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xBC, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xBE, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xC0, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xC2, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xC4, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xC6, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xC8, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xCA, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xCC, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xCE, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xD0, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xD2, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xD4, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xD6, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xD8, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xDA, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xDC, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0xDE, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x100, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x102, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x104, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x106, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x108, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x10A, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x10C, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x10E, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x200, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x202, 0xFFFF, 0); + cpu->memory.store16(cpu, BASE_IO | 0x204, 0, 0); + cpu->memory.store16(cpu, BASE_IO | 0x208, 0, 0); } } diff --git a/src/gba/cheats.c b/src/gba/cheats.c index 0bbd2b06b..097679bc5 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -110,13 +110,18 @@ void GBACheatDeviceCreate(struct GBACheatDevice* device) { } void GBACheatDeviceDestroy(struct GBACheatDevice* device) { + GBACheatDeviceClear(device); + GBACheatSetsDeinit(&device->cheats); +} + +void GBACheatDeviceClear(struct GBACheatDevice* device) { size_t i; for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i); GBACheatSetDeinit(set); free(set); } - GBACheatSetsDeinit(&device->cheats); + GBACheatSetsClear(&device->cheats); } void GBACheatSetInit(struct GBACheatSet* set, const char* name) { @@ -260,6 +265,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) { do { ++i; } while (isspace((int) cheat[i])); + cheat[strlen(cheat) - 1] = '\0'; // Remove trailing newline newSet = malloc(sizeof(*set)); GBACheatSetInit(newSet, &cheat[i]); newSet->enabled = !nextDisabled; diff --git a/src/gba/cheats.h b/src/gba/cheats.h index 73b08940a..2bf992ff2 100644 --- a/src/gba/cheats.h +++ b/src/gba/cheats.h @@ -192,6 +192,7 @@ struct VFile; void GBACheatDeviceCreate(struct GBACheatDevice*); void GBACheatDeviceDestroy(struct GBACheatDevice*); +void GBACheatDeviceClear(struct GBACheatDevice*); void GBACheatSetInit(struct GBACheatSet*, const char* name); void GBACheatSetDeinit(struct GBACheatSet*); diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c index 996de638d..3f54df670 100644 --- a/src/gba/cheats/gameshark.c +++ b/src/gba/cheats/gameshark.c @@ -75,12 +75,14 @@ void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1 } void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version) { - cheats->gsaVersion = 1; + cheats->gsaVersion = version; switch (version) { case 1: + case 2: memcpy(cheats->gsaSeeds, GBACheatGameSharkSeeds, 4 * sizeof(uint32_t)); break; case 3: + case 4: memcpy(cheats->gsaSeeds, GBACheatProActionReplaySeeds, 4 * sizeof(uint32_t)); break; } @@ -198,9 +200,11 @@ bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { switch (set->gsaVersion) { case 0: case 3: + case 4: GBACheatSetGameSharkVersion(set, 1); // Fall through case 1: + case 2: GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); return GBACheatAddGameSharkRaw(set, o1, o2); } diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index c5e01b66d..b355a18b0 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -298,9 +298,11 @@ bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t switch (set->gsaVersion) { case 0: case 1: + case 2: GBACheatSetGameSharkVersion(set, 3); // Fall through case 3: + case 4: GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); return GBACheatAddProActionReplayRaw(set, o1, o2); } diff --git a/src/gba/context/config.c b/src/gba/context/config.c index f092cda1e..e9c68cd8d 100644 --- a/src/gba/context/config.c +++ b/src/gba/context/config.c @@ -347,6 +347,11 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { _lookupIntValue(config, "width", &opts->width); _lookupIntValue(config, "height", &opts->height); + _lookupCharValue(config, "savegamePath", &opts->savegamePath); + _lookupCharValue(config, "savestatePath", &opts->savestatePath); + _lookupCharValue(config, "screenshotPath", &opts->screenshotPath); + _lookupCharValue(config, "patchPath", &opts->patchPath); + char* idleOptimization = 0; if (_lookupCharValue(config, "idleOptimization", &idleOptimization)) { if (strcasecmp(idleOptimization, "ignore") == 0) { @@ -409,6 +414,14 @@ struct Configuration* GBAConfigGetOverrides(struct GBAConfig* config) { void GBAConfigFreeOpts(struct GBAOptions* opts) { free(opts->bios); free(opts->shader); + free(opts->savegamePath); + free(opts->savestatePath); + free(opts->screenshotPath); + free(opts->patchPath); opts->bios = 0; opts->shader = 0; + opts->savegamePath = 0; + opts->savestatePath = 0; + opts->screenshotPath = 0; + opts->patchPath = 0; } diff --git a/src/gba/context/config.h b/src/gba/context/config.h index 2ff950c7d..ce35bf614 100644 --- a/src/gba/context/config.h +++ b/src/gba/context/config.h @@ -40,6 +40,11 @@ struct GBAOptions { bool suspendScreensaver; char* shader; + char* savegamePath; + char* savestatePath; + char* screenshotPath; + char* patchPath; + int volume; bool mute; diff --git a/src/gba/context/context.c b/src/gba/context/context.c index 21e9be4ae..d9e33730a 100644 --- a/src/gba/context/context.c +++ b/src/gba/context/context.c @@ -21,7 +21,9 @@ bool GBAContextInit(struct GBAContext* context, const char* port) { context->fname = 0; context->save = 0; context->renderer = 0; +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 GBADirectorySetInit(&context->dirs); +#endif memset(context->components, 0, sizeof(context->components)); if (!context->gba || !context->cpu) { @@ -79,11 +81,17 @@ void GBAContextDeinit(struct GBAContext* context) { mappedMemoryFree(context->gba, 0); mappedMemoryFree(context->cpu, 0); GBAConfigDeinit(&context->config); +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 GBADirectorySetDeinit(&context->dirs); +#endif } bool GBAContextLoadROM(struct GBAContext* context, const char* path, bool autoloadSave) { +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 context->rom = GBADirectorySetOpenPath(&context->dirs, path, GBAIsROM); +#else + context->rom = VFileOpen(path, O_RDONLY); +#endif if (!context->rom) { return false; } @@ -106,7 +114,9 @@ bool GBAContextLoadROM(struct GBAContext* context, const char* path, bool autolo void GBAContextUnloadROM(struct GBAContext* context) { GBAUnloadROM(context->gba); +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 GBADirectorySetDetachBase(&context->dirs); +#endif if (context->rom) { context->rom->close(context->rom); context->rom = 0; diff --git a/src/gba/context/context.h b/src/gba/context/context.h index d71385074..36168371a 100644 --- a/src/gba/context/context.h +++ b/src/gba/context/context.h @@ -21,7 +21,9 @@ struct GBAContext { const char* fname; struct VFile* save; struct VFile* bios; +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 struct GBADirectorySet dirs; +#endif struct ARMComponent* components[GBA_COMPONENT_MAX]; struct GBAConfig config; struct GBAOptions opts; diff --git a/src/gba/context/directories.c b/src/gba/context/directories.c index d18fde029..68f59dc01 100644 --- a/src/gba/context/directories.c +++ b/src/gba/context/directories.c @@ -5,8 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "directories.h" +#include "gba/context/config.h" #include "util/vfs.h" +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 void GBADirectorySetInit(struct GBADirectorySet* dirs) { dirs->base = 0; dirs->archive = 0; @@ -99,3 +101,46 @@ struct VFile* GBADirectorySetOpenPath(struct GBADirectorySet* dirs, const char* } return file; } + +void GBADirectorySetMapOptions(struct GBADirectorySet* dirs, const struct GBAOptions* opts) { + if (opts->savegamePath) { + struct VDir* dir = VDirOpen(opts->savegamePath); + if (dir) { + if (dirs->save && dirs->save != dirs->base) { + dirs->save->close(dirs->save); + } + dirs->save = dir; + } + } + + if (opts->savestatePath) { + struct VDir* dir = VDirOpen(opts->savestatePath); + if (dir) { + if (dirs->state && dirs->state != dirs->base) { + dirs->state->close(dirs->state); + } + dirs->state = dir; + } + } + + if (opts->screenshotPath) { + struct VDir* dir = VDirOpen(opts->screenshotPath); + if (dir) { + if (dirs->screenshot && dirs->screenshot != dirs->base) { + dirs->screenshot->close(dirs->screenshot); + } + dirs->screenshot = dir; + } + } + + if (opts->patchPath) { + struct VDir* dir = VDirOpen(opts->patchPath); + if (dir) { + if (dirs->patch && dirs->patch != dirs->base) { + dirs->patch->close(dirs->patch); + } + dirs->patch = dir; + } + } +} +#endif diff --git a/src/gba/context/directories.h b/src/gba/context/directories.h index bf906c4ab..e0a74c53e 100644 --- a/src/gba/context/directories.h +++ b/src/gba/context/directories.h @@ -8,6 +8,7 @@ #include "util/common.h" +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 struct VDir; struct GBADirectorySet { @@ -27,4 +28,8 @@ void GBADirectorySetDetachBase(struct GBADirectorySet* dirs); struct VFile* GBADirectorySetOpenPath(struct GBADirectorySet* dirs, const char* path, bool (*filter)(struct VFile*)); +struct GBAOptions; +void GBADirectorySetMapOptions(struct GBADirectorySet* dirs, const struct GBAOptions* opts); +#endif + #endif diff --git a/src/gba/gba.c b/src/gba/gba.c index 860d23cc0..08d82eee2 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -205,8 +205,19 @@ void GBASkipBIOS(struct GBA* gba) { } static void GBAProcessEvents(struct ARMCore* cpu) { + struct GBA* gba = (struct GBA*) cpu->master; + + gba->bus = cpu->prefetch[1]; + if (cpu->executionMode == MODE_THUMB) { + gba->bus |= cpu->prefetch[1] << 16; + } + + if (gba->springIRQ) { + ARMRaiseIRQ(cpu); + gba->springIRQ = 0; + } + do { - struct GBA* gba = (struct GBA*) cpu->master; int32_t cycles = cpu->nextEvent; int32_t nextEvent = INT_MAX; int32_t testEvent; @@ -216,16 +227,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) { } #endif - gba->bus = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - gba->bus |= cpu->prefetch[1] << 16; - } - - if (gba->springIRQ) { - ARMRaiseIRQ(cpu); - gba->springIRQ = 0; - } - testEvent = GBAVideoProcessEvents(&gba->video, cycles); if (testEvent < nextEvent) { nextEvent = testEvent; diff --git a/src/gba/gui/gui-runner.c b/src/gba/gui/gui-runner.c index 30adecb89..7d4f7eb17 100644 --- a/src/gba/gui/gui-runner.c +++ b/src/gba/gui/gui-runner.c @@ -15,6 +15,10 @@ #include "util/png-io.h" #include "util/vfs.h" +#ifdef _3DS +#include <3ds.h> +#endif + #include #define FPS_GRANULARITY 120 @@ -237,6 +241,12 @@ void GBAGUIRun(struct GBAGUIRunner* runner, const char* path) { runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; while (true) { +#ifdef _3DS + running = aptMainLoop(); + if (!running) { + break; + } +#endif uint32_t guiKeys; GUIPollInput(&runner->params, &guiKeys, 0); if (guiKeys & (1 << GUI_INPUT_CANCEL)) { diff --git a/src/gba/memory.c b/src/gba/memory.c index 653732154..1d3d87ff4 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -263,10 +263,18 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { cpu->memory.activeRegion = memory->iwram; cpu->memory.activeMask = SIZE_WORKING_IRAM - 1; break; + case REGION_PALETTE_RAM: + cpu->memory.activeRegion = (uint32_t*) gba->video.palette; + cpu->memory.activeMask = SIZE_PALETTE_RAM - 1; + break; case REGION_VRAM: cpu->memory.activeRegion = (uint32_t*) gba->video.renderer->vram; cpu->memory.activeMask = 0x0000FFFF; break; + case REGION_OAM: + cpu->memory.activeRegion = (uint32_t*) gba->video.oam.raw; + cpu->memory.activeMask = SIZE_OAM - 1; + break; case REGION_CART0: case REGION_CART0_EX: case REGION_CART1: diff --git a/src/gba/serialize.c b/src/gba/serialize.c index a3eefe2f8..44daecda5 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -6,6 +6,7 @@ #include "serialize.h" #include "gba/audio.h" +#include "gba/cheats.h" #include "gba/io.h" #include "gba/rr/rr.h" #include "gba/supervisor/thread.h" @@ -205,6 +206,14 @@ struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool writ return dir->openFile(dir, path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY); } +void GBADeleteState(struct GBA* gba, struct VDir* dir, int slot) { + char basename[PATH_MAX]; + separatePath(gba->activeFile, 0, basename, 0); + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s.ss%i", basename, slot); + dir->deleteFile(dir, path); +} + #ifdef USE_PNG static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) { unsigned stride; @@ -426,6 +435,20 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { } svf->close(svf); } + struct VFile* cheatVf = 0; + if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { + struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]; + cheatVf = VFileMemChunk(0, 0); + if (cheatVf) { + GBACheatSaveFile(device, cheatVf); + struct GBAExtdataItem item = { + .size = cheatVf->size(cheatVf), + .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ), + .clean = 0 + }; + GBAExtdataPut(&extdata, EXTDATA_CHEATS, &item); + } + }; #ifdef USE_PNG if (!(flags & SAVESTATE_SCREENSHOT)) { #else @@ -435,6 +458,9 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE); if (!state) { GBAExtdataDeinit(&extdata); + if (cheatVf) { + cheatVf->close(cheatVf); + } return false; } GBASerialize(gba, state); @@ -442,6 +468,9 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET); GBAExtdataSerialize(&extdata, vf); GBAExtdataDeinit(&extdata); + if (cheatVf) { + cheatVf->close(cheatVf); + } return true; #ifdef USE_PNG } @@ -461,6 +490,7 @@ struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* return _loadPNGState(vf, extdata); } #endif + vf->seek(vf, 0, SEEK_SET); if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) { return false; } @@ -501,6 +531,17 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { svf->close(svf); } } + if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] && GBAExtdataGet(&extdata, EXTDATA_CHEATS, &item)) { + if (item.size) { + struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]; + struct VFile* svf = VFileFromMemory(item.data, item.size); + if (svf) { + GBACheatDeviceClear(device); + GBACheatParseFile(device, svf); + svf->close(svf); + } + } + } GBAExtdataDeinit(&extdata); return success; } diff --git a/src/gba/serialize.h b/src/gba/serialize.h index a1df2330b..dd4b29c8b 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -339,11 +339,13 @@ enum GBAExtdataTag { EXTDATA_NONE = 0, EXTDATA_SCREENSHOT = 1, EXTDATA_SAVEDATA = 2, + EXTDATA_CHEATS = 3, EXTDATA_MAX }; #define SAVESTATE_SCREENSHOT 1 #define SAVESTATE_SAVEDATA 2 +#define SAVESTATE_CHEATS 4 struct GBAExtdataItem { int32_t size; @@ -364,6 +366,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, int flags); bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot, int flags); struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write); +void GBADeleteState(struct GBA* thread, struct VDir* dir, int slot); bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags); bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags); diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 8265b1775..40db25f1f 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -401,6 +401,8 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* thr } threadContext->idleOptimization = opts->idleOptimization; + + GBADirectorySetMapOptions(&threadContext->dirs, opts); } void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) { @@ -441,7 +443,6 @@ bool GBAThreadStart(struct GBAThread* threadContext) { return false; } - GBADirectorySetInit(&threadContext->dirs); _reloadDirectories(threadContext); MutexInit(&threadContext->stateMutex); @@ -571,8 +572,6 @@ void GBAThreadJoin(struct GBAThread* threadContext) { threadContext->patch->close(threadContext->patch); threadContext->patch = 0; } - - GBADirectorySetDeinit(&threadContext->dirs); } bool GBAThreadIsActive(struct GBAThread* threadContext) { diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h index 2a70d233c..9eee6a659 100644 --- a/src/gba/supervisor/thread.h +++ b/src/gba/supervisor/thread.h @@ -48,7 +48,9 @@ struct GBAThread { struct GBAVideoRenderer* renderer; struct GBASIODriverSet sioDrivers; struct ARMDebugger* debugger; +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 struct GBADirectorySet dirs; +#endif struct VFile* rom; struct VFile* save; struct VFile* bios; diff --git a/src/platform/3ds/3ds-vfs.c b/src/platform/3ds/3ds-vfs.c index 2963c78d1..24caff97d 100644 --- a/src/platform/3ds/3ds-vfs.c +++ b/src/platform/3ds/3ds-vfs.c @@ -45,6 +45,7 @@ static void _vd3dRewind(struct VDir* vd); static struct VDirEntry* _vd3dListNext(struct VDir* vd); static struct VFile* _vd3dOpenFile(struct VDir* vd, const char* path, int mode); static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path); +static bool _vd3dDeleteFile(struct VDir* vd, const char* path); static const char* _vd3deName(struct VDirEntry* vde); static enum VFSType _vd3deType(struct VDirEntry* vde); @@ -191,6 +192,7 @@ struct VDir* VDirOpen(const char* path) { vd3d->d.listNext = _vd3dListNext; vd3d->d.openFile = _vd3dOpenFile; vd3d->d.openDir = _vd3dOpenDir; + vd3d->d.deleteFile = _vd3dDeleteFile; vd3d->vde.d.name = _vd3deName; vd3d->vde.d.type = _vd3deType; @@ -257,6 +259,22 @@ static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path) { return vd2; } +static bool _vd3dDeleteFile(struct VDir* vd, const char* path) { + struct VDir3DS* vd3d = (struct VDir3DS*) vd; + if (!path) { + return 0; + } + const char* dir = vd3d->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s/%s", dir, path); + + // TODO: Use UTF-16 + FS_Path newPath = fsMakePath(PATH_ASCII, combined); + bool ret = !FSUSER_DeleteFile(sdmcArchive, newPath); + free(combined); + return ret; +} + static const char* _vd3deName(struct VDirEntry* vde) { struct VDirEntry3DS* vd3de = (struct VDirEntry3DS*) vde; if (!vd3de->utf8Name[0]) { diff --git a/src/platform/3ds/cia.rsf.in b/src/platform/3ds/cia.rsf.in index a6ebf7ff8..5c91583da 100644 --- a/src/platform/3ds/cia.rsf.in +++ b/src/platform/3ds/cia.rsf.in @@ -92,6 +92,7 @@ AccessControlInfo: MaxCpu : 0x9E # Default CpuSpeed : 804mhz + EnableL2Cache : true DisableDebug : true EnableForceDebug : false diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 1b3e483ec..7bec4ab44 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -70,6 +70,25 @@ unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { return defaultFontMetrics[glyph].width; } +void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) { + UNUSED(font); + if (icon >= GUI_ICON_MAX) { + if (w) { + *w = 0; + } + if (h) { + *h = 0; + } + } else { + if (w) { + *w = defaultIconMetrics[icon].width; + } + if (h) { + *h = defaultIconMetrics[icon].height; + } + } +} + void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint32_t color, uint32_t glyph) { ctrActivateTexture(&font->texture); @@ -124,3 +143,14 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment break; } } + +void GUIFontDrawIconSize(const struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon) { + ctrActivateTexture(&font->icons); + + if (icon >= GUI_ICON_MAX) { + return; + } + + struct GUIIconMetric metric = defaultIconMetrics[icon]; + ctrAddRectScaled(color, x, y, w ? w : metric.width, h ? h : metric.height, metric.x, metric.y, metric.width, metric.height); +} diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 1aed46f87..166c10f00 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -32,8 +32,9 @@ static enum ScreenMode { #define _3DS_INPUT 0x3344534B -#define AUDIO_SAMPLES 0x80 -#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 24) +#define AUDIO_SAMPLES 384 +#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16) +#define DSP_BUFFERS 4 FS_Archive sdmcArchive; @@ -43,7 +44,12 @@ static struct GBA3DSRotationSource { angularRate gyro; } rotation; -static bool hasSound; +static enum { + NO_SOUND, + DSP_SUPPORTED, + CSND_SUPPORTED +} hasSound; + // TODO: Move into context static struct GBAVideoSoftwareRenderer renderer; static struct GBAAVStream stream; @@ -53,6 +59,10 @@ static size_t audioPos = 0; static struct ctrTexture gbaOutputTexture; static int guiDrawn; static int screenCleanup; +static ndspWaveBuf dspBuffer[DSP_BUFFERS]; +static int bufferId = 0; + +static aptHookCookie cookie; enum { GUI_ACTIVE = 1, @@ -70,12 +80,65 @@ enum { extern bool allocateRomBuffer(void); +static void _cleanup(void) { + if (renderer.outputBuffer) { + linearFree(renderer.outputBuffer); + } + + if (gbaOutputTexture.data) { + ctrDeinitGpu(); + vramFree(gbaOutputTexture.data); + } + + gfxExit(); + + if (hasSound != NO_SOUND) { + linearFree(audioLeft); + } + + if (hasSound == CSND_SUPPORTED) { + linearFree(audioRight); + csndExit(); + } + + if (hasSound == DSP_SUPPORTED) { + ndspExit(); + } + + csndExit(); + ptmuExit(); +} + +static void _aptHook(APT_HookType hook, void* user) { + UNUSED(user); + switch (hook) { + case APTHOOK_ONSUSPEND: + case APTHOOK_ONSLEEP: + if (hasSound == CSND_SUPPORTED) { + CSND_SetPlayState(8, 0); + CSND_SetPlayState(9, 0); + csndExecCmds(false); + } + break; + case APTHOOK_ONEXIT: + if (hasSound == CSND_SUPPORTED) { + CSND_SetPlayState(8, 0); + CSND_SetPlayState(9, 0); + csndExecCmds(false); + } + _cleanup(); + exit(0); + break; + default: + break; + } +} + static void _map3DSKey(struct GBAInputMap* map, int ctrKey, enum GBAKey key) { GBAInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key); } -static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) -{ +static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) { u32 pleft = 0, pright = 0; int loopMode = (flags >> 10) & 3; @@ -187,7 +250,7 @@ static void _guiFinish(void) { static void _setup(struct GBAGUIRunner* runner) { runner->context.gba->rotationSource = &rotation.d; - if (hasSound) { + if (hasSound != NO_SOUND) { runner->context.gba->stream = &stream; } @@ -229,12 +292,16 @@ static void _gameLoaded(struct GBAGUIRunner* runner) { blip_set_rates(runner->context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768 * ratio); blip_set_rates(runner->context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768 * ratio); #endif - if (hasSound) { + if (hasSound != NO_SOUND) { + audioPos = 0; + } + if (hasSound == CSND_SUPPORTED) { memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - audioPos = 0; _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); csndExecCmds(false); + } else if (hasSound == DSP_SUPPORTED) { + memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t)); } unsigned mode; if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { @@ -244,7 +311,7 @@ static void _gameLoaded(struct GBAGUIRunner* runner) { } static void _gameUnloaded(struct GBAGUIRunner* runner) { - if (hasSound) { + if (hasSound == CSND_SUPPORTED) { CSND_SetPlayState(8, 0); CSND_SetPlayState(9, 0); csndExecCmds(false); @@ -311,7 +378,7 @@ static void _drawFrame(struct GBAGUIRunner* runner, bool faded) { GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1)); #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF - if (!hasSound) { + if (hasSound == NO_SOUND) { blip_clear(runner->context.gba->audio.left); blip_clear(runner->context.gba->audio.right); } @@ -446,23 +513,45 @@ static int32_t _readGyroZ(struct GBARotationSource* source) { static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) { UNUSED(stream); + if (hasSound == CSND_SUPPORTED) { #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF - blip_read_samples(audio->left, &audioLeft[audioPos], AUDIO_SAMPLES, false); - blip_read_samples(audio->right, &audioRight[audioPos], AUDIO_SAMPLES, false); + blip_read_samples(audio->left, &audioLeft[audioPos], AUDIO_SAMPLES, false); + blip_read_samples(audio->right, &audioRight[audioPos], AUDIO_SAMPLES, false); #elif RESAMPLE_LIBRARY == RESAMPLE_NN - GBAAudioCopy(audio, &audioLeft[audioPos], &audioRight[audioPos], AUDIO_SAMPLES); + GBAAudioCopy(audio, &audioLeft[audioPos], &audioRight[audioPos], AUDIO_SAMPLES); #endif - GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); - GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); - audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER; - if (audioPos == AUDIO_SAMPLES * 3) { - u8 playing = 0; - csndIsPlaying(0x8, &playing); - if (!playing) { - CSND_SetPlayState(0x8, 1); - CSND_SetPlayState(0x9, 1); - csndExecCmds(false); + GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); + GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); + audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER; + if (audioPos == AUDIO_SAMPLES * 3) { + u8 playing = 0; + csndIsPlaying(0x8, &playing); + if (!playing) { + CSND_SetPlayState(0x8, 1); + CSND_SetPlayState(0x9, 1); + csndExecCmds(false); + } } + } else if (hasSound == DSP_SUPPORTED) { + int startId = bufferId; + while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) { + bufferId = (bufferId + 1) & (DSP_BUFFERS - 1); + if (bufferId == startId) { + blip_clear(audio->left); + blip_clear(audio->right); + return; + } + } + void* tmpBuf = dspBuffer[bufferId].data_pcm16; + memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId])); + dspBuffer[bufferId].data_pcm16 = tmpBuf; + dspBuffer[bufferId].nsamples = AUDIO_SAMPLES; +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + blip_read_samples(audio->left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true); + blip_read_samples(audio->right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true); +#endif + DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t)); + ndspChnWaveBufAdd(0, &dspBuffer[bufferId]); } } @@ -480,10 +569,30 @@ int main() { return 1; } - ptmuInit(); - hasSound = !csndInit(); + aptHook(&cookie, _aptHook, 0); - if (hasSound) { + ptmuInit(); + hasSound = NO_SOUND; + if (!ndspInit()) { + hasSound = DSP_SUPPORTED; + ndspSetOutputMode(NDSP_OUTPUT_STEREO); + ndspSetOutputCount(1); + ndspChnReset(0); + ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16); + ndspChnSetInterp(0, NDSP_INTERP_NONE); + ndspChnSetRate(0, 0x8000); + ndspChnWaveBufClear(0); + audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80); + memset(dspBuffer, 0, sizeof(dspBuffer)); + int i; + for (i = 0; i < DSP_BUFFERS; ++i) { + dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2]; + dspBuffer[i].nsamples = AUDIO_SAMPLES; + } + } + + if (hasSound == NO_SOUND && !csndInit()) { + hasSound = CSND_SUPPORTED; audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); } @@ -492,7 +601,8 @@ int main() { if (ctrInitGpu() < 0) { gbaOutputTexture.data = 0; - goto cleanup; + _cleanup(); + return 1; } ctrTexture_Init(&gbaOutputTexture); @@ -504,7 +614,8 @@ int main() { void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2; if (!gbaOutputTexture.data) { - goto cleanup; + _cleanup(); + return 1; } // Zero texture data to make sure no garbage around the border interferes with filtering @@ -523,7 +634,8 @@ int main() { struct GUIFont* font = GUIFontCreate(); if (!font) { - goto cleanup; + _cleanup(); + return 1; } struct GBAGUIRunner runner = { @@ -610,24 +722,6 @@ int main() { GBAGUIRunloop(&runner); GBAGUIDeinit(&runner); -cleanup: - if (renderer.outputBuffer) { - linearFree(renderer.outputBuffer); - } - - if (gbaOutputTexture.data) { - ctrDeinitGpu(); - vramFree(gbaOutputTexture.data); - } - - gfxExit(); - - if (hasSound) { - linearFree(audioLeft); - linearFree(audioRight); - } - - csndExit(); - ptmuExit(); + _cleanup(); return 0; } diff --git a/src/platform/commandline.c b/src/platform/commandline.c index dfea7de7a..4e04277fa 100644 --- a/src/platform/commandline.c +++ b/src/platform/commandline.c @@ -36,7 +36,6 @@ static const struct option _options[] = { { "bios", required_argument, 0, 'b' }, { "cheats", required_argument, 0, 'c' }, - { "dirmode", required_argument, 0, 'D' }, { "frameskip", required_argument, 0, 's' }, #ifdef USE_CLI_DEBUGGER { "debug", no_argument, 0, 'd' }, @@ -56,7 +55,7 @@ static bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) { int ch; char options[64] = - "b:c:Dhl:p:s:v:" + "b:c:hl:p:s:v:" #ifdef USE_CLI_DEBUGGER "d" #endif @@ -86,9 +85,6 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg case 'c': opts->cheatsFile = strdup(optarg); break; - case 'D': - opts->dirmode = true; - break; #ifdef USE_CLI_DEBUGGER case 'd': if (opts->debuggerType != DEBUGGER_NONE) { diff --git a/src/platform/commandline.h b/src/platform/commandline.h index 5f45f444a..737fab8cc 100644 --- a/src/platform/commandline.h +++ b/src/platform/commandline.h @@ -25,7 +25,6 @@ struct GBAArguments { char* fname; char* patch; char* cheatsFile; - bool dirmode; char* movie; enum DebuggerType debuggerType; diff --git a/src/platform/openemu/Info.plist.in b/src/platform/openemu/Info.plist.in index 9ab3b28f5..0642d81f1 100644 --- a/src/platform/openemu/Info.plist.in +++ b/src/platform/openemu/Info.plist.in @@ -32,6 +32,8 @@ 0 OEGameCoreSupportsRewinding + OEGameCoreSupportsCheatCode + OEGameCorePlayerCount diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index 5f220fb95..a5749d6d8 100644 --- a/src/platform/openemu/mGBAGameCore.m +++ b/src/platform/openemu/mGBAGameCore.m @@ -27,6 +27,7 @@ #include "util/common.h" #include "gba/cheats.h" +#include "gba/cheats/gameshark.h" #include "gba/renderers/video-software.h" #include "gba/serialize.h" #include "gba/context/context.h" @@ -45,7 +46,7 @@ struct GBAContext context; struct GBAVideoSoftwareRenderer renderer; struct GBACheatDevice cheats; - struct GBACheatSet cheatSet; + NSMutableDictionary *cheatSets; uint16_t keys; } @end @@ -70,8 +71,7 @@ GBAAudioResizeBuffer(&context.gba->audio, SAMPLES); GBACheatDeviceCreate(&cheats); GBACheatAttachDevice(context.gba, &cheats); - GBACheatSetInit(&cheatSet, "openemu"); - GBACheatAddSet(&cheats, &cheatSet); + cheatSets = [[NSMutableDictionary alloc] init]; keys = 0; } @@ -81,9 +81,18 @@ - (void)dealloc { GBAContextDeinit(&context); - GBACheatRemoveSet(&cheats, &cheatSet); + [cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + UNUSED(key); + UNUSED(stop); + GBACheatRemoveSet(&cheats, [obj pointerValue]); + }]; GBACheatDeviceDestroy(&cheats); - GBACheatSetDeinit(&cheatSet); + [cheatSets enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + UNUSED(key); + UNUSED(stop); + GBACheatSetDeinit([obj pointerValue]); + }]; + [cheatSets release]; free(renderer.outputBuffer); [super dealloc]; @@ -275,5 +284,33 @@ const int GBAMap[] = { keys &= ~(1 << GBAMap[button]); } +#pragma mark - Cheats + +- (void)setCheat:(NSString *)code setType:(NSString *)type setEnabled:(BOOL)enabled +{ + code = [code stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + code = [code stringByReplacingOccurrencesOfString:@" " withString:@""]; + + NSString *codeId = [code stringByAppendingFormat:@"/%@", type]; + struct GBACheatSet* cheatSet = [[cheatSets objectForKey:codeId] pointerValue]; + if (cheatSet) { + cheatSet->enabled = enabled; + return; + } + cheatSet = malloc(sizeof(*cheatSet)); + GBACheatSetInit(cheatSet, [codeId UTF8String]); + if ([type isEqual:@"GameShark"]) { + GBACheatSetGameSharkVersion(cheatSet, 1); + } else if ([type isEqual:@"Action Replay"]) { + GBACheatSetGameSharkVersion(cheatSet, 3); + } + NSArray *codeSet = [code componentsSeparatedByString:@"+"]; + for (id c in codeSet) { + GBACheatAddLine(cheatSet, [c UTF8String]); + } + cheatSet->enabled = enabled; + [cheatSets setObject:[NSValue valueWithPointer:cheatSet] forKey:codeId]; + GBACheatAddSet(&cheats, cheatSet); +} @end diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 5d31b89ae..7ca41e577 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -99,12 +99,12 @@ void GBAGLContextPostFrame(struct VideoBackend* v, const void* frame) { glBindTexture(GL_TEXTURE_2D, context->tex); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index cf4ef8c0d..e19f8b44a 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -13,6 +13,13 @@ #define MAX_PASSES 8 +static const GLchar* const _gles2Header = + "#version 100\n" + "precision mediump float;\n"; + +static const GLchar* const _gl3Header = + "#version 120\n"; + static const char* const _vertexShader = "attribute vec4 position;\n" "varying vec2 texCoord;\n" @@ -35,12 +42,15 @@ static const char* const _fragmentShader = "varying vec2 texCoord;\n" "uniform sampler2D tex;\n" "uniform float gamma;\n" + "uniform vec3 desaturation;\n" "uniform vec3 scale;\n" "uniform vec3 bias;\n" "void main() {\n" " vec4 color = texture2D(tex, texCoord);\n" " color.a = 1.;\n" + " float average = dot(color.rgb, vec3(1.)) / 3.;\n" + " color.rgb = mix(color.rgb, vec3(average), desaturation);\n" " color.rgb = scale * pow(color.rgb, vec3(gamma, gamma, gamma)) + bias;\n" " gl_FragColor = color;\n" "}"; @@ -82,7 +92,7 @@ static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) { glClearColor(0.f, 0.f, 0.f, 1.f); - struct GBAGLES2Uniform* uniforms = malloc(sizeof(struct GBAGLES2Uniform) * 3); + struct GBAGLES2Uniform* uniforms = malloc(sizeof(struct GBAGLES2Uniform) * 4); uniforms[0].name = "gamma"; uniforms[0].readableName = "Gamma"; uniforms[0].type = GL_FLOAT; @@ -113,8 +123,20 @@ static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) { uniforms[2].max.fvec3[0] = 1.0f; uniforms[2].max.fvec3[1] = 1.0f; uniforms[2].max.fvec3[2] = 1.0f; - GBAGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, uniforms, 3); - GBAGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, 0, 0); + uniforms[3].name = "desaturation"; + uniforms[3].readableName = "Desaturation"; + uniforms[3].type = GL_FLOAT_VEC3; + uniforms[3].value.fvec3[0] = 0.0f; + uniforms[3].value.fvec3[1] = 0.0f; + uniforms[3].value.fvec3[2] = 0.0f; + uniforms[3].min.fvec3[0] = 0.0f; + uniforms[3].min.fvec3[1] = 0.0f; + uniforms[3].min.fvec3[2] = 0.0f; + uniforms[3].max.fvec3[0] = 1.0f; + uniforms[3].max.fvec3[1] = 1.0f; + uniforms[3].max.fvec3[2] = 1.0f; + GBAGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4); + GBAGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0); glDeleteFramebuffers(1, &context->finalShader.fbo); context->finalShader.fbo = 0; } @@ -173,6 +195,12 @@ void _drawShader(struct GBAGLES2Shader* shader) { drawH = viewport[3]; padH = viewport[1]; } + if (shader->integerScaling) { + padW = 0; + padH = 0; + drawW -= drawW % VIDEO_HORIZONTAL_PIXELS; + drawH -= drawH % VIDEO_VERTICAL_PIXELS; + } glViewport(padW, padH, drawW, drawH); if (!shader->width || !shader->height) { GLint oldTex; @@ -182,7 +210,7 @@ void _drawShader(struct GBAGLES2Shader* shader) { glBindTexture(GL_TEXTURE_2D, oldTex); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); @@ -263,7 +291,6 @@ void GBAGLES2ContextDrawFrame(struct VideoBackend* v) { void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { struct GBAGLES2Context* context = (struct GBAGLES2Context*) v; glBindTexture(GL_TEXTURE_2D, context->tex); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 256); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); @@ -273,7 +300,6 @@ void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } void GBAGLES2ContextCreate(struct GBAGLES2Context* context) { @@ -290,9 +316,10 @@ void GBAGLES2ContextCreate(struct GBAGLES2Context* context) { context->nShaders = 0; } -void GBAGLES2ShaderInit(struct GBAGLES2Shader* shader, const char* vs, const char* fs, int width, int height, struct GBAGLES2Uniform* uniforms, size_t nUniforms) { +void GBAGLES2ShaderInit(struct GBAGLES2Shader* shader, const char* vs, const char* fs, int width, int height, bool integerScaling, struct GBAGLES2Uniform* uniforms, size_t nUniforms) { shader->width = width >= 0 ? width : VIDEO_HORIZONTAL_PIXELS; shader->height = height >= 0 ? height : VIDEO_VERTICAL_PIXELS; + shader->integerScaling = integerScaling; shader->filter = false; shader->blend = false; shader->uniforms = uniforms; @@ -314,16 +341,27 @@ void GBAGLES2ShaderInit(struct GBAGLES2Shader* shader, const char* vs, const cha shader->program = glCreateProgram(); shader->vertexShader = glCreateShader(GL_VERTEX_SHADER); shader->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const GLchar* shaderBuffer[2]; + const GLubyte* version = glGetString(GL_VERSION); + if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) { + shaderBuffer[0] = _gl3Header; + } else { + shaderBuffer[0] = _gles2Header; + } if (vs) { - glShaderSource(shader->vertexShader, 1, (const GLchar**) &vs, 0); + shaderBuffer[1] = vs; } else { - glShaderSource(shader->vertexShader, 1, (const GLchar**) &_nullVertexShader, 0); + shaderBuffer[1] = _nullVertexShader; } + glShaderSource(shader->vertexShader, 2, shaderBuffer, 0); + if (fs) { - glShaderSource(shader->fragmentShader, 1, (const GLchar**) &fs, 0); + shaderBuffer[1] = fs; } else { - glShaderSource(shader->fragmentShader, 1, (const GLchar**) &_nullFragmentShader, 0); + shaderBuffer[1] = _nullFragmentShader; } + glShaderSource(shader->fragmentShader, 2, shaderBuffer, 0); + glAttachShader(shader->program, shader->vertexShader); glAttachShader(shader->program, shader->fragmentShader); char log[1024]; @@ -771,8 +809,10 @@ bool GBAGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { } int width = 0; int height = 0; + int scaling = 0; _lookupIntValue(&description, passName, "width", &width); _lookupIntValue(&description, passName, "height", &height); + _lookupIntValue(&description, passName, "integerScaling", &scaling); struct GBAGLES2UniformList uniformVector; GBAGLES2UniformListInit(&uniformVector, 0); @@ -790,7 +830,7 @@ bool GBAGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { memcpy(uniformBlock, GBAGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); GBAGLES2UniformListDeinit(&uniformVector); - GBAGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, uniformBlock, u); + GBAGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u); int b = 0; _lookupIntValue(&description, passName, "blend", &b); if (b) { diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index cf28eb602..fa623b94f 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -53,6 +53,7 @@ struct GBAGLES2Uniform { struct GBAGLES2Shader { unsigned width; unsigned height; + bool integerScaling; bool filter; bool blend; GLuint tex; @@ -83,7 +84,7 @@ struct GBAGLES2Context { void GBAGLES2ContextCreate(struct GBAGLES2Context*); -void GBAGLES2ShaderInit(struct GBAGLES2Shader*, const char* vs, const char* fs, int width, int height, struct GBAGLES2Uniform* uniforms, size_t nUniforms); +void GBAGLES2ShaderInit(struct GBAGLES2Shader*, const char* vs, const char* fs, int width, int height, bool integerScaling, struct GBAGLES2Uniform* uniforms, size_t nUniforms); void GBAGLES2ShaderDeinit(struct GBAGLES2Shader*); void GBAGLES2ShaderAttach(struct GBAGLES2Context*, struct GBAGLES2Shader*, size_t nShaders); void GBAGLES2ShaderDetach(struct GBAGLES2Context*); diff --git a/src/platform/psp2/gui-font.c b/src/platform/psp2/gui-font.c index 5d23e67d2..aa1e6cdc1 100644 --- a/src/platform/psp2/gui-font.c +++ b/src/platform/psp2/gui-font.c @@ -49,17 +49,36 @@ unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { return defaultFontMetrics[glyph].width * 2; } +void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) { + UNUSED(font); + if (icon >= GUI_ICON_MAX) { + if (w) { + *w = 0; + } + if (h) { + *h = 0; + } + } else { + if (w) { + *w = defaultIconMetrics[icon].width * 2; + } + if (h) { + *h = defaultIconMetrics[icon].height * 2; + } + } +} + void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) { if (glyph > 0x7F) { glyph = '?'; } struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; - vita2d_draw_texture_tint_part_scale(font->tex, x, y - GLYPH_HEIGHT + metric.padding.top * 2, + vita2d_draw_texture_tint_part(font->tex, x, y - GLYPH_HEIGHT + metric.padding.top * 2, (glyph & 15) * CELL_WIDTH + metric.padding.left * 2, (glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2, CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2, CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2, - 1, 1, color); + color); } void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) { @@ -86,13 +105,13 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment switch (orient) { case GUI_ORIENT_HMIRROR: - vita2d_draw_texture_tint_part_scale(font->icons, x, y, + vita2d_draw_texture_tint_part_scale(font->icons, x + metric.width * 2, y, metric.x * 2, metric.y * 2, metric.width * 2, metric.height * 2, -1, 1, color); return; case GUI_ORIENT_VMIRROR: - vita2d_draw_texture_tint_part_scale(font->icons, x, y, + vita2d_draw_texture_tint_part_scale(font->icons, x, y + metric.height * 2, metric.x * 2, metric.y * 2, metric.width * 2, metric.height * 2, 1, -1, color); @@ -107,3 +126,14 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment break; } } + +void GUIFontDrawIconSize(const struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon) { + if (icon >= GUI_ICON_MAX) { + return; + } + struct GUIIconMetric metric = defaultIconMetrics[icon]; + vita2d_draw_texture_tint_part_scale(font->icons, x, y, + metric.x * 2, metric.y * 2, + metric.width * 2, metric.height * 2, + w ? (w / (float) metric.width) : 1, h ? (h / (float) metric.height) : 1, color); +} diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 3c8fd30a3..b16abeae3 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -22,8 +22,6 @@ #include -PSP2_MODULE_INFO(0, 0, "mGBA"); - static void _drawStart(void) { vita2d_set_vblank_wait(false); vita2d_start_drawing(); @@ -43,29 +41,29 @@ static uint32_t _pollInput(void) { SceCtrlData pad; sceCtrlPeekBufferPositive(0, &pad, 1); int input = 0; - if (pad.buttons & PSP2_CTRL_TRIANGLE) { + if (pad.buttons & SCE_CTRL_TRIANGLE) { input |= 1 << GUI_INPUT_CANCEL; } - if (pad.buttons & PSP2_CTRL_SQUARE) { + if (pad.buttons & SCE_CTRL_SQUARE) { input |= 1 << GBA_GUI_INPUT_SCREEN_MODE; } - if (pad.buttons & PSP2_CTRL_CIRCLE) { + if (pad.buttons & SCE_CTRL_CIRCLE) { input |= 1 << GUI_INPUT_BACK; } - if (pad.buttons & PSP2_CTRL_CROSS) { + if (pad.buttons & SCE_CTRL_CROSS) { input |= 1 << GUI_INPUT_SELECT; } - if (pad.buttons & PSP2_CTRL_UP || pad.ly < 64) { + if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) { input |= 1 << GUI_INPUT_UP; } - if (pad.buttons & PSP2_CTRL_DOWN || pad.ly >= 192) { + if (pad.buttons & SCE_CTRL_DOWN || pad.ly >= 192) { input |= 1 << GUI_INPUT_DOWN; } - if (pad.buttons & PSP2_CTRL_LEFT || pad.lx < 64) { + if (pad.buttons & SCE_CTRL_LEFT || pad.lx < 64) { input |= 1 << GUI_INPUT_LEFT; } - if (pad.buttons & PSP2_CTRL_RIGHT || pad.lx >= 192) { + if (pad.buttons & SCE_CTRL_RIGHT || pad.lx >= 192) { input |= 1 << GUI_INPUT_RIGHT; } @@ -108,7 +106,7 @@ int main() { GUI_PARAMS_TRAIL }, .configExtra = (struct GUIMenuItem[]) { - { + { .title = "Screen mode", .data = "screenMode", .submenu = 0, @@ -138,10 +136,10 @@ int main() { "R", 0, // L2? 0, // R2? - "Triangle", - "Circle", - "Cross", - "Square" + "\1\xC", + "\1\xA", + "\1\xB", + "\1\xD" }, .nKeys = 16 }, diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 9c880b71c..3f567e209 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -65,7 +65,7 @@ static void _mapVitaKey(struct GBAInputMap* map, int pspKey, enum GBAKey key) { static THREAD_ENTRY _audioThread(void* context) { struct GBAPSP2AudioContext* audio = (struct GBAPSP2AudioContext*) context; struct GBAStereoSample buffer[PSP2_SAMPLES]; - int audioPort = sceAudioOutOpenPort(PSP2_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, PSP2_AUDIO_OUT_MODE_STEREO); + int audioPort = sceAudioOutOpenPort(SCE_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, SCE_AUDIO_OUT_MODE_STEREO); while (audio->running) { memset(buffer, 0, sizeof(buffer)); MutexLock(&audio->mutex); @@ -75,7 +75,7 @@ static THREAD_ENTRY _audioThread(void* context) { len = PSP2_SAMPLES; } if (len > 0) { - len &= ~(PSP2_AUDIO_MIN_LEN - 1); + len &= ~(SCE_AUDIO_MIN_LEN - 1); CircleBufferRead(&audio->buffer, buffer, len * sizeof(buffer[0])); MutexUnlock(&audio->mutex); sceAudioOutOutput(audioPort, buffer); @@ -137,16 +137,16 @@ uint16_t GBAPSP2PollInput(struct GBAGUIRunner* runner) { void GBAPSP2Setup(struct GBAGUIRunner* runner) { scePowerSetArmClockFrequency(80); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_CROSS, GBA_KEY_A); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_CIRCLE, GBA_KEY_B); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_START, GBA_KEY_START); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_SELECT, GBA_KEY_SELECT); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_UP, GBA_KEY_UP); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_DOWN, GBA_KEY_DOWN); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_LEFT, GBA_KEY_LEFT); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_RIGHT, GBA_KEY_RIGHT); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_LTRIGGER, GBA_KEY_L); - _mapVitaKey(&runner->context.inputMap, PSP2_CTRL_RTRIGGER, GBA_KEY_R); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_CROSS, GBA_KEY_A); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_CIRCLE, GBA_KEY_B); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_START, GBA_KEY_START); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_SELECT, GBA_KEY_SELECT); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_UP, GBA_KEY_UP); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_DOWN, GBA_KEY_DOWN); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_LEFT, GBA_KEY_LEFT); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_RIGHT, GBA_KEY_RIGHT); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_LTRIGGER, GBA_KEY_L); + _mapVitaKey(&runner->context.inputMap, SCE_CTRL_RTRIGGER, GBA_KEY_R); struct GBAAxis desc = { GBA_KEY_DOWN, GBA_KEY_UP, 192, 64 }; GBAInputBindAxis(&runner->context.inputMap, PSP2_INPUT, 0, &desc); diff --git a/src/platform/psp2/sce-vfs.c b/src/platform/psp2/sce-vfs.c index 6b7666320..2dac02319 100644 --- a/src/platform/psp2/sce-vfs.c +++ b/src/platform/psp2/sce-vfs.c @@ -43,6 +43,7 @@ static void _vdsceRewind(struct VDir* vd); static struct VDirEntry* _vdsceListNext(struct VDir* vd); static struct VFile* _vdsceOpenFile(struct VDir* vd, const char* path, int mode); static struct VDir* _vdsceOpenDir(struct VDir* vd, const char* path); +static bool _vdsceDeleteFile(struct VDir* vd, const char* path); static const char* _vdesceName(struct VDirEntry* vde); static enum VFSType _vdesceType(struct VDirEntry* vde); @@ -152,6 +153,7 @@ struct VDir* VDirOpen(const char* path) { vd->d.listNext = _vdsceListNext; vd->d.openFile = _vdsceOpenFile; vd->d.openDir = _vdsceOpenDir; + vd->d.deleteFile = _vdsceDeleteFile; vd->path = strdup(path); vd->de.d.name = _vdesceName; @@ -215,6 +217,20 @@ struct VDir* _vdsceOpenDir(struct VDir* vd, const char* path) { return vd2; } +bool _vdsceDeleteFile(struct VDir* vd, const char* path) { + struct VDirSce* vdsce = (struct VDirSce*) vd; + if (!path) { + return 0; + } + const char* dir = vdsce->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + strlen(PATH_SEP) + 1)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + bool ret = sceIoRemove(combined) >= 0; + free(combined); + return ret; +} + static const char* _vdesceName(struct VDirEntry* vde) { struct VDirEntrySce* vdesce = (struct VDirEntrySce*) vde; return vdesce->ent.d_name; @@ -222,7 +238,7 @@ static const char* _vdesceName(struct VDirEntry* vde) { static enum VFSType _vdesceType(struct VDirEntry* vde) { struct VDirEntrySce* vdesce = (struct VDirEntrySce*) vde; - if (PSP2_S_ISDIR(vdesce->ent.d_stat.st_mode)) { + if (SCE_S_ISDIR(vdesce->ent.d_stat.st_mode)) { return VFS_DIRECTORY; } return VFS_FILE; diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index 357a0c788..4ba25aef7 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -31,6 +31,7 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent) connect(m_ui.addSet, SIGNAL(clicked()), this, SLOT(addSet())); connect(m_ui.remove, SIGNAL(clicked()), this, SLOT(removeSet())); connect(controller, SIGNAL(gameStopped(GBAThread*)), &m_model, SLOT(invalidated())); + connect(controller, SIGNAL(stateLoaded(GBAThread*)), &m_model, SLOT(invalidated())); connect(m_ui.add, &QPushButton::clicked, [this]() { enterCheat(GBACheatAddLine); diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 6ab9d39d5..a2383333d 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -128,6 +128,7 @@ ConfigController::~ConfigController() { bool ConfigController::parseArguments(GBAArguments* args, int argc, char* argv[], SubParser* subparser) { if (::parseArguments(args, &m_config, argc, argv, subparser)) { + GBAConfigFreeOpts(&m_opts); GBAConfigMap(&m_config, &m_opts); return true; } @@ -262,6 +263,9 @@ void ConfigController::setMRU(const QList& mru) { void ConfigController::write() { GBAConfigSave(&m_config); m_settings->sync(); + + GBAConfigFreeOpts(&m_opts); + GBAConfigMap(&m_config, &m_opts); } void ConfigController::makePortable() { diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 014162562..5d76a5144 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -14,30 +14,34 @@ extern "C" { using namespace QGBA; -#ifdef BUILD_GL +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) Display::Driver Display::s_driver = Display::Driver::OPENGL; #else Display::Driver Display::s_driver = Display::Driver::QT; #endif Display* Display::create(QWidget* parent) { -#ifdef BUILD_GL +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); format.setSwapInterval(1); #endif switch (s_driver) { -#ifdef BUILD_GL +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) case Driver::OPENGL: - return new DisplayGL(format, parent); + return new DisplayGL(format, false, parent); +#endif +#ifdef BUILD_GL + case Driver::OPENGL1: + return new DisplayGL(format, true, parent); #endif case Driver::QT: return new DisplayQt(parent); default: -#ifdef BUILD_GL - return new DisplayGL(format, parent); +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) + return new DisplayGL(format, false, parent); #else return new DisplayQt(parent); #endif diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 097e4c12f..a4145f6df 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -22,8 +22,11 @@ Q_OBJECT public: enum class Driver { QT = 0, -#ifdef BUILD_GL +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) OPENGL = 1, +#endif +#ifdef BUILD_GL + OPENGL1 = 2, #endif }; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 249fb8f7f..91ddaf68f 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -24,14 +24,22 @@ extern "C" { using namespace QGBA; -DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) +DisplayGL::DisplayGL(const QGLFormat& format, bool force1, QWidget* parent) : Display(parent) , m_isDrawing(false) , m_gl(new EmptyGLWidget(format, this)) , m_drawThread(nullptr) , m_context(nullptr) { - m_painter = new PainterGL(m_gl, QGLFormat::openGLVersionFlags()); + QGLFormat::OpenGLVersionFlags versions = QGLFormat::openGLVersionFlags(); + if (force1) { + versions &= QGLFormat::OpenGL_Version_1_1 | + QGLFormat::OpenGL_Version_1_2 | + QGLFormat::OpenGL_Version_1_3 | + QGLFormat::OpenGL_Version_1_4 | + QGLFormat::OpenGL_Version_1_5; + } + m_painter = new PainterGL(m_gl, versions); m_gl->setMouseTracking(true); m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work? } @@ -188,7 +196,7 @@ PainterGL::PainterGL(QGLWidget* parent, QGLFormat::OpenGLVersionFlags glVersion) #endif #if !defined(_WIN32) || defined(USE_EPOXY) - if (glVersion & QGLFormat::OpenGL_Version_3_0) { + if (glVersion & (QGLFormat::OpenGL_Version_3_0 | QGLFormat::OpenGL_ES_Version_2_0)) { gl2Backend = new GBAGLES2Context; GBAGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d; @@ -366,7 +374,7 @@ void PainterGL::enqueue(const uint32_t* backing) { } else { buffer = m_free.takeLast(); } - memcpy(buffer, backing, 256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + memcpy(buffer, backing, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); m_queue.enqueue(buffer); m_mutex.unlock(); } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 98039418a..fa0b9e514 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -10,6 +10,9 @@ #ifdef USE_EPOXY #include +#ifndef GLdouble +#define GLdouble GLdouble +#endif #endif #include @@ -42,7 +45,7 @@ class DisplayGL : public Display { Q_OBJECT public: - DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); + DisplayGL(const QGLFormat& format, bool force1 = false, QWidget* parent = nullptr); ~DisplayGL(); bool isDrawing() const override { return m_isDrawing; } diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 71e684055..39502e855 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -41,12 +41,12 @@ void DisplayQt::framePosted(const uint32_t* buffer) { } #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB16); + m_backing = QImage(reinterpret_cast(buffer), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_RGB16); #else - m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB555); + m_backing = QImage(reinterpret_cast(buffer), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_RGB555); #endif #else - m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB32); + m_backing = QImage(reinterpret_cast(buffer), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_RGB32); #endif } diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index 9fb96b303..7e6da3bdf 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -124,28 +124,27 @@ GBAApp* GBAApp::app() { return g_app; } -void GBAApp::interruptAll() { +void GBAApp::pauseAll(QList* paused) { for (int i = 0; i < MAX_GBAS; ++i) { - if (!m_windows[i] || !m_windows[i]->controller()->isLoaded()) { + if (!m_windows[i] || !m_windows[i]->controller()->isLoaded() || m_windows[i]->controller()->isPaused()) { continue; } - m_windows[i]->controller()->threadInterrupt(); + m_windows[i]->controller()->setPaused(true); + paused->append(i); } } -void GBAApp::continueAll() { - for (int i = 0; i < MAX_GBAS; ++i) { - if (!m_windows[i] || !m_windows[i]->controller()->isLoaded()) { - continue; - } - m_windows[i]->controller()->threadContinue(); +void GBAApp::continueAll(const QList* paused) { + for (int i : *paused) { + m_windows[i]->controller()->setPaused(false); } } QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) { - interruptAll(); + QList paused; + pauseAll(&paused); QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getQtOption("lastDirectory").toString(), filter); - continueAll(); + continueAll(&paused); if (!filename.isEmpty()) { m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path()); } @@ -153,9 +152,21 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr } QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) { - interruptAll(); + QList paused; + pauseAll(&paused); QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getQtOption("lastDirectory").toString(), filter); - continueAll(); + continueAll(&paused); + if (!filename.isEmpty()) { + m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + } + return filename; +} + +QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) { + QList paused; + pauseAll(&paused); + QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController.getQtOption("lastDirectory").toString()); + continueAll(&paused); if (!filename.isEmpty()) { m_configController.setQtOption("lastDirectory", QFileInfo(filename).dir().path()); } @@ -210,12 +221,13 @@ GBAApp::FileDialog::FileDialog(GBAApp* app, QWidget* parent, const QString& capt } int GBAApp::FileDialog::exec() { - m_app->interruptAll(); + QList paused; + m_app->pauseAll(&paused); bool didAccept = QFileDialog::exec() == QDialog::Accepted; QStringList filenames = selectedFiles(); if (!filenames.isEmpty()) { m_app->m_configController.setQtOption("lastDirectory", QFileInfo(filenames[0]).dir().path()); } - m_app->continueAll(); + m_app->continueAll(&paused); return didAccept; } diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index e84f8f422..f30be51f4 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -36,6 +36,7 @@ public: QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = QString()); QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = QString()); + QString getOpenDirectoryName(QWidget* owner, const QString& title); QFileDialog* getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); QFileDialog* getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); @@ -43,10 +44,6 @@ public: const NoIntroDB* gameDB() const { return m_db; } bool reloadGameDB(); -public slots: - void interruptAll(); - void continueAll(); - protected: bool event(QEvent*); @@ -63,6 +60,9 @@ private: Window* newWindowInternal(); + void pauseAll(QList* paused); + void continueAll(const QList* paused); + ConfigController m_configController; Window* m_windows[MAX_GBAS]; MultiplayerController m_multiplayer; diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index f8f901c77..4594fbabd 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -15,6 +15,12 @@ #include "InputController.h" #include "KeyEditor.h" +#ifdef BUILD_SDL +extern "C" { +#include "platform/sdl/sdl-events.h" +} +#endif + using namespace QGBA; const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247; @@ -51,22 +57,19 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& #ifdef BUILD_SDL if (type == SDL_BINDING_BUTTON) { + controller->updateJoysticks(); controller->recalibrateAxes(); lookupAxes(map); m_profileSelect = new QComboBox(this); m_profileSelect->addItems(controller->connectedGamepads(type)); int activeGamepad = controller->gamepad(type); + selectGamepad(activeGamepad); if (activeGamepad > 0) { m_profileSelect->setCurrentIndex(activeGamepad); } - connect(m_profileSelect, static_cast(&QComboBox::currentIndexChanged), [this] (int i) { - m_controller->setGamepad(m_type, i); - m_profile = m_profileSelect->currentText(); - m_controller->loadProfile(m_type, m_profile); - refresh(); - }); + connect(m_profileSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectGamepad(int))); m_clear = new QWidget(this); QHBoxLayout* layout = new QHBoxLayout; @@ -105,9 +108,6 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& connect(setAll, SIGNAL(pressed()), this, SLOT(setAll())); layout->addWidget(setAll); - QPushButton* save = new QPushButton(tr("Save")); - connect(save, SIGNAL(pressed()), this, SLOT(save())); - layout->addWidget(save); layout->setSpacing(6); m_keyOrder = QList{ @@ -136,6 +136,10 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& setAll->setFocus(); } +GBAKeyEditor::~GBAKeyEditor() { + m_controller->releaseFocus(this); +} + void GBAKeyEditor::setAll() { m_currentKey = m_keyOrder.begin(); (*m_currentKey)->setFocus(); @@ -174,9 +178,10 @@ void GBAKeyEditor::closeEvent(QCloseEvent*) { } bool GBAKeyEditor::event(QEvent* event) { - if (event->type() == QEvent::WindowActivate) { + QEvent::Type type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::Show) { m_controller->stealFocus(this); - } else if (event->type() == QEvent::WindowDeactivate) { + } else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) { m_controller->releaseFocus(this); } return QWidget::event(event); @@ -280,7 +285,7 @@ void GBAKeyEditor::lookupAxes(const GBAInputMap* map) { void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { #ifdef BUILD_SDL - if (m_type == SDL_BINDING_BUTTON) { + if (m_type == SDL_BINDING_BUTTON && keyEditor->axis() >= 0) { m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key); } #endif @@ -309,6 +314,13 @@ void GBAKeyEditor::setAxisValue(int axis, int32_t value) { KeyEditor* focused = *m_currentKey; focused->setValueAxis(axis, value); } + +void GBAKeyEditor::selectGamepad(int index) { + m_controller->setGamepad(m_type, index); + m_profile = m_profileSelect->currentText(); + m_controller->loadProfile(m_type, m_profile); + refresh(); +} #endif KeyEditor* GBAKeyEditor::keyById(GBAKey key) { diff --git a/src/platform/qt/GBAKeyEditor.h b/src/platform/qt/GBAKeyEditor.h index 8ab83442d..6637571d2 100644 --- a/src/platform/qt/GBAKeyEditor.h +++ b/src/platform/qt/GBAKeyEditor.h @@ -28,6 +28,7 @@ Q_OBJECT public: GBAKeyEditor(InputController* controller, int type, const QString& profile = QString(), QWidget* parent = nullptr); + virtual ~GBAKeyEditor(); public slots: void setAll(); @@ -45,6 +46,7 @@ private slots: void refresh(); #ifdef BUILD_SDL void setAxisValue(int axis, int32_t value); + void selectGamepad(int index); #endif private: diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 197b7928d..8145fca4c 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -75,3 +75,12 @@ void GDBController::listen() { } m_gameController->threadContinue(); } + +void GDBController::breakInto() { + if (!isAttached()) { + return; + } + m_gameController->threadInterrupt(); + ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_MANUAL, 0); + m_gameController->threadContinue(); +} diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index dc310d6f7..cd18fb705 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -34,6 +34,7 @@ public slots: void attach(); void detach(); void listen(); + void breakInto(); signals: void listening(); diff --git a/src/platform/qt/GDBWindow.cpp b/src/platform/qt/GDBWindow.cpp index eeaa76ca9..98bc89801 100644 --- a/src/platform/qt/GDBWindow.cpp +++ b/src/platform/qt/GDBWindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "GDBController.h" @@ -45,10 +46,20 @@ GDBWindow::GDBWindow(GDBController* controller, QWidget* parent) connect(m_bindAddressEdit, SIGNAL(textChanged(const QString&)), this, SLOT(bindAddressChanged(const QString&))); settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft); + QHBoxLayout* buttons = new QHBoxLayout; + m_startStopButton = new QPushButton; - mainSegment->addWidget(m_startStopButton); + buttons->addWidget(m_startStopButton); + + m_breakButton = new QPushButton; + m_breakButton->setText(tr("Break")); + buttons->addWidget(m_breakButton); + + mainSegment->addLayout(buttons); + connect(m_gdbController, SIGNAL(listening()), this, SLOT(started())); connect(m_gdbController, SIGNAL(listenFailed()), this, SLOT(failed())); + connect(m_breakButton, SIGNAL(clicked()), controller, SLOT(breakInto())); if (m_gdbController->isAttached()) { started(); @@ -91,6 +102,7 @@ void GDBWindow::started() { m_portEdit->setEnabled(false); m_bindAddressEdit->setEnabled(false); m_startStopButton->setText(tr("Stop")); + m_breakButton->setEnabled(true); disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen())); connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach())); connect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped())); @@ -100,6 +112,7 @@ void GDBWindow::stopped() { m_portEdit->setEnabled(true); m_bindAddressEdit->setEnabled(true); m_startStopButton->setText(tr("Start")); + m_breakButton->setEnabled(false); disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach())); disconnect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped())); connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen())); diff --git a/src/platform/qt/GDBWindow.h b/src/platform/qt/GDBWindow.h index 9759df05b..4e91c0e90 100644 --- a/src/platform/qt/GDBWindow.h +++ b/src/platform/qt/GDBWindow.h @@ -36,6 +36,7 @@ private: QLineEdit* m_portEdit; QLineEdit* m_bindAddressEdit; QPushButton* m_startStopButton; + QPushButton* m_breakButton; }; } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 8b5e1c57b..90dfe10bf 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -19,6 +19,7 @@ extern "C" { #include "gba/audio.h" #include "gba/context/config.h" +#include "gba/context/directories.h" #include "gba/gba.h" #include "gba/serialize.h" #include "gba/sharkport.h" @@ -31,8 +32,8 @@ using namespace std; GameController::GameController(QObject* parent) : QObject(parent) - , m_drawContext(new uint32_t[256 * VIDEO_HORIZONTAL_PIXELS]) - , m_frontBuffer(new uint32_t[256 * 256]) + , m_drawContext(new uint32_t[VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS]) + , m_frontBuffer(new uint32_t[VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS]) , m_threadContext() , m_activeKeys(0) , m_inactiveKeys(0) @@ -57,11 +58,13 @@ GameController::GameController(QObject* parent) , m_stateSlot(1) , m_backupLoadState(nullptr) , m_backupSaveState(nullptr) + , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS) + , m_loadStateFlags(SAVESTATE_SCREENSHOT) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer); m_renderer->outputBuffer = (color_t*) m_drawContext; - m_renderer->outputBufferStride = 256; + m_renderer->outputBufferStride = VIDEO_HORIZONTAL_PIXELS; GBACheatDeviceCreate(&m_cheatDevice); GBASIODolphinCreate(&m_dolphin); @@ -75,6 +78,7 @@ GameController::GameController(QObject* parent) m_threadContext.rewindBufferCapacity = 0; m_threadContext.cheats = &m_cheatDevice; m_threadContext.logLevel = GBA_LOG_ALL; + GBADirectorySetInit(&m_threadContext.dirs); m_lux.p = this; m_lux.sample = [](GBALuminanceSource* context) { @@ -111,11 +115,8 @@ GameController::GameController(QObject* parent) context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4]; controller->m_fpsTarget = context->fpsTarget; - if (GBALoadState(context, context->dirs.state, 0, SAVESTATE_SCREENSHOT)) { - VFile* vf = GBAGetState(context->gba, context->dirs.state, 0, true); - if (vf) { - vf->truncate(vf, 0); - } + if (context->dirs.state && GBALoadState(context, context->dirs.state, 0, controller->m_loadStateFlags)) { + GBADeleteState(context->gba, context->dirs.state, 0); } QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(GBAThread*, context)); }; @@ -127,7 +128,7 @@ GameController::GameController(QObject* parent) m_threadContext.frameCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); - memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); + memcpy(controller->m_frontBuffer, controller->m_drawContext, VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { GBAThreadPauseFromThread(context); @@ -140,7 +141,7 @@ GameController::GameController(QObject* parent) return false; } GameController* controller = static_cast(context->userData); - if (!GBASaveState(context, context->dirs.state, 0, true)) { + if (!GBASaveState(context, context->dirs.state, 0, controller->m_saveStateFlags)) { return false; } QMetaObject::invokeMethod(controller, "closeGame"); @@ -218,6 +219,7 @@ GameController::~GameController() { detachDolphin(); closeGame(); GBACheatDeviceDestroy(&m_cheatDevice); + GBADirectorySetDeinit(&m_threadContext.dirs); delete m_renderer; delete[] m_drawContext; delete[] m_frontBuffer; @@ -279,6 +281,7 @@ void GameController::setOptions(const GBAOptions* opts) { setMute(opts->mute); threadInterrupt(); + GBADirectorySetMapOptions(&m_threadContext.dirs, opts); m_threadContext.idleOptimization = opts->idleOptimization; threadContinue(); } @@ -301,26 +304,22 @@ void GameController::setDebugger(ARMDebugger* debugger) { } #endif -void GameController::loadGame(const QString& path, bool dirmode) { +void GameController::loadGame(const QString& path) { closeGame(); - if (!dirmode) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - postLog(GBA_LOG_ERROR, tr("Failed to open game file: %1").arg(path)); - return; - } - file.close(); + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + postLog(GBA_LOG_ERROR, tr("Failed to open game file: %1").arg(path)); + return; } + file.close(); m_fname = path; - m_dirmode = dirmode; openGame(); } void GameController::bootBIOS() { closeGame(); m_fname = QString(); - m_dirmode = false; openGame(true); } @@ -360,7 +359,7 @@ void GameController::openGame(bool biosOnly) { } m_inputController->recalibrateAxes(); - memset(m_drawContext, 0xF8, 1024 * VIDEO_HORIZONTAL_PIXELS); + memset(m_drawContext, 0xF8, VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS * 4); if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false; @@ -720,6 +719,10 @@ void GameController::setUseBIOS(bool use) { } void GameController::loadState(int slot) { + if (!m_threadContext.fname) { + // We're in the BIOS + return; + } if (slot > 0 && slot != m_stateSlot) { m_stateSlot = slot; m_backupSaveState.clear(); @@ -730,7 +733,7 @@ void GameController::loadState(int slot) { controller->m_backupLoadState = new GBASerializedState; } GBASerialize(context->gba, controller->m_backupLoadState); - if (GBALoadState(context, context->dirs.state, controller->m_stateSlot, SAVESTATE_SCREENSHOT)) { + if (GBALoadState(context, context->dirs.state, controller->m_stateSlot, controller->m_loadStateFlags)) { controller->frameAvailable(controller->m_drawContext); controller->stateLoaded(context); } @@ -738,6 +741,10 @@ void GameController::loadState(int slot) { } void GameController::saveState(int slot) { + if (!m_threadContext.fname) { + // We're in the BIOS + return; + } if (slot > 0) { m_stateSlot = slot; } @@ -749,7 +756,7 @@ void GameController::saveState(int slot) { vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); vf->close(vf); } - GBASaveState(context, context->dirs.state, controller->m_stateSlot, SAVESTATE_SCREENSHOT | EXTDATA_SAVEDATA); + GBASaveState(context, context->dirs.state, controller->m_stateSlot, controller->m_saveStateFlags); }); } @@ -921,6 +928,14 @@ void GameController::reloadAudioDriver() { } } +void GameController::setSaveStateExtdata(int flags) { + m_saveStateFlags = flags; +} + +void GameController::setLoadStateExtdata(int flags) { + m_loadStateFlags = flags; +} + void GameController::setLuminanceValue(uint8_t value) { m_luxValue = value; value = std::max(value - 0x16, 0); diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 499378cfc..74eea2c6b 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -101,7 +101,7 @@ signals: void postLog(int level, const QString& log); public slots: - void loadGame(const QString& path, bool dirmode = false); + void loadGame(const QString& path); void loadBIOS(const QString& path); void yankPak(); void replaceGame(const QString& path); @@ -142,6 +142,8 @@ public slots: void setAVStream(GBAAVStream*); void clearAVStream(); void reloadAudioDriver(); + void setSaveStateExtdata(int flags); + void setLoadStateExtdata(int flags); #ifdef USE_PNG void screenshot(); @@ -186,7 +188,6 @@ private: GBASIODolphin m_dolphin; bool m_gameOpen; - bool m_dirmode; QString m_fname; QString m_bios; @@ -216,6 +217,8 @@ private: int m_stateSlot; GBASerializedState* m_backupLoadState; QByteArray m_backupSaveState; + int m_saveStateFlags; + int m_loadStateFlags; InputController* m_inputController; MultiplayerController* m_multiplayer; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 023ad7568..ebadbab4a 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -46,6 +46,7 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren ++s_sdlInited; m_sdlPlayer.bindings = &m_inputMap; GBASDLInitBindings(&m_inputMap); + updateJoysticks(); #endif m_gamepadTimer = new QTimer(this); @@ -146,9 +147,9 @@ const char* InputController::profileForType(uint32_t type) { #ifdef BUILD_SDL if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) { #if SDL_VERSION_ATLEAST(2, 0, 0) - return SDL_JoystickName(m_sdlPlayer.joystick); + return SDL_JoystickName(m_sdlPlayer.joystick->joystick); #else - return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick)); + return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick->joystick)); #endif } #endif @@ -161,12 +162,12 @@ QStringList InputController::connectedGamepads(uint32_t type) const { #ifdef BUILD_SDL if (type == SDL_BINDING_BUTTON) { QStringList pads; - for (size_t i = 0; i < s_sdlEvents.nJoysticks; ++i) { + for (size_t i = 0; i < SDL_JoystickListSize(&s_sdlEvents.joysticks); ++i) { const char* name; #if SDL_VERSION_ATLEAST(2, 0, 0) - name = SDL_JoystickName(s_sdlEvents.joysticks[i]); + name = SDL_JoystickName(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, i)->joystick); #else - name = SDL_JoystickName(SDL_JoystickIndex(s_sdlEvents.joysticks[i])); + name = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, i)->joystick)); #endif if (name) { pads.append(QString(name)); @@ -184,7 +185,7 @@ QStringList InputController::connectedGamepads(uint32_t type) const { int InputController::gamepad(uint32_t type) const { #ifdef BUILD_SDL if (type == SDL_BINDING_BUTTON) { - return m_sdlPlayer.joystickIndex; + return m_sdlPlayer.joystick ? m_sdlPlayer.joystick->index : 0; } #endif return 0; @@ -282,11 +283,17 @@ void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) { return GBAInputBindKey(&m_inputMap, type, key, gbaKey); } +void InputController::updateJoysticks() { +#ifdef BUILD_SDL + GBASDLUpdateJoysticks(&s_sdlEvents); +#endif +} + int InputController::pollEvents() { int activeButtons = 0; #ifdef BUILD_SDL - if (m_playerAttached) { - SDL_Joystick* joystick = m_sdlPlayer.joystick; + if (m_playerAttached && m_sdlPlayer.joystick) { + SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick; SDL_JoystickUpdate(); int numButtons = SDL_JoystickNumButtons(joystick); int i; @@ -336,8 +343,8 @@ int InputController::pollEvents() { QSet InputController::activeGamepadButtons(int type) { QSet activeButtons; #ifdef BUILD_SDL - if (m_playerAttached && type == SDL_BINDING_BUTTON) { - SDL_Joystick* joystick = m_sdlPlayer.joystick; + if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) { + SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick; SDL_JoystickUpdate(); int numButtons = SDL_JoystickNumButtons(joystick); int i; @@ -353,8 +360,8 @@ QSet InputController::activeGamepadButtons(int type) { void InputController::recalibrateAxes() { #ifdef BUILD_SDL - if (m_playerAttached) { - SDL_Joystick* joystick = m_sdlPlayer.joystick; + if (m_playerAttached && m_sdlPlayer.joystick) { + SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick; SDL_JoystickUpdate(); int numAxes = SDL_JoystickNumAxes(joystick); if (numAxes < 1) { @@ -372,8 +379,8 @@ void InputController::recalibrateAxes() { QSet> InputController::activeGamepadAxes(int type) { QSet> activeAxes; #ifdef BUILD_SDL - if (m_playerAttached && type == SDL_BINDING_BUTTON) { - SDL_Joystick* joystick = m_sdlPlayer.joystick; + if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) { + SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick; SDL_JoystickUpdate(); int numAxes = SDL_JoystickNumAxes(joystick); if (numAxes < 1) { @@ -399,14 +406,19 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct if (old) { description = *old; } + int deadzone = 0; + if (m_deadzones.size() > axis) { + deadzone = m_deadzones[axis]; + } switch (direction) { case GamepadAxisEvent::NEGATIVE: description.lowDirection = key; - description.deadLow = m_deadzones[axis] - AXIS_THRESHOLD; + + description.deadLow = deadzone - AXIS_THRESHOLD; break; case GamepadAxisEvent::POSITIVE: description.highDirection = key; - description.deadHigh = m_deadzones[axis] + AXIS_THRESHOLD; + description.deadHigh = deadzone + AXIS_THRESHOLD; break; default: return; diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index f73524bad..d44078068 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -52,6 +52,7 @@ public: const GBAInputMap* map() const { return &m_inputMap; } + void updateJoysticks(); int pollEvents(); static const int32_t AXIS_THRESHOLD = 0x3000; diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index 34e7cf068..ba39b69a3 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -9,6 +9,7 @@ #include "GamepadButtonEvent.h" #include "ShortcutController.h" +#include #include using namespace QGBA; @@ -72,7 +73,8 @@ void KeyEditor::clearAxis() { QSize KeyEditor::sizeHint() const { QSize hint = QLineEdit::sizeHint(); - hint.setWidth(40); + QFontMetrics fm(font()); + hint.setWidth(fm.height() * 3); return hint; } diff --git a/src/platform/qt/LogView.cpp b/src/platform/qt/LogView.cpp index cc34ea1ca..3abead151 100644 --- a/src/platform/qt/LogView.cpp +++ b/src/platform/qt/LogView.cpp @@ -70,15 +70,13 @@ LogView::LogView(LogController* log, QWidget* parent) void LogView::postLog(int level, const QString& log) { QString line = QString("%1:\t%2").arg(LogController::toString(level)).arg(log); - if (isVisible()) { - m_ui.view->appendPlainText(line); - } else { - m_pendingLines.enqueue(line); - } + // TODO: Log to file + m_pendingLines.enqueue(line); ++m_lines; if (m_lines > m_lineLimit) { clearLine(); } + update(); } void LogView::clear() { @@ -145,10 +143,11 @@ void LogView::setMaxLines(int limit) { } } -void LogView::showEvent(QShowEvent*) { +void LogView::paintEvent(QPaintEvent* event) { while (!m_pendingLines.isEmpty()) { m_ui.view->appendPlainText(m_pendingLines.dequeue()); } + QWidget::paintEvent(event); } void LogView::clearLine() { diff --git a/src/platform/qt/LogView.h b/src/platform/qt/LogView.h index 2a84168c8..afc89af01 100644 --- a/src/platform/qt/LogView.h +++ b/src/platform/qt/LogView.h @@ -38,7 +38,7 @@ private slots: void setMaxLines(int); protected: - virtual void showEvent(QShowEvent*) override; + virtual void paintEvent(QPaintEvent*) override; private: static const int DEFAULT_LINE_LIMIT = 1000; diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 6f144d209..10f0fb9f1 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -14,7 +14,9 @@ extern "C" { using namespace QGBA; -ROMInfo::ROMInfo(GameController* controller, QWidget* parent) { +ROMInfo::ROMInfo(GameController* controller, QWidget* parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ m_ui.setupUi(this); if (!controller->isLoaded()) { diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index bf270e348..f6341e74c 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -81,9 +81,10 @@ void SensorView::jiggerer(QAbstractButton* button, void (InputController::*sette } bool SensorView::event(QEvent* event) { - if (event->type() == QEvent::WindowActivate) { + QEvent::Type type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::Show) { m_input->stealFocus(this); - } else if (event->type() == QEvent::WindowDeactivate) { + } else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) { m_input->releaseFocus(this); } return QWidget::event(event); diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 23c370f34..ac7df35a3 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -9,56 +9,89 @@ #include "ConfigController.h" #include "Display.h" #include "GBAApp.h" +#include "GBAKeyEditor.h" +#include "InputController.h" +#include "ShortcutView.h" + +extern "C" { +#include "gba/serialize.h" +} using namespace QGBA; -SettingsView::SettingsView(ConfigController* controller, QWidget* parent) +SettingsView::SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) , m_controller(controller) { m_ui.setupUi(this); - loadSetting("bios", m_ui.bios); - loadSetting("useBios", m_ui.useBios); - loadSetting("skipBios", m_ui.skipBios); - loadSetting("audioBuffers", m_ui.audioBufferSize); - loadSetting("sampleRate", m_ui.sampleRate); - loadSetting("videoSync", m_ui.videoSync); - loadSetting("audioSync", m_ui.audioSync); - loadSetting("frameskip", m_ui.frameskip); - loadSetting("fpsTarget", m_ui.fpsTarget); - loadSetting("lockAspectRatio", m_ui.lockAspectRatio); - loadSetting("volume", m_ui.volume); - loadSetting("mute", m_ui.mute); - loadSetting("rewindEnable", m_ui.rewind); - loadSetting("rewindBufferInterval", m_ui.rewindInterval); - loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); - loadSetting("resampleVideo", m_ui.resampleVideo); - loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); - loadSetting("suspendScreensaver", m_ui.suspendScreensaver); + reloadConfig(); - double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); - if (fastForwardRatio <= 0) { - m_ui.fastForwardUnbounded->setChecked(true); - m_ui.fastForwardRatio->setEnabled(false); - } else { - m_ui.fastForwardUnbounded->setChecked(false); - m_ui.fastForwardRatio->setEnabled(true); - m_ui.fastForwardRatio->setValue(fastForwardRatio); + if (m_ui.savegamePath->text().isEmpty()) { + m_ui.savegameSameDir->setChecked(true); } - connect(m_ui.fastForwardUnbounded, &QAbstractButton::toggled, [this](bool checked) { - m_ui.fastForwardRatio->setEnabled(!checked); + connect(m_ui.savegameSameDir, &QAbstractButton::toggled, [this] (bool e) { + if (e) { + m_ui.savegamePath->clear(); + } + }); + connect(m_ui.savegameBrowse, &QAbstractButton::pressed, [this] () { + QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory"); + if (!path.isNull()) { + m_ui.savegameSameDir->setChecked(false); + m_ui.savegamePath->setText(path); + } }); - QString idleOptimization = loadSetting("idleOptimization"); - if (idleOptimization == "ignore") { - m_ui.idleOptimization->setCurrentIndex(0); - } else if (idleOptimization == "remove") { - m_ui.idleOptimization->setCurrentIndex(1); - } else if (idleOptimization == "detect") { - m_ui.idleOptimization->setCurrentIndex(2); + if (m_ui.savestatePath->text().isEmpty()) { + m_ui.savestateSameDir->setChecked(true); } + connect(m_ui.savestateSameDir, &QAbstractButton::toggled, [this] (bool e) { + if (e) { + m_ui.savestatePath->clear(); + } + }); + connect(m_ui.savestateBrowse, &QAbstractButton::pressed, [this] () { + QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory"); + if (!path.isNull()) { + m_ui.savestateSameDir->setChecked(false); + m_ui.savestatePath->setText(path); + } + }); + if (m_ui.screenshotPath->text().isEmpty()) { + m_ui.screenshotSameDir->setChecked(true); + } + connect(m_ui.screenshotSameDir, &QAbstractButton::toggled, [this] (bool e) { + if (e) { + m_ui.screenshotPath->clear(); + } + }); + connect(m_ui.screenshotBrowse, &QAbstractButton::pressed, [this] () { + QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory"); + if (!path.isNull()) { + m_ui.screenshotSameDir->setChecked(false); + m_ui.screenshotPath->setText(path); + } + }); + + if (m_ui.patchPath->text().isEmpty()) { + m_ui.patchSameDir->setChecked(true); + } + connect(m_ui.patchSameDir, &QAbstractButton::toggled, [this] (bool e) { + if (e) { + m_ui.patchPath->clear(); + } + }); + connect(m_ui.patchBrowse, &QAbstractButton::pressed, [this] () { + QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory"); + if (!path.isNull()) { + m_ui.patchSameDir->setChecked(false); + m_ui.patchPath->setText(path); + } + }); + + // TODO: Move to reloadConfig() QVariant audioDriver = m_controller->getQtOption("audioDriver"); #ifdef BUILD_QT_MULTIMEDIA m_ui.audioDriver->addItem(tr("Qt Multimedia"), static_cast(AudioProcessor::Driver::QT_MULTIMEDIA)); @@ -74,19 +107,27 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent) } #endif + // TODO: Move to reloadConfig() QVariant displayDriver = m_controller->getQtOption("displayDriver"); m_ui.displayDriver->addItem(tr("Software (Qt)"), static_cast(Display::Driver::QT)); if (!displayDriver.isNull() && displayDriver.toInt() == static_cast(Display::Driver::QT)) { m_ui.displayDriver->setCurrentIndex(m_ui.displayDriver->count() - 1); } -#ifdef BUILD_GL +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) m_ui.displayDriver->addItem(tr("OpenGL"), static_cast(Display::Driver::OPENGL)); if (displayDriver.isNull() || displayDriver.toInt() == static_cast(Display::Driver::OPENGL)) { m_ui.displayDriver->setCurrentIndex(m_ui.displayDriver->count() - 1); } #endif +#ifdef BUILD_GL + m_ui.displayDriver->addItem(tr("OpenGL (force version 1.x)"), static_cast(Display::Driver::OPENGL1)); + if (displayDriver.isNull() || displayDriver.toInt() == static_cast(Display::Driver::OPENGL1)) { + m_ui.displayDriver->setCurrentIndex(m_ui.displayDriver->count() - 1); + } +#endif + connect(m_ui.biosBrowse, SIGNAL(clicked()), this, SLOT(selectBios())); connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { @@ -94,6 +135,26 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent) updateConfig(); } }); + + GBAKeyEditor* editor = new GBAKeyEditor(inputController, InputController::KEYBOARD, QString(), this); + m_ui.stackedWidget->addWidget(editor); + m_ui.tabs->addItem("Keyboard"); + connect(m_ui.buttonBox, SIGNAL(accepted()), editor, SLOT(save())); + +#ifdef BUILD_SDL + inputController->recalibrateAxes(); + const char* profile = inputController->profileForType(SDL_BINDING_BUTTON); + editor = new GBAKeyEditor(inputController, SDL_BINDING_BUTTON, profile); + m_ui.stackedWidget->addWidget(editor); + m_ui.tabs->addItem("Controllers"); + connect(m_ui.buttonBox, SIGNAL(accepted()), editor, SLOT(save())); +#endif + + ShortcutView* shortcutView = new ShortcutView(); + shortcutView->setController(shortcutController); + shortcutView->setInputController(inputController); + m_ui.stackedWidget->addWidget(shortcutView); + m_ui.tabs->addItem("Shortcuts"); } void SettingsView::selectBios() { @@ -103,6 +164,14 @@ void SettingsView::selectBios() { } } +void SettingsView::recalculateRewind() { + int interval = m_ui.rewindInterval->value(); + int capacity = m_ui.rewindCapacity->value(); + double duration = m_ui.fpsTarget->value(); + m_ui.rewindDuration->setValue(interval * capacity / duration); + +} + void SettingsView::updateConfig() { saveSetting("bios", m_ui.bios); saveSetting("useBios", m_ui.useBios); @@ -122,6 +191,11 @@ void SettingsView::updateConfig() { saveSetting("resampleVideo", m_ui.resampleVideo); saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); saveSetting("suspendScreensaver", m_ui.suspendScreensaver); + saveSetting("pauseOnFocusLost", m_ui.pauseOnFocusLost); + saveSetting("savegamePath", m_ui.savegamePath); + saveSetting("savestatePath", m_ui.savestatePath); + saveSetting("screenshotPath", m_ui.screenshotPath); + saveSetting("patchPath", m_ui.patchPath); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -141,6 +215,18 @@ void SettingsView::updateConfig() { break; } + int loadState = 0; + loadState |= m_ui.loadStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0; + loadState |= m_ui.loadStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0; + loadState |= m_ui.loadStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; + saveSetting("loadStateExtdata", loadState); + + int saveState = 0; + saveState |= m_ui.saveStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0; + saveState |= m_ui.saveStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0; + saveState |= m_ui.saveStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; + saveSetting("saveStateExtdata", saveState); + QVariant audioDriver = m_ui.audioDriver->itemData(m_ui.audioDriver->currentIndex()); if (audioDriver != m_controller->getQtOption("audioDriver")) { m_controller->setQtOption("audioDriver", audioDriver); @@ -157,9 +243,76 @@ void SettingsView::updateConfig() { m_controller->write(); + emit pathsChanged(); emit biosLoaded(m_ui.bios->text()); } +void SettingsView::reloadConfig() { + loadSetting("bios", m_ui.bios); + loadSetting("useBios", m_ui.useBios); + loadSetting("skipBios", m_ui.skipBios); + loadSetting("audioBuffers", m_ui.audioBufferSize); + loadSetting("sampleRate", m_ui.sampleRate); + loadSetting("videoSync", m_ui.videoSync); + loadSetting("audioSync", m_ui.audioSync); + loadSetting("frameskip", m_ui.frameskip); + loadSetting("fpsTarget", m_ui.fpsTarget); + loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("volume", m_ui.volume); + loadSetting("mute", m_ui.mute); + loadSetting("rewindEnable", m_ui.rewind); + loadSetting("rewindBufferInterval", m_ui.rewindInterval); + loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); + loadSetting("resampleVideo", m_ui.resampleVideo); + loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); + loadSetting("suspendScreensaver", m_ui.suspendScreensaver); + loadSetting("pauseOnFocusLost", m_ui.pauseOnFocusLost); + loadSetting("savegamePath", m_ui.savegamePath); + loadSetting("savestatePath", m_ui.savestatePath); + loadSetting("screenshotPath", m_ui.screenshotPath); + loadSetting("patchPath", m_ui.patchPath); + + double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); + if (fastForwardRatio <= 0) { + m_ui.fastForwardUnbounded->setChecked(true); + m_ui.fastForwardRatio->setEnabled(false); + } else { + m_ui.fastForwardUnbounded->setChecked(false); + m_ui.fastForwardRatio->setEnabled(true); + m_ui.fastForwardRatio->setValue(fastForwardRatio); + } + + connect(m_ui.rewindInterval, SIGNAL(valueChanged(int)), this, SLOT(recalculateRewind())); + connect(m_ui.rewindCapacity, SIGNAL(valueChanged(int)), this, SLOT(recalculateRewind())); + connect(m_ui.fpsTarget, SIGNAL(valueChanged(double)), this, SLOT(recalculateRewind())); + + QString idleOptimization = loadSetting("idleOptimization"); + if (idleOptimization == "ignore") { + m_ui.idleOptimization->setCurrentIndex(0); + } else if (idleOptimization == "remove") { + m_ui.idleOptimization->setCurrentIndex(1); + } else if (idleOptimization == "detect") { + m_ui.idleOptimization->setCurrentIndex(2); + } + + bool ok; + int loadState = loadSetting("loadStateExtdata").toInt(&ok); + if (!ok) { + loadState = SAVESTATE_SCREENSHOT; + } + m_ui.loadStateScreenshot->setChecked(loadState & SAVESTATE_SCREENSHOT); + m_ui.loadStateSave->setChecked(loadState & SAVESTATE_SAVEDATA); + m_ui.loadStateCheats->setChecked(loadState & SAVESTATE_CHEATS); + + int saveState = loadSetting("saveStateExtdata").toInt(&ok); + if (!ok) { + saveState = SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS; + } + m_ui.saveStateScreenshot->setChecked(saveState & SAVESTATE_SCREENSHOT); + m_ui.saveStateSave->setChecked(saveState & SAVESTATE_SAVEDATA); + m_ui.saveStateCheats->setChecked(saveState & SAVESTATE_CHEATS); +} + void SettingsView::saveSetting(const char* key, const QAbstractButton* field) { m_controller->setOption(key, field->isChecked()); m_controller->updateOption(key); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 2a8d1302d..77d672bdf 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -13,26 +13,32 @@ namespace QGBA { class ConfigController; +class InputController; +class ShortcutController; class SettingsView : public QDialog { Q_OBJECT public: - SettingsView(ConfigController* controller, QWidget* parent = nullptr); + SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, QWidget* parent = nullptr); signals: void biosLoaded(const QString&); void audioDriverChanged(); void displayDriverChanged(); + void pathsChanged(); private slots: void selectBios(); + void recalculateRewind(); void updateConfig(); + void reloadConfig(); private: Ui::SettingsView m_ui; ConfigController* m_controller; + InputController* m_input; void saveSetting(const char* key, const QAbstractButton*); void saveSetting(const char* key, const QComboBox*); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 2e70122bf..0b1b45333 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -6,8 +6,8 @@ 0 0 - 707 - 420 + 544 + 425 @@ -19,16 +19,65 @@ Settings - + QLayout::SetFixedSize - - + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + + 140 + 16777215 + + + + 0 + + + Audio/Video + + + + + Emulation + + + + + Savestates + + + + + Paths + + + + + + + + 0 + + - QFormLayout::ExpandingFieldsGrow + QFormLayout::FieldsStayAtSizeHint @@ -120,6 +169,50 @@ + + + + + + true + + + 44100 + + + 2 + + + + 22050 + + + + + 32000 + + + + + 44100 + + + + + 48000 + + + + + + + + Hz + + + + + @@ -131,6 +224,12 @@ + + + 128 + 0 + + 256 @@ -155,7 +254,7 @@ - + Qt::Horizontal @@ -238,7 +337,7 @@ - + Qt::Horizontal @@ -283,61 +382,13 @@ - - - - - - true - - - 44100 - - - 2 - - - - 22050 - - - - - 32000 - - - - - 44100 - - - - - 48000 - - - - - - - - Hz - - - - - - - - - - Qt::Vertical + + + + + QFormLayout::FieldsStayAtSizeHint - - - - @@ -366,6 +417,16 @@ + + + + Use BIOS file if found + + + true + + + @@ -373,120 +434,21 @@ - - + + Qt::Horizontal - - + + - Enable rewind + Fast forward speed - - - - Create rewind state: - - - - - - - - - Every - - - - - - - - - - frames - - - - - - - - - Rewind history: - - - - - - - - - - - - states - - - - - - - - - Qt::Horizontal - - - - - - - Allow opposing input directions - - - - - - - Suspend screensaver - - - true - - - - - - - Idle loops - - - - - - - - Run all - - - - - Remove known - - - - - Detect and remove - - - - - + false @@ -508,14 +470,7 @@ - - - - Fast forward speed - - - - + Unbounded @@ -525,32 +480,423 @@ - - + + Qt::Horizontal - - + + - Use BIOS file + Allow opposing input directions + + + + + + + Suspend screensaver true + + + + Pause when inactive + + + + + + + Idle loops + + + + + + + + Run all + + + + + Remove known + + + + + Detect and remove + + + + - - - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok - + + + + + QFormLayout::FieldsStayAtSizeHint + + + + + Save extra data + + + + + + + Screenshot + + + true + + + + + + + Save data + + + true + + + + + + + Cheat codes + + + true + + + + + + + Qt::Horizontal + + + + + + + Load extra data + + + + + + + Screenshot + + + true + + + + + + + Save data + + + + + + + Cheat codes + + + + + + + Qt::Horizontal + + + + + + + Enable rewind + + + + + + + Create rewind state: + + + + + + + + + Every + + + + + + + + + + frames + + + + + + + + + Rewind history: + + + + + + + + + + + + states + + + + + + + + + + + false + + + 999.990000000000009 + + + + + + + seconds + + + + + + + + + + + QFormLayout::FieldsStayAtSizeHint + + + + + Save games + + + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + + + + + Browse + + + + + + + + + Same directory as the ROM + + + + + + + Qt::Horizontal + + + + + + + Save states + + + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + + + + + Browse + + + + + + + + + Same directory as the ROM + + + + + + + Qt::Horizontal + + + + + + + Screenshots + + + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + + + + + Browse + + + + + + + + + Same directory as the ROM + + + + + + + Qt::Horizontal + + + + + + + Patches + + + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + + + + + Browse + + + + + + + + + Same directory as the ROM + + + + + @@ -590,18 +936,98 @@ - useBios - toggled(bool) - skipBios - setEnabled(bool) + tabs + currentRowChanged(int) + stackedWidget + setCurrentIndex(int) - 520 - 62 + 61 + 209 - 525 - 83 + 315 + 209 + + + + + savegameSameDir + toggled(bool) + savegamePath + setDisabled(bool) + + + 392 + 82 + + + 366 + 48 + + + + + savestateSameDir + toggled(bool) + savestatePath + setDisabled(bool) + + + 392 + 161 + + + 366 + 127 + + + + + screenshotSameDir + toggled(bool) + screenshotPath + setDisabled(bool) + + + 392 + 240 + + + 366 + 206 + + + + + patchSameDir + toggled(bool) + patchPath + setDisabled(bool) + + + 345 + 319 + + + 340 + 285 + + + + + fastForwardUnbounded + toggled(bool) + fastForwardRatio + setDisabled(bool) + + + 338 + 163 + + + 327 + 135 diff --git a/src/platform/qt/ShaderSelector.cpp b/src/platform/qt/ShaderSelector.cpp index 599a1e5fe..253303132 100644 --- a/src/platform/qt/ShaderSelector.cpp +++ b/src/platform/qt/ShaderSelector.cpp @@ -28,7 +28,7 @@ extern "C" { using namespace QGBA; ShaderSelector::ShaderSelector(Display* display, ConfigController* config, QWidget* parent) - : QDialog(parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) , m_display(display) , m_config(config) , m_shaderPath("") diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp index dcbe004fc..8dce4ad8a 100644 --- a/src/platform/qt/ShortcutController.cpp +++ b/src/platform/qt/ShortcutController.cpp @@ -137,10 +137,13 @@ void ShortcutController::addFunctions(QMenu* menu, std::function press, smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name); endInsertRows(); ShortcutItem* item = &smenu->items().last(); + bool loadedShortcut = false; if (m_config) { - loadShortcuts(item); + loadedShortcut = loadShortcuts(item); + } + if (!loadedShortcut && !m_heldKeys.contains(shortcut)) { + m_heldKeys[shortcut] = item; } - m_heldKeys[shortcut] = item; emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); } @@ -387,10 +390,11 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { return false; } -void ShortcutController::loadShortcuts(ShortcutItem* item) { +bool ShortcutController::loadShortcuts(ShortcutItem* item) { if (item->name().isNull()) { - return; + return false; } + loadGamepadShortcuts(item); QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); if (!shortcut.isNull()) { if (shortcut.toString().endsWith("+")) { @@ -398,8 +402,9 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) { } else { updateKey(item, QKeySequence(shortcut.toString())[0]); } + return true; } - loadGamepadShortcuts(item); + return false; } void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) { diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index a486b631c..b128d91e6 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -128,7 +128,7 @@ protected: private: ShortcutItem* itemAt(const QModelIndex& index); const ShortcutItem* itemAt(const QModelIndex& index) const; - void loadShortcuts(ShortcutItem*); + bool loadShortcuts(ShortcutItem*); void loadGamepadShortcuts(ShortcutItem*); void onSubitems(ShortcutItem*, std::function func); void updateKey(ShortcutItem* item, int keySequence); diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp index 00d4385a6..9046f043c 100644 --- a/src/platform/qt/ShortcutView.cpp +++ b/src/platform/qt/ShortcutView.cpp @@ -37,6 +37,10 @@ ShortcutView::ShortcutView(QWidget* parent) connect(m_ui.clearButton, SIGNAL(clicked()), this, SLOT(clear())); } +ShortcutView::~ShortcutView() { + m_input->releaseFocus(this); +} + void ShortcutView::setController(ShortcutController* controller) { m_controller = controller; m_ui.shortcutTable->setModel(controller); @@ -117,9 +121,10 @@ void ShortcutView::closeEvent(QCloseEvent*) { bool ShortcutView::event(QEvent* event) { if (m_input) { - if (event->type() == QEvent::WindowActivate) { + QEvent::Type type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::Show) { m_input->stealFocus(this); - } else if (event->type() == QEvent::WindowDeactivate) { + } else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) { m_input->releaseFocus(this); } } diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h index 37c95c887..945f21d81 100644 --- a/src/platform/qt/ShortcutView.h +++ b/src/platform/qt/ShortcutView.h @@ -22,6 +22,7 @@ Q_OBJECT public: ShortcutView(QWidget* parent = nullptr); + ~ShortcutView(); void setController(ShortcutController* controller); void setInputController(InputController* input); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 5a07404e3..d31974d18 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -20,7 +20,6 @@ #include "Display.h" #include "GameController.h" #include "GBAApp.h" -#include "GBAKeyEditor.h" #include "GDBController.h" #include "GDBWindow.h" #include "GIFView.h" @@ -36,7 +35,6 @@ #include "SettingsView.h" #include "ShaderSelector.h" #include "ShortcutController.h" -#include "ShortcutView.h" #include "VideoView.h" extern "C" { @@ -74,6 +72,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) , m_shortcutController(new ShortcutController(this)) , m_playerId(playerId) , m_fullscreenOnStart(false) + , m_autoresume(false) { setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); @@ -105,7 +104,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(m_controller, SIGNAL(rewound(GBAThread*)), m_display, SLOT(forceDraw())); connect(m_controller, &GameController::gamePaused, [this]() { QImage currentImage(reinterpret_cast(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS, - VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGBX8888); + VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL, QImage::Format_RGBX8888); QPixmap pixmap; pixmap.convertFromImage(currentImage); m_screenWidget->setPixmap(pixmap); @@ -142,6 +141,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(this, SIGNAL(sampleRateChanged(unsigned)), m_controller, SLOT(setAudioSampleRate(unsigned))); connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); + connect(&m_focusCheck, SIGNAL(timeout()), this, SLOT(focusCheck())); connect(m_display, &Display::hideCursor, [this]() { if (static_cast(m_screenWidget->layout())->currentWidget() == m_display) { m_screenWidget->setCursor(Qt::BlankCursor); @@ -154,6 +154,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_log.setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); + m_focusCheck.setInterval(200); m_shortcutController->setConfigController(m_config); setupMenu(menuBar()); @@ -179,7 +180,7 @@ void Window::argumentsPassed(GBAArguments* args) { } if (args->fname) { - m_controller->loadGame(args->fname, args->dirmode); + m_controller->loadGame(args->fname); } } @@ -197,16 +198,7 @@ void Window::setConfig(ConfigController* config) { void Window::loadConfig() { const GBAOptions* opts = m_config->options(); - - m_log.setLevels(opts->logLevel); - - m_controller->setOptions(opts); - m_display->lockAspectRatio(opts->lockAspectRatio); - m_display->filter(opts->resampleVideo); - - if (opts->bios) { - m_controller->loadBIOS(opts->bios); - } + reloadConfig(); // TODO: Move these to ConfigController if (opts->fpsTarget) { @@ -238,14 +230,41 @@ void Window::loadConfig() { } } - m_inputController.setScreensaverSuspendable(opts->suspendScreensaver); - m_mruFiles = m_config->getMRU(); updateMRU(); m_inputController.setConfiguration(m_config); } +void Window::reloadConfig() { + const GBAOptions* opts = m_config->options(); + + m_log.setLevels(opts->logLevel); + + QString saveStateExtdata = m_config->getOption("saveStateExtdata"); + bool ok; + int flags = saveStateExtdata.toInt(&ok); + if (ok) { + m_controller->setSaveStateExtdata(flags); + } + + QString loadStateExtdata = m_config->getOption("loadStateExtdata"); + flags = loadStateExtdata.toInt(&ok); + if (ok) { + m_controller->setLoadStateExtdata(flags); + } + + m_controller->setOptions(opts); + m_display->lockAspectRatio(opts->lockAspectRatio); + m_display->filter(opts->resampleVideo); + + if (opts->bios) { + m_controller->loadBIOS(opts->bios); + } + + m_inputController.setScreensaverSuspendable(opts->suspendScreensaver); +} + void Window::saveConfig() { m_inputController.saveConfiguration(); m_config->write(); @@ -254,7 +273,7 @@ void Window::saveConfig() { void Window::selectROM() { QStringList formats{ "*.gba", -#ifdef USE_LIBZIP +#if defined(USE_LIBZIP) || defined(USE_ZLIB) "*.zip", #endif #ifdef USE_LZMA @@ -274,7 +293,7 @@ void Window::selectROM() { void Window::replaceROM() { QStringList formats{ "*.gba", -#ifdef USE_LIBZIP +#if defined(USE_LIBZIP) || defined(USE_ZLIB) "*.zip", #endif #ifdef USE_LZMA @@ -344,29 +363,15 @@ void Window::exportSharkport() { } } -void Window::openKeymapWindow() { - GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); - openView(keyEditor); -} - void Window::openSettingsWindow() { - SettingsView* settingsWindow = new SettingsView(m_config); + SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_shortcutController); connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart())); + connect(settingsWindow, SIGNAL(pathsChanged()), this, SLOT(reloadConfig())); openView(settingsWindow); } -void Window::openShortcutWindow() { -#ifdef BUILD_SDL - m_inputController.recalibrateAxes(); -#endif - ShortcutView* shortcutView = new ShortcutView(); - shortcutView->setController(m_shortcutController); - shortcutView->setInputController(&m_inputController); - openView(shortcutView); -} - void Window::openOverrideWindow() { OverrideView* overrideWindow = new OverrideView(m_controller, m_config); openView(overrideWindow); @@ -407,14 +412,6 @@ void Window::openROMInfo() { openView(romInfo); } -#ifdef BUILD_SDL -void Window::openGamepadWindow() { - const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON); - GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON, profile); - openView(keyEditor); -} -#endif - #ifdef USE_FFMPEG void Window::openVideoWindow() { if (!m_videoView) { @@ -629,6 +626,7 @@ void Window::gameStarted(GBAThread* context) { m_hitUnimplementedBiosCall = false; m_fpsTimer.start(); + m_focusCheck.start(); } void Window::gameStopped() { @@ -643,6 +641,7 @@ void Window::gameStopped() { m_screenWidget->unsetCursor(); m_fpsTimer.stop(); + m_focusCheck.stop(); } void Window::gameCrashed(const QString& errorMessage) { @@ -1196,18 +1195,6 @@ void Window::setupMenu(QMenuBar* menubar) { toolsMenu->addSeparator(); addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings"); - addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), - "shortcuts"); - - QAction* keymap = new QAction(tr("Remap keyboard..."), toolsMenu); - connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow())); - addControlledAction(toolsMenu, keymap, "remapKeyboard"); - -#ifdef BUILD_SDL - QAction* gamepad = new QAction(tr("Remap gamepad..."), toolsMenu); - connect(gamepad, SIGNAL(triggered()), this, SLOT(openGamepadWindow())); - addControlledAction(toolsMenu, gamepad, "remapGamepad"); -#endif toolsMenu->addSeparator(); @@ -1271,6 +1258,16 @@ void Window::setupMenu(QMenuBar* menubar) { m_inputController.setAllowOpposing(value.toBool()); }, this); + ConfigOption* saveStateExtdata = m_config->addOption("saveStateExtdata"); + saveStateExtdata->connect([this](const QVariant& value) { + m_controller->setSaveStateExtdata(value.toInt()); + }, this); + + ConfigOption* loadStateExtdata = m_config->addOption("loadStateExtdata"); + loadStateExtdata->connect([this](const QVariant& value) { + m_controller->setLoadStateExtdata(value.toInt()); + }, this); + QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu); connect(exitFullScreen, SIGNAL(triggered()), this, SLOT(exitFullScreen())); exitFullScreen->setShortcut(QKeySequence("Esc")); @@ -1397,6 +1394,18 @@ QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& na return action; } +void Window::focusCheck() { + if (!m_config->getOption("pauseOnFocusLost").toInt()) { + return; + } + if (QGuiApplication::focusWindow() && m_autoresume) { + m_controller->setPaused(false); + } else if (!QGuiApplication::focusWindow() && !m_controller->isPaused()) { + m_autoresume = true; + m_controller->setPaused(true); + } +} + WindowBackground::WindowBackground(QWidget* parent) : QLabel(parent) { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 32a8f459a..4dc927da6 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -66,6 +66,7 @@ public slots: void exitFullScreen(); void toggleFullScreen(); void loadConfig(); + void reloadConfig(); void saveConfig(); void replaceROM(); @@ -75,10 +76,7 @@ public slots: void importSharkport(); void exportSharkport(); - void openKeymapWindow(); void openSettingsWindow(); - void openShortcutWindow(); - void openOverrideWindow(); void openSensorWindow(); void openCheatsWindow(); @@ -90,10 +88,6 @@ public slots: void openAboutScreen(); void openROMInfo(); -#ifdef BUILD_SDL - void openGamepadWindow(); -#endif - #ifdef USE_FFMPEG void openVideoWindow(); #endif @@ -130,6 +124,7 @@ private slots: void recordFrame(); void showFPS(); + void focusCheck(); private: static const int FPS_TIMER_INTERVAL = 2000; @@ -171,6 +166,8 @@ private: ShaderSelector* m_shaderView; int m_playerId; bool m_fullscreenOnStart; + QTimer m_focusCheck; + bool m_autoresume; bool m_hitUnimplementedBiosCall; diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index bb927bfce..cd5986f96 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -30,8 +30,8 @@ void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { GBASDLGLCommonInit(renderer); - renderer->d.outputBuffer = malloc(256 * 256 * BYTES_PER_PIXEL); - renderer->d.outputBufferStride = 256; + renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; GBAGLContextCreate(&renderer->gl); renderer->gl.d.user = renderer; diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index bbdffffe0..4bcbb0f94 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -93,8 +93,8 @@ bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer) { GBASDLGLCommonInit(renderer); #endif - renderer->d.outputBuffer = memalign(16, 256 * 256 * 4); - renderer->d.outputBufferStride = 256; + renderer->d.outputBuffer = memalign(16, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); + renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; GBAGLES2ContextCreate(&renderer->gl2); renderer->gl2.d.user = renderer; diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index d1c883707..ebbbaae93 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -95,7 +95,7 @@ int main(int argc, char** argv) { #ifdef BUILD_GL GBASDLGLCreate(&renderer); -#elif defined(BUILD_GLES2) +#elif defined(BUILD_GLES2) || defined(USE_EPOXY) GBASDLGLES2Create(&renderer); #else GBASDLSWCreate(&renderer); @@ -164,6 +164,7 @@ int main(int argc, char** argv) { GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config); free(context.debugger); + GBADirectorySetDeinit(&context.dirs); GBASDLDetachPlayer(&renderer.events, &renderer.player); GBAInputMapDeinit(&inputMap); diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index 5504fe988..28ea116a4 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -26,7 +26,7 @@ #pragma GCC diagnostic pop #endif -#ifdef BUILD_GLES2 +#if defined(BUILD_GLES2) || defined(USE_EPOXY) #include "platform/opengl/gles2.h" #endif @@ -63,7 +63,7 @@ struct SDLSoftwareRenderer { #ifdef BUILD_GL struct GBAGLContext gl; #endif -#ifdef BUILD_GLES2 +#if defined(BUILD_GLES2) || defined(USE_EPOXY) struct GBAGLES2Context gl2; #endif @@ -92,7 +92,7 @@ void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer); void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer); #endif -#ifdef BUILD_GLES2 +#if defined(BUILD_GLES2) || defined(USE_EPOXY) void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer); #endif #endif diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 42b14b209..323877e60 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -24,6 +24,8 @@ #define GYRO_STEPS 100 #define RUMBLE_PWM 20 +DEFINE_VECTOR(SDL_JoystickList, struct SDL_JoystickCombo); + #if SDL_VERSION_ATLEAST(2, 0, 0) static void _GBASDLSetRumble(struct GBARumble* rumble, int enable); #endif @@ -52,22 +54,9 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) { SDL_JoystickEventState(SDL_ENABLE); int nJoysticks = SDL_NumJoysticks(); + SDL_JoystickListInit(&context->joysticks, nJoysticks); if (nJoysticks > 0) { - context->nJoysticks = nJoysticks; - context->joysticks = calloc(context->nJoysticks, sizeof(SDL_Joystick*)); -#if SDL_VERSION_ATLEAST(2, 0, 0) - context->haptic = calloc(context->nJoysticks, sizeof(SDL_Haptic*)); -#endif - size_t i; - for (i = 0; i < context->nJoysticks; ++i) { - context->joysticks[i] = SDL_JoystickOpen(i); -#if SDL_VERSION_ATLEAST(2, 0, 0) - context->haptic[i] = SDL_HapticOpenFromJoystick(context->joysticks[i]); -#endif - } - } else { - context->nJoysticks = 0; - context->joysticks = 0; + GBASDLUpdateJoysticks(context); } context->playersAttached = 0; @@ -75,7 +64,6 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) { size_t i; for (i = 0; i < MAX_PLAYERS; ++i) { context->preferredJoysticks[i] = 0; - context->joysticksClaimed[i] = SIZE_MAX; } #if !SDL_VERSION_ATLEAST(2, 0, 0) @@ -88,13 +76,14 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) { void GBASDLDeinitEvents(struct GBASDLEvents* context) { size_t i; - for (i = 0; i < context->nJoysticks; ++i) { + for (i = 0; i < SDL_JoystickListSize(&context->joysticks); ++i) { + struct SDL_JoystickCombo* joystick = SDL_JoystickListGetPointer(&context->joysticks, i); #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_HapticClose(context->haptic[i]); + SDL_HapticClose(joystick->haptic); #endif - SDL_JoystickClose(context->joysticks[i]); + SDL_JoystickClose(joystick->joystick); } - + SDL_JoystickListDeinit(&context->joysticks); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } @@ -160,7 +149,6 @@ void GBASDLInitBindings(struct GBAInputMap* inputMap) { bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player) { player->joystick = 0; - player->joystickIndex = SIZE_MAX; if (events->playersAttached >= MAX_PLAYERS) { return false; @@ -187,15 +175,17 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player player->rotation.p = player; player->playerId = events->playersAttached; + events->players[player->playerId] = player; size_t firstUnclaimed = SIZE_MAX; + size_t index = SIZE_MAX; size_t i; - for (i = 0; i < events->nJoysticks; ++i) { + for (i = 0; i < SDL_JoystickListSize(&events->joysticks); ++i) { bool claimed = false; int p; for (p = 0; p < events->playersAttached; ++p) { - if (events->joysticksClaimed[p] == i) { + if (events->players[p]->joystick == SDL_JoystickListGetPointer(&events->joysticks, i)) { claimed = true; break; } @@ -210,28 +200,26 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player const char* joystickName; #if SDL_VERSION_ATLEAST(2, 0, 0) - joystickName = SDL_JoystickName(events->joysticks[i]); + joystickName = SDL_JoystickName(SDL_JoystickListGetPointer(&events->joysticks, i)->joystick); #else - joystickName = SDL_JoystickName(SDL_JoystickIndex(events->joysticks[i])); + joystickName = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&events->joysticks, i)->joystick)); #endif if (events->preferredJoysticks[player->playerId] && strcmp(events->preferredJoysticks[player->playerId], joystickName) == 0) { - player->joystickIndex = i; + index = i; break; } } - if (player->joystickIndex == SIZE_MAX && firstUnclaimed != SIZE_MAX) { - player->joystickIndex = firstUnclaimed; + if (index == SIZE_MAX && firstUnclaimed != SIZE_MAX) { + index = firstUnclaimed; } - if (player->joystickIndex != SIZE_MAX) { - player->joystick = events->joysticks[player->joystickIndex]; - events->joysticksClaimed[player->playerId] = player->joystickIndex; + if (index != SIZE_MAX) { + player->joystick = SDL_JoystickListGetPointer(&events->joysticks, index); #if SDL_VERSION_ATLEAST(2, 0, 0) - player->haptic = events->haptic[player->joystickIndex]; - if (player->haptic) { - SDL_HapticRumbleInit(player->haptic); + if (player->joystick->haptic) { + SDL_HapticRumbleInit(player->joystick->haptic); } #endif } @@ -241,7 +229,19 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player } void GBASDLDetachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player) { - events->joysticksClaimed[player->playerId] = SIZE_MAX; + if (player != events->players[player->playerId]) { + return; + } + int i; + for (i = player->playerId; i < events->playersAttached; ++i) { + if (i + 1 < MAX_PLAYERS) { + events->players[i] = events->players[i + 1]; + } + if (i < events->playersAttached - 1) { + events->players[i]->playerId = i; + } + } + --events->playersAttached; CircleBufferDeinit(&player->rotation.zHistory); } @@ -250,15 +250,15 @@ void GBASDLPlayerLoadConfig(struct GBASDLPlayer* context, const struct Configura if (context->joystick) { GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config); #if SDL_VERSION_ATLEAST(2, 0, 0) - const char* name = SDL_JoystickName(context->joystick); + const char* name = SDL_JoystickName(context->joystick->joystick); #else - const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick)); + const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick->joystick)); #endif GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, name); const char* value; char* end; - int numAxes = SDL_JoystickNumAxes(context->joystick); + int numAxes = SDL_JoystickNumAxes(context->joystick->joystick); int axis; value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisX", name); if (value) { @@ -301,9 +301,9 @@ void GBASDLPlayerLoadConfig(struct GBASDLPlayer* context, const struct Configura void GBASDLPlayerSaveConfig(const struct GBASDLPlayer* context, struct Configuration* config) { if (context->joystick) { #if SDL_VERSION_ATLEAST(2, 0, 0) - const char* name = SDL_JoystickName(context->joystick); + const char* name = SDL_JoystickName(context->joystick->joystick); #else - const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick)); + const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick->joystick)); #endif char value[12]; snprintf(value, sizeof(value), "%i", context->rotation.axisX); @@ -320,14 +320,54 @@ void GBASDLPlayerSaveConfig(const struct GBASDLPlayer* context, struct Configura } void GBASDLPlayerChangeJoystick(struct GBASDLEvents* events, struct GBASDLPlayer* player, size_t index) { - if (player->playerId >= MAX_PLAYERS || index >= events->nJoysticks) { + if (player->playerId >= MAX_PLAYERS || index >= SDL_JoystickListSize(&events->joysticks)) { return; } - events->joysticksClaimed[player->playerId] = index; - player->joystickIndex = index; - player->joystick = events->joysticks[index]; + player->joystick = SDL_JoystickListGetPointer(&events->joysticks, index); +} + +void GBASDLUpdateJoysticks(struct GBASDLEvents* events) { + // Pump SDL joystick events without eating the rest of the events + SDL_JoystickUpdate(); #if SDL_VERSION_ATLEAST(2, 0, 0) - player->haptic = events->haptic[index]; + SDL_Event event; + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED) > 0) { + if (event.type == SDL_JOYDEVICEADDED) { + struct SDL_JoystickCombo* joystick = SDL_JoystickListAppend(&events->joysticks); + joystick->joystick = SDL_JoystickOpen(event.jdevice.which); + joystick->id = SDL_JoystickInstanceID(joystick->joystick); + joystick->index = SDL_JoystickListSize(&events->joysticks) - 1; +#if SDL_VERSION_ATLEAST(2, 0, 0) + joystick->haptic = SDL_HapticOpenFromJoystick(joystick->joystick); +#endif + } else if (event.type == SDL_JOYDEVICEREMOVED) { + SDL_JoystickID ids[MAX_PLAYERS]; + size_t i; + for (i = 0; (int) i < events->playersAttached; ++i) { + if (events->players[i]->joystick) { + ids[i] = events->players[i]->joystick->id; + events->players[i]->joystick = 0; + } else { + ids[i] = -1; + } + } + for (i = 0; i < SDL_JoystickListSize(&events->joysticks);) { + struct SDL_JoystickCombo* joystick = SDL_JoystickListGetPointer(&events->joysticks, i); + if (joystick->id == event.jdevice.which) { + SDL_JoystickListShift(&events->joysticks, i, 1); + continue; + } + SDL_JoystickListGetPointer(&events->joysticks, i)->index = i; + int p; + for (p = 0; p < events->playersAttached; ++p) { + if (joystick->id == ids[p]) { + events->players[p]->joystick = SDL_JoystickListGetPointer(&events->joysticks, i); + } + } + ++i; + } + } + } #endif } @@ -540,7 +580,7 @@ void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContex #if SDL_VERSION_ATLEAST(2, 0, 0) static void _GBASDLSetRumble(struct GBARumble* rumble, int enable) { struct GBASDLRumble* sdlRumble = (struct GBASDLRumble*) rumble; - if (!sdlRumble->p->haptic || !SDL_HapticRumbleSupported(sdlRumble->p->haptic)) { + if (!sdlRumble->p->joystick->haptic || !SDL_HapticRumbleSupported(sdlRumble->p->joystick->haptic)) { return; } sdlRumble->level += enable; @@ -551,15 +591,18 @@ static void _GBASDLSetRumble(struct GBARumble* rumble, int enable) { } CircleBufferWrite8(&sdlRumble->history, enable); if (sdlRumble->level) { - SDL_HapticRumblePlay(sdlRumble->p->haptic, sdlRumble->level / (float) RUMBLE_PWM, 20); + SDL_HapticRumblePlay(sdlRumble->p->joystick->haptic, sdlRumble->level / (float) RUMBLE_PWM, 20); } else { - SDL_HapticRumbleStop(sdlRumble->p->haptic); + SDL_HapticRumbleStop(sdlRumble->p->joystick->haptic); } } #endif static int32_t _readTilt(struct GBASDLPlayer* player, int axis) { - return SDL_JoystickGetAxis(player->joystick, axis) * 0x3800; + if (!player->joystick) { + return 0; + } + return SDL_JoystickGetAxis(player->joystick->joystick, axis) * 0x3800; } static int32_t _GBASDLReadTiltX(struct GBARotationSource* source) { @@ -581,9 +624,12 @@ static int32_t _GBASDLReadGyroZ(struct GBARotationSource* source) { static void _GBASDLRotationSample(struct GBARotationSource* source) { struct GBASDLRotation* rotation = (struct GBASDLRotation*) source; SDL_JoystickUpdate(); + if (!rotation->p->joystick) { + return; + } - int x = SDL_JoystickGetAxis(rotation->p->joystick, rotation->gyroX); - int y = SDL_JoystickGetAxis(rotation->p->joystick, rotation->gyroY); + int x = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroX); + int y = SDL_JoystickGetAxis(rotation->p->joystick->joystick, rotation->gyroY); union { float f; int32_t i; diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index eceb9c532..015868f2e 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -8,6 +8,7 @@ #include "util/common.h" #include "util/circle-buffer.h" +#include "util/vector.h" #include "gba/supervisor/thread.h" @@ -21,14 +22,25 @@ struct GBAVideoSoftwareRenderer; struct Configuration; +struct SDL_JoystickCombo { + SDL_JoystickID id; + size_t index; + SDL_Joystick* joystick; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_Haptic* haptic; +#endif +}; + +DECLARE_VECTOR(SDL_JoystickList, struct SDL_JoystickCombo); + +struct GBASDLPlayer; + struct GBASDLEvents { - SDL_Joystick** joysticks; - size_t nJoysticks; + struct SDL_JoystickList joysticks; const char* preferredJoysticks[MAX_PLAYERS]; int playersAttached; - size_t joysticksClaimed[MAX_PLAYERS]; + struct GBASDLPlayer* players[MAX_PLAYERS]; #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_Haptic** haptic; int screensaverSuspendDepth; bool screensaverSuspendable; #endif @@ -37,13 +49,11 @@ struct GBASDLEvents { struct GBASDLPlayer { size_t playerId; struct GBAInputMap* bindings; - SDL_Joystick* joystick; - size_t joystickIndex; + struct SDL_JoystickCombo* joystick; #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Window* window; int fullscreen; int windowUpdated; - SDL_Haptic* haptic; struct GBASDLRumble { struct GBARumble d; @@ -80,6 +90,7 @@ bool GBASDLAttachPlayer(struct GBASDLEvents*, struct GBASDLPlayer*); void GBASDLDetachPlayer(struct GBASDLEvents*, struct GBASDLPlayer*); void GBASDLEventsLoadConfig(struct GBASDLEvents*, const struct Configuration*); void GBASDLPlayerChangeJoystick(struct GBASDLEvents*, struct GBASDLPlayer*, size_t index); +void GBASDLUpdateJoysticks(struct GBASDLEvents* events); void GBASDLInitBindings(struct GBAInputMap* inputMap); void GBASDLPlayerLoadConfig(struct GBASDLPlayer*, const struct Configuration*); diff --git a/src/platform/wii/gui-font.c b/src/platform/wii/gui-font.c index 731490e6a..8ca4c6550 100644 --- a/src/platform/wii/gui-font.c +++ b/src/platform/wii/gui-font.c @@ -65,6 +65,25 @@ unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { return defaultFontMetrics[glyph].width * 2; } +void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) { + UNUSED(font); + if (icon >= GUI_ICON_MAX) { + if (w) { + *w = 0; + } + if (h) { + *h = 0; + } + } else { + if (w) { + *w = defaultIconMetrics[icon].width * 2; + } + if (h) { + *h = defaultIconMetrics[icon].height * 2; + } + } +} + void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) { color = (color >> 24) | (color << 8); GXTexObj tex; @@ -180,3 +199,55 @@ void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment GX_TexCoord2f32(u[3], v[3]); GX_End(); } + +void GUIFontDrawIconSize(const struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon) { + if (icon >= GUI_ICON_MAX) { + return; + } + + color = (color >> 24) | (color << 8); + GXTexObj tex; + + struct GUIFont* ncfont = font; + TPL_GetTexture(&ncfont->iconsTdf, 0, &tex); + GX_LoadTexObj(&tex, GX_TEXMAP0); + + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + + struct GUIIconMetric metric = defaultIconMetrics[icon]; + + float u[4]; + float v[4]; + + if (!h) { + h = metric.height * 2; + } + if (!w) { + w = metric.width * 2; + } + + u[0] = u[3] = metric.x / 256.f; + u[1] = u[2] = (metric.x + metric.width) / 256.f; + v[0] = v[1] = (metric.y + metric.height) / 64.f; + v[2] = v[3] = metric.y / 64.f; + + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + GX_Position2s16(x, y + h); + GX_Color1u32(color); + GX_TexCoord2f32(u[0], v[0]); + + GX_Position2s16(x + w, y + h); + GX_Color1u32(color); + GX_TexCoord2f32(u[1], v[1]); + + GX_Position2s16(x + w, y); + GX_Color1u32(color); + GX_TexCoord2f32(u[2], v[2]); + + GX_Position2s16(x, y); + GX_Color1u32(color); + GX_TexCoord2f32(u[3], v[3]); + GX_End(); +} diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index c44092198..cf35ff13e 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -264,15 +264,15 @@ int main(int argc, char* argv[]) { "1", "B", "A", - "Minus", + "-", 0, 0, - "Home", + "\1\xE", "Left", "Right", "Down", "Up", - "Plus", + "+", 0, 0, 0, @@ -311,9 +311,9 @@ int main(int argc, char* argv[]) { "ZL", 0, "R", - "Plus", - "Home", - "Minus", + "+", + "\1\xE", + "-", "L", "Down", "Right", @@ -426,18 +426,18 @@ static uint32_t _pollInput(void) { int keys = 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 < -0x40 || w_x < -0x40) { + int w_x = WPAD_StickX(0, 0); + int w_y = WPAD_StickY(0, 0); + if (x < -0x20 || w_x < -0x20) { keys |= 1 << GUI_INPUT_LEFT; } - if (x > 0x40 || w_x > 0x40) { + if (x > 0x20 || w_x > 0x20) { keys |= 1 << GUI_INPUT_RIGHT; } - if (y < -0x40 || w_y <- 0x40) { + if (y < -0x20 || w_y <- 0x20) { keys |= 1 << GUI_INPUT_DOWN; } - if (y > 0x40 || w_y > 0x40) { + if (y > 0x20 || w_y > 0x20) { keys |= 1 << GUI_INPUT_UP; } if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || @@ -552,10 +552,10 @@ void _setup(struct GBAGUIRunner* runner) { _mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L); _mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R); - struct GBAAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x40, -0x40 }; + struct GBAAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x20, -0x20 }; GBAInputBindAxis(&runner->context.inputMap, GCN1_INPUT, 0, &desc); GBAInputBindAxis(&runner->context.inputMap, CLASSIC_INPUT, 0, &desc); - desc = (struct GBAAxis) { GBA_KEY_UP, GBA_KEY_DOWN, 0x40, -0x40 }; + desc = (struct GBAAxis) { GBA_KEY_UP, GBA_KEY_DOWN, 0x20, -0x20 }; GBAInputBindAxis(&runner->context.inputMap, GCN1_INPUT, 1, &desc); GBAInputBindAxis(&runner->context.inputMap, CLASSIC_INPUT, 1, &desc); diff --git a/src/platform/windows/threading.h b/src/platform/windows/threading.h index 37427bf23..efcf3a091 100644 --- a/src/platform/windows/threading.h +++ b/src/platform/windows/threading.h @@ -34,7 +34,7 @@ static inline int MutexLock(Mutex* mutex) { static inline int MutexTryLock(Mutex* mutex) { if (TryEnterCriticalSection(mutex)) { - return GetLastError(); + return 0; } return 1; } diff --git a/src/util/gui/font-metrics.c b/src/util/gui/font-metrics.c index 3430e3b4b..684635d26 100644 --- a/src/util/gui/font-metrics.c +++ b/src/util/gui/font-metrics.c @@ -151,5 +151,5 @@ struct GUIIconMetric defaultIconMetrics[] = { [GUI_ICON_BUTTON_CROSS] = { 18, 34, 12, 11 }, [GUI_ICON_BUTTON_TRIANGLE] = { 34, 34, 12, 11 }, [GUI_ICON_BUTTON_SQUARE] = { 50, 34, 12, 11 }, - [GUI_ICON_BUTTON_HOME] = { 66, 34, 16, 16 }, + [GUI_ICON_BUTTON_HOME] = { 66, 34, 12, 11 }, }; diff --git a/src/util/gui/font.c b/src/util/gui/font.c index a24ca9466..03965bf24 100644 --- a/src/util/gui/font.c +++ b/src/util/gui/font.c @@ -5,12 +5,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/gui/font.h" +#include "util/string.h" + unsigned GUIFontSpanWidth(const struct GUIFont* font, const char* text) { unsigned width = 0; - size_t i; - for (i = 0; text[i]; ++i) { - char c = text[i]; - width += GUIFontGlyphWidth(font, c); + size_t len = strlen(text); + while (len) { + uint32_t c = utf8Char(&text, &len); + if (c == '\1') { + c = utf8Char(&text, &len); + if (c < GUI_ICON_MAX) { + unsigned w; + GUIFontIconMetrics(font, c, &w, 0); + width += w; + } + } else { + width += GUIFontGlyphWidth(font, c); + } } return width; } @@ -29,8 +40,18 @@ void GUIFontPrint(const struct GUIFont* font, int x, int y, enum GUIAlignment al size_t len = strlen(text); while (len) { uint32_t c = utf8Char(&text, &len); - GUIFontDrawGlyph(font, x, y, color, c); - x += GUIFontGlyphWidth(font, c); + if (c == '\1') { + c = utf8Char(&text, &len); + if (c < GUI_ICON_MAX) { + GUIFontDrawIcon(font, x, y, GUI_ALIGN_BOTTOM, GUI_ORIENT_0, color, c); + unsigned w; + GUIFontIconMetrics(font, c, &w, 0); + x += w; + } + } else { + GUIFontDrawGlyph(font, x, y, color, c); + x += GUIFontGlyphWidth(font, c); + } } } diff --git a/src/util/gui/font.h b/src/util/gui/font.h index f422d61ee..7c936ffb9 100644 --- a/src/util/gui/font.h +++ b/src/util/gui/font.h @@ -75,11 +75,13 @@ struct GUIIconMetric { unsigned GUIFontHeight(const struct GUIFont*); unsigned GUIFontGlyphWidth(const struct GUIFont*, uint32_t glyph); unsigned GUIFontSpanWidth(const struct GUIFont*, const char* text); +void GUIFontIconMetrics(const struct GUIFont*, enum GUIIcon icon, unsigned* w, unsigned* h); ATTRIBUTE_FORMAT(printf, 6, 7) void GUIFontPrintf(const struct GUIFont*, int x, int y, enum GUIAlignment, uint32_t color, const char* text, ...); void GUIFontPrint(const struct GUIFont*, int x, int y, enum GUIAlignment, uint32_t color, const char* text); void GUIFontDrawGlyph(const struct GUIFont*, int x, int y, uint32_t color, uint32_t glyph); void GUIFontDrawIcon(const struct GUIFont*, int x, int y, enum GUIAlignment, enum GUIOrientation, uint32_t color, enum GUIIcon); +void GUIFontDrawIconSize(const struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon); #endif diff --git a/src/util/gui/menu.c b/src/util/gui/menu.c index b65d76052..a64de2c48 100644 --- a/src/util/gui/menu.c +++ b/src/util/gui/menu.c @@ -8,6 +8,10 @@ #include "util/gui.h" #include "util/gui/font.h" +#ifdef _3DS +#include <3ds.h> +#endif + DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem); enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) { @@ -23,6 +27,11 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men GUIInvalidateKeys(params); while (true) { +#ifdef _3DS + if (!aptMainLoop()) { + return GUI_MENU_EXIT_CANCEL; + } +#endif uint32_t newInput = 0; GUIPollInput(params, &newInput, 0); unsigned cx, cy; @@ -88,7 +97,7 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men ++menu->index; } else if (cy <= params->height - lineHeight && cy > 2 * lineHeight) { size_t location = cy - 2 * lineHeight; - location *= GUIMenuItemListSize(&menu->items); + location *= GUIMenuItemListSize(&menu->items) - 1; menu->index = location / (params->height - 3 * lineHeight); } } @@ -140,10 +149,10 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men int color = 0xE0A0A0A0; if (i == menu->index) { color = 0xFFFFFFFF; - GUIFontDrawIcon(params->font, 2, y, GUI_ALIGN_BOTTOM | GUI_ALIGN_LEFT, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_POINTER); + GUIFontDrawIcon(params->font, lineHeight * 0.8f, y, GUI_ALIGN_BOTTOM | GUI_ALIGN_RIGHT, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_POINTER); } struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i); - GUIFontPrintf(params->font, 0, y, GUI_ALIGN_LEFT, color, " %s", item->title); + GUIFontPrint(params->font, lineHeight, y, GUI_ALIGN_LEFT, color, item->title); if (item->validStates && item->validStates[item->state]) { GUIFontPrintf(params->font, params->width, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]); } @@ -154,15 +163,18 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men } if (itemsPerScreen < GUIMenuItemListSize(&menu->items)) { - y = 2 * lineHeight; - GUIFontDrawIcon(params->font, params->width - 8, y, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); - for (; y < params->height - 16; y += 16) { - GUIFontDrawIcon(params->font, params->width - 8, y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_TRACK); - } - GUIFontDrawIcon(params->font, params->width - 8, y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); - size_t top = 2 * lineHeight; - y = menu->index * (y - top - 16) / GUIMenuItemListSize(&menu->items); + size_t bottom = params->height - 8; + unsigned w; + unsigned right; + GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0); + GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_TRACK, &w, 0); + right = (right - w) / 2; + GUIFontDrawIcon(params->font, params->width - 8, top, GUI_ALIGN_HCENTER | GUI_ALIGN_BOTTOM, GUI_ORIENT_VMIRROR, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); + GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_TRACK); + GUIFontDrawIcon(params->font, params->width - 8, bottom, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_BUTTON); + + y = menu->index * (bottom - top - 16) / GUIMenuItemListSize(&menu->items); GUIFontDrawIcon(params->font, params->width - 8, top + y, GUI_ALIGN_HCENTER | GUI_ALIGN_TOP, GUI_ORIENT_0, 0xFFFFFFFF, GUI_ICON_SCROLLBAR_THUMB); } diff --git a/src/util/vfs.c b/src/util/vfs.c index d6102632f..5f6533762 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -40,27 +40,27 @@ struct VFile* VFileOpen(const char* path, int flags) { } return VFileFOpen(path, chflags); #elif defined(PSP2) - int sceFlags = PSP2_O_RDONLY; + int sceFlags = SCE_O_RDONLY; switch (flags & O_ACCMODE) { case O_WRONLY: - sceFlags = PSP2_O_WRONLY; + sceFlags = SCE_O_WRONLY; break; case O_RDWR: - sceFlags = PSP2_O_RDWR; + sceFlags = SCE_O_RDWR; break; case O_RDONLY: - sceFlags = PSP2_O_RDONLY; + sceFlags = SCE_O_RDONLY; break; } if (flags & O_APPEND) { - sceFlags |= PSP2_O_APPEND; + sceFlags |= SCE_O_APPEND; } if (flags & O_TRUNC) { - sceFlags |= PSP2_O_TRUNC; + sceFlags |= SCE_O_TRUNC; } if (flags & O_CREAT) { - sceFlags |= PSP2_O_CREAT; + sceFlags |= SCE_O_CREAT; } return VFileOpenSce(path, sceFlags, 0666); #elif defined(USE_VFS_3DS) diff --git a/src/util/vfs.h b/src/util/vfs.h index 5cc8dc203..19c71da92 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -59,6 +59,7 @@ struct VDir { struct VDirEntry* (*listNext)(struct VDir* vd); struct VFile* (*openFile)(struct VDir* vd, const char* name, int mode); struct VDir* (*openDir)(struct VDir* vd, const char* name); + bool (*deleteFile)(struct VDir* vd, const char* name); }; struct VFile* VFileOpen(const char* path, int flags); diff --git a/src/util/vfs/vfs-devlist.c b/src/util/vfs/vfs-devlist.c index 9fbbefbea..8f25feb17 100644 --- a/src/util/vfs/vfs-devlist.c +++ b/src/util/vfs/vfs-devlist.c @@ -12,6 +12,7 @@ static void _vdlRewind(struct VDir* vd); static struct VDirEntry* _vdlListNext(struct VDir* vd); static struct VFile* _vdlOpenFile(struct VDir* vd, const char* path, int mode); static struct VDir* _vdlOpenDir(struct VDir* vd, const char* path); +static bool _vdlDeleteFile(struct VDir* vd, const char* path); static const char* _vdleName(struct VDirEntry* vde); static enum VFSType _vdleType(struct VDirEntry* vde); @@ -38,6 +39,7 @@ struct VDir* VDeviceList() { vd->d.listNext = _vdlListNext; vd->d.openFile = _vdlOpenFile; vd->d.openDir = _vdlOpenDir; + vd->d.deleteFile = _vdlDeleteFile; vd->vde.d.name = _vdleName; vd->vde.d.type = _vdleType; @@ -93,6 +95,12 @@ static struct VDir* _vdlOpenDir(struct VDir* vd, const char* path) { return VDirOpen(path); } +static bool _vdlDeleteFile(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return false; +} + static const char* _vdleName(struct VDirEntry* vde) { struct VDirEntryDevList* vdle = (struct VDirEntryDevList*) vde; return vdle->name; diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index 5f37be05a..7104af808 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -15,6 +15,7 @@ static void _vdRewind(struct VDir* vd); static struct VDirEntry* _vdListNext(struct VDir* vd); static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); static struct VDir* _vdOpenDir(struct VDir* vd, const char* path); +static bool _vdDeleteFile(struct VDir* vd, const char* path); static const char* _vdeName(struct VDirEntry* vde); static enum VFSType _vdeType(struct VDirEntry* vde); @@ -55,6 +56,7 @@ struct VDir* VDirOpen(const char* path) { vd->d.listNext = _vdListNext; vd->d.openFile = _vdOpenFile; vd->d.openDir = _vdOpenDir; + vd->d.deleteFile = _vdDeleteFile; vd->path = strdup(path); vd->de = de; @@ -121,6 +123,20 @@ struct VDir* _vdOpenDir(struct VDir* vd, const char* path) { return vd2; } +bool _vdDeleteFile(struct VDir* vd, const char* path) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (!path) { + return false; + } + const char* dir = vdde->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + bool ret = !unlink(combined); + free(combined); + return ret; +} + const char* _vdeName(struct VDirEntry* vde) { struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; if (vdede->ent) { diff --git a/src/util/vfs/vfs-lzma.c b/src/util/vfs/vfs-lzma.c index 6867ec456..255e87322 100644 --- a/src/util/vfs/vfs-lzma.c +++ b/src/util/vfs/vfs-lzma.c @@ -63,6 +63,7 @@ static void _vd7zRewind(struct VDir* vd); static struct VDirEntry* _vd7zListNext(struct VDir* vd); static struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode); static struct VDir* _vd7zOpenDir(struct VDir* vd, const char* path); +static bool _vd7zDeleteFile(struct VDir* vd, const char* path); static const char* _vde7zName(struct VDirEntry* vde); static enum VFSType _vde7zType(struct VDirEntry* vde); @@ -102,7 +103,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) { return 0; } - vd->dirent.index = 0; + vd->dirent.index = -1; vd->dirent.utf8 = 0; vd->dirent.vd = vd; vd->dirent.d.name = _vde7zName; @@ -113,6 +114,7 @@ struct VDir* VDirOpen7z(const char* path, int flags) { vd->d.listNext = _vd7zListNext; vd->d.openFile = _vd7zOpenFile; vd->d.openDir = _vd7zOpenDir; + vd->d.deleteFile = _vd7zDeleteFile; return &vd->d; } @@ -309,6 +311,13 @@ struct VDir* _vd7zOpenDir(struct VDir* vd, const char* path) { return 0; } +bool _vd7zDeleteFile(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + // TODO + return false; +} + bool _vf7zSync(struct VFile* vf, const void* memory, size_t size) { UNUSED(vf); UNUSED(memory); diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 0c1f95f5a..83936f6e2 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -74,6 +74,7 @@ static void _vdzRewind(struct VDir* vd); static struct VDirEntry* _vdzListNext(struct VDir* vd); static struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode); static struct VDir* _vdzOpenDir(struct VDir* vd, const char* path); +static bool _vdzDeleteFile(struct VDir* vd, const char* path); static const char* _vdezName(struct VDirEntry* vde); static enum VFSType _vdezType(struct VDirEntry* vde); @@ -172,6 +173,7 @@ struct VDir* VDirOpenZip(const char* path, int flags) { vd->d.listNext = _vdzListNext; vd->d.openFile = _vdzOpenFile; vd->d.openDir = _vdzOpenDir; + vd->d.deleteFile = _vdzDeleteFile; vd->z = z; #ifndef USE_LIBZIP @@ -410,6 +412,13 @@ struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) { return 0; } +bool _vdzDeleteFile(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + // TODO + return false; +} + bool _vfzSync(struct VFile* vf, const void* memory, size_t size) { UNUSED(vf); UNUSED(memory); @@ -624,6 +633,13 @@ struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) { return 0; } +bool _vdzDeleteFile(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + // TODO + return false; +} + bool _vfzSync(struct VFile* vf, const void* memory, size_t size) { UNUSED(vf); UNUSED(memory); diff --git a/version.cmake b/version.cmake index 5ae523ab4..dbb5c423f 100644 --- a/version.cmake +++ b/version.cmake @@ -2,9 +2,9 @@ if(NOT PROJECT_NAME) set(PROJECT_NAME "mGBA") endif() set(LIB_VERSION_MAJOR 0) -set(LIB_VERSION_MINOR 4) +set(LIB_VERSION_MINOR 5) set(LIB_VERSION_PATCH 0) -set(LIB_VERSION_ABI 0.4) +set(LIB_VERSION_ABI 0.5) set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator")