Merge branch 'master' into feature/sio-dolphin

This commit is contained in:
Jeffrey Pfau 2016-02-09 01:11:55 -08:00
commit 962e91135a
99 changed files with 2579 additions and 741 deletions

74
CHANGES
View File

@ -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:

View File

@ -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))
<a name="flashdetect">[2]</a> 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.

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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
}

View File

@ -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; \

View File

@ -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");
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;

View File

@ -11,7 +11,7 @@
#include <string.h>
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;
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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*);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -40,6 +40,11 @@ struct GBAOptions {
bool suspendScreensaver;
char* shader;
char* savegamePath;
char* savestatePath;
char* screenshotPath;
char* patchPath;
int volume;
bool mute;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -15,6 +15,10 @@
#include "util/png-io.h"
#include "util/vfs.h"
#ifdef _3DS
#include <3ds.h>
#endif
#include <sys/time.h>
#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)) {

View File

@ -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:

View File

@ -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;
}

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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]) {

View File

@ -92,6 +92,7 @@ AccessControlInfo:
MaxCpu : 0x9E # Default
CpuSpeed : 804mhz
EnableL2Cache : true
DisableDebug : true
EnableForceDebug : false

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -25,7 +25,6 @@ struct GBAArguments {
char* fname;
char* patch;
char* cheatsFile;
bool dirmode;
char* movie;
enum DebuggerType debuggerType;

View File

@ -32,6 +32,8 @@
<integer>0</integer>
<key>OEGameCoreSupportsRewinding</key>
<true/>
<key>OEGameCoreSupportsCheatCode</key>
<true/>
</dict>
</dict>
<key>OEGameCorePlayerCount</key>

View File

@ -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

View File

@ -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
}

View File

@ -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) {

View File

@ -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*);

View File

@ -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);
}

View File

@ -22,8 +22,6 @@
#include <vita2d.h>
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;
}
@ -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
},

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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<QString>& mru) {
void ConfigController::write() {
GBAConfigSave(&m_config);
m_settings->sync();
GBAConfigFreeOpts(&m_opts);
GBAConfigMap(&m_config, &m_opts);
}
void ConfigController::makePortable() {

View File

@ -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

View File

@ -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
};

View File

@ -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();
}

View File

@ -10,6 +10,9 @@
#ifdef USE_EPOXY
#include <epoxy/gl.h>
#ifndef GLdouble
#define GLdouble GLdouble
#endif
#endif
#include <QGLWidget>
@ -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; }

View File

@ -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<const uchar*>(buffer), 256, 256, QImage::Format_RGB16);
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_RGB16);
#else
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB555);
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_RGB555);
#endif
#else
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB32);
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_RGB32);
#endif
}

View File

@ -124,28 +124,27 @@ GBAApp* GBAApp::app() {
return g_app;
}
void GBAApp::interruptAll() {
void GBAApp::pauseAll(QList<int>* 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<int>* paused) {
for (int i : *paused) {
m_windows[i]->controller()->setPaused(false);
}
}
QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) {
interruptAll();
QList<int> 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<int> 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<int> 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<int> 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;
}

View File

@ -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<int>* paused);
void continueAll(const QList<int>* paused);
ConfigController m_configController;
Window* m_windows[MAX_GBAS];
MultiplayerController m_multiplayer;

View File

@ -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<void (QComboBox::*)(int)>(&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<KeyEditor*>{
@ -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) {

View File

@ -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:

View File

@ -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();
}

View File

@ -34,6 +34,7 @@ public slots:
void attach();
void detach();
void listen();
void breakInto();
signals:
void listening();

View File

@ -11,6 +11,7 @@
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#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()));

View File

@ -36,6 +36,7 @@ private:
QLineEdit* m_portEdit;
QLineEdit* m_bindAddressEdit;
QPushButton* m_startStopButton;
QPushButton* m_breakButton;
};
}

View File

