diff --git a/CHANGES b/CHANGES
index 540ad89d1..77643b674 100644
--- a/CHANGES
+++ b/CHANGES
@@ -27,8 +27,11 @@ Features:
- Undo-able savestate loading and saving
- Controller profiles now store shortcut settings
- Default controller profiles for several common controllers
- - Libretro now supports BIOS and rumble
+ - Libretro now supports BIOS, rumble and solar sensor
- Implement BIOS call Stop, for sleep mode
+ - Automatically load patches, if found
+ - Improved video synchronization
+ - Configurable audio output sample rate
Bugfixes:
- ARM7: Fix SWI and IRQ timings
- GBA Audio: Force audio FIFOs to 32-bit
@@ -64,6 +67,11 @@ Bugfixes:
- Qt: Fix a missing va_end call in the log handler lambda within the GameController constructor
- GBA Cheats: Fix Pro Action Replay and GameShark issues when used together
- Qt: Fix analog buttons not getting unmapped
+ - GBA Video: Prevent tiles < 512 from being used in modes 3 - 5
+ - Qt: Fix passing command line options
+ - Qt: Fix crashes on Windows by using using QMetaObject to do cross-thread calls
+ - GBA Video: Fix timing on first scanline
+ - GBA: Ensure cycles never go negative
Misc:
- Qt: Handle saving input settings better
- Debugger: Free watchpoints in addition to breakpoints
@@ -109,6 +117,12 @@ Misc:
- GBA Input: Allow axes and buttons to be mapped to the same key
- GBA BIOS: Stub out SoundBias
- Qt: Gamepads can now have both buttons and analog axes mapped to the same key
+ - Qt: Increase usability of key mapper
+ - Qt: Show checkmark for window sizes
+ - Qt: Set window path to loaded ROM
+ - GBA Memory: Run multiple DMAs in a tight loop if they all occur before present
+ - GBA Audio: Process multiple audio events at once, if necessary
+ - GBA: Process multiple timer events at once, if necessary
0.2.1: (2015-05-13)
Bugfixes:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 765dc4266..e0fb81313 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,7 @@ set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
set(BUILD_GL ON CACHE STRING "Build with OpenGL")
+set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2")
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c)
@@ -44,13 +45,17 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE)
endif()
+set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}")
+
+include(GNUInstallDirs)
+
if (NOT DEFINED LIBDIR)
set(LIBDIR "lib")
endif()
-set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}")
-
-include(GNUInstallDirs)
+if (NOT DEFINED MANDIR)
+ set(MANDIR ${CMAKE_INSTALL_MANDIR})
+endif()
# Function definitions
include(FindPkgConfig)
@@ -126,7 +131,14 @@ endif()
if(BUILD_GL)
find_package(OpenGL QUIET)
if(NOT OPENGL_FOUND)
- set(BUILD_GL OFF)
+ set(BUILD_GL OFF CACHE BOOL "OpenGL not found" FORCE)
+ endif()
+endif()
+if(BUILD_GLES2 AND NOT BUILD_RASPI)
+ find_path(OPENGLES2_INCLUDE_DIR NAMES GLES2/gl2.h)
+ find_library(OPENGLES2_LIBRARY NAMES GLESv2 GLESv2_CM)
+ if(NOT OPENGLES2_INCLUDE_DIR OR NOT OPENGLES2_LIBRARY)
+ set(BUILD_GLES2 OFF CACHE BOOL "OpenGL|ES 2 not found" FORCE)
endif()
endif()
find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale")
@@ -177,6 +189,10 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
endif()
endif()
+if(BUILD_RASPI)
+ set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE)
+endif()
+
if(BUILD_PANDORA)
add_definitions(-DBUILD_PANDORA)
endif()
@@ -371,6 +387,7 @@ endif()
if(BUILD_SHARED)
add_library(${BINARY_NAME} SHARED ${SRC})
+ set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI})
if(BUILD_STATIC)
add_library(${BINARY_NAME}-static STATIC ${SRC})
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
@@ -384,7 +401,7 @@ endif()
add_dependencies(${BINARY_NAME} version-info)
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
-install(TARGETS ${BINARY_NAME} DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
+install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
if(UNIX AND NOT APPLE)
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
@@ -402,6 +419,10 @@ if(BUILD_GL)
add_definitions(-DBUILD_GL)
endif()
+if(BUILD_GLES2)
+ add_definitions(-DBUILD_GLES2)
+endif()
+
if(BUILD_LIBRETRO)
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
diff --git a/PORTING.md b/PORTING.md
index baa6f3712..ef7216c21 100644
--- a/PORTING.md
+++ b/PORTING.md
@@ -30,13 +30,12 @@ The ports are vaguely usable, but by no means should be considered stable.
### PS Vita (port/psp2)
* Add menu
-* Add audio
+* Fix audio
* Make it faster
* Threaded renderer shim
* Hardware acceleration
### Wii (port/wii)
* Add menu
-* Add audio
* Thread support
* Clean up video detection
diff --git a/README.md b/README.md
index be79140fc..eb96a9f70 100644
--- a/README.md
+++ b/README.md
@@ -126,8 +126,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))
-- Game Pak prefetch ([Bug #195](http://mgba.io/b/195))
-- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199))
[2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
diff --git a/doc/mgba-qt.6 b/doc/mgba-qt.6
new file mode 100644
index 000000000..256384721
--- /dev/null
+++ b/doc/mgba-qt.6
@@ -0,0 +1,118 @@
+.\" Copyright (c) 2015 Anthony J. Bentley
+.\"
+.\" This Source Code Form is subject to the terms of the Mozilla Public
+.\" License, v. 2.0. If a copy of the MPL was not distributed with this
+.\" file, you can obtain one at https://mozilla.org/MPL/2.0/.
+.Dd July 29, 2015
+.Dt MGBA-QT 6
+.Os
+.Sh NAME
+.Nm mgba-qt
+.Nd Game Boy Advance emulator
+.Sh SYNOPSIS
+.Nm mgba-qt
+.Op Fl b Ar biosfile
+.Op Fl l Ar loglevel
+.Op Fl p Ar patchfile
+.Op Fl s Ar n
+.Ar file
+.Sh DESCRIPTION
+.Nm
+is a Game Boy Advance emulator.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar biosfile , Fl -bios Ar biosfile
+Specify a BIOS file to use during boot.
+If this flag is omitted,
+.Nm
+will use the BIOS specified in the configuration file,
+or a high\(hylevel emulated BIOS if none is specified.
+.It Fl l Ar loglevel
+Log messages during emulation.
+.Ar loglevel
+is a bitmask defining which types of messages to log:
+.Bl -bullet -compact
+.It
+1 \(en fatal errors
+.It
+2 \(en errors
+.It
+4 \(en warnings
+.It
+8 \(en informative messages
+.It
+16 \(en debugging messages
+.It
+32 \(en stub messages for unimplemented features
+.It
+256 \(en in\(hygame errors
+.It
+512 \(en software interrupts
+.It
+1024 \(en emulator status messages
+.It
+2048 \(en serial I/O messages
+.El
+The default is to log warnings, errors, fatal errors, and status messages.
+.It Fl p Ar patchfile , Fl -patch Ar patchfile
+Specify a patch file in BPS, IPS, or UPS format.
+.It Fl s Ar n , Fl -frameskip Ar n
+Skip every
+.Ar n
+frames.
+.El
+.Sh CONTROLS
+The default controls are as follows:
+.Bl -hang -width "Frame advance" -compact
+.It A
+.Cm x
+.It B
+.Cm z
+.It L
+.Cm a
+.It R
+.Cm s
+.It Start
+.Aq Cm Enter
+.It Select
+.Aq Cm Backspace
+.It Load state
+.Cm F1 Ns \(en Ns Cm F9
+.It Save state
+.Ao Cm Shift Ac Ns \(hy Ns Cm F1 Ns \(en Ns Cm F9
+.It Frame advance
+.Ao Cm Ctrl Ac Ns \(hy Ns Cm n
+.El
+.Sh FILES
+.Bl -tag -width ~/.config/mgba/config.ini -compact
+.It Pa ~/.config/mgba/config.ini
+Default
+.Xr mgba 6
+configuration file.
+.It Pa ~/.config/mgba/qt.ini
+Default
+.Nm mgba-qt
+configuration file.
+.It Pa portable.ini
+If this file exists in the current directory,
+.Nm
+will read
+.Pa config.ini
+and
+.Pa qt.ini
+from the current directory instead of
+.Pa ~/.config/mgba .
+.El
+.Sh AUTHORS
+.An Jeffrey Pfau Aq Mt jeffrey@endrift.com
+.Sh HOMEPAGE
+.Bl -bullet
+.It
+.Lk https://mgba.io/ "mGBA homepage"
+.It
+.Lk https://github.com/mgba-emu/mgba "Development repository"
+.It
+.Lk https://github.com/mgba-emu/mgba/issues "Bug tracker"
+.It
+.Lk https://forums.mgba.io/ "Message board"
+.El
diff --git a/doc/mgba.6 b/doc/mgba.6
new file mode 100644
index 000000000..d2a4cb05b
--- /dev/null
+++ b/doc/mgba.6
@@ -0,0 +1,259 @@
+.\" Copyright (c) 2015 Anthony J. Bentley
+.\"
+.\" This Source Code Form is subject to the terms of the Mozilla Public
+.\" License, v. 2.0. If a copy of the MPL was not distributed with this
+.\" file, you can obtain one at https://mozilla.org/MPL/2.0/.
+.Dd July 29, 2015
+.Dt MGBA 6
+.Os
+.Sh NAME
+.Nm mgba
+.Nd Game Boy Advance emulator
+.Sh SYNOPSIS
+.Nm mgba
+.Op Fl 123456dfg
+.Op Fl b Ar biosfile
+.Op Fl c Ar cheatfile
+.Op Fl l Ar loglevel
+.Op Fl p Ar patchfile
+.Op Fl s Ar n
+.Op Fl v Ar moviefile
+.Ar file
+.Sh DESCRIPTION
+.Nm
+is a Game Boy Advance emulator.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 1
+Scale the window 1\(mu.
+.It Fl 2
+Scale the window 2\(mu.
+.It Fl 3
+Scale the window 3\(mu.
+.It Fl 4
+Scale the window 4\(mu.
+.It Fl 5
+Scale the window 5\(mu.
+.It Fl 6
+Scale the window 6\(mu.
+.It Fl b Ar biosfile , Fl -bios Ar biosfile
+Specify a BIOS file to use during boot.
+If this flag is omitted,
+.Nm
+will use the BIOS specified in the configuration file,
+or a high\(hylevel emulated BIOS if none is specified.
+.It Fl c Ar cheatfile , Fl -cheats Ar cheatfile
+Apply cheat codes from
+.Ar cheatfile .
+.It Fl d
+Start emulating via the command\(hyline debugger.
+.It Fl f
+Start the emulator full\(hyscreen.
+.It Fl g
+Start a
+.Xr gdb 1
+session.
+By default the session starts on port 2345.
+.It Fl l Ar loglevel
+Log messages during emulation to
+.Dv stdout .
+.Ar loglevel
+is a bitmask defining which types of messages to log:
+.Bl -bullet -compact
+.It
+1 \(en fatal errors
+.It
+2 \(en errors
+.It
+4 \(en warnings
+.It
+8 \(en informative messages
+.It
+16 \(en debugging messages
+.It
+32 \(en stub messages for unimplemented features
+.It
+256 \(en in\(hygame errors
+.It
+512 \(en software interrupts
+.It
+1024 \(en emulator status messages
+.It
+2048 \(en serial I/O messages
+.El
+The default is to log warnings, errors, fatal errors, and status messages.
+.It Fl p Ar patchfile , Fl -patch Ar patchfile
+Specify a patch file in BPS, IPS, or UPS format.
+.It Fl s Ar n , Fl -frameskip Ar n
+Skip every
+.Ar n
+frames.
+.It Fl v Ar moviefile , Fl -movie Ar moviefile
+Play back a movie of recording input from
+.Ar moviefile .
+.El
+.Sh CONTROLS
+The default controls are as follows:
+.Bl -hang -width "Frame advance" -compact
+.It A
+.Cm x
+.It B
+.Cm z
+.It L
+.Cm a
+.It R
+.Cm s
+.It Start
+.Aq Cm Enter
+.It Select
+.Aq Cm Backspace
+.It Load state
+.Cm F1 Ns \(en Ns Cm F9
+.It Save state
+.Ao Cm Shift Ac Ns \(hy Ns Cm F1 Ns \(en Ns Cm F9
+.It Frame advance
+.Ao Cm Ctrl Ac Ns \(hy Ns Cm n
+.El
+.Sh DEBUGGER
+When
+.Nm
+is run with the
+.Fl d
+option, the command\(hyline debugger is enabled.
+It supports the following commands:
+.Pp
+.Bl -tag -compact -width 1
+.It Cm b Ns Oo Cm reak Oc Ar address
+.It Cm b Ns Oo Cm reak Oc Ns Cm /a Ar address
+.It Cm b Ns Oo Cm reak Oc Ns Cm /t Ar address
+Set a breakpoint \(en ARM
+.Pq Ql /a ,
+Thumb
+.Pq Ql /t ,
+or the current CPU mode \(en at
+.Ar address .
+.It Cm c Ns Op Cm ontinue
+Continue execution.
+.It Cm d Ns Oo Cm elete Oc Ar address
+Delete a breakpoint at
+.Ar address .
+.It Cm dis Ns Oo Cm asm Oc Op Ar address Op Ar count
+.It Cm dis Ns Oo Cm asm Oc Ns Cm /a Op Ar address Op Ar count
+.It Cm dis Ns Oo Cm asm Oc Ns Cm /t Op Ar address Op Ar count
+.It Cm dis Ns Oo Cm assemble Oc Op Ar address Op Ar count
+.It Cm dis Ns Oo Cm assemble Oc Ns Cm /a Op Ar address Op Ar count
+.It Cm dis Ns Oo Cm assemble Oc Ns Cm /t Op Ar address Op Ar count
+Disassemble
+.Ar count
+instructions starting at
+.Ar address ,
+as ARM
+.Pq Ql /a ,
+Thumb
+.Pq Ql /t ,
+or the current CPU mode.
+If
+.Ar count
+is not specified, only disassemble the instruction at
+.Ar address .
+If
+.Ar address
+is not specified, only disassemble the current address.
+.It Cm h Ns Op Cm elp
+Print help.
+.It Cm i Ns Op Cm nfo
+.It Cm status
+Print the current contents of general\(hypurpose registers and the current
+program state register, and disassemble the current instruction.
+.It Cm n Ns Op Cm ext
+Execute the next instruction.
+.It Cm p Ns Oo Cm rint Oc Ar value ...
+.It Cm p Ns Oo Cm rint Oc Ns Cm /t Ar value ...
+.It Cm p Ns Oo Cm rint Oc Ns Cm /x Ar value ...
+Print one or more
+.Ar value Ns s
+as binary
+.Pq Ql /t ,
+hexadecimal
+.Pq Ql /x ,
+or decimal.
+.It Cm q Ns Op Cm uit
+Quit the emulator.
+.It Cm reset
+Reset the emulation.
+.It Cm r/1 Ar address
+.It Cm r/2 Ar address
+.It Cm r/4 Ar address
+Read a byte
+.Pq Ql /1 ,
+halfword
+.Pq Ql /2 ,
+or word
+.Pq Ql /4
+from
+.Ar address .
+.It Cm w Ns Oo Cm atch Oc Ar address
+Set a watchpoint at
+.Ar address .
+.It Cm w/1 Ar address data
+.It Cm w/2 Ar address data
+.It Cm w/4 Ar address data
+Write
+.Ar data
+as a byte
+.Pq Ql /1 ,
+halfword
+.Pq Ql /2 ,
+or word
+.Pq Ql /4
+to
+.Ar address .
+.It Cm w/r Ar register data
+Write
+.Ar data
+as a word to
+.Ar register .
+.It Cm x/1 Ar address Op Ar count
+.It Cm x/2 Ar address Op Ar count
+.It Cm x/4 Ar address Op Ar count
+Examine
+.Ar count
+bytes
+.Pq Ql /1 ,
+halfwords
+.Pq Ql /2 ,
+or words
+.Pq Ql /4
+from
+.Ar address .
+If
+.Ar count
+is not specified, examine 16 bytes, 8 halfwords, or 4 words.
+.El
+.Sh FILES
+.Bl -tag -width ~/.config/mgba/config.ini -compact
+.It Pa ~/.config/mgba/config.ini
+Default
+.Nm
+configuration file.
+.It Pa portable.ini
+If this file exists in the current directory,
+.Nm
+will read
+.Pa config.ini
+from the current directory instead of
+.Pa ~/.config/mgba .
+.El
+.Sh AUTHORS
+.An Jeffrey Pfau Aq Mt jeffrey@endrift.com
+.Sh HOMEPAGE
+.Bl -bullet
+.It
+.Lk https://mgba.io/ "mGBA homepage"
+.It
+.Lk https://github.com/mgba-emu/mgba "Development repository"
+.It
+.Lk https://github.com/mgba-emu/mgba/issues "Bug tracker"
+.It
+.Lk https://forums.mgba.io/ "Message board"
+.El
diff --git a/res/keymap.qpic b/res/keymap.qpic
index ffb7f8b7d..782d0c7f2 100644
Binary files a/res/keymap.qpic and b/res/keymap.qpic differ
diff --git a/res/keymap.svg b/res/keymap.svg
index 5ec9d5099..9b37a3aa4 100644
--- a/res/keymap.svg
+++ b/res/keymap.svg
@@ -7,130 +7,266 @@
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/mgba-1024.png b/res/mgba-1024.png
index 4ef829578..2afd2aa8b 100644
Binary files a/res/mgba-1024.png and b/res/mgba-1024.png differ
diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c
index 90d657af1..c3f0c9de1 100644
--- a/src/debugger/cli-debugger.c
+++ b/src/debugger/cli-debugger.c
@@ -18,7 +18,9 @@ static const char* ERROR_OVERFLOW = "Arguments overflow";
static struct CLIDebugger* _activeDebugger;
+#ifndef NDEBUG
static void _breakInto(struct CLIDebugger*, struct CLIDebugVector*);
+#endif
static void _continue(struct CLIDebugger*, struct CLIDebugVector*);
static void _disassemble(struct CLIDebugger*, struct CLIDebugVector*);
static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*);
@@ -56,6 +58,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
{ "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
{ "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
+ { "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
+ { "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
{ "c", _continue, 0, "Continue execution" },
{ "continue", _continue, 0, "Continue execution" },
{ "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" },
@@ -97,7 +101,9 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" },
{ "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" },
{ "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" },
+#ifndef NDEBUG
{ "!", _breakInto, 0, "Break into attached debugger (for developers)" },
+#endif
{ 0, 0, 0, 0 }
};
@@ -112,6 +118,7 @@ static inline void _printPSR(union PSR psr) {
psr.t ? 'T' : '-');
}
+#ifndef NDEBUG
static void _handleDeath(int sig) {
UNUSED(sig);
printf("No debugger attached!\n");
@@ -133,6 +140,7 @@ static void _breakInto(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
#endif
sigaction(SIGTRAP, &osa, 0);
}
+#endif
static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
UNUSED(dv);
diff --git a/src/gba/audio.c b/src/gba/audio.c
index 372c7ef25..6fac940b3 100644
--- a/src/gba/audio.c
+++ b/src/gba/audio.c
@@ -167,7 +167,7 @@ void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
int32_t GBAAudioProcessEvents(struct GBAAudio* audio, int32_t cycles) {
audio->nextEvent -= cycles;
audio->eventDiff += cycles;
- if (audio->nextEvent <= 0) {
+ while (audio->nextEvent <= 0) {
audio->nextEvent = INT_MAX;
if (audio->enable) {
if (audio->playingCh1 && !audio->ch1.envelope.dead) {
diff --git a/src/gba/gba.c b/src/gba/gba.c
index 732da9eed..a7356ad3a 100644
--- a/src/gba/gba.c
+++ b/src/gba/gba.c
@@ -94,6 +94,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
gba->idleDetectionFailures = 0;
gba->realisticTiming = true;
+ gba->hardCrash = true;
gba->performingDMA = false;
}
@@ -183,6 +184,11 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
int32_t cycles = cpu->nextEvent;
int32_t nextEvent = INT_MAX;
int32_t testEvent;
+#ifndef NDEBUG
+ if (cycles < 0) {
+ GBALog(gba, GBA_LOG_FATAL, "Negative cycles passed: %i", cycles);
+ }
+#endif
gba->bus = cpu->prefetch[1];
if (cpu->executionMode == MODE_THUMB) {
@@ -238,7 +244,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
if (timer->enable) {
timer->nextEvent -= cycles;
timer->lastEvent -= cycles;
- if (timer->nextEvent <= 0) {
+ while (timer->nextEvent <= 0) {
timer->lastEvent = timer->nextEvent;
timer->nextEvent += timer->overflowInterval;
gba->memory.io[REG_TM0CNT_LO >> 1] = timer->reload;
diff --git a/src/gba/gba.h b/src/gba/gba.h
index 46eea057c..e472a14ac 100644
--- a/src/gba/gba.h
+++ b/src/gba/gba.h
@@ -124,6 +124,7 @@ struct GBA {
bool taintedRegisters[16];
bool realisticTiming;
+ bool hardCrash;
};
struct GBACartridge {
diff --git a/src/gba/hardware.c b/src/gba/hardware.c
index bec3fa398..4f2276ba4 100644
--- a/src/gba/hardware.c
+++ b/src/gba/hardware.c
@@ -9,6 +9,8 @@
#include "gba/serialize.h"
#include "util/hash.h"
+const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 };
+
static void _readPins(struct GBACartridgeHardware* hw);
static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);
@@ -610,6 +612,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
hw->readWrite = state->hw.readWrite;
hw->pinState = state->hw.pinState;
hw->direction = state->hw.pinDirection;
+ hw->devices = state->hw.devices;
hw->rtc = state->hw.rtc;
hw->gyroSample = state->hw.gyroSample;
hw->gyroEdge = state->hw.gyroEdge;
@@ -622,4 +625,7 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
hw->gbpInputsPosted = state->hw.gbpInputsPosted;
hw->gbpTxPosition = state->hw.gbpTxPosition;
hw->gbpNextEvent = state->hw.gbpNextEvent;
+ if (hw->devices & HW_GB_PLAYER) {
+ GBASIOSetDriver(&hw->p->sio, &hw->gbpDriver.d, SIO_NORMAL_32);
+ }
}
diff --git a/src/gba/input.c b/src/gba/input.c
index a204741a2..6ae95844c 100644
--- a/src/gba/input.c
+++ b/src/gba/input.c
@@ -433,8 +433,9 @@ int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, in
void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) {
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
- TableEnumerate(&impl->axes, _unbindAxis, &description->highDirection);
- TableEnumerate(&impl->axes, _unbindAxis, &description->lowDirection);
+ struct GBAAxis d2 = *description;
+ TableEnumerate(&impl->axes, _unbindAxis, &d2.highDirection);
+ TableEnumerate(&impl->axes, _unbindAxis, &d2.lowDirection);
struct GBAAxis* dup = malloc(sizeof(struct GBAAxis));
*dup = *description;
TableInsert(&impl->axes, axis, dup);
diff --git a/src/gba/interface.h b/src/gba/interface.h
index 3daf0dcb8..396f4b549 100644
--- a/src/gba/interface.h
+++ b/src/gba/interface.h
@@ -79,6 +79,8 @@ struct GBARotationSource {
int32_t (*readGyroZ)(struct GBARotationSource*);
};
+extern const int GBA_LUX_LEVELS[10];
+
struct GBALuminanceSource {
void (*sample)(struct GBALuminanceSource*);
diff --git a/src/gba/memory.c b/src/gba/memory.c
index 9e7a478bb..9da33f31b 100644
--- a/src/gba/memory.c
+++ b/src/gba/memory.c
@@ -278,9 +278,11 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
memory->activeRegion = -1;
cpu->memory.activeRegion = _deadbeef;
cpu->memory.activeMask = 0;
- if (!gba->yankedRomSize) {
- GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
+ enum GBALogLevel errorLevel = GBA_LOG_FATAL;
+ if (gba->yankedRomSize || !gba->hardCrash) {
+ errorLevel = GBA_LOG_GAME_ERROR;
}
+ GBALog(gba, errorLevel, "Jumped to invalid address: %08X", address);
return;
}
cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion];
@@ -634,8 +636,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
#define STORE_VRAM \
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \
+ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \
+ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \
} else { \
STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \
+ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \
+ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \
} \
wait += waitstatesRegion[REGION_VRAM];
@@ -728,8 +734,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
case REGION_VRAM:
if ((address & 0x0001FFFF) < SIZE_VRAM) {
STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram);
+ gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
} else {
STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram);
+ gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
}
break;
case REGION_OAM:
@@ -794,8 +802,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OBJ: 0x%08X", address);
break;
}
- ((int8_t*) gba->video.renderer->vram)[address & 0x1FFFE] = value;
- ((int8_t*) gba->video.renderer->vram)[(address & 0x1FFFE) | 1] = value;
+ gba->video.renderer->vram[(address & 0x1FFFE) >> 1] = ((uint8_t) value) | (value << 8);
+ gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
break;
case REGION_OAM:
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OAM: 0x%08X", address);
@@ -1397,7 +1405,7 @@ int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles) {
}
memory->nextDMA -= cycles;
memory->eventDiff += cycles;
- if (memory->nextDMA <= 0) {
+ while (memory->nextDMA <= 0) {
struct GBADMA* dma = &memory->dma[memory->activeDMA];
GBAMemoryServiceDMA(gba, memory->activeDMA, dma);
GBAMemoryUpdateDMAs(gba, memory->eventDiff);
diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c
index 2d13b7340..eb793db23 100644
--- a/src/gba/renderers/software-obj.c
+++ b/src/gba/renderers/software-obj.c
@@ -116,6 +116,9 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
x >>= 23;
uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20;
+ if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) {
+ return 0;
+ }
int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) {
int target2 = renderer->target2Bd << 4;
diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c
index f2b7d842c..7dae88af6 100644
--- a/src/gba/renderers/video-software.c
+++ b/src/gba/renderers/video-software.c
@@ -13,6 +13,7 @@
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
+static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
@@ -46,6 +47,7 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
renderer->d.reset = GBAVideoSoftwareRendererReset;
renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
+ renderer->d.writeVRAM = GBAVideoSoftwareRendererWriteVRAM;
renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
@@ -327,6 +329,11 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
return value;
}
+static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
+ UNUSED(renderer);
+ UNUSED(address);
+}
+
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
softwareRenderer->oamDirty = 1;
diff --git a/src/gba/serialize.c b/src/gba/serialize.c
index e7aa56fac..96e7c0daa 100644
--- a/src/gba/serialize.c
+++ b/src/gba/serialize.c
@@ -87,6 +87,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative");
error = true;
}
+ if (state->cpu.nextEvent < 0) {
+ GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: Next event is negative");
+ error = true;
+ }
if (state->video.eventDiff < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative");
error = true;
@@ -99,6 +103,10 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative");
error = true;
}
+ if (state->timers[0].nextEvent < 0 || state->timers[1].nextEvent < 0 || state->timers[2].nextEvent < 0 || state->timers[3].nextEvent < 0) {
+ GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: timer nextEvent is negative");
+ error = true;
+ }
if (state->audio.eventDiff < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative");
error = true;
diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c
index 19e5c2bd6..ecf175dbb 100644
--- a/src/gba/supervisor/config.c
+++ b/src/gba/supervisor/config.c
@@ -247,6 +247,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
opts->audioBuffers = audioBuffers;
}
+ _lookupUIntValue(config, "sampleRate", &opts->sampleRate);
int fakeBool;
if (_lookupIntValue(config, "useBios", &fakeBool)) {
@@ -305,6 +306,7 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval);
ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
+ ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);
diff --git a/src/gba/supervisor/config.h b/src/gba/supervisor/config.h
index 07c824cd2..35d822838 100644
--- a/src/gba/supervisor/config.h
+++ b/src/gba/supervisor/config.h
@@ -29,6 +29,7 @@ struct GBAOptions {
int rewindBufferInterval;
float fpsTarget;
size_t audioBuffers;
+ unsigned sampleRate;
int fullscreen;
int width;
diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c
index 08b5b7402..94eff4a61 100644
--- a/src/gba/supervisor/thread.c
+++ b/src/gba/supervisor/thread.c
@@ -419,6 +419,16 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
+ if (!threadContext->patch) {
+ threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ups", O_RDONLY);
+ }
+ if (!threadContext->patch) {
+ threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ips", O_RDONLY);
+ }
+ if (!threadContext->patch) {
+ threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".bps", O_RDONLY);
+ }
+
MutexInit(&threadContext->stateMutex);
ConditionInit(&threadContext->stateCond);
diff --git a/src/gba/video.c b/src/gba/video.c
index 66ebedd34..fef6e1b54 100644
--- a/src/gba/video.c
+++ b/src/gba/video.c
@@ -17,6 +17,7 @@ static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer);
static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer);
static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer);
static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
+static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
@@ -47,6 +48,7 @@ static struct GBAVideoRenderer dummyRenderer = {
.reset = GBAVideoDummyRendererReset,
.deinit = GBAVideoDummyRendererDeinit,
.writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister,
+ .writeVRAM = GBAVideoDummyRendererWriteVRAM,
.writePalette = GBAVideoDummyRendererWritePalette,
.writeOAM = GBAVideoDummyRendererWriteOAM,
.drawScanline = GBAVideoDummyRendererDrawScanline,
@@ -65,7 +67,7 @@ void GBAVideoReset(struct GBAVideo* video) {
video->lastHblank = 0;
video->nextHblank = VIDEO_HDRAW_LENGTH;
video->nextEvent = video->nextHblank;
- video->eventDiff = video->nextEvent;
+ video->eventDiff = 0;
video->nextHblankIRQ = 0;
video->nextVblankIRQ = 0;
@@ -222,6 +224,12 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer*
return value;
}
+static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
+ UNUSED(renderer);
+ UNUSED(address);
+ // Nothing to do
+}
+
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
UNUSED(renderer);
UNUSED(address);
diff --git a/src/gba/video.h b/src/gba/video.h
index a94235238..6713a8f0b 100644
--- a/src/gba/video.h
+++ b/src/gba/video.h
@@ -161,6 +161,7 @@ struct GBAVideoRenderer {
void (*deinit)(struct GBAVideoRenderer* renderer);
uint16_t (*writeVideoRegister)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
+ void (*writeVRAM)(struct GBAVideoRenderer* renderer, uint32_t address);
void (*writePalette)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
void (*writeOAM)(struct GBAVideoRenderer* renderer, uint32_t oam);
void (*drawScanline)(struct GBAVideoRenderer* renderer, int y);
diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c
index 1ae1a9fee..9a986dd0e 100644
--- a/src/platform/libretro/libretro.c
+++ b/src/platform/libretro/libretro.c
@@ -8,6 +8,7 @@
#include "util/common.h"
#include "gba/gba.h"
+#include "gba/interface.h"
#include "gba/renderers/video-software.h"
#include "gba/serialize.h"
#include "gba/supervisor/overrides.h"
@@ -18,6 +19,8 @@
#define SAMPLES 1024
#define RUMBLE_PWM 35
+#define SOLAR_SENSOR_LEVEL "mgba_solar_sensor_level"
+
static retro_environment_t environCallback;
static retro_video_refresh_t videoCallback;
static retro_audio_sample_batch_t audioCallback;
@@ -31,6 +34,8 @@ static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const
static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
static void _setRumble(struct GBARumble* rumble, int enable);
+static uint8_t _readLux(struct GBALuminanceSource* lux);
+static void _updateLux(struct GBALuminanceSource* lux);
static struct GBA gba;
static struct ARMCore cpu;
@@ -44,6 +49,8 @@ static struct GBAAVStream stream;
static int rumbleLevel;
static struct CircleBuffer rumbleHistory;
static struct GBARumble rumble;
+static struct GBALuminanceSource lux;
+static int luxLevel;
unsigned retro_api_version(void) {
return RETRO_API_VERSION;
@@ -51,6 +58,13 @@ unsigned retro_api_version(void) {
void retro_set_environment(retro_environment_t env) {
environCallback = env;
+
+ struct retro_variable vars[] = {
+ { SOLAR_SENSOR_LEVEL, "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" },
+ { 0, 0 }
+ };
+
+ environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars);
}
void retro_set_video_refresh(retro_video_refresh_t video) {
@@ -115,7 +129,9 @@ void retro_init(void) {
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
- { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" }
};
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
@@ -130,6 +146,11 @@ void retro_init(void) {
rumbleCallback = 0;
}
+ luxLevel = 0;
+ lux.readLuminance = _readLux;
+ lux.sample = _updateLux;
+ _updateLux(&lux);
+
struct retro_log_callback log;
if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
logCallback = log.log;
@@ -151,6 +172,7 @@ void retro_init(void) {
if (rumbleCallback) {
gba.rumble = &rumble;
}
+ gba.luminanceSource = &lux;
rom = 0;
const char* sysDir = 0;
@@ -201,6 +223,26 @@ void retro_run(void) {
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
+ static bool wasAdjustingLux = false;
+ if (wasAdjustingLux) {
+ wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
+ inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
+ } else {
+ if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
+ ++luxLevel;
+ if (luxLevel > 10) {
+ luxLevel = 10;
+ }
+ wasAdjustingLux = true;
+ } else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
+ --luxLevel;
+ if (luxLevel < 0) {
+ luxLevel = 0;
+ }
+ wasAdjustingLux = true;
+ }
+ }
+
int frameCount = gba.video.frameCounter;
while (gba.video.frameCounter == frameCount) {
ARMRunLoop(&cpu);
@@ -401,3 +443,40 @@ static void _setRumble(struct GBARumble* rumble, int enable) {
CircleBufferWrite8(&rumbleHistory, enable);
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
}
+
+static void _updateLux(struct GBALuminanceSource* lux) {
+ UNUSED(lux);
+ struct retro_variable var = {
+ .key = SOLAR_SENSOR_LEVEL,
+ .value = 0
+ };
+
+ bool updated = false;
+ if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) {
+ return;
+ }
+ if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
+ return;
+ }
+
+ char* end;
+ int newLuxLevel = strtol(var.value, &end, 10);
+ if (!*end) {
+ if (newLuxLevel > 10) {
+ luxLevel = 10;
+ } else if (newLuxLevel < 0) {
+ luxLevel = 0;
+ } else {
+ luxLevel = newLuxLevel;
+ }
+ }
+}
+
+static uint8_t _readLux(struct GBALuminanceSource* lux) {
+ UNUSED(lux);
+ int value = 0x16;
+ if (luxLevel > 0) {
+ value += GBA_LUX_LEVELS[luxLevel - 1];
+ }
+ return 0xFF - value;
+}
diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c
new file mode 100644
index 000000000..a667cf8e8
--- /dev/null
+++ b/src/platform/opengl/gles2.c
@@ -0,0 +1,136 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "gles2.h"
+
+#include "gba/video.h"
+
+static const char* const _vertexShader =
+ "attribute vec4 position;\n"
+ "varying vec2 texCoord;\n"
+
+ "void main() {\n"
+ " gl_Position = position;\n"
+ " texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n"
+ "}";
+
+static const char* const _fragmentShader =
+ "varying vec2 texCoord;\n"
+ "uniform sampler2D tex;\n"
+
+ "void main() {\n"
+ " vec4 color = texture2D(tex, texCoord);\n"
+ " color.a = 1.;\n"
+ " gl_FragColor = color;"
+ "}";
+
+static const GLfloat _vertices[] = {
+ -1.f, -1.f,
+ -1.f, 1.f,
+ 1.f, 1.f,
+ 1.f, -1.f,
+};
+
+static void GBAGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
+ UNUSED(handle);
+ struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
+ glGenTextures(1, &context->tex);
+ glBindTexture(GL_TEXTURE_2D, context->tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#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, 0);
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
+#endif
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+#endif
+
+ glShaderSource(context->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0);
+ glShaderSource(context->vertexShader, 1, (const GLchar**) &_vertexShader, 0);
+ glAttachShader(context->program, context->vertexShader);
+ glAttachShader(context->program, context->fragmentShader);
+ char log[1024];
+ glCompileShader(context->fragmentShader);
+ glCompileShader(context->vertexShader);
+ glGetShaderInfoLog(context->fragmentShader, 1024, 0, log);
+ glGetShaderInfoLog(context->vertexShader, 1024, 0, log);
+ glLinkProgram(context->program);
+ glGetProgramInfoLog(context->program, 1024, 0, log);
+ printf("%s\n", log);
+ context->texLocation = glGetUniformLocation(context->program, "tex");
+ context->positionLocation = glGetAttribLocation(context->program, "position");
+ glClearColor(0.f, 0.f, 0.f, 1.f);
+}
+
+static void GBAGLES2ContextDeinit(struct VideoBackend* v) {
+ struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
+ glDeleteTextures(1, &context->tex);
+}
+
+static void GBAGLES2ContextResized(struct VideoBackend* v, int w, int h) {
+ int drawW = w;
+ int drawH = h;
+ if (v->lockAspectRatio) {
+ if (w * 2 > h * 3) {
+ drawW = h * 3 / 2;
+ } else if (w * 2 < h * 3) {
+ drawH = w * 2 / 3;
+ }
+ }
+ glViewport(0, 0, 240, 160);
+ glClearColor(0.f, 0.f, 0.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
+}
+
+static void GBAGLES2ContextClear(struct VideoBackend* v) {
+ UNUSED(v);
+ glClearColor(0.f, 0.f, 0.f, 1.f);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+void GBAGLES2ContextDrawFrame(struct VideoBackend* v) {
+ struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
+ glUseProgram(context->program);
+ glUniform1i(context->texLocation, 0);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, context->tex);
+ glVertexAttribPointer(context->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
+ glEnableVertexAttribArray(context->positionLocation);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ glUseProgram(0);
+}
+
+void GBAGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) {
+ struct GBAGLES2Context* context = (struct GBAGLES2Context*) v;
+ 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);
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, 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);
+#endif
+}
+
+void GBAGLES2ContextCreate(struct GBAGLES2Context* context) {
+ context->d.init = GBAGLES2ContextInit;
+ context->d.deinit = GBAGLES2ContextDeinit;
+ context->d.resized = GBAGLES2ContextResized;
+ context->d.swap = 0;
+ context->d.clear = GBAGLES2ContextClear;
+ context->d.postFrame = GBAGLES2ContextPostFrame;
+ context->d.drawFrame = GBAGLES2ContextDrawFrame;
+ context->d.setMessage = 0;
+ context->d.clearMessage = 0;
+}
diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h
new file mode 100644
index 000000000..c1f97bc27
--- /dev/null
+++ b/src/platform/opengl/gles2.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef GLES2_H
+#define GLES2_H
+
+#include
+
+#include "platform/video-backend.h"
+
+struct GBAGLES2Context {
+ struct VideoBackend d;
+
+ GLuint tex;
+ GLuint fragmentShader;
+ GLuint vertexShader;
+ GLuint program;
+ GLuint bufferObject;
+ GLuint texLocation;
+ GLuint positionLocation;
+};
+
+void GBAGLES2ContextCreate(struct GBAGLES2Context*);
+
+#endif
diff --git a/src/platform/perf-main.c b/src/platform/perf-main.c
index 564765314..942dd57d9 100644
--- a/src/platform/perf-main.c
+++ b/src/platform/perf-main.c
@@ -186,7 +186,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet)
}
}
GBASyncWaitFrameEnd(&context->sync);
- if (*frames == duration) {
+ if (duration > 0 && *frames == duration) {
_GBAPerfShutdown(0);
}
if (_dispatchExiting) {
diff --git a/src/platform/posix/threading.h b/src/platform/posix/threading.h
index 7ca05ce15..71015526a 100644
--- a/src/platform/posix/threading.h
+++ b/src/platform/posix/threading.h
@@ -10,7 +10,7 @@
#include
#include
-#ifdef __FreeBSD__
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
#include
#endif
@@ -79,7 +79,7 @@ static inline int ThreadJoin(Thread thread) {
static inline int ThreadSetName(const char* name) {
#ifdef __APPLE__
return pthread_setname_np(name);
-#elif defined(__FreeBSD__)
+#elif defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
return 0;
#else
diff --git a/src/platform/qt/AboutScreen.cpp b/src/platform/qt/AboutScreen.cpp
new file mode 100644
index 000000000..ca53d334a
--- /dev/null
+++ b/src/platform/qt/AboutScreen.cpp
@@ -0,0 +1,36 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "AboutScreen.h"
+
+#include "util/version.h"
+
+#include
+
+using namespace QGBA;
+
+AboutScreen::AboutScreen(QWidget* parent)
+ : QDialog(parent)
+{
+ m_ui.setupUi(this);
+
+ QPixmap logo(":/res/mgba-1024.png");
+ logo = logo.scaled(m_ui.logo->minimumSize() * devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ logo.setDevicePixelRatio(devicePixelRatio());
+ m_ui.logo->setPixmap(logo);
+
+ m_ui.projectName->setText(QLatin1String(projectName));
+ m_ui.projectVersion->setText(QLatin1String(projectVersion));
+ QString gitInfo = m_ui.gitInfo->text();
+ gitInfo.replace("{gitBranch}", QLatin1String(gitBranch));
+ gitInfo.replace("{gitCommit}", QLatin1String(gitCommit));
+ m_ui.gitInfo->setText(gitInfo);
+ QString description = m_ui.description->text();
+ description.replace("{projectName}", QLatin1String(projectName));
+ m_ui.description->setText(description);
+ QString extraLinks = m_ui.extraLinks->text();
+ extraLinks.replace("{gitBranch}", QLatin1String(gitBranch));
+ m_ui.extraLinks->setText(extraLinks);
+}
diff --git a/src/platform/qt/AboutScreen.h b/src/platform/qt/AboutScreen.h
new file mode 100644
index 000000000..aebf1bdc1
--- /dev/null
+++ b/src/platform/qt/AboutScreen.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 2013-2014 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef QGBA_ABOUT_SCREEN
+#define QGBA_ABOUT_SCREEN
+
+#include
+
+#include "ui_AboutScreen.h"
+
+namespace QGBA {
+
+class AboutScreen : public QDialog {
+Q_OBJECT
+
+public:
+ AboutScreen(QWidget* parent = nullptr);
+
+private:
+ Ui::AboutScreen m_ui;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/AboutScreen.ui b/src/platform/qt/AboutScreen.ui
new file mode 100644
index 000000000..6d2662c45
--- /dev/null
+++ b/src/platform/qt/AboutScreen.ui
@@ -0,0 +1,177 @@
+
+
+ AboutScreen
+
+
+
+ 0
+ 0
+ 565
+ 268
+
+
+
+ About
+
+
+
+ QLayout::SetFixedSize
+
+ -
+
+
+
+ 36
+ 75
+ true
+
+
+
+ {projectName}
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+
+ -
+
+
+
+ 10
+
+
+
+ © 2013 – 2015 Jeffrey Pfau — Game Boy Advance is a registered trademark of Nintendo Co., Ltd.
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ {projectVersion}
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
+ -
+
+
+ 8
+
+
+ 16
+
+
+ 12
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 256
+ 192
+
+
+
+ {logo}
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
+ {projectName} is an open-source Game Boy Advance emulator
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+ -
+
+
+ <a href="http://mgba.io/">Website</a> • <a href="https://forums.mgba.io/">Forums / Support</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Source</a> • <a href="https://github.com/mgba-emu/mgba/blob/{gitBranch}/LICENSE">License</a>
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+ -
+
+
+
+ 10
+
+
+
+ Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt>
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
+
+
+
+
+
diff --git a/src/platform/qt/AudioProcessor.cpp b/src/platform/qt/AudioProcessor.cpp
index 6c58e2c39..df4e269de 100644
--- a/src/platform/qt/AudioProcessor.cpp
+++ b/src/platform/qt/AudioProcessor.cpp
@@ -38,10 +38,10 @@ AudioProcessor* AudioProcessor::create() {
#endif
default:
-#ifdef BUILD_QT_MULTIMEDIA
- return new AudioProcessorQt();
-#else
+#ifdef BUILD_SDL
return new AudioProcessorSDL();
+#else
+ return new AudioProcessorQt();
#endif
}
}
diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h
index abbca9395..74d76a345 100644
--- a/src/platform/qt/AudioProcessor.h
+++ b/src/platform/qt/AudioProcessor.h
@@ -31,6 +31,7 @@ public:
virtual void setInput(GBAThread* input);
int getBufferSamples() const { return m_samples; }
+ virtual unsigned sampleRate() const = 0;
public slots:
virtual void start() = 0;
@@ -39,7 +40,7 @@ public slots:
virtual void setBufferSamples(int samples) = 0;
virtual void inputParametersChanged() = 0;
- virtual unsigned sampleRate() const = 0;
+ virtual void requestSampleRate(unsigned) = 0;
protected:
GBAThread* input() { return m_context; }
diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp
index 281ca6bba..c28d934b9 100644
--- a/src/platform/qt/AudioProcessorQt.cpp
+++ b/src/platform/qt/AudioProcessorQt.cpp
@@ -20,6 +20,7 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent)
: AudioProcessor(parent)
, m_audioOutput(nullptr)
, m_device(nullptr)
+ , m_sampleRate(44100)
{
}
@@ -45,7 +46,7 @@ void AudioProcessorQt::start() {
if (!m_audioOutput) {
QAudioFormat format;
- format.setSampleRate(44100);
+ format.setSampleRate(m_sampleRate);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
@@ -84,6 +85,15 @@ void AudioProcessorQt::inputParametersChanged() {
}
}
+void AudioProcessorQt::requestSampleRate(unsigned rate) {
+ m_sampleRate = rate;
+ if (m_device) {
+ QAudioFormat format(m_audioOutput->format());
+ format.setSampleRate(rate);
+ m_device->setFormat(format);
+ }
+}
+
unsigned AudioProcessorQt::sampleRate() const {
if (!m_audioOutput) {
return 0;
diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h
index ee9dde24d..52d7b606c 100644
--- a/src/platform/qt/AudioProcessorQt.h
+++ b/src/platform/qt/AudioProcessorQt.h
@@ -20,6 +20,7 @@ public:
AudioProcessorQt(QObject* parent = nullptr);
virtual void setInput(GBAThread* input);
+ virtual unsigned sampleRate() const override;
public slots:
virtual void start();
@@ -28,11 +29,12 @@ public slots:
virtual void setBufferSamples(int samples);
virtual void inputParametersChanged();
- virtual unsigned sampleRate() const override;
+ virtual void requestSampleRate(unsigned) override;
private:
QAudioOutput* m_audioOutput;
AudioDevice* m_device;
+ unsigned m_sampleRate;
};
}
diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp
index 0caa9765c..2db807f3f 100644
--- a/src/platform/qt/AudioProcessorSDL.cpp
+++ b/src/platform/qt/AudioProcessorSDL.cpp
@@ -15,7 +15,7 @@ using namespace QGBA;
AudioProcessorSDL::AudioProcessorSDL(QObject* parent)
: AudioProcessor(parent)
- , m_audio()
+ , m_audio{ 2048, 44100 }
{
}
@@ -55,6 +55,18 @@ void AudioProcessorSDL::setBufferSamples(int samples) {
void AudioProcessorSDL::inputParametersChanged() {
}
-unsigned AudioProcessorSDL::sampleRate() const {
- return m_audio.obtainedSpec.freq;
+void AudioProcessorSDL::requestSampleRate(unsigned rate) {
+ m_audio.sampleRate = rate;
+ if (m_audio.thread) {
+ GBASDLDeinitAudio(&m_audio);
+ GBASDLInitAudio(&m_audio, input());
+ }
+}
+
+unsigned AudioProcessorSDL::sampleRate() const {
+ if (m_audio.thread) {
+ return m_audio.obtainedSpec.freq;
+ } else {
+ return 0;
+ }
}
diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h
index 002dcd839..c98d00473 100644
--- a/src/platform/qt/AudioProcessorSDL.h
+++ b/src/platform/qt/AudioProcessorSDL.h
@@ -22,6 +22,8 @@ public:
AudioProcessorSDL(QObject* parent = nullptr);
~AudioProcessorSDL();
+ virtual unsigned sampleRate() const override;
+
public slots:
virtual void start();
virtual void pause();
@@ -29,7 +31,7 @@ public slots:
virtual void setBufferSamples(int samples);
virtual void inputParametersChanged();
- virtual unsigned sampleRate() const override;
+ virtual void requestSampleRate(unsigned) override;
private:
GBASDLAudio m_audio;
diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt
index e7db28908..3c1de1151 100644
--- a/src/platform/qt/CMakeLists.txt
+++ b/src/platform/qt/CMakeLists.txt
@@ -28,17 +28,31 @@ endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
-find_package(Qt5Multimedia)
+if(NOT WIN32 OR NOT BUILD_SDL)
+ find_package(Qt5Multimedia)
+endif()
find_package(Qt5OpenGL)
find_package(Qt5Widgets)
-if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
+if(NOT BUILD_GL AND NOT BUILD_GLES2)
+ message(WARNING "OpenGL is required to build the Qt port")
+ set(BUILD_QT OFF PARENT_SCOPE)
+ return()
+endif()
+
+if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND)
message(WARNING "Cannot find Qt modules")
set(BUILD_QT OFF PARENT_SCOPE)
return()
endif()
+if(BUILD_GL)
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
+endif()
+
+if(BUILD_GLES2)
+list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c)
+endif()
get_target_property(QT_TYPE Qt5::Core TYPE)
if(QT_TYPE STREQUAL STATIC_LIBRARY)
@@ -47,6 +61,7 @@ if(QT_TYPE STREQUAL STATIC_LIBRARY)
endif()
set(SOURCE_FILES
+ AboutScreen.cpp
AudioProcessor.cpp
CheatsModel.cpp
CheatsView.cpp
@@ -83,6 +98,7 @@ set(SOURCE_FILES
VideoView.cpp)
qt5_wrap_ui(UI_FILES
+ AboutScreen.ui
CheatsView.ui
GIFView.ui
LoadSaveState.ui
@@ -142,7 +158,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
-target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
+target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
install(TARGETS ${BINARY_NAME}-qt
@@ -154,6 +170,9 @@ if(UNIX AND NOT APPLE)
install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/applications/\")")
endif()
endif()
+if(UNIX)
+ install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba-qt.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-qt)
+endif()
if(APPLE OR WIN32)
set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
endif()
diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp
index c39ad398b..357a0c788 100644
--- a/src/platform/qt/CheatsView.cpp
+++ b/src/platform/qt/CheatsView.cpp
@@ -99,19 +99,29 @@ void CheatsView::removeSet() {
}
void CheatsView::enterCheat(std::function callback) {
- GBACheatSet* set;
+ GBACheatSet* set = nullptr;
QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes();
- if (selection.count() != 1) {
- return;
+ QModelIndex index;
+ if (selection.count() == 0) {
+ set = new GBACheatSet;
+ GBACheatSetInit(set, nullptr);
+ } else if (selection.count() == 1) {
+ index = selection[0];
+ set = m_model.itemAt(index);
}
- set = m_model.itemAt(selection[0]);
+
if (!set) {
return;
}
m_controller->threadInterrupt();
+ if (selection.count() == 0) {
+ m_model.addSet(set);
+ index = m_model.index(m_model.rowCount() - 1, 0, QModelIndex());
+ m_ui.cheatList->selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
+ }
QStringList cheats = m_ui.codeEntry->toPlainText().split('\n', QString::SkipEmptyParts);
for (const QString& string : cheats) {
- m_model.beginAppendRow(selection[0]);
+ m_model.beginAppendRow(index);
callback(set, string.toUtf8().constData());
m_model.endAppendRow();
}
diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp
index a5859fc83..021908680 100644
--- a/src/platform/qt/ConfigController.cpp
+++ b/src/platform/qt/ConfigController.cpp
@@ -107,7 +107,7 @@ ConfigController::ConfigController(QObject* parent)
m_opts.audioSync = GameController::AUDIO_SYNC;
m_opts.videoSync = GameController::VIDEO_SYNC;
m_opts.fpsTarget = 60;
- m_opts.audioBuffers = 2048;
+ m_opts.audioBuffers = 1536;
m_opts.volume = GBA_AUDIO_VOLUME_MAX;
m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS;
m_opts.rewindEnable = false;
@@ -115,8 +115,8 @@ ConfigController::ConfigController(QObject* parent)
m_opts.rewindBufferCapacity = 0;
m_opts.useBios = true;
m_opts.suspendScreensaver = true;
- GBAConfigLoadDefaults(&m_config, &m_opts);
GBAConfigLoad(&m_config);
+ GBAConfigLoadDefaults(&m_config, &m_opts);
GBAConfigMap(&m_config, &m_opts);
}
@@ -126,7 +126,11 @@ ConfigController::~ConfigController() {
}
bool ConfigController::parseArguments(GBAArguments* args, int argc, char* argv[]) {
- return ::parseArguments(args, &m_config, argc, argv, 0);
+ if (::parseArguments(args, &m_config, argc, argv, 0)) {
+ GBAConfigMap(&m_config, &m_opts);
+ return true;
+ }
+ return false;
}
ConfigOption* ConfigController::addOption(const char* key) {
diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp
index 6a11a5c64..3766baf7b 100644
--- a/src/platform/qt/Display.cpp
+++ b/src/platform/qt/Display.cpp
@@ -22,7 +22,7 @@ Display::Driver Display::s_driver = Display::Driver::QT;
Display* Display::create(QWidget* parent) {
#ifdef BUILD_GL
- QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
+ QGLFormat format(QGLFormat(QGL::Rgba | QGL::SingleBuffer));
format.setSwapInterval(1);
#endif
@@ -72,6 +72,9 @@ void Display::filter(bool filter) {
void Display::showMessage(const QString& message) {
m_messagePainter.showMessage(message);
+ if (!isDrawing()) {
+ forceDraw();
+ }
}
void Display::mouseMoveEvent(QMouseEvent*) {
diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h
index cd3f1fd2a..3e6d738f2 100644
--- a/src/platform/qt/Display.h
+++ b/src/platform/qt/Display.h
@@ -33,6 +33,8 @@ public:
bool isAspectRatioLocked() const { return m_lockAspectRatio; }
bool isFiltered() const { return m_filter; }
+ virtual bool isDrawing() const = 0;
+
signals:
void showCursor();
void hideCursor();
diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp
index ca41f0f46..d0a5f4db3 100644
--- a/src/platform/qt/DisplayGL.cpp
+++ b/src/platform/qt/DisplayGL.cpp
@@ -16,6 +16,7 @@ using namespace QGBA;
DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
: Display(parent)
+ , m_isDrawing(false)
, m_gl(new EmptyGLWidget(format, this))
, m_painter(new PainterGL(m_gl))
, m_drawThread(nullptr)
@@ -33,6 +34,7 @@ void DisplayGL::startDrawing(GBAThread* thread) {
if (m_drawThread) {
return;
}
+ m_isDrawing = true;
m_painter->setContext(thread);
m_painter->setMessagePainter(messagePainter());
m_context = thread;
@@ -55,6 +57,7 @@ void DisplayGL::startDrawing(GBAThread* thread) {
void DisplayGL::stopDrawing() {
if (m_drawThread) {
+ m_isDrawing = false;
if (GBAThreadIsActive(m_context)) {
GBAThreadInterrupt(m_context);
}
@@ -69,6 +72,7 @@ void DisplayGL::stopDrawing() {
void DisplayGL::pauseDrawing() {
if (m_drawThread) {
+ m_isDrawing = false;
if (GBAThreadIsActive(m_context)) {
GBAThreadInterrupt(m_context);
}
@@ -81,6 +85,7 @@ void DisplayGL::pauseDrawing() {
void DisplayGL::unpauseDrawing() {
if (m_drawThread) {
+ m_isDrawing = true;
if (GBAThreadIsActive(m_context)) {
GBAThreadInterrupt(m_context);
}
@@ -113,7 +118,8 @@ void DisplayGL::filter(bool filter) {
void DisplayGL::framePosted(const uint32_t* buffer) {
if (m_drawThread && buffer) {
- QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer));
+ m_painter->enqueue(buffer);
+ QMetaObject::invokeMethod(m_painter, "draw");
}
}
@@ -135,7 +141,11 @@ PainterGL::PainterGL(QGLWidget* parent)
, m_context(nullptr)
, m_messagePainter(nullptr)
{
+#ifdef BUILD_GL
GBAGLContextCreate(&m_backend);
+#elif defined(BUILD_GLES2)
+ GBAGLES2ContextCreate(&m_backend);
+#endif
m_backend.d.swap = [](VideoBackend* v) {
PainterGL* painter = static_cast(v->user);
painter->m_gl->swapBuffers();
@@ -143,6 +153,19 @@ PainterGL::PainterGL(QGLWidget* parent)
m_backend.d.user = this;
m_backend.d.filter = false;
m_backend.d.lockAspectRatio = false;
+
+ for (int i = 0; i < 2; ++i) {
+ m_free.append(new uint32_t[256 * 256]);
+ }
+}
+
+PainterGL::~PainterGL() {
+ while (!m_queue.isEmpty()) {
+ delete[] m_queue.dequeue();
+ }
+ for (auto item : m_free) {
+ delete[] item;
+ }
}
void PainterGL::setContext(GBAThread* context) {
@@ -153,15 +176,6 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
m_messagePainter = messagePainter;
}
-void PainterGL::setBacking(const uint32_t* backing) {
- m_gl->makeCurrent();
- m_backend.d.postFrame(&m_backend.d, backing);
- if (m_active) {
- draw();
- }
- m_gl->doneCurrent();
-}
-
void PainterGL::resize(const QSize& size) {
m_size = size;
if (m_active) {
@@ -191,7 +205,11 @@ void PainterGL::start() {
}
void PainterGL::draw() {
- if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) {
+ if (m_queue.isEmpty()) {
+ return;
+ }
+ if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip) || !m_queue.isEmpty()) {
+ dequeue();
m_painter.begin(m_gl->context()->device());
performDraw();
m_painter.end();
@@ -200,6 +218,9 @@ void PainterGL::draw() {
} else {
GBASyncWaitFrameEnd(&m_context->sync);
}
+ if (!m_queue.isEmpty()) {
+ QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
+ }
}
void PainterGL::forceDraw() {
@@ -212,6 +233,7 @@ void PainterGL::forceDraw() {
void PainterGL::stop() {
m_active = false;
m_gl->makeCurrent();
+ dequeueAll();
m_backend.d.clear(&m_backend.d);
m_backend.d.swap(&m_backend.d);
m_backend.d.deinit(&m_backend.d);
@@ -222,9 +244,6 @@ void PainterGL::stop() {
void PainterGL::pause() {
m_active = false;
- // Make sure both buffers are filled
- forceDraw();
- forceDraw();
}
void PainterGL::unpause() {
@@ -241,3 +260,41 @@ void PainterGL::performDraw() {
m_messagePainter->paint(&m_painter);
}
}
+
+void PainterGL::enqueue(const uint32_t* backing) {
+ m_mutex.lock();
+ uint32_t* buffer;
+ if (m_free.isEmpty()) {
+ buffer = m_queue.dequeue();
+ } else {
+ buffer = m_free.takeLast();
+ }
+ memcpy(buffer, backing, 256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
+ m_queue.enqueue(buffer);
+ m_mutex.unlock();
+}
+
+void PainterGL::dequeue() {
+ m_mutex.lock();
+ if (m_queue.isEmpty()) {
+ m_mutex.unlock();
+ return;
+ }
+ uint32_t* buffer = m_queue.dequeue();
+ m_backend.d.postFrame(&m_backend.d, buffer);
+ m_free.append(buffer);
+ m_mutex.unlock();
+}
+
+void PainterGL::dequeueAll() {
+ uint32_t* buffer = 0;
+ m_mutex.lock();
+ while (!m_queue.isEmpty()) {
+ buffer = m_queue.dequeue();
+ m_free.append(buffer);
+ }
+ if (buffer) {
+ m_backend.d.postFrame(&m_backend.d, buffer);
+ }
+ m_mutex.unlock();
+}
diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h
index fc4cb500f..bad1b252a 100644
--- a/src/platform/qt/DisplayGL.h
+++ b/src/platform/qt/DisplayGL.h
@@ -9,12 +9,18 @@
#include "Display.h"
#include
+#include
#include
+#include
#include
#include
extern "C" {
+#ifdef BUILD_GL
#include "platform/opengl/gl.h"
+#elif defined(BUILD_GLES2)
+#include "platform/opengl/gles2.h"
+#endif
}
struct GBAThread;
@@ -39,6 +45,8 @@ public:
DisplayGL(const QGLFormat& format, QWidget* parent = nullptr);
~DisplayGL();
+ bool isDrawing() const override { return m_isDrawing; }
+
public slots:
void startDrawing(GBAThread* context) override;
void stopDrawing() override;
@@ -56,6 +64,7 @@ protected:
private:
void resizePainter();
+ bool m_isDrawing;
QGLWidget* m_gl;
PainterGL* m_painter;
QThread* m_drawThread;
@@ -67,12 +76,13 @@ Q_OBJECT
public:
PainterGL(QGLWidget* parent);
+ ~PainterGL();
void setContext(GBAThread*);
void setMessagePainter(MessagePainter*);
+ void enqueue(const uint32_t* backing);
public slots:
- void setBacking(const uint32_t*);
void forceDraw();
void draw();
void start();
@@ -85,12 +95,21 @@ public slots:
private:
void performDraw();
+ void dequeue();
+ void dequeueAll();
+ QList m_free;
+ QQueue m_queue;
QPainter m_painter;
+ QMutex m_mutex;
QGLWidget* m_gl;
bool m_active;
GBAThread* m_context;
+#ifdef BUILD_GL
GBAGLContext m_backend;
+#elif defined(BUILD_GLES2)
+ GBAGLES2Context m_backend;
+#endif
QSize m_size;
MessagePainter* m_messagePainter;
};
diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp
index 182ecd2bc..71e684055 100644
--- a/src/platform/qt/DisplayQt.cpp
+++ b/src/platform/qt/DisplayQt.cpp
@@ -15,11 +15,13 @@ using namespace QGBA;
DisplayQt::DisplayQt(QWidget* parent)
: Display(parent)
+ , m_isDrawing(false)
, m_backing(nullptr)
{
}
void DisplayQt::startDrawing(GBAThread*) {
+ m_isDrawing = true;
}
void DisplayQt::lockAspectRatio(bool lock) {
diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h
index f7fefb0ee..1d5acc5b4 100644
--- a/src/platform/qt/DisplayQt.h
+++ b/src/platform/qt/DisplayQt.h
@@ -21,11 +21,13 @@ Q_OBJECT
public:
DisplayQt(QWidget* parent = nullptr);
+ bool isDrawing() const override { return m_isDrawing; }
+
public slots:
void startDrawing(GBAThread* context) override;
- void stopDrawing() override {}
- void pauseDrawing() override {}
- void unpauseDrawing() override {}
+ void stopDrawing() override { m_isDrawing = false; }
+ void pauseDrawing() override { m_isDrawing = false; }
+ void unpauseDrawing() override { m_isDrawing = true; }
void forceDraw() override { update(); }
void lockAspectRatio(bool lock) override;
void filter(bool filter) override;
@@ -35,6 +37,7 @@ protected:
virtual void paintEvent(QPaintEvent*) override;
private:
+ bool m_isDrawing;
QImage m_backing;
};
diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp
index c40eb42ba..dc0399273 100644
--- a/src/platform/qt/GBAApp.cpp
+++ b/src/platform/qt/GBAApp.cpp
@@ -15,6 +15,7 @@
#include
extern "C" {
+#include "gba/supervisor/thread.h"
#include "platform/commandline.h"
#include "util/socket.h"
}
@@ -33,10 +34,13 @@ GBAApp::GBAApp(int& argc, char* argv[])
SDL_Init(SDL_INIT_NOPARACHUTE);
#endif
+#ifndef Q_OS_MAC
setWindowIcon(QIcon(":/res/mgba-1024.png"));
+#endif
SocketSubsystemInit();
qRegisterMetaType("const uint32_t*");
+ qRegisterMetaType("GBAThread*");
QApplication::setApplicationName(projectName);
QApplication::setApplicationVersion(projectVersion);
@@ -45,31 +49,30 @@ GBAApp::GBAApp(int& argc, char* argv[])
Display::setDriver(static_cast(m_configController.getQtOption("displayDriver").toInt()));
}
+ GBAArguments args;
+ bool loaded = m_configController.parseArguments(&args, argc, argv);
+ if (loaded && args.showHelp) {
+ usage(argv[0], 0);
+ ::exit(0);
+ return;
+ }
+
+ AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt()));
Window* w = new Window(&m_configController);
connect(w, &Window::destroyed, [this]() {
m_windows[0] = nullptr;
});
m_windows[0] = w;
-#ifndef Q_OS_MAC
- w->show();
-#endif
-
- GBAArguments args;
- if (m_configController.parseArguments(&args, argc, argv)) {
+ if (loaded) {
w->argumentsPassed(&args);
} else {
w->loadConfig();
}
freeArguments(&args);
-
- AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt()));
- w->controller()->reloadAudioDriver();
+ w->show();
w->controller()->setMultiplayerController(&m_multiplayer);
-#ifdef Q_OS_MAC
- w->show();
-#endif
}
bool GBAApp::event(QEvent* event) {
@@ -91,14 +94,9 @@ Window* GBAApp::newWindow() {
});
m_windows[windowId] = w;
w->setAttribute(Qt::WA_DeleteOnClose);
-#ifndef Q_OS_MAC
- w->show();
-#endif
w->loadConfig();
- w->controller()->setMultiplayerController(&m_multiplayer);
-#ifdef Q_OS_MAC
w->show();
-#endif
+ w->controller()->setMultiplayerController(&m_multiplayer);
return w;
}
diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp
index c86ed2bab..73e0f6ac6 100644
--- a/src/platform/qt/GBAKeyEditor.cpp
+++ b/src/platform/qt/GBAKeyEditor.cpp
@@ -18,9 +18,9 @@
using namespace QGBA;
const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247;
-const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431;
-const qreal GBAKeyEditor::DPAD_WIDTH = 0.1;
-const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
+const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432;
+const qreal GBAKeyEditor::DPAD_WIDTH = 0.12;
+const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12;
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent)
: QWidget(parent)
@@ -148,8 +148,8 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y);
setLocation(m_keySelect, 0.415, 0.93);
setLocation(m_keyStart, 0.585, 0.93);
- setLocation(m_keyA, 0.826, 0.451);
- setLocation(m_keyB, 0.667, 0.490);
+ setLocation(m_keyA, 0.826, 0.475);
+ setLocation(m_keyB, 0.667, 0.514);
setLocation(m_keyL, 0.1, 0.1);
setLocation(m_keyR, 0.9, 0.1);
diff --git a/src/platform/qt/GDBWindow.cpp b/src/platform/qt/GDBWindow.cpp
index 26191b6e0..10e4b55d8 100644
--- a/src/platform/qt/GDBWindow.cpp
+++ b/src/platform/qt/GDBWindow.cpp
@@ -18,7 +18,7 @@
using namespace QGBA;
GDBWindow::GDBWindow(GDBController* controller, QWidget* parent)
- : QWidget(parent)
+ : QDialog(parent)
, m_gdbController(controller)
{
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
diff --git a/src/platform/qt/GDBWindow.h b/src/platform/qt/GDBWindow.h
index 683e7c632..9759df05b 100644
--- a/src/platform/qt/GDBWindow.h
+++ b/src/platform/qt/GDBWindow.h
@@ -6,7 +6,7 @@
#ifndef QGBA_GDB_WINDOW
#define QGBA_GDB_WINDOW
-#include
+#include
class QLineEdit;
class QPushButton;
@@ -15,7 +15,7 @@ namespace QGBA {
class GDBController;
-class GDBWindow : public QWidget {
+class GDBWindow : public QDialog {
Q_OBJECT
public:
diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp
index 32c5aa41d..57216ddb3 100644
--- a/src/platform/qt/GameController.cpp
+++ b/src/platform/qt/GameController.cpp
@@ -29,11 +29,10 @@ extern "C" {
using namespace QGBA;
using namespace std;
-const int GameController::LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 };
-
GameController::GameController(QObject* parent)
: QObject(parent)
- , m_drawContext(new uint32_t[256 * 256])
+ , m_drawContext(new uint32_t[256 * VIDEO_HORIZONTAL_PIXELS])
+ , m_frontBuffer(new uint32_t[256 * 256])
, m_threadContext()
, m_activeKeys(0)
, m_inactiveKeys(0)
@@ -49,6 +48,8 @@ GameController::GameController(QObject* parent)
, m_turboForced(false)
, m_turboSpeed(-1)
, m_wasPaused(false)
+ , m_audioChannels{ true, true, true, true, true, true }
+ , m_videoLayers{ true, true, true, true, true }
, m_inputController(nullptr)
, m_multiplayer(nullptr)
, m_stateSlot(1)
@@ -86,12 +87,25 @@ GameController::GameController(QObject* parent)
m_threadContext.startCallback = [](GBAThread* context) {
GameController* controller = static_cast(context->userData);
- controller->m_audioProcessor->setInput(context);
+ if (controller->m_audioProcessor) {
+ controller->m_audioProcessor->setInput(context);
+ }
context->gba->luminanceSource = &controller->m_lux;
GBARTCGenericSourceInit(&controller->m_rtc, context->gba);
context->gba->rtcSource = &controller->m_rtc.d;
context->gba->rumble = controller->m_inputController->rumble();
context->gba->rotationSource = controller->m_inputController->rotationSource();
+ context->gba->audio.forceDisableCh[0] = !controller->m_audioChannels[0];
+ context->gba->audio.forceDisableCh[1] = !controller->m_audioChannels[1];
+ context->gba->audio.forceDisableCh[2] = !controller->m_audioChannels[2];
+ context->gba->audio.forceDisableCh[3] = !controller->m_audioChannels[3];
+ context->gba->audio.forceDisableChA = !controller->m_audioChannels[4];
+ context->gba->audio.forceDisableChB = !controller->m_audioChannels[5];
+ context->gba->video.renderer->disableBG[0] = !controller->m_videoLayers[0];
+ context->gba->video.renderer->disableBG[1] = !controller->m_videoLayers[1];
+ context->gba->video.renderer->disableBG[2] = !controller->m_videoLayers[2];
+ context->gba->video.renderer->disableBG[3] = !controller->m_videoLayers[3];
+ context->gba->video.renderer->disableOBJ = !controller->m_videoLayers[4];
controller->m_fpsTarget = context->fpsTarget;
if (GBALoadState(context, context->stateDir, 0)) {
@@ -100,24 +114,25 @@ GameController::GameController(QObject* parent)
vf->truncate(vf, 0);
}
}
- controller->gameStarted(context);
+ QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(GBAThread*, context));
};
m_threadContext.cleanCallback = [](GBAThread* context) {
GameController* controller = static_cast(context->userData);
- controller->gameStopped(context);
+ QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(GBAThread*, context));
};
m_threadContext.frameCallback = [](GBAThread* context) {
GameController* controller = static_cast(context->userData);
+ if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) {
+ memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
+ QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer));
+ } else {
+ QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, nullptr));
+ }
if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
GBAThreadPauseFromThread(context);
- controller->gamePaused(&controller->m_threadContext);
- }
- if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) {
- controller->frameAvailable(controller->m_drawContext);
- } else {
- controller->frameAvailable(nullptr);
+ QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(GBAThread*, context));
}
};
@@ -146,7 +161,7 @@ GameController::GameController(QObject* parent)
va_copy(argc, args);
int immediate = va_arg(argc, int);
va_end(argc);
- controller->unimplementedBiosCall(immediate);
+ QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
} else if (level == GBA_LOG_STATUS) {
// Slot 0 is reserved for suspend points
if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
@@ -174,9 +189,9 @@ GameController::GameController(QObject* parent)
}
QString message(QString().vsprintf(format, args));
if (level == GBA_LOG_STATUS) {
- controller->statusPosted(message);
+ QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
}
- controller->postLog(level, message);
+ QMetaObject::invokeMethod(controller, "postLog", Q_ARG(int, level), Q_ARG(const QString&, message));
};
connect(&m_rewindTimer, &QTimer::timeout, [this]() {
@@ -205,6 +220,7 @@ GameController::~GameController() {
GBACheatDeviceDestroy(&m_cheatDevice);
delete m_renderer;
delete[] m_drawContext;
+ delete[] m_frontBuffer;
delete m_backupLoadState;
}
@@ -328,6 +344,7 @@ void GameController::openGame(bool biosOnly) {
}
m_inputController->recalibrateAxes();
+ memset(m_drawContext, 0xF8, 1024 * VIDEO_HORIZONTAL_PIXELS);
if (!GBAThreadStart(&m_threadContext)) {
m_gameOpen = false;
@@ -453,8 +470,7 @@ void GameController::setPaused(bool paused) {
return;
}
if (paused) {
- GBAThreadPause(&m_threadContext);
- emit gamePaused(&m_threadContext);
+ m_pauseAfterFrame.testAndSetRelaxed(false, true);
} else {
GBAThreadUnpause(&m_threadContext);
emit gameUnpaused(&m_threadContext);
@@ -519,9 +535,9 @@ void GameController::startRewinding() {
return;
}
m_wasPaused = isPaused();
- bool signalsBlocked = blockSignals(true);
- setPaused(true);
- blockSignals(signalsBlocked);
+ if (!GBAThreadIsPaused(&m_threadContext)) {
+ GBAThreadPause(&m_threadContext);
+ }
m_rewindTimer.start();
}
@@ -574,10 +590,64 @@ void GameController::clearKeys() {
}
void GameController::setAudioBufferSamples(int samples) {
- threadInterrupt();
- redoSamples(samples);
- threadContinue();
- QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
+ if (m_audioProcessor) {
+ threadInterrupt();
+ redoSamples(samples);
+ threadContinue();
+ QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
+ }
+}
+
+void GameController::setAudioSampleRate(unsigned rate) {
+ if (m_audioProcessor) {
+ threadInterrupt();
+ redoSamples(m_audioProcessor->getBufferSamples());
+ threadContinue();
+ QMetaObject::invokeMethod(m_audioProcessor, "requestSampleRate", Q_ARG(unsigned, rate));
+ }
+}
+
+void GameController::setAudioChannelEnabled(int channel, bool enable) {
+ if (channel > 5 || channel < 0) {
+ return;
+ }
+ m_audioChannels[channel] = enable;
+ if (m_gameOpen) {
+ switch (channel) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ m_threadContext.gba->audio.forceDisableCh[channel] = !enable;
+ break;
+ case 4:
+ m_threadContext.gba->audio.forceDisableChA = !enable;
+ break;
+ case 5:
+ m_threadContext.gba->audio.forceDisableChB = !enable;
+ break;
+ }
+ }
+}
+
+void GameController::setVideoLayerEnabled(int layer, bool enable) {
+ if (layer > 4 || layer < 0) {
+ return;
+ }
+ m_videoLayers[layer] = enable;
+ if (m_gameOpen) {
+ switch (layer) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ m_threadContext.gba->video.renderer->disableBG[layer] = !enable;
+ break;
+ case 4:
+ m_threadContext.gba->video.renderer->disableOBJ = !enable;
+ break;
+ }
+ }
}
void GameController::setFPSTarget(float fps) {
@@ -587,7 +657,9 @@ void GameController::setFPSTarget(float fps) {
if (m_turbo && m_turboSpeed > 0) {
m_threadContext.fpsTarget *= m_turboSpeed;
}
- redoSamples(m_audioProcessor->getBufferSamples());
+ if (m_audioProcessor) {
+ redoSamples(m_audioProcessor->getBufferSamples());
+ }
threadContinue();
}
@@ -598,9 +670,14 @@ void GameController::setSkipBIOS(bool set) {
}
void GameController::setUseBIOS(bool use) {
- threadInterrupt();
+ if (use == m_useBios) {
+ return;
+ }
m_useBios = use;
- threadContinue();
+ if (m_gameOpen) {
+ closeGame();
+ openGame();
+ }
}
void GameController::loadState(int slot) {
@@ -744,7 +821,9 @@ void GameController::enableTurbo() {
m_threadContext.sync.audioWait = true;
m_threadContext.sync.videoFrameWait = false;
}
- redoSamples(m_audioProcessor->getBufferSamples());
+ if (m_audioProcessor) {
+ redoSamples(m_audioProcessor->getBufferSamples());
+ }
threadContinue();
}
@@ -773,11 +852,21 @@ void GameController::screenshot() {
#endif
void GameController::reloadAudioDriver() {
- QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection);
- int samples = m_audioProcessor->getBufferSamples();
- delete m_audioProcessor;
+ int samples = 0;
+ unsigned sampleRate = 0;
+ if (m_audioProcessor) {
+ QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection);
+ samples = m_audioProcessor->getBufferSamples();
+ sampleRate = m_audioProcessor->sampleRate();
+ delete m_audioProcessor;
+ }
m_audioProcessor = AudioProcessor::create();
- m_audioProcessor->setBufferSamples(samples);
+ if (samples) {
+ m_audioProcessor->setBufferSamples(samples);
+ }
+ if (sampleRate) {
+ m_audioProcessor->requestSampleRate(sampleRate);
+ }
m_audioProcessor->moveToThread(m_audioThread);
connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));
@@ -794,7 +883,7 @@ void GameController::setLuminanceValue(uint8_t value) {
value = std::max(value - 0x16, 0);
m_luxLevel = 10;
for (int i = 0; i < 10; ++i) {
- if (value < LUX_LEVELS[i]) {
+ if (value < GBA_LUX_LEVELS[i]) {
m_luxLevel = i;
break;
}
@@ -806,7 +895,7 @@ void GameController::setLuminanceLevel(int level) {
int value = 0x16;
level = std::max(0, std::min(10, level));
if (level > 0) {
- value += LUX_LEVELS[level - 1];
+ value += GBA_LUX_LEVELS[level - 1];
}
setLuminanceValue(value);
}
diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h
index bbd5c2171..a4de17fff 100644
--- a/src/platform/qt/GameController.h
+++ b/src/platform/qt/GameController.h
@@ -72,6 +72,8 @@ public:
void setOptions(const GBAOptions*);
+ int stateSlot() const { return m_stateSlot; }
+
#ifdef USE_GDB_STUB
ARMDebugger* debugger();
void setDebugger(ARMDebugger*);
@@ -117,6 +119,9 @@ public slots:
void keyReleased(int key);
void clearKeys();
void setAudioBufferSamples(int samples);
+ void setAudioSampleRate(unsigned rate);
+ void setAudioChannelEnabled(int channel, bool enable = true);
+ void setVideoLayerEnabled(int layer, bool enable = true);
void setFPSTarget(float fps);
void loadState(int slot = 0);
void saveState(int slot = 0);
@@ -163,6 +168,7 @@ private:
void enableTurbo();
uint32_t* m_drawContext;
+ uint32_t* m_frontBuffer;
GBAThread m_threadContext;
GBAVideoSoftwareRenderer* m_renderer;
GBACheatDevice m_cheatDevice;
@@ -193,6 +199,9 @@ private:
QTimer m_rewindTimer;
bool m_wasPaused;
+ bool m_audioChannels[6];
+ bool m_videoLayers[5];
+
int m_stateSlot;
GBASerializedState* m_backupLoadState;
QByteArray m_backupSaveState;
@@ -207,8 +216,6 @@ private:
uint8_t m_luxValue;
int m_luxLevel;
- static const int LUX_LEVELS[10];
-
GBARTCGenericSource m_rtc;
};
diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp
index ebc73e267..85bf49b1c 100644
--- a/src/platform/qt/InputController.cpp
+++ b/src/platform/qt/InputController.cpp
@@ -208,9 +208,11 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device)
GBARumble* InputController::rumble() {
#ifdef BUILD_SDL
+#if SDL_VERSION_ATLEAST(2, 0, 0)
if (m_playerAttached) {
return &m_sdlPlayer.rumble.d;
}
+#endif
#endif
return nullptr;
}
@@ -497,19 +499,29 @@ bool InputController::hasPendingEvent(GBAKey key) const {
return m_pendingEvents.contains(key);
}
-#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0)
void InputController::suspendScreensaver() {
+#ifdef BUILD_SDL
+#if SDL_VERSION_ATLEAST(2, 0, 0)
GBASDLSuspendScreensaver(&s_sdlEvents);
+#endif
+#endif
}
void InputController::resumeScreensaver() {
+#ifdef BUILD_SDL
+#if SDL_VERSION_ATLEAST(2, 0, 0)
GBASDLResumeScreensaver(&s_sdlEvents);
+#endif
+#endif
}
void InputController::setScreensaverSuspendable(bool suspendable) {
+#ifdef BUILD_SDL
+#if SDL_VERSION_ATLEAST(2, 0, 0)
GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
-}
#endif
+#endif
+}
void InputController::stealFocus(QWidget* focus) {
m_focusParent = focus;
diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h
index b0e25e92f..f73524bad 100644
--- a/src/platform/qt/InputController.h
+++ b/src/platform/qt/InputController.h
@@ -87,12 +87,10 @@ signals:
public slots:
void testGamepad(int type);
-#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0)
// TODO: Move these to somewhere that makes sense
void suspendScreensaver();
void resumeScreensaver();
void setScreensaverSuspendable(bool);
-#endif
private:
void postPendingEvent(GBAKey);
diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp
index 15796b67e..c14f7ccd7 100644
--- a/src/platform/qt/LoadSaveState.cpp
+++ b/src/platform/qt/LoadSaveState.cpp
@@ -23,9 +23,10 @@ using namespace QGBA;
LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
: QWidget(parent)
, m_controller(controller)
- , m_currentFocus(0)
+ , m_currentFocus(controller->stateSlot() - 1)
, m_mode(LoadSave::LOAD)
{
+ setAttribute(Qt::WA_TranslucentBackground);
m_ui.setupUi(this);
m_slots[0] = m_ui.state1;
@@ -45,6 +46,13 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
}
+ if (m_currentFocus >= 9) {
+ m_currentFocus = 0;
+ }
+ if (m_currentFocus < 0) {
+ m_currentFocus = 0;
+ }
+
QAction* escape = new QAction(this);
escape->connect(escape, SIGNAL(triggered()), this, SLOT(close()));
escape->setShortcut(QKeySequence("Esc"));
@@ -201,6 +209,5 @@ void LoadSaveState::showEvent(QShowEvent* event) {
void LoadSaveState::paintEvent(QPaintEvent*) {
QPainter painter(this);
QRect full(QPoint(), size());
- painter.drawPixmap(full, m_currentImage);
painter.fillRect(full, QColor(0, 0, 0, 128));
}
diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp
index 738760684..b836f7ce0 100644
--- a/src/platform/qt/OverrideView.cpp
+++ b/src/platform/qt/OverrideView.cpp
@@ -15,7 +15,7 @@ extern "C" {
using namespace QGBA;
OverrideView::OverrideView(GameController* controller, ConfigController* config, QWidget* parent)
- : QWidget(parent)
+ : QDialog(parent)
, m_controller(controller)
, m_config(config)
{
diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h
index 6a217edd5..3ed5d3e20 100644
--- a/src/platform/qt/OverrideView.h
+++ b/src/platform/qt/OverrideView.h
@@ -6,7 +6,7 @@
#ifndef QGBA_OVERRIDE_VIEW
#define QGBA_OVERRIDE_VIEW
-#include
+#include
#include "ui_OverrideView.h"
@@ -21,7 +21,7 @@ namespace QGBA {
class ConfigController;
class GameController;
-class OverrideView : public QWidget {
+class OverrideView : public QDialog {
Q_OBJECT
public:
diff --git a/src/platform/qt/SavestateButton.cpp b/src/platform/qt/SavestateButton.cpp
index c9f6560b0..d99ac34f6 100644
--- a/src/platform/qt/SavestateButton.cpp
+++ b/src/platform/qt/SavestateButton.cpp
@@ -13,7 +13,7 @@ using namespace QGBA;
SavestateButton::SavestateButton(QWidget* parent)
: QAbstractButton(parent)
{
- // Nothing to do
+ setAttribute(Qt::WA_TranslucentBackground);
}
void SavestateButton::paintEvent(QPaintEvent*) {
diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp
index 501b05fd9..9a2edd6e4 100644
--- a/src/platform/qt/SensorView.cpp
+++ b/src/platform/qt/SensorView.cpp
@@ -12,7 +12,7 @@
using namespace QGBA;
SensorView::SensorView(GameController* controller, InputController* input, QWidget* parent)
- : QWidget(parent)
+ : QDialog(parent)
, m_controller(controller)
, m_input(input)
, m_rotation(input->rotationSource())
diff --git a/src/platform/qt/SensorView.h b/src/platform/qt/SensorView.h
index a37fb6e65..50c96ecf1 100644
--- a/src/platform/qt/SensorView.h
+++ b/src/platform/qt/SensorView.h
@@ -7,7 +7,7 @@
#define QGBA_SENSOR_VIEW
#include
-#include
+#include
#include
@@ -22,7 +22,7 @@ class GameController;
class GamepadAxisEvent;
class InputController;
-class SensorView : public QWidget {
+class SensorView : public QDialog {
Q_OBJECT
public:
diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp
index cc343cd29..f60a43cd3 100644
--- a/src/platform/qt/SettingsView.cpp
+++ b/src/platform/qt/SettingsView.cpp
@@ -13,8 +13,8 @@
using namespace QGBA;
SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
- : QWidget(parent)
- , m_controller(controller)
+ : QDialog(parent)
+ , m_controller(controller)
{
m_ui.setupUi(this);
@@ -22,6 +22,7 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
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);
@@ -102,6 +103,7 @@ void SettingsView::updateConfig() {
saveSetting("useBios", m_ui.useBios);
saveSetting("skipBios", m_ui.skipBios);
saveSetting("audioBuffers", m_ui.audioBufferSize);
+ saveSetting("sampleRate", m_ui.sampleRate);
saveSetting("videoSync", m_ui.videoSync);
saveSetting("audioSync", m_ui.audioSync);
saveSetting("frameskip", m_ui.frameskip);
diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h
index 782e20e63..e47cda889 100644
--- a/src/platform/qt/SettingsView.h
+++ b/src/platform/qt/SettingsView.h
@@ -6,7 +6,7 @@
#ifndef QGBA_SETTINGS_VIEW
#define QGBA_SETTINGS_VIEW
-#include
+#include
#include "ui_SettingsView.h"
@@ -14,7 +14,7 @@ namespace QGBA {
class ConfigController;
-class SettingsView : public QWidget {
+class SettingsView : public QDialog {
Q_OBJECT
public:
diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui
index bfd7af451..f9302a87c 100644
--- a/src/platform/qt/SettingsView.ui
+++ b/src/platform/qt/SettingsView.ui
@@ -62,26 +62,41 @@
true
- 2048
+ 1536
- 2
+ 3
-
512
+ -
+
+ 768
+
+
-
1024
+ -
+
+ 1536
+
+
-
2048
+ -
+
+ 3072
+
+
-
4096
@@ -99,13 +114,20 @@
-
+
+
+ Sample rate:
+
+
+
+ -
Volume:
- -
+
-
-
@@ -132,28 +154,38 @@
- -
+
-
Qt::Horizontal
- -
+
-
Display driver:
- -
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ -
Frameskip:
- -
+
-
-
@@ -174,14 +206,14 @@
- -
+
-
FPS target:
- -
+
-
-
@@ -205,21 +237,21 @@
- -
+
-
Qt::Horizontal
- -
+
-
Sync:
- -
+
-
-
@@ -237,29 +269,63 @@
- -
+
-
Lock aspect ratio
- -
+
-
Resample video
- -
-
-
-
- 0
- 0
-
-
-
+
-
+
+
-
+
+
+ true
+
+
+ 44100
+
+
+ 2
+
+
-
+
+ 22050
+
+
+ -
+
+ 32000
+
+
+ -
+
+ 44100
+
+
+ -
+
+ 48000
+
+
+
+
+ -
+
+
+ Hz
+
+
+
+
diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h
index 5cc6f06a4..811a34f34 100644
--- a/src/platform/qt/ShortcutController.h
+++ b/src/platform/qt/ShortcutController.h
@@ -103,6 +103,8 @@ public:
const QKeySequence& shortcut, const QString& visibleName, const QString& name);
void addMenu(QMenu* menu, QMenu* parent = nullptr);
+ QAction* getAction(const QString& name);
+
QKeySequence shortcutAt(const QModelIndex& index) const;
bool isMenuAt(const QModelIndex& index) const;
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index 29f800c0f..6307fdf12 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -13,6 +13,7 @@
#include
#include
+#include "AboutScreen.h"
#include "CheatsView.h"
#include "ConfigController.h"
#include "Display.h"
@@ -40,8 +41,8 @@ extern "C" {
using namespace QGBA;
-#ifdef __WIN32
-// This is a macro everywhere except MinGW, it seems
+#if defined(__WIN32) || defined(__OpenBSD__)
+// This is a macro everywhere except MinGW and OpenBSD, it seems
using std::isnan;
#endif
@@ -99,6 +100,14 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), &m_inputController, SLOT(resumeScreensaver()));
connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw()));
connect(m_controller, SIGNAL(rewound(GBAThread*)), m_display, SLOT(forceDraw()));
+ connect(m_controller, &GameController::gamePaused, [this]() {
+ QImage currentImage(reinterpret_cast(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS,
+ VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGBX8888);
+ QPixmap pixmap;
+ pixmap.convertFromImage(currentImage);
+ m_screenWidget->setPixmap(pixmap);
+ m_screenWidget->setLockAspectRatio(3, 2);
+ });
connect(m_controller, SIGNAL(gamePaused(GBAThread*)), m_display, SLOT(pauseDrawing()));
#ifndef Q_OS_MAC
connect(m_controller, SIGNAL(gamePaused(GBAThread*)), menuBar(), SLOT(show()));
@@ -126,15 +135,16 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame()));
connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide()));
connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
+ 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_display, &Display::hideCursor, [this]() {
if (static_cast(m_screenWidget->layout())->currentWidget() == m_display) {
- setCursor(Qt::BlankCursor);
+ m_screenWidget->setCursor(Qt::BlankCursor);
}
});
connect(m_display, &Display::showCursor, [this]() {
- unsetCursor();
+ m_screenWidget->unsetCursor();
});
connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_shortcutController, SLOT(loadProfile(const QString&)));
@@ -171,6 +181,7 @@ void Window::argumentsPassed(GBAArguments* args) {
void Window::resizeFrame(int width, int height) {
QSize newSize(width, height);
+ m_screenWidget->setSizeHint(newSize);
newSize -= m_screenWidget->size();
newSize += size();
resize(newSize);
@@ -193,6 +204,7 @@ void Window::loadConfig() {
m_controller->loadBIOS(opts->bios);
}
+ // TODO: Move these to ConfigController
if (opts->fpsTarget) {
emit fpsTargetChanged(opts->fpsTarget);
}
@@ -201,6 +213,10 @@ void Window::loadConfig() {
emit audioBufferSamplesChanged(opts->audioBuffers);
}
+ if (opts->sampleRate) {
+ emit sampleRateChanged(opts->sampleRate);
+ }
+
if (opts->width && opts->height) {
resizeFrame(opts->width, opts->height);
}
@@ -344,6 +360,11 @@ void Window::openMemoryWindow() {
openView(memoryWindow);
}
+void Window::openAboutScreen() {
+ AboutScreen* about = new AboutScreen();
+ openView(about);
+}
+
#ifdef BUILD_SDL
void Window::openGamepadWindow() {
const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON);
@@ -386,9 +407,7 @@ void Window::gdbOpen() {
m_gdbController = new GDBController(m_controller, this);
}
GDBWindow* window = new GDBWindow(m_gdbController);
- connect(this, SIGNAL(shutdown()), window, SLOT(close()));
- window->setAttribute(Qt::WA_DeleteOnClose);
- window->show();
+ openView(window);
}
#endif
@@ -420,14 +439,34 @@ void Window::keyReleaseEvent(QKeyEvent* event) {
event->accept();
}
-void Window::resizeEvent(QResizeEvent*) {
+void Window::resizeEvent(QResizeEvent* event) {
if (!isFullScreen()) {
m_config->setOption("height", m_screenWidget->height());
m_config->setOption("width", m_screenWidget->width());
}
+
+ int factor = 0;
+ if (event->size().width() % VIDEO_HORIZONTAL_PIXELS == 0 && event->size().height() % VIDEO_VERTICAL_PIXELS == 0 &&
+ event->size().width() / VIDEO_HORIZONTAL_PIXELS == event->size().height() / VIDEO_VERTICAL_PIXELS) {
+ factor = event->size().width() / VIDEO_HORIZONTAL_PIXELS;
+ }
+ for (QMap::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) {
+ bool enableSignals = iter.value()->blockSignals(true);
+ if (iter.key() == factor) {
+ iter.value()->setChecked(true);
+ } else {
+ iter.value()->setChecked(false);
+ }
+ iter.value()->blockSignals(enableSignals);
+ }
+
m_config->setOption("fullscreen", isFullScreen());
}
+void Window::showEvent(QShowEvent* event) {
+ resizeFrame(m_screenWidget->sizeHint().width(), m_screenWidget->sizeHint().height());
+}
+
void Window::closeEvent(QCloseEvent* event) {
emit shutdown();
m_config->setQtOption("windowPos", pos());
@@ -435,6 +474,10 @@ void Window::closeEvent(QCloseEvent* event) {
QMainWindow::closeEvent(event);
}
+void Window::focusInEvent(QFocusEvent*) {
+ m_display->forceDraw();
+}
+
void Window::focusOutEvent(QFocusEvent*) {
m_controller->setTurbo(false, false);
m_controller->stopRewinding();
@@ -475,7 +518,6 @@ void Window::enterFullScreen() {
return;
}
showFullScreen();
- setCursor(Qt::BlankCursor);
#ifndef Q_OS_MAC
if (m_controller->isLoaded() && !m_controller->isPaused()) {
menuBar()->hide();
@@ -487,7 +529,7 @@ void Window::exitFullScreen() {
if (!isFullScreen()) {
return;
}
- unsetCursor();
+ m_screenWidget->unsetCursor();
menuBar()->show();
showNormal();
}
@@ -515,6 +557,7 @@ void Window::gameStarted(GBAThread* context) {
action->setDisabled(false);
}
if (context->fname) {
+ setWindowFilePath(context->fname);
appendMRU(context->fname);
}
updateTitle();
@@ -534,10 +577,12 @@ void Window::gameStopped() {
foreach (QAction* action, m_gameActions) {
action->setDisabled(true);
}
+ setWindowFilePath(QString());
updateTitle();
detachWidget(m_display);
m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height());
m_screenWidget->setPixmap(m_logo);
+ m_screenWidget->unsetCursor();
m_fpsTimer.stop();
}
@@ -640,7 +685,7 @@ void Window::openStateWindow(LoadSave ls) {
connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(close()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_stateWindow, SLOT(close()));
connect(m_stateWindow, &LoadSaveState::closed, [this]() {
- m_screenWidget->layout()->removeWidget(m_stateWindow);
+ detachWidget(m_stateWindow);
m_stateWindow = nullptr;
QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection);
});
@@ -752,6 +797,14 @@ void Window::setupMenu(QMenuBar* menubar) {
});
addControlledAction(fileMenu, multiWindow, "multiWindow");
+#ifndef Q_OS_MAC
+ fileMenu->addSeparator();
+#endif
+
+ QAction* about = new QAction(tr("About"), fileMenu);
+ connect(about, SIGNAL(triggered()), this, SLOT(openAboutScreen()));
+ fileMenu->addAction(about);
+
#ifndef Q_OS_MAC
addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit");
#endif
@@ -782,13 +835,6 @@ void Window::setupMenu(QMenuBar* menubar) {
connect(pause, SIGNAL(triggered(bool)), m_controller, SLOT(setPaused(bool)));
connect(m_controller, &GameController::gamePaused, [this, pause]() {
pause->setChecked(true);
-
- QImage currentImage(reinterpret_cast(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS,
- VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGB32);
- QPixmap pixmap;
- pixmap.convertFromImage(currentImage.rgbSwapped());
- m_screenWidget->setPixmap(pixmap);
- m_screenWidget->setLockAspectRatio(3, 2);
});
connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); });
m_gameActions.append(pause);
@@ -897,10 +943,15 @@ void Window::setupMenu(QMenuBar* menubar) {
m_shortcutController->addMenu(frameMenu, avMenu);
for (int i = 1; i <= 6; ++i) {
QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu);
- connect(setSize, &QAction::triggered, [this, i]() {
+ setSize->setCheckable(true);
+ connect(setSize, &QAction::triggered, [this, i, setSize]() {
showNormal();
resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i);
+ bool enableSignals = setSize->blockSignals(true);
+ setSize->setChecked(true);
+ setSize->blockSignals(enableSignals);
});
+ m_frameSizes[i] = setSize;
addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i)));
}
QKeySequence fullscreenKeys;
@@ -937,20 +988,6 @@ void Window::setupMenu(QMenuBar* menubar) {
avMenu->addSeparator();
- QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size"));
- ConfigOption* buffers = m_config->addOption("audioBuffers");
- buffers->connect([this](const QVariant& value) {
- emit audioBufferSamplesChanged(value.toInt());
- }, this);
- buffers->addValue(tr("512"), 512, buffersMenu);
- buffers->addValue(tr("768"), 768, buffersMenu);
- buffers->addValue(tr("1024"), 1024, buffersMenu);
- buffers->addValue(tr("2048"), 2048, buffersMenu);
- buffers->addValue(tr("4096"), 4096, buffersMenu);
- m_config->updateOption("audioBuffers");
-
- avMenu->addSeparator();
-
QMenu* target = avMenu->addMenu(tr("FPS target"));
ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget");
fpsTargetOption->connect([this](const QVariant& value) {
@@ -997,16 +1034,14 @@ void Window::setupMenu(QMenuBar* menubar) {
QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers);
enableBg->setCheckable(true);
enableBg->setChecked(true);
- connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; });
- m_gameActions.append(enableBg);
+ connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->setVideoLayerEnabled(i, enable); });
addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i));
}
QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers);
enableObj->setCheckable(true);
enableObj->setChecked(true);
- connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; });
- m_gameActions.append(enableObj);
+ connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); });
addControlledAction(videoLayers, enableObj, "enableOBJ");
QMenu* audioChannels = avMenu->addMenu(tr("Audio channels"));
@@ -1015,23 +1050,20 @@ void Window::setupMenu(QMenuBar* menubar) {
QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels);
enableCh->setCheckable(true);
enableCh->setChecked(true);
- connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; });
- m_gameActions.append(enableCh);
+ connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(i, enable); });
addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1));
}
QAction* enableChA = new QAction(tr("Channel A"), audioChannels);
enableChA->setCheckable(true);
enableChA->setChecked(true);
- connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; });
- m_gameActions.append(enableChA);
+ connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(4, enable); });
addControlledAction(audioChannels, enableChA, QString("enableChA"));
QAction* enableChB = new QAction(tr("Channel B"), audioChannels);
enableChB->setCheckable(true);
enableChB->setChecked(true);
- connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; });
- m_gameActions.append(enableChB);
+ connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); });
addControlledAction(audioChannels, enableChB, QString("enableChB"));
QMenu* toolsMenu = menubar->addMenu(tr("&Tools"));
@@ -1091,6 +1123,21 @@ void Window::setupMenu(QMenuBar* menubar) {
m_controller->setSkipBIOS(value.toBool());
}, this);
+ ConfigOption* useBios = m_config->addOption("useBios");
+ useBios->connect([this](const QVariant& value) {
+ m_controller->setUseBIOS(value.toBool());
+ }, this);
+
+ ConfigOption* buffers = m_config->addOption("audioBuffers");
+ buffers->connect([this](const QVariant& value) {
+ emit audioBufferSamplesChanged(value.toInt());
+ }, this);
+
+ ConfigOption* sampleRate = m_config->addOption("sampleRate");
+ sampleRate->connect([this](const QVariant& value) {
+ emit sampleRateChanged(value.toUInt());
+ }, this);
+
ConfigOption* volume = m_config->addOption("volume");
volume->connect([this](const QVariant& value) {
m_controller->setVolume(value.toInt());
@@ -1133,7 +1180,7 @@ void Window::setupMenu(QMenuBar* menubar) {
void Window::attachWidget(QWidget* widget) {
m_screenWidget->layout()->addWidget(widget);
- unsetCursor();
+ m_screenWidget->unsetCursor();
static_cast(m_screenWidget->layout())->setCurrentWidget(widget);
}
@@ -1215,10 +1262,10 @@ void WindowBackground::paintEvent(QPaintEvent*) {
painter.fillRect(QRect(QPoint(), size()), Qt::black);
QSize s = size();
QSize ds = s;
- if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) {
- ds.setWidth(s.height() * m_aspectWidth / m_aspectHeight);
- } else if (s.width() * m_aspectHeight < s.height() * m_aspectWidth) {
- ds.setHeight(s.width() * m_aspectHeight / m_aspectWidth);
+ if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {
+ ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight);
+ } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) {
+ ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth);
}
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
QRect full(origin, ds);
diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h
index 7db12500c..ebc923776 100644
--- a/src/platform/qt/Window.h
+++ b/src/platform/qt/Window.h
@@ -54,6 +54,7 @@ signals:
void startDrawing(GBAThread*);
void shutdown();
void audioBufferSamplesChanged(int samples);
+ void sampleRateChanged(unsigned samples);
void fpsTargetChanged(float target);
public slots:
@@ -82,6 +83,8 @@ public slots:
void openPaletteWindow();
void openMemoryWindow();
+ void openAboutScreen();
+
#ifdef BUILD_SDL
void openGamepadWindow();
#endif
@@ -102,7 +105,9 @@ protected:
virtual void keyPressEvent(QKeyEvent* event) override;
virtual void keyReleaseEvent(QKeyEvent* event) override;
virtual void resizeEvent(QResizeEvent*) override;
+ virtual void showEvent(QShowEvent*) override;
virtual void closeEvent(QCloseEvent*) override;
+ virtual void focusInEvent(QFocusEvent*) override;
virtual void focusOutEvent(QFocusEvent*) override;
virtual void dragEnterEvent(QDragEnterEvent*) override;
virtual void dropEvent(QDropEvent*) override;
@@ -144,6 +149,7 @@ private:
GameController* m_controller;
Display* m_display;
QList m_gameActions;
+ QMap m_frameSizes;
LogController m_log;
LogView* m_logView;
LoadSaveState* m_stateWindow;
diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp
index 8152cf3a2..3dfca440b 100644
--- a/src/platform/qt/main.cpp
+++ b/src/platform/qt/main.cpp
@@ -10,9 +10,11 @@
#include
#ifdef _WIN32
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
+#ifdef BUILD_QT_MULTIMEDIA
Q_IMPORT_PLUGIN(QWindowsAudioPlugin);
#endif
#endif
+#endif
int main(int argc, char* argv[]) {
QGBA::GBAApp application(argc, argv);
diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt
index 88510b37b..3368a15ce 100644
--- a/src/platform/sdl/CMakeLists.txt
+++ b/src/platform/sdl/CMakeLists.txt
@@ -51,13 +51,16 @@ set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c)
if(BUILD_RASPI)
add_definitions(-DBUILD_RASPI)
- set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-sdl.c)
- set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
+ list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
+ list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c)
+ set(OPENGLES2_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
+ set(BUILD_GLES2 ON CACHE BOOL "Using OpenGL|ES 2" FORCE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline")
- add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC})
+ add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC})
set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
- target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
+ target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGLES2_LIBRARY})
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi)
+ unset(OPENGLES2_INCLUDE_DIR} CACHE) # Clear NOTFOUND
endif()
if(BUILD_PANDORA)
@@ -66,13 +69,21 @@ else()
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c)
if(BUILD_GL)
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c)
- list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
+ list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
include_directories(${OPENGL_INCLUDE_DIR})
endif()
+ if(BUILD_GLES2)
+ list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c)
+ list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
+ include_directories(${OPENGLES2_INCLUDE_DIR})
+ endif()
endif()
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
-target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY})
+target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
+if(UNIX)
+ install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl)
+endif()
diff --git a/src/platform/sdl/egl-sdl.c b/src/platform/sdl/egl-sdl.c
deleted file mode 100644
index e14b753a2..000000000
--- a/src/platform/sdl/egl-sdl.c
+++ /dev/null
@@ -1,166 +0,0 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include "main.h"
-
-static const char* _vertexShader =
- "attribute vec4 position;\n"
- "varying vec2 texCoord;\n"
-
- "void main() {\n"
- " gl_Position = position;\n"
- " texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.46875, -0.3125);\n"
- "}";
-
-static const char* _fragmentShader =
- "varying vec2 texCoord;\n"
- "uniform sampler2D tex;\n"
-
- "void main() {\n"
- " vec4 color = texture2D(tex, texCoord);\n"
- " color.a = 1.;\n"
- " gl_FragColor = color;"
- "}";
-
-static const GLfloat _vertices[] = {
- -1.f, -1.f,
- -1.f, 1.f,
- 1.f, 1.f,
- 1.f, -1.f,
-};
-
-bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
- bcm_host_init();
- renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- int major, minor;
- if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) {
- printf("Failed to initialize EGL");
- return false;
- }
-
- if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) {
- printf("Failed to get GLES API");
- return false;
- }
-
- const EGLint requestConfig[] = {
- EGL_RED_SIZE, 5,
- EGL_GREEN_SIZE, 5,
- EGL_BLUE_SIZE, 5,
- EGL_ALPHA_SIZE, 1,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
- EGL_NONE
- };
-
- EGLConfig config;
- EGLint numConfigs;
-
- if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) {
- printf("Failed to choose EGL config\n");
- return false;
- }
-
- const EGLint contextAttributes[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE
- };
-
- int dispWidth = 240, dispHeight = 160, adjWidth;
- renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes);
- graphics_get_display_size(0, &dispWidth, &dispHeight);
- adjWidth = dispHeight / 2 * 3;
-
- DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0);
- DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
-
- VC_RECT_T destRect = {
- .x = (dispWidth - adjWidth) / 2,
- .y = 0,
- .width = adjWidth,
- .height = dispHeight
- };
-
- VC_RECT_T srcRect = {
- .x = 0,
- .y = 0,
- .width = 240 << 16,
- .height = 160 << 16
- };
-
- DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0);
- vc_dispmanx_update_submit_sync(update);
-
- renderer->window.element = element;
- renderer->window.width = dispWidth;
- renderer->window.height = dispHeight;
-
- renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0);
- if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) {
- return false;
- }
-
- renderer->d.outputBuffer = memalign(16, 256 * 256 * 4);
- renderer->d.outputBufferStride = 256;
- glGenTextures(1, &renderer->tex);
- glBindTexture(GL_TEXTURE_2D, renderer->tex);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
- renderer->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
- renderer->vertexShader = glCreateShader(GL_VERTEX_SHADER);
- renderer->program = glCreateProgram();
-
- glShaderSource(renderer->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0);
- glShaderSource(renderer->vertexShader, 1, (const GLchar**) &_vertexShader, 0);
- glAttachShader(renderer->program, renderer->vertexShader);
- glAttachShader(renderer->program, renderer->fragmentShader);
- char log[1024];
- glCompileShader(renderer->fragmentShader);
- glCompileShader(renderer->vertexShader);
- glGetShaderInfoLog(renderer->fragmentShader, 1024, 0, log);
- glGetShaderInfoLog(renderer->vertexShader, 1024, 0, log);
- glLinkProgram(renderer->program);
- glGetProgramInfoLog(renderer->program, 1024, 0, log);
- printf("%s\n", log);
- renderer->texLocation = glGetUniformLocation(renderer->program, "tex");
- renderer->positionLocation = glGetAttribLocation(renderer->program, "position");
- glClearColor(1.f, 0.f, 0.f, 1.f);
-}
-
-void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
- SDL_Event event;
-
- while (context->state < THREAD_EXITING) {
- while (SDL_PollEvent(&event)) {
- GBASDLHandleEvent(context, &renderer->player, &event);
- }
-
- if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
- glViewport(0, 0, 240, 160);
- glClear(GL_COLOR_BUFFER_BIT);
- glUseProgram(renderer->program);
- glUniform1i(renderer->texLocation, 0);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, renderer->tex);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer);
- glVertexAttribPointer(renderer->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
- glEnableVertexAttribArray(renderer->positionLocation);
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- glUseProgram(0);
- eglSwapBuffers(renderer->display, renderer->surface);
- }
- GBASyncWaitFrameEnd(&context->sync);
- }
-}
-
-void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
- eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroySurface(renderer->display, renderer->surface);
- eglDestroyContext(renderer->display, renderer->context);
- eglTerminate(renderer->display);
- bcm_host_deinit();
-}
diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c
new file mode 100644
index 000000000..30158653d
--- /dev/null
+++ b/src/platform/sdl/gl-common.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "main.h"
+
+void GBASDLGLCommonSwap(struct VideoBackend* context) {
+ struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user;
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ SDL_GL_SwapWindow(renderer->window);
+#else
+ UNUSED(renderer);
+ SDL_GL_SwapBuffers();
+#endif
+}
+
+void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer) {
+#ifndef COLOR_16_BIT
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+#else
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
+#ifdef COLOR_5_6_5
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
+#else
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
+#endif
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
+#endif
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
+ renderer->glCtx = SDL_GL_CreateContext(renderer->window);
+ SDL_GL_SetSwapInterval(1);
+ SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
+ renderer->player.window = renderer->window;
+#else
+ SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
+#ifdef COLOR_16_BIT
+ SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL);
+#else
+ SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL);
+#endif
+#endif
+}
diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h
new file mode 100644
index 000000000..c07fd7926
--- /dev/null
+++ b/src/platform/sdl/gl-common.h
@@ -0,0 +1,13 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef SDL_GL_COMMON_H
+#define SDL_GL_COMMON_H
+#include "main.h"
+
+void GBASDLGLCommonSwap(struct VideoBackend* context);
+void GBASDLGLCommonInit(struct SDLSoftwareRenderer* renderer);
+
+#endif
diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c
index 429d67a65..12073b12a 100644
--- a/src/platform/sdl/gl-sdl.c
+++ b/src/platform/sdl/gl-sdl.c
@@ -5,18 +5,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "main.h"
+#include "gl-common.h"
+
#include "gba/supervisor/thread.h"
#include "platform/opengl/gl.h"
-static void _sdlSwap(struct VideoBackend* context) {
- struct SDLSoftwareRenderer* renderer = (struct SDLSoftwareRenderer*) context->user;
-#if SDL_VERSION_ATLEAST(2, 0, 0)
- SDL_GL_SwapWindow(renderer->window);
-#else
- SDL_GL_SwapBuffers();
-#endif
-}
-
static void _doViewport(int w, int h, struct VideoBackend* v) {
v->resized(v, w, h);
v->clear(v);
@@ -35,34 +28,7 @@ void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) {
}
bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) {
-#ifndef COLOR_16_BIT
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
-#else
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
-#ifdef COLOR_5_6_5
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
-#else
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
-#endif
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
-#endif
-
-#if SDL_VERSION_ATLEAST(2, 0, 0)
- renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
- renderer->glCtx = SDL_GL_CreateContext(renderer->window);
- SDL_GL_SetSwapInterval(1);
- SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
- renderer->player.window = renderer->window;
-#else
- SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
-#ifdef COLOR_16_BIT
- SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_OPENGL);
-#else
- SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL);
-#endif
-#endif
+ GBASDLGLCommonInit(renderer);
renderer->d.outputBuffer = malloc(256 * 256 * BYTES_PER_PIXEL);
renderer->d.outputBufferStride = 256;
@@ -71,7 +37,7 @@ bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) {
renderer->gl.d.user = renderer;
renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio;
renderer->gl.d.filter = renderer->filter;
- renderer->gl.d.swap = _sdlSwap;
+ renderer->gl.d.swap = GBASDLGLCommonSwap;
renderer->gl.d.init(&renderer->gl.d, 0);
_doViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d);
diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c
new file mode 100644
index 000000000..a3a617471
--- /dev/null
+++ b/src/platform/sdl/gles2-sdl.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "main.h"
+
+#include "gl-common.h"
+
+#include
+
+static bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer);
+static void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer);
+static void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer);
+
+void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer) {
+ renderer->init = GBASDLGLES2Init;
+ renderer->deinit = GBASDLGLES2Deinit;
+ renderer->runloop = GBASDLGLES2Runloop;
+}
+
+bool GBASDLGLES2Init(struct SDLSoftwareRenderer* renderer) {
+#ifdef BUILD_RASPI
+ bcm_host_init();
+ renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ int major, minor;
+ if (EGL_FALSE == eglInitialize(renderer->display, &major, &minor)) {
+ printf("Failed to initialize EGL");
+ return false;
+ }
+
+ if (EGL_FALSE == eglBindAPI(EGL_OPENGL_ES_API)) {
+ printf("Failed to get GLES API");
+ return false;
+ }
+
+ const EGLint requestConfig[] = {
+ EGL_RED_SIZE, 5,
+ EGL_GREEN_SIZE, 5,
+ EGL_BLUE_SIZE, 5,
+ EGL_ALPHA_SIZE, 1,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+
+ EGLConfig config;
+ EGLint numConfigs;
+
+ if (EGL_FALSE == eglChooseConfig(renderer->display, requestConfig, &config, 1, &numConfigs)) {
+ printf("Failed to choose EGL config\n");
+ return false;
+ }
+
+ const EGLint contextAttributes[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+
+ int dispWidth = 240, dispHeight = 160, adjWidth;
+ renderer->context = eglCreateContext(renderer->display, config, EGL_NO_CONTEXT, contextAttributes);
+ graphics_get_display_size(0, &dispWidth, &dispHeight);
+ adjWidth = dispHeight / 2 * 3;
+
+ DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0);
+ DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
+
+ VC_RECT_T destRect = {
+ .x = (dispWidth - adjWidth) / 2,
+ .y = 0,
+ .width = adjWidth,
+ .height = dispHeight
+ };
+
+ VC_RECT_T srcRect = {
+ .x = 0,
+ .y = 0,
+ .width = 240 << 16,
+ .height = 160 << 16
+ };
+
+ DISPMANX_ELEMENT_HANDLE_T element = vc_dispmanx_element_add(update, display, 0, &destRect, 0, &srcRect, DISPMANX_PROTECTION_NONE, 0, 0, 0);
+ vc_dispmanx_update_submit_sync(update);
+
+ renderer->window.element = element;
+ renderer->window.width = dispWidth;
+ renderer->window.height = dispHeight;
+
+ renderer->surface = eglCreateWindowSurface(renderer->display, config, &renderer->window, 0);
+ if (EGL_FALSE == eglMakeCurrent(renderer->display, renderer->surface, renderer->surface, renderer->context)) {
+ return false;
+ }
+#else
+ GBASDLGLCommonInit(renderer);
+#endif
+
+ renderer->d.outputBuffer = memalign(16, 256 * 256 * 4);
+ renderer->d.outputBufferStride = 256;
+
+ GBAGLES2ContextCreate(&renderer->gl);
+ renderer->gl.d.user = renderer;
+ renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio;
+ renderer->gl.d.filter = renderer->filter;
+ renderer->gl.d.swap = GBASDLGLCommonSwap;
+ renderer->gl.d.init(&renderer->gl.d, 0);
+ return true;
+}
+
+void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
+ SDL_Event event;
+ struct VideoBackend* v = &renderer->gl.d;
+
+ while (context->state < THREAD_EXITING) {
+ while (SDL_PollEvent(&event)) {
+ GBASDLHandleEvent(context, &renderer->player, &event);
+ }
+
+ if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
+ v->postFrame(v, renderer->d.outputBuffer);
+ }
+ v->drawFrame(v);
+ GBASyncWaitFrameEnd(&context->sync);
+#ifdef BUILD_RASPI
+ eglSwapBuffers(renderer->display, renderer->surface);
+#else
+ v->swap(v);
+#endif
+ }
+}
+
+void GBASDLGLES2Deinit(struct SDLSoftwareRenderer* renderer) {
+ if (renderer->gl.d.deinit) {
+ renderer->gl.d.deinit(&renderer->gl.d);
+ }
+#ifdef BUILD_RASPI
+ eglMakeCurrent(renderer->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroySurface(renderer->display, renderer->surface);
+ eglDestroyContext(renderer->display, renderer->context);
+ eglTerminate(renderer->display);
+ bcm_host_deinit();
+#elif SDL_VERSION_ATLEAST(2, 0, 0)
+ SDL_GL_DeleteContext(renderer->glCtx);
+#endif
+ free(renderer->d.outputBuffer);
+}
diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c
index 0b3143eeb..78dda4706 100644
--- a/src/platform/sdl/main.c
+++ b/src/platform/sdl/main.c
@@ -86,6 +86,8 @@ int main(int argc, char** argv) {
#ifdef BUILD_GL
GBASDLGLCreate(&renderer);
+#elif defined(BUILD_GLES2)
+ GBASDLGLES2Create(&renderer);
#else
GBASDLSWCreate(&renderer);
#endif
@@ -110,6 +112,10 @@ int main(int argc, char** argv) {
bool didFail = false;
renderer.audio.samples = context.audioBuffers;
+ renderer.audio.sampleRate = 44100;
+ if (opts.sampleRate) {
+ renderer.audio.sampleRate = opts.sampleRate;
+ }
if (!GBASDLInitAudio(&renderer.audio, &context)) {
didFail = true;
}
diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h
index 4a428c190..66ae8c75c 100644
--- a/src/platform/sdl/main.h
+++ b/src/platform/sdl/main.h
@@ -20,13 +20,16 @@
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#include
-#include
#include
#include
#pragma GCC diagnostic pop
#endif
+#ifdef BUILD_GLES2
+#include "platform/opengl/gles2.h"
+#endif
+
#ifdef USE_PIXMAN
#include
#endif
@@ -57,6 +60,8 @@ struct SDLSoftwareRenderer {
#ifdef BUILD_GL
struct GBAGLContext gl;
+#elif BUILD_GLES2
+ struct GBAGLES2Context gl;
#endif
#ifdef USE_PIXMAN
@@ -69,13 +74,6 @@ struct SDLSoftwareRenderer {
EGLSurface surface;
EGLContext context;
EGL_DISPMANX_WINDOW_T window;
- GLuint tex;
- GLuint fragmentShader;
- GLuint vertexShader;
- GLuint program;
- GLuint bufferObject;
- GLuint texLocation;
- GLuint positionLocation;
#endif
#ifdef BUILD_PANDORA
@@ -90,4 +88,8 @@ void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer);
#ifdef BUILD_GL
void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer);
#endif
+
+#ifdef BUILD_GLES2
+void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer);
+#endif
#endif
diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c
index 6e0fdebb2..321679a96 100644
--- a/src/platform/sdl/sdl-audio.c
+++ b/src/platform/sdl/sdl-audio.c
@@ -22,7 +22,7 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
return false;
}
- context->desiredSpec.freq = 44100;
+ context->desiredSpec.freq = context->sampleRate;
context->desiredSpec.format = AUDIO_S16SYS;
context->desiredSpec.channels = 2;
context->desiredSpec.samples = context->samples;
diff --git a/src/platform/sdl/sdl-audio.h b/src/platform/sdl/sdl-audio.h
index 6730333eb..f2f0b8df2 100644
--- a/src/platform/sdl/sdl-audio.h
+++ b/src/platform/sdl/sdl-audio.h
@@ -15,6 +15,7 @@
struct GBASDLAudio {
// Input
size_t samples;
+ unsigned sampleRate;
// State
SDL_AudioSpec desiredSpec;
diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c
index e316a7f2f..c63e5dc76 100644
--- a/src/platform/sdl/sdl-events.c
+++ b/src/platform/sdl/sdl-events.c
@@ -22,7 +22,7 @@
#endif
#define GYRO_STEPS 100
-#define RUMBLE_PWM 35
+#define RUMBLE_PWM 20
#if SDL_VERSION_ATLEAST(2, 0, 0)
static void _GBASDLSetRumble(struct GBARumble* rumble, int enable);
diff --git a/tools/debian/changelog b/tools/debian/changelog
new file mode 100644
index 000000000..18bed0d13
--- /dev/null
+++ b/tools/debian/changelog
@@ -0,0 +1,5 @@
+mgba (0.3.0-1) UNRELEASED; urgency=low
+
+ * Initial release (closes: Bug#787470).
+
+ -- Sérgio Benjamim Mon, 17 Aug 2015 18:40:00 -0300
diff --git a/tools/debian/clean b/tools/debian/clean
new file mode 100644
index 000000000..d5cc2a095
--- /dev/null
+++ b/tools/debian/clean
@@ -0,0 +1,2 @@
+debian/libmgba.install
+debian/libretro-mgba.install
diff --git a/tools/debian/compat b/tools/debian/compat
new file mode 100644
index 000000000..ec635144f
--- /dev/null
+++ b/tools/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/tools/debian/control b/tools/debian/control
new file mode 100644
index 000000000..79b508833
--- /dev/null
+++ b/tools/debian/control
@@ -0,0 +1,78 @@
+Source: mgba
+Section: otherosfs
+Priority: extra
+Maintainer: Sérgio Benjamim
+Build-Depends: cmake (>= 2.8.11),
+ debhelper (>= 9),
+ libavcodec-dev,
+ libavformat-dev,
+ libavresample-dev,
+ libavutil-dev,
+ libedit-dev,
+ libmagickwand-dev,
+ libpng-dev,
+ libqt5opengl5-dev,
+ libsdl2-dev,
+ libswscale-dev,
+ libzip-dev,
+ pkg-config,
+ qtbase5-dev,
+ qtmultimedia5-dev,
+ zlib1g-dev
+Standards-Version: 3.9.6
+Homepage: http://mgba.io/
+
+Package: libmgba
+Architecture: any
+Multi-Arch: same
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Game Boy Advance emulator (common library for mGBA)
+ mGBA is a new emulator for running Game Boy Advance games. It aims to be faster
+ and more accurate than many existing Game Boy Advance emulators, as well as
+ adding features that other emulators lack.
+ .
+ This package provides the common library for mGBA.
+ .
+ Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is
+ not affiliated with or endorsed by any of the companies mentioned.
+
+Package: mgba-qt
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Game Boy Advance emulator (Qt frontend for mGBA)
+ mGBA is a new emulator for running Game Boy Advance games. It aims to be faster
+ and more accurate than many existing Game Boy Advance emulators, as well as
+ adding features that other emulators lack.
+ .
+ This package provides the Qt GUI frontend for mGBA.
+ .
+ Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is
+ not affiliated with or endorsed by any of the companies mentioned.
+
+Package: mgba-sdl
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Game Boy Advance emulator (SDL frontend for mGBA)
+ mGBA is a new emulator for running Game Boy Advance games. It aims to be faster
+ and more accurate than many existing Game Boy Advance emulators, as well as
+ adding features that other emulators lack.
+ .
+ This package provides the SDL UI console for mGBA.
+ .
+ Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is
+ not affiliated with or endorsed by any of the companies mentioned.
+
+Package: libretro-mgba
+Architecture: any
+Multi-Arch: same
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Libretro wrapper for mGBA
+ This wrapper makes mGBA API compatible with libretro, thus allowing its use
+ with libretro frontends, such as RetroArch.
+ .
+ mGBA is a new emulator for running Game Boy Advance games. It aims to be faster
+ and more accurate than many existing Game Boy Advance emulators, as well as
+ adding features that other emulators lack.
+ .
+ Game Boy Advance is a registered trademark of Nintendo of America Inc. mGBA is
+ not affiliated with or endorsed by any of the companies mentioned.
diff --git a/tools/debian/copyright b/tools/debian/copyright
new file mode 100644
index 000000000..75a308d13
--- /dev/null
+++ b/tools/debian/copyright
@@ -0,0 +1,472 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: mGBA
+Upstream-Contact: Jeffrey Pfau (aka endrift)
+Source: https://github.com/mgba-emu/mgba
+Comment: This package was debianized by
+ Sergio Benjamim (sergio-br2) on Mon, 01 Jun 2015 18:40:00 -0300
+
+
+Files: *
+Copyright: 2013-2015 Jeffrey Pfau
+License: MPL-2.0
+
+Files: src/third-party/blip_buf/*
+Copyright: 2003-2009 Shay Green
+License: LGPL-2.1+
+
+Files: src/third-party/inih/*
+Copyright: 2009 Brush Technology. All rights reserved.
+License: BSD-3-clause-Brush-Technology
+
+Files: src/third-party/lzma/*
+Copyright: 2008-2015 Igor Pavlov
+License: public-domain
+ These files have been put in the public domain by their author
+
+Files: src/platform/libretro/libretro.h
+Copyright: 2010-2015 The RetroArch Team
+License: Expat
+
+Files: debian/*
+Copyright: 2015 Sergio Benjamim (sergio-br2)
+License: MPL-2.0
+
+
+License: MPL-2.0
+ Mozilla Public License Version 2.0
+ ==================================
+ .
+ 1. Definitions
+ --------------
+ .
+ 1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+ .
+ 1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+ .
+ 1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+ .
+ 1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+ .
+ 1.5. "Incompatible With Secondary Licenses"
+ means
+ .
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+ .
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+ .
+ 1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+ .
+ 1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+ .
+ 1.8. "License"
+ means this document.
+ .
+ 1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+ .
+ 1.10. "Modifications"
+ means any of the following:
+ .
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+ .
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+ .
+ 1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+ .
+ 1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+ .
+ 1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+ .
+ 1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+ .
+ 2. License Grants and Conditions
+ --------------------------------
+ .
+ 2.1. Grants
+ .
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+ .
+ (a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+ .
+ (b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+ .
+ 2.2. Effective Date
+ .
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+ .
+ 2.3. Limitations on Grant Scope
+ .
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+ .
+ (a) for any code that a Contributor has removed from Covered Software;
+ or
+ .
+ (b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+ .
+ (c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+ .
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+ .
+ 2.4. Subsequent Licenses
+ .
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+ .
+ 2.5. Representation
+ .
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights
+ to grant the rights to its Contributions conveyed by this License.
+ .
+ 2.6. Fair Use
+ .
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+ .
+ 2.7. Conditions
+ .
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+ in Section 2.1.
+ .
+ 3. Responsibilities
+ -------------------
+ .
+ 3.1. Distribution of Source Form
+ .
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+ .
+ 3.2. Distribution of Executable Form
+ .
+ If You distribute Covered Software in Executable Form then:
+ .
+ (a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+ .
+ (b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+ .
+ 3.3. Distribution of a Larger Work
+ .
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+ .
+ 3.4. Notices
+ .
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty,
+ or limitations of liability) contained within the Source Code Form of
+ the Covered Software, except that You may alter any license notices to
+ the extent required to remedy known factual inaccuracies.
+ .
+ 3.5. Application of Additional Terms
+ .
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+ .
+ 4. Inability to Comply Due to Statute or Regulation
+ ---------------------------------------------------
+ .
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Software due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description must
+ be placed in a text file included with all distributions of the Covered
+ Software under this License. Except to the extent prohibited by statute
+ or regulation, such description must be sufficiently detailed for a
+ recipient of ordinary skill to be able to understand it.
+ .
+ 5. Termination
+ --------------
+ .
+ 5.1. The rights granted under this License will terminate automatically
+ if You fail to comply with any of its terms. However, if You become
+ compliant, then the rights granted under this License from a particular
+ Contributor are reinstated (a) provisionally, unless and until such
+ Contributor explicitly and finally terminates Your grants, and (b) on an
+ ongoing basis, if such Contributor fails to notify You of the
+ non-compliance by some reasonable means prior to 60 days after You have
+ come back into compliance. Moreover, Your grants from a particular
+ Contributor are reinstated on an ongoing basis if such Contributor
+ notifies You of the non-compliance by some reasonable means, this is the
+ first time You have received notice of non-compliance with this License
+ from such Contributor, and You become compliant prior to 30 days after
+ Your receipt of the notice.
+ .
+ 5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+ .
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+ end user license agreements (excluding distributors and resellers) which
+ have been validly granted by You or Your distributors under this License
+ prior to termination shall survive termination.
+ .
+ ************************************************************************
+ * *
+ * 6. Disclaimer of Warranty *
+ * ------------------------- *
+ * *
+ * Covered Software is provided under this License on an "as is" *
+ * basis, without warranty of any kind, either expressed, implied, or *
+ * statutory, including, without limitation, warranties that the *
+ * Covered Software is free of defects, merchantable, fit for a *
+ * particular purpose or non-infringing. The entire risk as to the *
+ * quality and performance of the Covered Software is with You. *
+ * Should any Covered Software prove defective in any respect, You *
+ * (not any Contributor) assume the cost of any necessary servicing, *
+ * repair, or correction. This disclaimer of warranty constitutes an *
+ * essential part of this License. No use of any Covered Software is *
+ * authorized under this License except under this disclaimer. *
+ * *
+ ************************************************************************
+ .
+ ************************************************************************
+ * *
+ * 7. Limitation of Liability *
+ * -------------------------- *
+ * *
+ * Under no circumstances and under no legal theory, whether tort *
+ * (including negligence), contract, or otherwise, shall any *
+ * Contributor, or anyone who distributes Covered Software as *
+ * permitted above, be liable to You for any direct, indirect, *
+ * special, incidental, or consequential damages of any character *
+ * including, without limitation, damages for lost profits, loss of *
+ * goodwill, work stoppage, computer failure or malfunction, or any *
+ * and all other commercial damages or losses, even if such party *
+ * shall have been informed of the possibility of such damages. This *
+ * limitation of liability shall not apply to liability for death or *
+ * personal injury resulting from such party's negligence to the *
+ * extent applicable law prohibits such limitation. Some *
+ * jurisdictions do not allow the exclusion or limitation of *
+ * incidental or consequential damages, so this exclusion and *
+ * limitation may not apply to You. *
+ * *
+ ************************************************************************
+ .
+ 8. Litigation
+ -------------
+ .
+ Any litigation relating to this License may be brought only in the
+ courts of a jurisdiction where the defendant maintains its principal
+ place of business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions.
+ Nothing in this Section shall prevent a party's ability to bring
+ cross-claims or counter-claims.
+ .
+ 9. Miscellaneous
+ ----------------
+ .
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides
+ that the language of a contract shall be construed against the drafter
+ shall not be used to construe this License against a Contributor.
+ .
+ 10. Versions of the License
+ ---------------------------
+ .
+ 10.1. New Versions
+ .
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+ .
+ 10.2. Effect of New Versions
+ .
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+ .
+ 10.3. Modified Versions
+ .
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+ .
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses
+ .
+ If You choose to distribute Source Code Form that is Incompatible With
+ Secondary Licenses under the terms of this version of the License, the
+ notice described in Exhibit B of this License must be attached.
+ .
+ Exhibit A - Source Code Form License Notice
+ -------------------------------------------
+ .
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ .
+ If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a LICENSE
+ file in a relevant directory) where a recipient would be likely to look
+ for such a notice.
+ .
+ You may add additional accurate notices of copyright ownership.
+ .
+ Exhibit B - "Incompatible With Secondary Licenses" Notice
+ ---------------------------------------------------------
+ .
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
+
+License: BSD-3-clause-Brush-Technology
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Brush Technology nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+License: Expat
+ 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.
+
+
+License: LGPL-2.1+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
+ .
+ On Debian systems, the full text of the GNU Lesser General Public
+ License version 2.1 can be found in the file
+ `/usr/share/common-licenses/LGPL-2.1'.
diff --git a/tools/debian/docs b/tools/debian/docs
new file mode 100644
index 000000000..77001cf9b
--- /dev/null
+++ b/tools/debian/docs
@@ -0,0 +1,2 @@
+README.md
+CHANGES
diff --git a/tools/debian/libmgba.install.in b/tools/debian/libmgba.install.in
new file mode 100644
index 000000000..f29fc8c85
--- /dev/null
+++ b/tools/debian/libmgba.install.in
@@ -0,0 +1 @@
+obj/libmgba.so* usr/lib/@DEB_HOST_MULTIARCH@/
diff --git a/tools/debian/libretro-mgba.install.in b/tools/debian/libretro-mgba.install.in
new file mode 100644
index 000000000..4f15c2df0
--- /dev/null
+++ b/tools/debian/libretro-mgba.install.in
@@ -0,0 +1 @@
+obj/mgba_libretro.so usr/lib/@DEB_HOST_MULTIARCH@/libretro
diff --git a/tools/debian/mgba-qt.install b/tools/debian/mgba-qt.install
new file mode 100644
index 000000000..7216bba6f
--- /dev/null
+++ b/tools/debian/mgba-qt.install
@@ -0,0 +1,3 @@
+usr/bin/mgba-qt
+res/mgba-qt.desktop usr/share/applications
+usr/share/icons
diff --git a/tools/debian/mgba-qt.manpages b/tools/debian/mgba-qt.manpages
new file mode 100644
index 000000000..2c3d1c5ed
--- /dev/null
+++ b/tools/debian/mgba-qt.manpages
@@ -0,0 +1 @@
+doc/mgba-qt.6
diff --git a/tools/debian/mgba-sdl.install b/tools/debian/mgba-sdl.install
new file mode 100644
index 000000000..104a1b436
--- /dev/null
+++ b/tools/debian/mgba-sdl.install
@@ -0,0 +1 @@
+usr/bin/mgba
diff --git a/tools/debian/mgba-sdl.manpages b/tools/debian/mgba-sdl.manpages
new file mode 100644
index 000000000..96e337fb7
--- /dev/null
+++ b/tools/debian/mgba-sdl.manpages
@@ -0,0 +1 @@
+doc/mgba.6
diff --git a/tools/debian/rules b/tools/debian/rules
new file mode 100755
index 000000000..ebe066d37
--- /dev/null
+++ b/tools/debian/rules
@@ -0,0 +1,27 @@
+#!/usr/bin/make -f
+
+# Copyright (C) 2015 Sergio Benjamim
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+ARCH=$(shell dpkg-architecture -qDEB_HOST_ARCH)
+
+ifeq ($(ARCH),armhf)
+ ARM=-DBUILD_GL=OFF -DBUILD_GLES2=ON
+endif
+
+%:
+ dh $@ --buildsystem=cmake --builddirectory=obj --parallel
+
+override_dh_auto_configure:
+ dh_auto_configure -- -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_SKIP_RPATH=ON -DBUILD_LIBRETRO=ON $(ARM)
+ sed 's/@DEB_HOST_MULTIARCH@/$(DEB_HOST_MULTIARCH)/g' \
+ debian/libretro-mgba.install.in > debian/libretro-mgba.install
+ sed 's/@DEB_HOST_MULTIARCH@/$(DEB_HOST_MULTIARCH)/g' \
+ debian/libmgba.install.in > debian/libmgba.install
+
+override_dh_installchangelogs:
+ dh_installchangelogs -k CHANGES
diff --git a/tools/debian/source/format b/tools/debian/source/format
new file mode 100644
index 000000000..163aaf8d8
--- /dev/null
+++ b/tools/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/tools/debian/watch b/tools/debian/watch
new file mode 100644
index 000000000..90bf499e4
--- /dev/null
+++ b/tools/debian/watch
@@ -0,0 +1,3 @@
+version=3
+opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/mgba-$1\.tar\.gz/ \
+ https://github.com/mgba-emu/mgba/tags .*/v?(\d\S*)\.tar\.gz
diff --git a/tools/sanitize-deb.sh b/tools/sanitize-deb.sh
index e1c523fb8..c63cd5bc3 100755
--- a/tools/sanitize-deb.sh
+++ b/tools/sanitize-deb.sh
@@ -56,8 +56,8 @@ while [ $# -gt 0 ]; do
sed -i~ "s/,$//g" deb-temp/DEBIAN/control
sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control
rm deb-temp/DEBIAN/control~
- chown -R 0:0 deb-temp
- chmod 600 deb-temp/DEBIAN/md5sums
+ chmod 644 deb-temp/DEBIAN/md5sums
+ chown -R root:root deb-temp
dpkg-deb -b deb-temp $DEB
rm -rf deb-temp
shift