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")