@ -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<GameController*>(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<GameController*>(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<int>(value - 0x16, 0);

View File

@ -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;

View File

@ -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<int> InputController::activeGamepadButtons(int type) {
QSet<int> 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<int> 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<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes(int type) {
QSet<QPair<int, GamepadAxisEvent::Direction>> 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;

View File

@ -52,6 +52,7 @@ public:
const GBAInputMap* map() const { return &m_inputMap; }
void updateJoysticks();
int pollEvents();
static const int32_t AXIS_THRESHOLD = 0x3000;

View File

@ -9,6 +9,7 @@
#include "GamepadButtonEvent.h"
#include "ShortcutController.h"
#include <QFontMetrics>
#include <QKeyEvent>
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;
}

View File

@ -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() {

View File

@ -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;

View File

@ -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()) {

View File

@ -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);

View File

@ -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<int>(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<int>(Display::Driver::QT));
if (!displayDriver.isNull() && displayDriver.toInt() == static_cast<int>(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<int>(Display::Driver::OPENGL));
if (displayDriver.isNull() || displayDriver.toInt() == static_cast<int>(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<int>(Display::Driver::OPENGL1));
if (displayDriver.isNull() || displayDriver.toInt() == static_cast<int>(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);

View File

@ -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*);

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>420</height>
<width>544</width>
<height>425</height>
</rect>
</property>
<property name="sizePolicy">
@ -19,16 +19,65 @@
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QListWidget" name="tabs">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>140</width>
<height>16777215</height>
</size>
</property>
<property name="currentRow">
<number>0</number>
</property>
<item>
<property name="text">
<string>Audio/Video</string>
</property>
</item>
<item>
<property name="text">
<string>Emulation</string>
</property>
</item>
<item>
<property name="text">
<string>Savestates</string>
</property>
</item>
<item>
<property name="text">
<string>Paths</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="stackedWidgetPage1">
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_14">
@ -120,6 +169,50 @@
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QComboBox" name="sampleRate">
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText" stdset="0">
<string>44100</string>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>22050</string>
</property>
</item>
<item>
<property name="text">
<string>32000</string>
</property>
</item>
<item>
<property name="text">
<string>44100</string>
</property>
</item>
<item>
<property name="text">
<string>48000</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>Hz</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
@ -131,6 +224,12 @@
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QSlider" name="volume">
<property name="minimumSize">
<size>
<width>128</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>256</number>
</property>
@ -155,7 +254,7 @@
</layout>
</item>
<item row="4" column="0" colspan="2">
<widget class="Line" name="line">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -238,7 +337,7 @@
</layout>
</item>
<item row="8" column="0" colspan="2">
<widget class="Line" name="line_4">
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -283,61 +382,13 @@
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QComboBox" name="sampleRate">
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText" stdset="0">
<string>44100</string>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>22050</string>
</property>
</item>
<item>
<property name="text">
<string>32000</string>
</property>
</item>
<item>
<property name="text">
<string>44100</string>
</property>
</item>
<item>
<property name="text">
<string>48000</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>Hz</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</widget>
<widget class="QWidget" name="stackedWidgetPage2">
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@ -366,6 +417,16 @@
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="useBios">
<property name="text">
<string>Use BIOS file if found</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="skipBios">
<property name="text">
@ -373,120 +434,21 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="Line" name="line_3">
<item row="3" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="rewind">
<item row="4" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Enable rewind</string>
<string>Fast forward speed</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Create rewind state:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Every</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="rewindInterval"/>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>frames</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Rewind history:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QSpinBox" name="rewindCapacity"/>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>states</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="11" column="0" colspan="2">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<property name="text">
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="suspendScreensaver">
<property name="text">
<string>Suspend screensaver</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Idle loops</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QComboBox" name="idleOptimization">
<item>
<property name="text">
<string>Run all</string>
</property>
</item>
<item>
<property name="text">
<string>Remove known</string>
</property>
</item>
<item>
<property name="text">
<string>Detect and remove</string>
</property>
</item>
</widget>
</item>
<item row="9" column="1">
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="fastForwardRatio">
<property name="enabled">
<bool>false</bool>
@ -508,14 +470,7 @@
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Fast forward speed</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="5" column="1">
<widget class="QCheckBox" name="fastForwardUnbounded">
<property name="text">
<string>Unbounded</string>
@ -525,32 +480,423 @@
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="Line" name="line_6">
<item row="6" column="0" colspan="2">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="useBios">
<item row="7" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<property name="text">
<string>Use BIOS file</string>
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="suspendScreensaver">
<property name="text">
<string>Suspend screensaver</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="pauseOnFocusLost">
<property name="text">
<string>Pause when inactive</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Idle loops</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QComboBox" name="idleOptimization">
<item>
<property name="text">
<string>Run all</string>
</property>
</item>
<item>
<property name="text">
<string>Remove known</string>
</property>
</item>
<item>
<property name="text">
<string>Detect and remove</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QFormLayout" name="formLayout_4">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Save extra data</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="saveStateScreenshot">
<property name="text">
<string>Screenshot</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="saveStateSave">
<property name="text">
<string>Save data</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="saveStateCheats">
<property name="text">
<string>Cheat codes</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Load extra data</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="loadStateScreenshot">
<property name="text">
<string>Screenshot</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="loadStateSave">
<property name="text">
<string>Save data</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="loadStateCheats">
<property name="text">
<string>Cheat codes</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="rewind">
<property name="text">
<string>Enable rewind</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Create rewind state:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Every</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="rewindInterval"/>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>frames</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Rewind history:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QSpinBox" name="rewindCapacity"/>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>states</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="12" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QDoubleSpinBox" name="rewindDuration">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximum">
<double>999.990000000000009</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_26">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="page">
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Save games</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="savegamePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="savegameBrowse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="savegameSameDir">
<property name="text">
<string>Same directory as the ROM</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Save states</string>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLineEdit" name="savestatePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="savestateBrowse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="savestateSameDir">
<property name="text">
<string>Same directory as the ROM</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Screenshots</string>
</property>
</widget>
</item>
<item row="7" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="screenshotPath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="screenshotBrowse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="screenshotSameDir">
<property name="text">
<string>Same directory as the ROM</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="Line" name="line_15">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_47">
<property name="text">
<string>Patches</string>
</property>
</widget>
</item>
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_26">
<item>
<widget class="QLineEdit" name="patchPath">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="patchBrowse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="patchSameDir">
<property name="text">
<string>Same directory as the ROM</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@ -590,18 +936,98 @@
</hints>
</connection>
<connection>
<sender>useBios</sender>
<signal>toggled(bool)</signal>
<receiver>skipBios</receiver>
<slot>setEnabled(bool)</slot>
<sender>tabs</sender>
<signal>currentRowChanged(int)</signal>
<receiver>stackedWidget</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>520</x>
<y>62</y>
<x>61</x>
<y>209</y>
</hint>
<hint type="destinationlabel">
<x>525</x>
<y>83</y>
<x>315</x>
<y>209</y>
</hint>
</hints>
</connection>
<connection>
<sender>savegameSameDir</sender>
<signal>toggled(bool)</signal>
<receiver>savegamePath</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>392</x>
<y>82</y>
</hint>
<hint type="destinationlabel">
<x>366</x>
<y>48</y>
</hint>
</hints>
</connection>
<connection>
<sender>savestateSameDir</sender>
<signal>toggled(bool)</signal>
<receiver>savestatePath</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>392</x>
<y>161</y>
</hint>
<hint type="destinationlabel">
<x>366</x>
<y>127</y>
</hint>
</hints>
</connection>
<connection>
<sender>screenshotSameDir</sender>
<signal>toggled(bool)</signal>
<receiver>screenshotPath</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>392</x>
<y>240</y>
</hint>
<hint type="destinationlabel">
<x>366</x>
<y>206</y>
</hint>
</hints>
</connection>
<connection>
<sender>patchSameDir</sender>
<signal>toggled(bool)</signal>
<receiver>patchPath</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>345</x>
<y>319</y>
</hint>
<hint type="destinationlabel">
<x>340</x>
<y>285</y>
</hint>
</hints>
</connection>
<connection>
<sender>fastForwardUnbounded</sender>
<signal>toggled(bool)</signal>
<receiver>fastForwardRatio</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>338</x>
<y>163</y>
</hint>
<hint type="destinationlabel">
<x>327</x>
<y>135</y>
</hint>
</hints>
</connection>

View File

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

View File

@ -137,10 +137,13 @@ void ShortcutController::addFunctions(QMenu* menu, std::function<void()> 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) {

View File

@ -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<void(ShortcutItem*)> func);
void updateKey(ShortcutItem* item, int keySequence);

View File

@ -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);
}
}

View File

@ -22,6 +22,7 @@ Q_OBJECT
public:
ShortcutView(QWidget* parent = nullptr);
~ShortcutView();
void setController(ShortcutController* controller);
void setInputController(InputController* input);

View File

@ -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<const uchar*>(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<QStackedLayout*>(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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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*);

View File

@ -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();
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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 },
};

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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);
}

View File

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

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

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