all repos — mgba @ e17e4fd19003209ff9db1f0ac1394e463c7b4a47

mGBA Game Boy Advance Emulator

Merge branch 'master' into port/3ds
Jeffrey Pfau jeffrey@endrift.com
Mon, 17 Aug 2015 00:02:36 -0700
commit

e17e4fd19003209ff9db1f0ac1394e463c7b4a47

parent

3c18fe162c2c76eca80692d37083f6809bae4c8d

186 files changed, 7315 insertions(+), 3211 deletions(-)

jump to
A .clang-format

@@ -0,0 +1,34 @@

+Language: Cpp +BasedOnStyle: WebKit +AccessModifierOffset: -4 +AlignAfterOpenBracket: true +AlignEscapedNewlinesLeft: true +AlignOperands: false +AlignTrailingComments: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 120 +ConstructorInitializerIndentWidth: 4 +IndentCaseLabels: false +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PointerAlignment: Left +SpaceAfterCStyleCast: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpacesInContainerLiterals: true +SpaceInEmptyParentheses: false +SpacesInAngles: false +Standard: Cpp11 +TabWidth: 4 +UseTab: ForIndentation
M .gitignore.gitignore

@@ -1,3 +1,4 @@

/build +*.user* *~ *.swp
M CHANGESCHANGES

@@ -1,4 +1,4 @@

-0.3.0: (Future) +0.3.0: (2015-08-16) Features: - Ability to hide individual background layers, or OBJs - Ability to mute individual audio channels

@@ -21,61 +21,111 @@ - Ability to cap fast forward speed

- Finer control over FPS target - Holdable shortcut for rewinding one frame at a time - Ability to boot directly into the BIOS + - Preliminary support for yanking out the game pak while a game is running + - Thumb-drive mode by putting a file called portable.ini in the same folder + - Configurable display driver, between software and OpenGL + - Undo-able savestate loading and saving + - Controller profiles now store shortcut settings + - Default controller profiles for several common controllers + - 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 + - ARM7: Fix Thumb MUL timing + - ARM7: Fix timing of multiplies to use N cycles + - ARM7: ARMHotplugDetach should call deinit + - Debugger: Fix use-after-free in breakpoint clearing code + - GBA: Fix crash if a 512kb flash save is loaded when a game has a 1Mb flash override + - GBA: Cap audio FIFO read size during deserialization + - GBA: Check for corrupted savestates when loading + - GBA: Check for improperly sized savestates when loading + - GBA: Check for savestates made from differently sized ROMs + - GBA: Fix calls to endian-independent loadstores + - GBA: Fix timing of reading from timer registers + - GBA: Ensure cycles never go negative - GBA Audio: Force audio FIFOs to 32-bit + - GBA Audio: Fix audio pitch changing when adjusting buffer size + - GBA Audio: Fix sample order in audio channel 3 + - GBA Audio: Fix 8-bit writes to audio channel 3 frequency + - GBA Cheats: Fix Pro Action Replay and GameShark issues when used together - GBA Memory: Improve Thumb open bus behavior - - VFS: Fix resource leaks if some allocations fail - - Video: Fix an issue with very long filenames + - GBA Memory: Fix potential DMA issue when loading a savestate + - GBA Memory: Fix load/store multiple video memory waitstates + - GBA SIO: Fix reseting when there are SIO devices attached - GBA Video: Blended sprites should never have other effects applied - - GBA: Fix crash if a 512kb flash save is loaded when a game has a 1Mb flash override + - GBA Video: Fix out-of-bounds tiles in mosaic + - GBA Video: Fix windows not affecting sprites + - GBA Video: Prevent tiles < 512 from being used in modes 3 - 5 + - GBA Video: Fix timing on first scanline - Qt: Better cleanup when a game crashes - Qt: Fix open ROM dialog filtering for archive formats - - ARM7: Fix Thumb MUL timing - Qt: Cap the maximum number of multiplayer windows - Qt: Fix maximum year in sensor override - - GBA: Cap audio FIFO read size during deserialization - - GBA: Check for corrupted savestates when loading - - GBA: Check for improperly sized savestates when loading - - GBA: Check for savestates made from differently sized ROMs - - GBA Video: Fix out-of-bounds tiles in mosaic - - GBA Memory: Fix potential DMA issue when loading a savestate - - GBA Audio: Fix audio pitch changing when adjusting buffer size + - Qt: Fix window being too tall after exiting fullscreen + - Qt: Fix a missing va_end call in the log handler lambda within the GameController constructor + - Qt: Fix analog buttons not getting unmapped + - Qt: Fix passing command line options + - Qt: Fix crashes on Windows by using using QMetaObject to do cross-thread calls - SDL: Fix SDL build when OpenGL is missing - - ARM7: Fix timing of multiplies to use N cycles - - GBA: Fix calls to endian-independent loadstores - - GBA Video: Fix windows not affecting sprites + - Util: Allow loading IPS patches that grow the ROM + - VFS: Fix resource leaks if some allocations fail - VFS: Fix line-reading to return proper values + - Video: Fix an issue with very long filenames + - Util: Fix formatting of floats Misc: - - Qt: Handle saving input settings better - - Debugger: Free watchpoints in addition to breakpoints - - Qt: Move GL frame drawing back onto its own thread - - GBA: Add status log level - - GBA Thread: Add functionality for running callbacks on the GBA thread - - Qt: Fast forward (held) option moved from Other to Emulation menu - All: Add --help flag for command line programs - - Qt: Show version info in window title - All: Fix sanitize-deb script to set file permissions properly if run as (fake)root - - GBA SIO: Add a dummy driver for Normal mode - - GBA: GBARewind now returns how many states it has rewound - All: Enable static linking for Windows - All: Enable static linking for OS X + - All: Threads are now named + - All: Proper handling of Unicode file paths + - ARM7: Add emulation for Undefined CPU mode + - ARM7: Reduce the size of the Thumb instruction table + - GBA: Add status log level + - GBA: GBARewind now returns how many states it has rewound + - GBA: SIO logging layer + - GBA BIOS: Stub out SoundBias + - GBA: More accurate cycle estimation for ROM prefetch and flash save chips + - GBA: Don't include GBACLIDebugger struct unless needed + - GBA: Savedata is now synced shortly after data finishes being written + - GBA: Process multiple timer events at once, if necessary + - GBA Audio: Implement audio reset for channels A/B + - GBA Audio: Process multiple audio events at once, if necessary + - GBA Hardware: Backport generic RTC source into core + - GBA Input: Allow axes and buttons to be mapped to the same key + - GBA Memory: Run multiple DMAs in a tight loop if they all occur before present + - GBA SIO: Add a dummy driver for Normal mode + - GBA Thread: Add functionality for running callbacks on the GBA thread + - GBA Thread: Split GBASync into a separate file + - GBA Video: Refactor software renderer into separate files + - GBA Video: Slightly optimize mode 0 mosaic rendering + - Debugger: Free watchpoints in addition to breakpoints + - Qt: Handle saving input settings better + - Qt: Move GL frame drawing back onto its own thread + - Qt: Fast forward (held) option moved from Other to Emulation menu + - Qt: Show version info in window title - Qt: Migrate multiplayer window handling into GBAApp - Qt: Unified file opening and saving with last location - Qt: Fix windows being resizable when they shouldn't have been - Qt: Only hide cursor in full screen - - Perf: Ability to load savestates immediately on launch - Qt: Replace pause-after-frame mutex with an atomic - - Util: Allow disabling the threading code entirely - - GBA: SIO logging layer - Qt: Add application icon and XDG desktop files - - GBA Thread: Split GBASync into a separate file - - SDL: Properly check for initialization - - SDL: Clean up initialization functions - - All: Threads are now named - Qt: Rename "Fullscreen" to "Toggle fullscreen" - Qt: Don't save window size when entering fullscreen - Qt: Make the default fullscreen binding for Windows be Alt-Enter + - 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 + - Perf: Ability to load savestates immediately on launch + - SDL: Properly check for initialization + - SDL: Clean up initialization functions + - SDL: Clean up GL context + - Util: Allow disabling the threading code entirely + - VFS: Add sync method to force syncing with backing 0.2.1: (2015-05-13) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -1,7 +1,11 @@

cmake_minimum_required(VERSION 2.6) project(mGBA C) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99") +if(NOT MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99") +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146") +endif() set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger") set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger") set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")

@@ -17,13 +21,14 @@ 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) file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) -file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) +file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/*.c) file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)

@@ -39,10 +44,18 @@ 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}/lib") +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}") include(GNUInstallDirs) +if (NOT DEFINED LIBDIR) + set(LIBDIR "lib") +endif() + +if (NOT DEFINED MANDIR) + set(MANDIR ${CMAKE_INSTALL_MANDIR}) +endif() + # Function definitions include(FindPkgConfig) function(find_feature FEATURE_NAME FEATURE_REQUIRES)

@@ -72,52 +85,18 @@ endforeach()

endfunction() # Version information -set(LIB_VERSION_MAJOR 0) -set(LIB_VERSION_MINOR 3) -set(LIB_VERSION_PATCH 0) -set(LIB_VERSION_ABI 0.3) -set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) - -execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - -if(GIT_REV STREQUAL "") - set(GIT_REV -1) -endif() -if(NOT GIT_TAG STREQUAL "") - set(VERSION_STRING ${GIT_TAG}) -elseif(GIT_BRANCH STREQUAL "") - set(VERSION_STRING ${LIB_VERSION_STRING}) -else() - if(GIT_BRANCH STREQUAL "master") - set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT}) - else() - set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT}) - endif() - - if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH) - set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING}) - endif() -endif() - -add_custom_target(version-info ALL touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in +add_custom_target(version-info ALL ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/util/version.c.in COMMAND ${CMAKE_COMMAND} - -DGIT_COMMIT=${GIT_COMMIT} - -DGIT_COMMIT_SHORT=${GIT_COMMIT_SHORT} - -DGIT_BRANCH=${GIT_BRANCH} - -DGIT_REV=${GIT_REV} -DBINARY_NAME=${BINARY_NAME} - -DPROJECT_NAME=${PROJECT_NAME} - -DVERSION_STRING=${VERSION_STRING} - -D${BINARY_NAME}_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DCONFIG_FILE=${CMAKE_SOURCE_DIR}/src/util/version.c.in + -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/version.c -P ${CMAKE_SOURCE_DIR}/version.cmake WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) include(${CMAKE_SOURCE_DIR}/version.cmake) +configure_file(${CMAKE_SOURCE_DIR}/src/util/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c) list(APPEND UTIL_SRC ${CMAKE_BINARY_DIR}/version.c) +source_group("Generated sources" FILES ${CMAKE_BINARY_DIR}/version.c) # Advanced settings set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization")

@@ -151,7 +130,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")

@@ -163,7 +149,7 @@ # Platform support

if(WIN32) set(WIN32_VERSION "${LIB_VERSION_MAJOR},${LIB_VERSION_MINOR},${LIB_VERSION_PATCH}") add_definitions(-D_WIN32_WINNT=0x0600) - list(APPEND OS_LIB ws2_32) + list(APPEND OS_LIB ws2_32 shlwapi) list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c) source_group("Windows-specific code" FILES ${OS_SRC})

@@ -192,7 +178,7 @@ add_definitions(-D_DARWIN_C_SOURCE)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") endif() -if(NOT HAIKU) +if(NOT HAIKU AND NOT MSVC) list(APPEND OS_LIB m) endif()

@@ -207,6 +193,10 @@ add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)

endif() endif() +if(BUILD_RASPI) + set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE) +endif() + if(BUILD_PANDORA) add_definitions(-DBUILD_PANDORA) endif()

@@ -228,6 +218,7 @@ endif()

check_function_exists(newlocale HAVE_NEWLOCALE) check_function_exists(freelocale HAVE_FREELOCALE) check_function_exists(uselocale HAVE_USELOCALE) +check_function_exists(setlocale HAVE_SETLOCALE) if(HAVE_STRDUP) add_definitions(-DHAVE_STRDUP)

@@ -247,6 +238,9 @@ add_definitions(-DHAVE_SNPRINTF_L)

endif() endif() +if(HAVE_SETLOCALE) + add_definitions(-DHAVE_SETLOCALE) +endif() # Features set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)

@@ -364,6 +358,10 @@ foreach(FEATURE IN LISTS FEATURES)

list(APPEND FEATURE_DEFINES "USE_${FEATURE}") endforeach() +source_group("Virtual files" FILES ${VFS_SRC}) +source_group("Extra features" FILES ${FEATURE_SRC}) +source_group("Third-party code" FILES ${THIRD_PARTY_SRC}) + # Binaries set(CORE_SRC ${ARM_SRC}

@@ -389,10 +387,11 @@ 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}") - install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME}) + install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) add_dependencies(${BINARY_NAME}-static version-info) endif() else()

@@ -402,7 +401,7 @@

add_dependencies(${BINARY_NAME} version-info) target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB}) -install(TARGETS ${BINARY_NAME} DESTINATION lib 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})

@@ -418,6 +417,10 @@ set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}")

if(BUILD_GL) add_definitions(-DBUILD_GL) +endif() + +if(BUILD_GLES2) + add_definitions(-DBUILD_GLES2) endif() if(BUILD_LIBRETRO)
A PORTING.md

@@ -0,0 +1,41 @@

+Porting +======= + +Porting is preferentially done upstream so as to avoid fragmenting the codebase into individually unmergeable forks. As such, precaution must be taken to keep changes separate enough to not interfere with other ports, while still maintaining the ability to add new port-specific code seamlessly. + +Folders for each port should be under the `src/platform` folder, and make minimally invasive changes to the rest of the tree. If any changes are needed, try to make sure they are generic and have the ability to be ironed out in the future. For example, if a function doesn't work on a specific platform, maybe a way to make that function more portable should be added. + +The general porting process involves branching `master`, making the needed changes, and, when the port is mature enough to not have major effects to other ports, merged into `port/crucible`. The crucible is used for mixing upcoming ports to make sure they aren't fragile when `master` merges into it every so often. At this time, the crucible hasn't yet been merged into `master`, but in the future this may occur regularly. Until then, if a port is to get merged into master, make sure the changes to each port occur on the port-specific branch before being merged into `port/crucible`. + +Port-specific TODO +------------------ + +The ports are vaguely usable, but by no means should be considered stable. + +### 3DS (port/3ds) +* Add menu +* Add audio +* Thread support testing +* Make it faster + * ARMv6 dynarec + * Hardware acceleration + +### PSP (port/psp) +* Add menu +* Add audio +* Thread support +* Make it faster + * MIPS dynarec + * Hardware acceleration + +### PS Vita (port/psp2) +* Add menu +* Fix audio +* Make it faster + * Threaded renderer shim + * Hardware acceleration + +### Wii (port/wii) +* Add menu +* Thread support +* Clean up video detection
M README.mdREADME.md

@@ -15,6 +15,7 @@ - Fast emulation. Known to run at full speed even on low end hardware, such as netbooks.

- Qt and SDL ports for a heavy-weight and a light-weight frontend. - Local (same computer) link cable support. - Save type detection, even for flash memory size[<sup>[2]</sup>](#flashdetect). +- Support for cartridges with motion sensors and rumble (only usable with game controllers). - Real-time clock support, even without configuration. - A built-in BIOS implementation, and ability to load external BIOS files. - Turbo/fast-forward support by holding Tab.

@@ -26,8 +27,9 @@ - Video and GIF recording.

- Remappable controls for both keyboards and gamepads. - Loading from ZIP and 7z files. - IPS, UPS and BPS patch support. -- Game debugging via a command-line interface (not available with Qt port) and GDB remote support. +- Game debugging via a command-line interface (not available with Qt port) and GDB remote support, compatible with IDA Pro. - Configurable emulation rewinding. +- Support for loading and exporting GameShark and Action Replay snapshots. ### Planned features

@@ -74,7 +76,7 @@

Compiling --------- -Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. To use CMake to build on a Unix-based system, the recommended commands are as follows: +Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. Support for Visual Studio 2015 and newer is coming soon. To use CMake to build on a Unix-based system, the recommended commands are as follows: mkdir build cd build

@@ -84,6 +86,26 @@ sudo make install

This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them. +#### Windows developer building + +To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MinGW-w64 Win32 Shell") and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 500MiB of packages, so it will take a long time): + + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + +Check out the source code by running this command: + + git clone https://github.com/mgba-emu/mgba.git + +Then finally build it by running these commands: + + cd mgba + mkdir build + cd build + cmake .. -G "MSYS Makefiles" + make + +Please note that this build of mGBA for Windows is not suitable for distribution, due to the scattering of DLLs it needs to run, but is perfect for development. + ### Dependencies mGBA has no hard dependencies, however, the following optional dependencies are required for specific features. The features will be disabled if the dependencies can't be found.

@@ -104,9 +126,6 @@

- 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)) -- Audio channel reset flags ([Bug #142](http://mgba.io/b/142)) -- Game Pak prefetch ([Bug #195](http://mgba.io/b/195)) -- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199)) <a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.

@@ -124,4 +143,5 @@ mGBA contains the following third-party libraries:

- [inih](https://code.google.com/p/inih/), which is copyright © 2009 Brush Technology and used under a BSD 3-clause license. - [blip-buf](https://code.google.com/p/blip-buf/), which is copyright © 2003 – 2009 Shay Green and used under a Lesser GNU Public License. -- [LZMA SDK](http://www.7-zip.org/sdk.html), which is public doman. +- [LZMA SDK](http://www.7-zip.org/sdk.html), which is public domain. +- [MurmurHash3](https://code.google.com/p/smhasher/wiki/MurmurHash3) implementation by Austin Appleby, which is public domain.
A doc/mgba-qt.6

@@ -0,0 +1,118 @@

+.\" Copyright (c) 2015 Anthony J. Bentley <anthony@anjbe.name> +.\" +.\" 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
A doc/mgba.6

@@ -0,0 +1,259 @@

+.\" Copyright (c) 2015 Anthony J. Bentley <anthony@anjbe.name> +.\" +.\" 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
M res/keymap.svgres/keymap.svg

@@ -7,130 +7,266 @@ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="239.9995" y1="0" x2="239.9995" y2="480.0005">

<stop offset="0" style="stop-color:#7A65F5"/> <stop offset="1" style="stop-color:#302575"/> </linearGradient> -<path fill="url(#SVGID_1_)" d="M480,450c0,16.5684-13.4316,30-30,30H30c-16.5684,0-30-13.4316-30-30V30C0,13.4316,13.4316,0,30,0 - h420c16.5684,0,30,13.4316,30,30V450z"/> -<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="239.9995" y1="-479" x2="239.9994" y2="1159.0004"> - <stop offset="0" style="stop-color:#7A65F5"/> - <stop offset="1" style="stop-color:#302575"/> +<path fill="url(#SVGID_1_)" d="M480,450c0,16.568-13.432,30-30,30H30c-16.568,0-30-13.432-30-30V30C0,13.432,13.432,0,30,0h420 + c16.568,0,30,13.432,30,30V450z"/> +<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="239.9995" y1="-479.0015" x2="239.9994" y2="1158.9995"> + <stop offset="0" style="stop-color:#7762F5"/> + <stop offset="1" style="stop-color:#372E75"/> </linearGradient> -<path fill="url(#SVGID_2_)" d="M30,473c-12.6821,0-23-10.3174-23-23V30C7,17.3179,17.3179,7,30,7h420c12.6826,0,23,10.3179,23,23 - v420c0,12.6826-10.3174,23-23,23H30z"/> -<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="118.3335" y1="291.667" x2="118.3335" y2="47.9991"> - <stop offset="0" style="stop-color:#7A65F5"/> +<path fill="url(#SVGID_2_)" d="M30,473c-12.682,0-23-10.317-23-23V30C7,17.318,17.318,7,30,7h420c12.683,0,23,10.318,23,23v420 + c0,12.683-10.317,23-23,23H30z"/> +<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="118.3335" y1="291.667" x2="118.3335" y2="47.9995"> + <stop offset="0" style="stop-color:#7466CF"/> <stop offset="1" style="stop-color:#302575"/> </linearGradient> -<path fill="url(#SVGID_3_)" d="M118.3335,122.333c-46.7603,0-84.667,37.9067-84.667,84.667c0,46.7607,37.9067,84.667,84.667,84.667 - s84.667-37.9062,84.667-84.667C203.0005,160.2397,165.0938,122.333,118.3335,122.333z M118.334,278.7344 - c-39.6172,0-71.7339-32.1172-71.7339-71.7344c0-39.6177,32.1167-71.7339,71.7339-71.7339S190.0679,167.3823,190.0679,207 - C190.0679,246.6172,157.9512,278.7344,118.334,278.7344z"/> +<path fill="url(#SVGID_3_)" d="M118.333,122.333c-46.76,0-84.667,37.907-84.667,84.667c0,46.761,37.907,84.667,84.667,84.667 + S203,253.761,203,207C203,160.24,165.094,122.333,118.333,122.333z M118.334,278.734C78.717,278.734,46.6,246.617,46.6,207 + c0-39.618,32.117-71.734,71.734-71.734s71.734,32.116,71.734,71.734C190.068,246.617,157.951,278.734,118.334,278.734z"/> <g> - <radialGradient id="SVGID_4_" cx="118.8335" cy="138.167" r="103.2725" fx="165.1467" fy="138.167" gradientTransform="matrix(-4.371139e-08 1 -1.42 -6.206884e-08 315.0264 19.3335)" gradientUnits="userSpaceOnUse"> + <radialGradient id="SVGID_4_" cx="118.8335" cy="138.167" r="103.2721" fx="165.1465" fy="138.167" gradientTransform="matrix(-4.371139e-08 1 -1.42 -6.206886e-08 315.0265 19.3335)" gradientUnits="userSpaceOnUse"> <stop offset="0" style="stop-color:#9C9CB3"/> <stop offset="1" style="stop-color:#333045"/> </radialGradient> - <path fill="url(#SVGID_4_)" d="M172.1973,207c0-9.667-3.0029-17.333-3.0029-17.333H135.667v-33.5283 - c0,0-7.667-3.0029-17.3335-3.0029s-17.333,3.0029-17.333,3.0029v33.5283H67.4727c0,0-3.0029,7.666-3.0029,17.333 - c0,9.666,3.0029,17.333,3.0029,17.333h33.5278v33.5273c0,0,7.6665,3.0039,17.333,3.0039s17.3335-3.0039,17.3335-3.0039V224.333 - h33.5273C169.1943,224.333,172.1973,216.666,172.1973,207z"/> - <path fill="#5C567D" d="M118.3335,258.8643c-6.9185,0-12.833-1.625-15.333-2.4287V222.333h-34.103 - c-0.8027-2.5-2.4277-8.4146-2.4277-15.333s1.6245-12.833,2.4277-15.333h34.103v-34.1035c2.5-0.8027,8.4146-2.4277,15.333-2.4277 - c6.9453,0,12.8408,1.6226,15.3335,2.4258v34.1055h34.1025c0.8032,2.5,2.4277,8.4146,2.4277,15.333 - c0,6.9448-1.6226,12.8403-2.4258,15.333H133.667v34.1025C131.167,257.2393,125.252,258.8643,118.3335,258.8643z"/> - <radialGradient id="SVGID_5_" cx="118.333" cy="220.397" r="69.7522" gradientUnits="userSpaceOnUse"> + <path fill="url(#SVGID_4_)" d="M172.197,207c0-9.667-3.003-17.333-3.003-17.333h-33.527v-33.528c0,0-7.667-3.003-17.333-3.003 + S101,156.139,101,156.139v33.528H67.473c0,0-3.003,7.666-3.003,17.333c0,9.666,3.003,17.333,3.003,17.333H101v33.527 + c0,0,7.667,3.004,17.333,3.004s17.333-3.004,17.333-3.004v-33.527h33.527C169.194,224.333,172.197,216.666,172.197,207z"/> + <path fill="#5C567D" d="M118.333,258.864c-6.918,0-12.833-1.625-15.333-2.429v-34.103H68.897c-0.803-2.5-2.428-8.415-2.428-15.333 + s1.625-12.833,2.428-15.333H103v-34.104c2.5-0.803,8.415-2.428,15.333-2.428c6.945,0,12.841,1.623,15.333,2.426v34.105h34.103 + c0.803,2.5,2.428,8.415,2.428,15.333c0,6.945-1.623,12.84-2.426,15.333h-34.104v34.103 + C131.167,257.239,125.252,258.864,118.333,258.864z"/> + <radialGradient id="SVGID_5_" cx="118.333" cy="220.397" r="69.7523" gradientUnits="userSpaceOnUse"> <stop offset="0" style="stop-color:#706F8A"/> <stop offset="0.9951" style="stop-color:#2D2842"/> </radialGradient> - <circle fill="url(#SVGID_5_)" cx="118.333" cy="207" r="21.2749"/> + <circle fill="url(#SVGID_5_)" cx="118.333" cy="207" r="21.275"/> </g> <g> <g> - <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="198.9673" y1="490" x2="198.9673" y2="384.981"> + <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="198.9673" y1="490" x2="198.9673" y2="384.9809"> <stop offset="0" style="stop-color:#7A65F5"/> <stop offset="1" style="stop-color:#302575"/> </linearGradient> - <path fill="url(#SVGID_6_)" d="M198.9678,387.0303c-18.8418,0-34.1162,15.2734-34.1162,34.1162 - c0,18.8418,15.2744,34.1152,34.1162,34.1152c18.8408,0,34.1152-15.2734,34.1152-34.1152 - C233.083,402.3037,217.8086,387.0303,198.9678,387.0303z M198.9678,445.7871c-13.6089,0-24.6401-11.0332-24.6401-24.6406 - c0-13.6094,11.0312-24.6406,24.6401-24.6406c13.6074,0,24.6396,11.0312,24.6396,24.6406 - C223.6074,434.7539,212.5752,445.7871,198.9678,445.7871z"/> + <path fill="url(#SVGID_6_)" d="M198.968,387.03c-18.842,0-34.116,15.273-34.116,34.116c0,18.842,15.274,34.115,34.116,34.115 + c18.841,0,34.115-15.273,34.115-34.115C233.083,402.304,217.809,387.03,198.968,387.03z M198.968,445.787 + c-13.609,0-24.64-11.033-24.64-24.641c0-13.609,11.031-24.641,24.64-24.641c13.607,0,24.64,11.031,24.64,24.641 + C223.607,434.754,212.575,445.787,198.968,445.787z"/> <g> <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="198.9673" y1="437.4414" x2="198.9673" y2="404.8516"> <stop offset="0" style="stop-color:#333045"/> <stop offset="1" style="stop-color:#9C9CB3"/> </linearGradient> - <circle fill="url(#SVGID_7_)" cx="198.9673" cy="421.1465" r="16.2949"/> - <circle fill="#5C567D" cx="198.9673" cy="421.1465" r="15.0737"/> + <circle fill="url(#SVGID_7_)" cx="198.967" cy="421.146" r="16.295"/> + <circle fill="#5C567D" cx="198.967" cy="421.146" r="15.074"/> </g> </g> <g> - <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="281.0312" y1="490" x2="281.0312" y2="384.981"> + <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="281.0312" y1="490" x2="281.0312" y2="384.9809"> <stop offset="0" style="stop-color:#7A65F5"/> <stop offset="1" style="stop-color:#302575"/> </linearGradient> - <path fill="url(#SVGID_8_)" d="M281.0322,387.0303c-18.8418,0-34.1162,15.2734-34.1162,34.1162 - c0,18.8418,15.2744,34.1152,34.1162,34.1152c18.8408,0,34.1152-15.2734,34.1152-34.1152 - C315.1475,402.3037,299.873,387.0303,281.0322,387.0303z M281.0322,445.7871c-13.6084,0-24.6396-11.0332-24.6396-24.6406 - c0-13.6094,11.0312-24.6406,24.6396-24.6406c13.6074,0,24.6396,11.0312,24.6396,24.6406 - C305.6719,434.7539,294.6396,445.7871,281.0322,445.7871z"/> + <path fill="url(#SVGID_8_)" d="M281.032,387.03c-18.842,0-34.116,15.273-34.116,34.116c0,18.842,15.274,34.115,34.116,34.115 + c18.841,0,34.115-15.273,34.115-34.115C315.147,402.304,299.873,387.03,281.032,387.03z M281.032,445.787 + c-13.608,0-24.64-11.033-24.64-24.641c0-13.609,11.031-24.641,24.64-24.641c13.607,0,24.64,11.031,24.64,24.641 + C305.672,434.754,294.64,445.787,281.032,445.787z"/> <g> <linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="281.0322" y1="437.4414" x2="281.0322" y2="404.8516"> <stop offset="0" style="stop-color:#333045"/> <stop offset="1" style="stop-color:#9C9CB3"/> </linearGradient> - <circle fill="url(#SVGID_9_)" cx="281.0322" cy="421.1465" r="16.2949"/> - <circle fill="#5C567D" cx="281.0317" cy="421.1465" r="15.0737"/> + <circle fill="url(#SVGID_9_)" cx="281.032" cy="421.146" r="16.295"/> + <circle fill="#5C567D" cx="281.032" cy="421.146" r="15.074"/> </g> </g> </g> -<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="356.1787" y1="280.2178" x2="356.1787" y2="134.9952"> - <stop offset="0" style="stop-color:#7A65F5"/> +<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="356.1787" y1="280.2178" x2="356.1787" y2="134.9951"> + <stop offset="0" style="stop-color:#7466CF"/> <stop offset="1" style="stop-color:#302575"/> </linearGradient> -<path fill="url(#SVGID_10_)" d="M437.9521,179.5503c-7.0088-23.9434-32.0986-37.6724-56.043-30.6646l-76.8369,22.4912 - c-23.9453,7.0073-37.6748,32.0991-30.666,56.043c7.0088,23.9443,32.0996,37.6719,56.0449,30.6621l76.8369-22.4902 - C431.2314,228.585,444.96,203.4946,437.9521,179.5503z M426.3311,209.6025c-4.6377,8.4756-12.2979,14.6382-21.5703,17.3516 - l-76.8379,22.4902c-3.3301,0.9746-6.7559,1.4688-10.1816,1.4688c-0.001,0-0.001,0-0.002,0 - c-15.9443-0.001-30.2109-10.7012-34.6953-26.0215c-2.7148-9.2729-1.6553-19.0479,2.9824-27.5244 - c4.6387-8.4761,12.2998-14.6387,21.5732-17.3525l76.8379-22.4912c3.3281-0.9741,6.7539-1.4683,10.1807-1.4683 - c15.9434,0,30.2109,10.7012,34.6963,26.0234C432.0283,191.3516,430.9688,201.1265,426.3311,209.6025z"/> +<path fill="url(#SVGID_10_)" d="M437.952,179.55c-7.009-23.943-32.099-37.672-56.043-30.665l-76.837,22.491 + c-23.945,7.007-37.675,32.099-30.666,56.043c7.009,23.944,32.1,37.672,56.045,30.662l76.837-22.49 + C431.231,228.585,444.96,203.495,437.952,179.55z M426.331,209.603c-4.638,8.476-12.298,14.638-21.57,17.352l-76.838,22.49 + c-3.33,0.975-6.756,1.469-10.182,1.469c-0.001,0-0.001,0-0.001,0c-15.945-0.001-30.212-10.701-34.696-26.021 + c-2.715-9.273-1.655-19.048,2.982-27.524c4.639-8.476,12.3-14.639,21.573-17.353l76.838-22.491 + c3.328-0.974,6.754-1.468,10.181-1.468c15.943,0,30.211,10.701,34.696,26.023C432.028,191.352,430.969,201.126,426.331,209.603z"/> <g> <linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="395.9062" y1="217.8188" x2="395.9062" y2="166.6519"> <stop offset="0" style="stop-color:#333045"/> <stop offset="1" style="stop-color:#9C9CB3"/> </linearGradient> - <circle fill="url(#SVGID_11_)" cx="395.9062" cy="192.2358" r="25.584"/> - <circle fill="#5C567D" cx="395.9072" cy="192.2358" r="23.666"/> + <circle fill="url(#SVGID_11_)" cx="395.906" cy="192.236" r="25.584"/> + <circle fill="#5C567D" cx="395.907" cy="192.236" r="23.666"/> </g> <g> <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="318.9785" y1="239.6406" x2="318.9785" y2="188.4722"> <stop offset="0" style="stop-color:#333045"/> <stop offset="1" style="stop-color:#9C9CB3"/> </linearGradient> - <circle fill="url(#SVGID_12_)" cx="318.979" cy="214.0571" r="25.5835"/> - <circle fill="#5C567D" cx="318.98" cy="214.0571" r="23.6665"/> + <circle fill="url(#SVGID_12_)" cx="318.979" cy="214.057" r="25.583"/> + <circle fill="#5C567D" cx="318.98" cy="214.057" r="23.667"/> </g> <g> - <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="-15.0425" y1="77.9346" x2="55.6245" y2="-5.3989"> + <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="-15.0435" y1="77.9331" x2="55.6236" y2="-5.4006"> <stop offset="0" style="stop-color:#333045"/> <stop offset="1" style="stop-color:#9C9CB3"/> </linearGradient> - <path fill="url(#SVGID_13_)" d="M33.6665,0H61.5c2.9697,1.0112,2.3306,7,2.3306,7l0.0581,8.4844 - C63.8887,31.3535,48.9922,50,27.9922,50H7.0068c0,0-5.2197,2.312-7.0068-8.0029V30C0,11.5,12.333,0,33.6665,0z"/> - <path fill="#5C567D" d="M9.7783,49.1743c-0.2793-0.251-1.166-1.2764-1.7744-4.5308V33.0029c0-16.5234,10.813-26,29.6665-26h26.0854 - c0.1318,0.709,0.1821,1.7549,0.0996,2.5908L63.835,9.8032l0.0576,8.7114c0,14.3774-13.6406,30.4883-31.8965,30.4883H10.1646 - L9.7783,49.1743z"/> + <path fill="url(#SVGID_13_)" d="M33.667,0H61.5c2.97,1.011,2.331,7,2.331,7l0.058,8.484C63.889,31.354,48.992,50,27.992,50H7.007 + c0,0-5.22,2.312-7.007-8.003V30C0,11.5,12.333,0,33.667,0z"/> + <path fill="#5C567D" d="M9.778,49.174c-0.279-0.251-1.166-1.276-1.774-4.531V33.003c0-16.523,10.813-26,29.667-26h26.085 + c0.132,0.709,0.182,1.755,0.1,2.591l-0.021,0.209l0.058,8.711c0,14.377-13.641,30.488-31.896,30.488H10.165L9.778,49.174z"/> </g> <g> - <linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="401.0615" y1="77.9336" x2="471.7285" y2="-5.3999" gradientTransform="matrix(-1 0 0 1 896.1055 0)"> + <linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="401.0605" y1="77.9331" x2="471.7277" y2="-5.4007" gradientTransform="matrix(-1 0 0 1 896.1055 0)"> <stop offset="0" style="stop-color:#333045"/> <stop offset="1" style="stop-color:#9C9CB3"/> </linearGradient> - <path fill="url(#SVGID_14_)" d="M446.334,0h-27.833c-2.9697,1.0112-2.3311,7-2.3311,7l-0.0576,8.4844 - C416.1123,31.3535,431.0088,50,452.0088,50h20.9854c0,0,5.2197,2.312,7.0068-8.0029V30C480.001,11.5,467.668,0,446.334,0z"/> - <path fill="#5C567D" d="M470.2227,49.1743c0.2793-0.251,1.166-1.2764,1.7744-4.5308V33.0029c0-16.5234-10.8135-26-29.667-26 - h-26.085c-0.1318,0.709-0.1826,1.7549-0.0996,2.5908l0.0205,0.2095l-0.0576,8.7114c0,14.3774,13.6406,30.4883,31.8965,30.4883 - h21.8311L470.2227,49.1743z"/> + <path fill="url(#SVGID_14_)" d="M446.334,0h-27.833c-2.97,1.011-2.331,7-2.331,7l-0.058,8.484 + c0,15.869,14.896,34.516,35.896,34.516h20.985c0,0,5.22,2.312,7.007-8.003V30C480.001,11.5,467.668,0,446.334,0z"/> + <path fill="#5C567D" d="M470.223,49.174c0.279-0.251,1.166-1.276,1.774-4.531V33.003c0-16.523-10.813-26-29.667-26h-26.085 + c-0.132,0.709-0.183,1.755-0.1,2.591l0.021,0.209l-0.058,8.711c0,14.377,13.641,30.488,31.896,30.488h21.831L470.223,49.174z"/> +</g> +<g opacity="0.5"> + <path fill="#9C9CB3" stroke="#9C9CB3" stroke-miterlimit="10" d="M312.197,226v-21.475h8.057c1.641,0,2.956,0.217,3.947,0.652 + s1.768,1.104,2.33,2.007c0.561,0.903,0.842,1.848,0.842,2.834c0,0.918-0.249,1.782-0.747,2.593s-1.25,1.465-2.256,1.963 + c1.299,0.381,2.297,1.03,2.995,1.948s1.048,2.002,1.048,3.252c0,1.006-0.212,1.941-0.638,2.805c-0.424,0.864-0.949,1.531-1.574,2 + s-1.409,0.823-2.352,1.062S321.753,226,320.386,226H312.197z M315.039,213.549h4.644c1.26,0,2.163-0.083,2.71-0.249 + c0.723-0.215,1.268-0.571,1.633-1.069c0.367-0.498,0.55-1.123,0.55-1.875c0-0.713-0.171-1.34-0.513-1.882s-0.83-0.913-1.465-1.113 + s-1.724-0.3-3.267-0.3h-4.292V213.549z M315.039,223.466h5.347c0.918,0,1.562-0.034,1.934-0.103 + c0.654-0.117,1.201-0.312,1.641-0.586s0.801-0.671,1.084-1.194s0.425-1.125,0.425-1.809c0-0.801-0.205-1.497-0.615-2.087 + s-0.979-1.006-1.707-1.245s-1.774-0.359-3.142-0.359h-4.966V223.466z"/> +</g> +<g opacity="0.5"> + <path fill="#9C9CB3" stroke="#9C9CB3" stroke-miterlimit="10" d="M385.956,203l8.247-21.475h3.062L406.054,203h-3.237l-2.505-6.504 + h-8.979L388.974,203H385.956z M392.152,194.182h7.28l-2.241-5.947c-0.684-1.807-1.191-3.291-1.523-4.453 + c-0.273,1.377-0.659,2.744-1.157,4.102L392.152,194.182z"/> +</g> +<g> + <path fill="#333045" d="M312.197,224v-21.475h8.057c1.641,0,2.956,0.217,3.947,0.652s1.768,1.104,2.33,2.007 + c0.561,0.903,0.842,1.848,0.842,2.834c0,0.918-0.249,1.782-0.747,2.593s-1.25,1.465-2.256,1.963 + c1.299,0.381,2.297,1.03,2.995,1.948s1.048,2.002,1.048,3.252c0,1.006-0.212,1.941-0.638,2.805c-0.424,0.864-0.949,1.531-1.574,2 + s-1.409,0.823-2.352,1.062S321.753,224,320.386,224H312.197z M315.039,211.549h4.644c1.26,0,2.163-0.083,2.71-0.249 + c0.723-0.215,1.268-0.571,1.633-1.069c0.367-0.498,0.55-1.123,0.55-1.875c0-0.713-0.171-1.34-0.513-1.882s-0.83-0.913-1.465-1.113 + s-1.724-0.3-3.267-0.3h-4.292V211.549z M315.039,221.466h5.347c0.918,0,1.562-0.034,1.934-0.103 + c0.654-0.117,1.201-0.312,1.641-0.586s0.801-0.671,1.084-1.194s0.425-1.125,0.425-1.809c0-0.801-0.205-1.497-0.615-2.087 + s-0.979-1.006-1.707-1.245s-1.774-0.359-3.142-0.359h-4.966V221.466z"/> +</g> +<g> + <path fill="#333045" d="M385.956,201l8.247-21.475h3.062L406.054,201h-3.237l-2.505-6.504h-8.979L388.974,201H385.956z + M392.152,192.182h7.28l-2.241-5.947c-0.684-1.807-1.191-3.291-1.523-4.453c-0.273,1.377-0.659,2.744-1.157,4.102L392.152,192.182z + "/> +</g> +<g opacity="0.5"> + <g> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M320.35,425.264l2.658-0.258 + c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627c0.88,0,1.543-0.186,1.988-0.559 + c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586 + c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723 + c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117 + c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457 + c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822 + c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375 + c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508 + c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C321.076,427.93,320.528,426.777,320.35,425.264z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M336.694,429.666v-11.24h-4.015v-2.289h10.751 + v2.289h-4.005v11.24H336.694z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M355.99,429.666h-2.972l-1.182-3.072h-5.407 + l-1.117,3.072h-2.897l5.27-13.529h2.889L355.99,429.666z M350.961,424.314l-1.864-5.021l-1.827,5.021H350.961z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M357.449,429.666v-13.529h5.749 + c1.445,0,2.496,0.123,3.151,0.365c0.655,0.244,1.18,0.676,1.573,1.297s0.591,1.332,0.591,2.131c0,1.016-0.299,1.854-0.896,2.516 + s-1.488,1.078-2.676,1.25c0.591,0.346,1.078,0.723,1.463,1.135c0.384,0.412,0.902,1.145,1.555,2.197l1.652,2.639h-3.268 + l-1.975-2.943c-0.701-1.053-1.182-1.715-1.439-1.988c-0.259-0.273-0.532-0.463-0.821-0.562c-0.289-0.102-0.748-0.152-1.375-0.152 + h-0.554v5.646H357.449z M360.181,421.859h2.021c1.311,0,2.129-0.055,2.455-0.166s0.581-0.301,0.766-0.572s0.277-0.609,0.277-1.016 + c0-0.455-0.122-0.822-0.364-1.102c-0.243-0.281-0.587-0.457-1.029-0.531c-0.222-0.031-0.886-0.047-1.993-0.047h-2.132V421.859z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M374.134,429.666v-11.24h-4.015v-2.289h10.751 + v2.289h-4.005v11.24H374.134z"/> + </g> +</g> +<g opacity="0.5"> + <g> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M87.35,425.264l2.658-0.258 + c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627c0.88,0,1.543-0.186,1.988-0.559 + c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586 + c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723 + c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117 + c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457 + c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822 + c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375 + c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508 + c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C88.076,427.93,87.528,426.777,87.35,425.264z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M100.648,429.666v-13.529h10.031v2.289h-7.3v3 + h6.792v2.279h-6.792v3.682h7.559v2.279H100.648z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M113.328,429.666v-13.418h2.731v11.139h6.792 + v2.279H113.328z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M124.799,429.666v-13.529h10.031v2.289h-7.3v3 + h6.792v2.279h-6.792v3.682h7.559v2.279H124.799z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M146.062,424.691l2.648,0.84 + c-0.406,1.477-1.081,2.574-2.025,3.291s-2.143,1.074-3.595,1.074c-1.796,0-3.272-0.613-4.43-1.84 + c-1.156-1.229-1.734-2.906-1.734-5.035c0-2.252,0.581-4,1.744-5.246c1.162-1.246,2.691-1.869,4.586-1.869 + c1.655,0,3,0.49,4.033,1.469c0.615,0.578,1.076,1.408,1.384,2.49l-2.703,0.646c-0.16-0.701-0.494-1.256-1.002-1.66 + c-0.507-0.406-1.124-0.609-1.85-0.609c-1.003,0-1.817,0.359-2.441,1.08c-0.624,0.719-0.937,1.885-0.937,3.496 + c0,1.711,0.308,2.93,0.923,3.654c0.615,0.727,1.415,1.09,2.399,1.09c0.726,0,1.351-0.23,1.873-0.691 + C145.459,426.408,145.834,425.684,146.062,424.691z"/> + <path fill="#1B0942" stroke="#1B0942" stroke-width="2" stroke-miterlimit="10" d="M154.101,429.666v-11.24h-4.015v-2.289h10.751 + v2.289h-4.005v11.24H154.101z"/> + </g> +</g> +<g> + <g> + <path fill="#7A65F5" d="M320.35,423.264l2.658-0.258c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627 + c0.88,0,1.543-0.186,1.988-0.559c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586 + c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723 + c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117 + c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457 + c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822 + c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375 + c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508 + c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C321.076,425.93,320.528,424.777,320.35,423.264z"/> + <path fill="#7A65F5" d="M336.694,427.666v-11.24h-4.015v-2.289h10.751v2.289h-4.005v11.24H336.694z"/> + <path fill="#7A65F5" d="M355.99,427.666h-2.972l-1.182-3.072h-5.407l-1.117,3.072h-2.897l5.27-13.529h2.889L355.99,427.666z + M350.961,422.314l-1.864-5.021l-1.827,5.021H350.961z"/> + <path fill="#7A65F5" d="M357.449,427.666v-13.529h5.749c1.445,0,2.496,0.123,3.151,0.365c0.655,0.244,1.18,0.676,1.573,1.297 + s0.591,1.332,0.591,2.131c0,1.016-0.299,1.854-0.896,2.516s-1.488,1.078-2.676,1.25c0.591,0.346,1.078,0.723,1.463,1.135 + c0.384,0.412,0.902,1.145,1.555,2.197l1.652,2.639h-3.268l-1.975-2.943c-0.701-1.053-1.182-1.715-1.439-1.988 + c-0.259-0.273-0.532-0.463-0.821-0.562c-0.289-0.102-0.748-0.152-1.375-0.152h-0.554v5.646H357.449z M360.181,419.859h2.021 + c1.311,0,2.129-0.055,2.455-0.166s0.581-0.301,0.766-0.572s0.277-0.609,0.277-1.016c0-0.455-0.122-0.822-0.364-1.102 + c-0.243-0.281-0.587-0.457-1.029-0.531c-0.222-0.031-0.886-0.047-1.993-0.047h-2.132V419.859z"/> + <path fill="#7A65F5" d="M374.134,427.666v-11.24h-4.015v-2.289h10.751v2.289h-4.005v11.24H374.134z"/> + </g> +</g> +<g> + <g> + <path fill="#7A65F5" d="M87.35,423.264l2.658-0.258c0.16,0.893,0.484,1.547,0.974,1.967c0.489,0.418,1.148,0.627,1.979,0.627 + c0.88,0,1.543-0.186,1.988-0.559c0.446-0.373,0.669-0.807,0.669-1.307c0-0.318-0.094-0.592-0.281-0.816s-0.515-0.42-0.982-0.586 + c-0.32-0.111-1.049-0.307-2.188-0.59c-1.464-0.363-2.491-0.809-3.082-1.338c-0.831-0.744-1.246-1.652-1.246-2.723 + c0-0.689,0.195-1.334,0.586-1.934s0.954-1.057,1.689-1.371c0.734-0.312,1.622-0.471,2.662-0.471c1.698,0,2.977,0.373,3.834,1.117 + c0.858,0.744,1.31,1.738,1.353,2.98l-2.731,0.121c-0.117-0.695-0.368-1.195-0.753-1.5c-0.384-0.305-0.961-0.457-1.729-0.457 + c-0.794,0-1.416,0.162-1.864,0.488c-0.29,0.209-0.435,0.49-0.435,0.84c0,0.32,0.136,0.594,0.406,0.822 + c0.345,0.289,1.182,0.59,2.511,0.904s2.312,0.639,2.948,0.973c0.637,0.336,1.135,0.795,1.495,1.375 + c0.359,0.582,0.54,1.301,0.54,2.156c0,0.775-0.216,1.5-0.646,2.178c-0.431,0.676-1.04,1.18-1.827,1.508 + c-0.787,0.33-1.769,0.494-2.943,0.494c-1.711,0-3.024-0.395-3.941-1.186C88.076,425.93,87.528,424.777,87.35,423.264z"/> + <path fill="#7A65F5" d="M100.648,427.666v-13.529h10.031v2.289h-7.3v3h6.792v2.279h-6.792v3.682h7.559v2.279H100.648z"/> + <path fill="#7A65F5" d="M113.328,427.666v-13.418h2.731v11.139h6.792v2.279H113.328z"/> + <path fill="#7A65F5" d="M124.799,427.666v-13.529h10.031v2.289h-7.3v3h6.792v2.279h-6.792v3.682h7.559v2.279H124.799z"/> + <path fill="#7A65F5" d="M146.062,422.691l2.648,0.84c-0.406,1.477-1.081,2.574-2.025,3.291s-2.143,1.074-3.595,1.074 + c-1.796,0-3.272-0.613-4.43-1.84c-1.156-1.229-1.734-2.906-1.734-5.035c0-2.252,0.581-4,1.744-5.246 + c1.162-1.246,2.691-1.869,4.586-1.869c1.655,0,3,0.49,4.033,1.469c0.615,0.578,1.076,1.408,1.384,2.49l-2.703,0.646 + c-0.16-0.701-0.494-1.256-1.002-1.66c-0.507-0.406-1.124-0.609-1.85-0.609c-1.003,0-1.817,0.359-2.441,1.08 + c-0.624,0.719-0.937,1.885-0.937,3.496c0,1.711,0.308,2.93,0.923,3.654c0.615,0.727,1.415,1.09,2.399,1.09 + c0.726,0,1.351-0.23,1.873-0.691C145.459,424.408,145.834,423.684,146.062,422.691z"/> + <path fill="#7A65F5" d="M154.101,427.666v-11.24h-4.015v-2.289h10.751v2.289h-4.005v11.24H154.101z"/> + </g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> </g> </svg>
M res/mgba-qt.desktopres/mgba-qt.desktop

@@ -6,6 +6,7 @@ Terminal=false

Type=Application Name=mGBA GenericName=Game Boy Advance Emulator +Comment=Nintendo Game Boy Advance Emulator Categories=Game;Emulator; MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom; - +Keywords=emulator;Nintendo;advance;gba;Game Boy Advance;
M src/arm/arm.csrc/arm/arm.c

@@ -42,30 +42,29 @@ cpu->gprs[ARM_LR] = cpu->bankedRegisters[newBank][1];

cpu->bankedSPSRs[oldBank] = cpu->spsr.packed; cpu->spsr.packed = cpu->bankedSPSRs[newBank]; - } cpu->privilegeMode = mode; } static inline enum RegisterBank _ARMSelectBank(enum PrivilegeMode mode) { switch (mode) { - case MODE_USER: - case MODE_SYSTEM: - // No banked registers - return BANK_NONE; - case MODE_FIQ: - return BANK_FIQ; - case MODE_IRQ: - return BANK_IRQ; - case MODE_SUPERVISOR: - return BANK_SUPERVISOR; - case MODE_ABORT: - return BANK_ABORT; - case MODE_UNDEFINED: - return BANK_UNDEFINED; - default: - // This should be unreached - return BANK_NONE; + case MODE_USER: + case MODE_SYSTEM: + // No banked registers + return BANK_NONE; + case MODE_FIQ: + return BANK_FIQ; + case MODE_IRQ: + return BANK_IRQ; + case MODE_SUPERVISOR: + return BANK_SUPERVISOR; + case MODE_ABORT: + return BANK_ABORT; + case MODE_UNDEFINED: + return BANK_UNDEFINED; + default: + // This should be unreached + return BANK_NONE; } }

@@ -108,7 +107,7 @@ void ARMHotplugDetach(struct ARMCore* cpu, size_t slot) {

if (slot >= cpu->numComponents) { return; } - cpu->components[slot]->init(cpu, cpu->components[slot]); + cpu->components[slot]->deinit(cpu->components[slot]); } void ARMReset(struct ARMCore* cpu) {

@@ -190,6 +189,26 @@ cpu->cpsr.i = 1;

cpu->cycles += currentCycles; } +void ARMRaiseUndefined(struct ARMCore* cpu) { + union PSR cpsr = cpu->cpsr; + int instructionWidth; + if (cpu->executionMode == MODE_THUMB) { + instructionWidth = WORD_SIZE_THUMB; + } else { + instructionWidth = WORD_SIZE_ARM; + } + ARMSetPrivilegeMode(cpu, MODE_UNDEFINED); + cpu->cpsr.priv = MODE_UNDEFINED; + cpu->gprs[ARM_LR] = cpu->gprs[ARM_PC] - instructionWidth; + cpu->gprs[ARM_PC] = BASE_UNDEF; + int currentCycles = 0; + ARM_WRITE_PC; + _ARMSetMode(cpu, MODE_ARM); + cpu->spsr = cpsr; + cpu->cpsr.i = 1; + cpu->cycles += currentCycles; +} + static inline void ARMStep(struct ARMCore* cpu) { uint32_t opcode = cpu->prefetch[0]; cpu->prefetch[0] = cpu->prefetch[1];

@@ -288,7 +307,7 @@ cpu->irqh.processEvents(cpu);

} void ARMRunFake(struct ARMCore* cpu, uint32_t opcode) { - if (cpu->executionMode== MODE_ARM) { + if (cpu->executionMode == MODE_ARM) { cpu->gprs[ARM_PC] -= WORD_SIZE_ARM; } else { cpu->gprs[ARM_PC] -= WORD_SIZE_THUMB;
M src/arm/arm.hsrc/arm/arm.h

@@ -101,8 +101,10 @@ void (*store32)(struct ARMCore*, uint32_t address, int32_t value, int* cycleCounter);

void (*store16)(struct ARMCore*, uint32_t address, int16_t value, int* cycleCounter); void (*store8)(struct ARMCore*, uint32_t address, int8_t value, int* cycleCounter); - uint32_t (*loadMultiple)(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter); - uint32_t (*storeMultiple)(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter); + uint32_t (*loadMultiple)(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, + int* cycleCounter); + uint32_t (*storeMultiple)(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, + int* cycleCounter); uint32_t* activeRegion; uint32_t activeMask;

@@ -110,8 +112,7 @@ uint32_t activeSeqCycles32;

uint32_t activeSeqCycles16; uint32_t activeNonseqCycles32; uint32_t activeNonseqCycles16; - uint32_t activeUncachedCycles32; - uint32_t activeUncachedCycles16; + int32_t (*stall)(struct ARMCore*, int32_t wait); void (*setActiveRegion)(struct ARMCore*, uint32_t address); };

@@ -172,6 +173,7 @@ void ARMReset(struct ARMCore* cpu);

void ARMSetPrivilegeMode(struct ARMCore*, enum PrivilegeMode); void ARMRaiseIRQ(struct ARMCore*); void ARMRaiseSWI(struct ARMCore*); +void ARMRaiseUndefined(struct ARMCore*); void ARMRun(struct ARMCore* cpu); void ARMRunLoop(struct ARMCore* cpu);
M src/arm/decoder-inlines.hsrc/arm/decoder-inlines.h

@@ -13,11 +13,11 @@

#include <stdio.h> #include <string.h> -#define LOAD_CYCLES \ +#define LOAD_CYCLES \ info->iCycles = 1; \ info->nDataCycles = 1; -#define STORE_CYCLES \ +#define STORE_CYCLES \ info->sInstructionCycles = 0; \ info->nInstructionCycles = 1; \ info->nDataCycles = 1;
M src/arm/decoder-thumb.csrc/arm/decoder-thumb.c

@@ -16,9 +16,9 @@ info->mnemonic = ARM_MN_ ## MNEMONIC; \

BODY; \ } -#define DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(NAME, IMMEDIATE, MNEMONIC, WIDTH) \ +#define DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(NAME, MNEMONIC) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->op3.immediate = IMMEDIATE; \ + info->op3.immediate = (opcode >> 6) & 0x0007; \ info->op1.reg = opcode & 0x0007; \ info->op2.reg = (opcode >> 3) & 0x0007; \ info->affectsCPSR = 1; \

@@ -27,11 +27,11 @@ ARM_OPERAND_AFFECTED_1 | \

ARM_OPERAND_REGISTER_2 | \ ARM_OPERAND_IMMEDIATE_3;) -#define DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, IMMEDIATE, MNEMONIC, CYCLES, WIDTH) \ +#define DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, MNEMONIC, CYCLES, WIDTH) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ info->op1.reg = opcode & 0x0007; \ info->memory.baseReg = (opcode >> 3) & 0x0007; \ - info->memory.offset.immediate = IMMEDIATE * WIDTH; \ + info->memory.offset.immediate = ((opcode >> 6) & 0x001F) * WIDTH; \ info->memory.width = (enum ARMMemoryAccessType) WIDTH; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ ARM_OPERAND_AFFECTED_1 | \

@@ -40,70 +40,52 @@ info->memory.format = ARM_MEMORY_REGISTER_BASE | \

ARM_MEMORY_IMMEDIATE_OFFSET; \ CYCLES) -#define DEFINE_IMMEDIATE_5_DECODER_MEM_LOAD_THUMB(NAME, IMMEDIATE, MNEMONIC, WIDTH) \ - DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, IMMEDIATE, MNEMONIC, LOAD_CYCLES, WIDTH) - -#define DEFINE_IMMEDIATE_5_DECODER_MEM_STORE_THUMB(NAME, IMMEDIATE, MNEMONIC, WIDTH) \ - DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, IMMEDIATE, MNEMONIC, STORE_CYCLES, WIDTH) - -#define DEFINE_IMMEDIATE_5_DECODER_THUMB(NAME, MNEMONIC, TYPE, WIDTH) \ - COUNT_CALL_5(DEFINE_IMMEDIATE_5_DECODER_ ## TYPE ## _THUMB, NAME ## _, MNEMONIC, WIDTH) - -DEFINE_IMMEDIATE_5_DECODER_THUMB(LSL1, LSL, DATA,) -DEFINE_IMMEDIATE_5_DECODER_THUMB(LSR1, LSR, DATA,) -DEFINE_IMMEDIATE_5_DECODER_THUMB(ASR1, ASR, DATA,) -DEFINE_IMMEDIATE_5_DECODER_THUMB(LDR1, LDR, MEM_LOAD, 4) -DEFINE_IMMEDIATE_5_DECODER_THUMB(LDRB1, LDR, MEM_LOAD, 1) -DEFINE_IMMEDIATE_5_DECODER_THUMB(LDRH1, LDR, MEM_LOAD, 2) -DEFINE_IMMEDIATE_5_DECODER_THUMB(STR1, STR, MEM_STORE, 4) -DEFINE_IMMEDIATE_5_DECODER_THUMB(STRB1, STR, MEM_STORE, 1) -DEFINE_IMMEDIATE_5_DECODER_THUMB(STRH1, STR, MEM_STORE, 2) +DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(LSL1, LSL) +DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(LSR1, LSR) +DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(ASR1, ASR) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDR1, LDR, LOAD_CYCLES, 4) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRB1, LDR, LOAD_CYCLES, 1) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRH1, LDR, LOAD_CYCLES, 2) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STR1, STR, STORE_CYCLES, 4) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRB1, STR, STORE_CYCLES, 1) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRH1, STR, STORE_CYCLES, 2) -#define DEFINE_DATA_FORM_1_DECODER_EX_THUMB(NAME, RM, MNEMONIC) \ +#define DEFINE_DATA_FORM_1_DECODER_THUMB(NAME, MNEMONIC) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ info->op1.reg = opcode & 0x0007; \ info->op2.reg = (opcode >> 3) & 0x0007; \ - info->op3.reg = RM; \ + info->op3.reg = (opcode >> 6) & 0x0007; \ info->affectsCPSR = 1; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ ARM_OPERAND_AFFECTED_1 | \ ARM_OPERAND_REGISTER_2 | \ ARM_OPERAND_REGISTER_3;) -#define DEFINE_DATA_FORM_1_DECODER_THUMB(NAME) \ - COUNT_CALL_3(DEFINE_DATA_FORM_1_DECODER_EX_THUMB, NAME ## 3_R, NAME) - -DEFINE_DATA_FORM_1_DECODER_THUMB(ADD) -DEFINE_DATA_FORM_1_DECODER_THUMB(SUB) +DEFINE_DATA_FORM_1_DECODER_THUMB(ADD3, ADD) +DEFINE_DATA_FORM_1_DECODER_THUMB(SUB3, SUB) -#define DEFINE_DATA_FORM_2_DECODER_EX_THUMB(NAME, IMMEDIATE, MNEMONIC) \ +#define DEFINE_DATA_FORM_2_DECODER_THUMB(NAME, MNEMONIC) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ info->op1.reg = opcode & 0x0007; \ info->op2.reg = (opcode >> 3) & 0x0007; \ - info->op3.immediate = IMMEDIATE; \ + info->op3.immediate = (opcode >> 6) & 0x0007; \ info->affectsCPSR = 1; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ ARM_OPERAND_AFFECTED_1 | \ ARM_OPERAND_REGISTER_2 | \ ARM_OPERAND_IMMEDIATE_3;) -#define DEFINE_DATA_FORM_2_DECODER_THUMB(NAME) \ - COUNT_CALL_3(DEFINE_DATA_FORM_2_DECODER_EX_THUMB, NAME ## 1_, NAME) - -DEFINE_DATA_FORM_2_DECODER_THUMB(ADD) -DEFINE_DATA_FORM_2_DECODER_THUMB(SUB) +DEFINE_DATA_FORM_2_DECODER_THUMB(ADD1, ADD) +DEFINE_DATA_FORM_2_DECODER_THUMB(SUB1, SUB) -#define DEFINE_DATA_FORM_3_DECODER_EX_THUMB(NAME, RD, MNEMONIC, AFFECTED) \ +#define DEFINE_DATA_FORM_3_DECODER_THUMB(NAME, MNEMONIC, AFFECTED) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->op1.reg = RD; \ + info->op1.reg = (opcode >> 8) & 0x0007; \ info->op2.immediate = opcode & 0x00FF; \ info->affectsCPSR = 1; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ AFFECTED | \ ARM_OPERAND_IMMEDIATE_2;) - -#define DEFINE_DATA_FORM_3_DECODER_THUMB(NAME, MNEMONIC, AFFECTED) \ - COUNT_CALL_3(DEFINE_DATA_FORM_3_DECODER_EX_THUMB, NAME ## _R, MNEMONIC, AFFECTED) DEFINE_DATA_FORM_3_DECODER_THUMB(ADD2, ADD, ARM_OPERAND_AFFECTED_1) DEFINE_DATA_FORM_3_DECODER_THUMB(CMP1, CMP, ARM_OPERAND_NONE)

@@ -159,9 +141,9 @@ DEFINE_DECODER_WITH_HIGH_THUMB(ADD4, ADD, ARM_OPERAND_AFFECTED_1, 0)

DEFINE_DECODER_WITH_HIGH_THUMB(CMP3, CMP, ARM_OPERAND_NONE, 1) DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0) -#define DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(NAME, RD, MNEMONIC, REG) \ +#define DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(NAME, MNEMONIC, REG) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->op1.reg = RD; \ + info->op1.reg = (opcode >> 6) & 0x0007; \ info->op2.reg = REG; \ info->op3.immediate = (opcode & 0x00FF) << 2; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \

@@ -169,9 +151,9 @@ ARM_OPERAND_AFFECTED_1 | \

ARM_OPERAND_REGISTER_2 | \ ARM_OPERAND_IMMEDIATE_3;) -#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, RD, MNEMONIC, REG, CYCLES) \ +#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->op1.reg = RD; \ + info->op1.reg = (opcode >> 8) & 0x0007; \ info->memory.baseReg = REG; \ info->memory.offset.immediate = (opcode & 0x00FF) << 2; \ info->memory.width = ARM_ACCESS_WORD; \

@@ -182,25 +164,16 @@ info->memory.format = ARM_MEMORY_REGISTER_BASE | \

ARM_MEMORY_IMMEDIATE_OFFSET; \ CYCLES;) -#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_LOAD_THUMB(NAME, RD, MNEMONIC, REG) \ - DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, RD, MNEMONIC, REG, LOAD_CYCLES) - -#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_STORE_THUMB(NAME, RD, MNEMONIC, REG) \ - DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, RD, MNEMONIC, REG, STORE_CYCLES) +DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR3, LDR, ARM_PC, LOAD_CYCLES) +DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR4, LDR, ARM_SP, LOAD_CYCLES) +DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(STR3, STR, ARM_SP, STORE_CYCLES) -#define DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(NAME, MNEMONIC, TYPE, REG) \ - COUNT_CALL_3(DEFINE_IMMEDIATE_WITH_REGISTER_ ## TYPE ## _THUMB, NAME ## _R, MNEMONIC, REG) +DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD5, ADD, ARM_PC) +DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD6, ADD, ARM_SP) -DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(LDR3, LDR, MEM_LOAD, ARM_PC) -DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(LDR4, LDR, MEM_LOAD, ARM_SP) -DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(STR3, STR, MEM_STORE, ARM_SP) - -DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(ADD5, ADD, DATA, ARM_PC) -DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(ADD6, ADD, DATA, ARM_SP) - -#define DEFINE_LOAD_STORE_WITH_REGISTER_EX_THUMB(NAME, RM, MNEMONIC, CYCLES, TYPE) \ +#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, MNEMONIC, CYCLES, TYPE) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->memory.offset.reg = RM; \ + info->memory.offset.reg = (opcode >> 6) & 0x0007; \ info->op1.reg = opcode & 0x0007; \ info->memory.baseReg = (opcode >> 3) & 0x0007; \ info->memory.width = TYPE; \

@@ -210,9 +183,6 @@ ARM_OPERAND_MEMORY_2; \

info->memory.format = ARM_MEMORY_REGISTER_BASE | \ ARM_MEMORY_REGISTER_OFFSET; \ CYCLES;) - -#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, MNEMONIC, CYCLES, TYPE) \ - COUNT_CALL_3(DEFINE_LOAD_STORE_WITH_REGISTER_EX_THUMB, NAME ## _R, MNEMONIC, CYCLES, TYPE) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, LDR, LOAD_CYCLES, ARM_ACCESS_WORD) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, LDR, LOAD_CYCLES, ARM_ACCESS_BYTE)

@@ -237,7 +207,7 @@ ARM_MEMORY_WRITEBACK | \

DIRECTION;) #define DEFINE_LOAD_STORE_MULTIPLE_THUMB(NAME) \ - COUNT_CALL_3(DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB, NAME ## IA_R, NAME, ARM_MEMORY_INCREMENT_AFTER, 0) + DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(NAME ## IA, (opcode >> 8) & 0x0007, NAME, ARM_MEMORY_INCREMENT_AFTER, 0) DEFINE_LOAD_STORE_MULTIPLE_THUMB(LDM) DEFINE_LOAD_STORE_MULTIPLE_THUMB(STM)
M src/arm/decoder.hsrc/arm/decoder.h

@@ -209,7 +209,8 @@ };

void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info); void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info); -bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, struct ARMInstructionInfo* out); +bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, + struct ARMInstructionInfo* out); int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, int blen); #endif
M src/arm/emitter-inlines.hsrc/arm/emitter-inlines.h

@@ -29,105 +29,4 @@ #define DO_INTERLACE(LEFT, RIGHT) \

LEFT, \ RIGHT -#define APPLY(F, ...) F(__VA_ARGS__) - -#define COUNT_CALL_1(EMITTER, PREFIX, ...) \ - EMITTER(PREFIX ## 0, 0, __VA_ARGS__) \ - EMITTER(PREFIX ## 1, 1, __VA_ARGS__) - -#define COUNT_CALL_2(EMITTER, PREFIX, ...) \ - COUNT_CALL_1(EMITTER, PREFIX, __VA_ARGS__) \ - EMITTER(PREFIX ## 2, 2, __VA_ARGS__) \ - EMITTER(PREFIX ## 3, 3, __VA_ARGS__) - -#define COUNT_CALL_3(EMITTER, PREFIX, ...) \ - COUNT_CALL_2(EMITTER, PREFIX, __VA_ARGS__) \ - EMITTER(PREFIX ## 4, 4, __VA_ARGS__) \ - EMITTER(PREFIX ## 5, 5, __VA_ARGS__) \ - EMITTER(PREFIX ## 6, 6, __VA_ARGS__) \ - EMITTER(PREFIX ## 7, 7, __VA_ARGS__) - -#define COUNT_CALL_4(EMITTER, PREFIX, ...) \ - COUNT_CALL_3(EMITTER, PREFIX, __VA_ARGS__) \ - EMITTER(PREFIX ## 8, 8, __VA_ARGS__) \ - EMITTER(PREFIX ## 9, 9, __VA_ARGS__) \ - EMITTER(PREFIX ## A, 10, __VA_ARGS__) \ - EMITTER(PREFIX ## B, 11, __VA_ARGS__) \ - EMITTER(PREFIX ## C, 12, __VA_ARGS__) \ - EMITTER(PREFIX ## D, 13, __VA_ARGS__) \ - EMITTER(PREFIX ## E, 14, __VA_ARGS__) \ - EMITTER(PREFIX ## F, 15, __VA_ARGS__) - -#define COUNT_CALL_5(EMITTER, PREFIX, ...) \ - COUNT_CALL_4(EMITTER, PREFIX ## 0, __VA_ARGS__) \ - EMITTER(PREFIX ## 10, 16, __VA_ARGS__) \ - EMITTER(PREFIX ## 11, 17, __VA_ARGS__) \ - EMITTER(PREFIX ## 12, 18, __VA_ARGS__) \ - EMITTER(PREFIX ## 13, 19, __VA_ARGS__) \ - EMITTER(PREFIX ## 14, 20, __VA_ARGS__) \ - EMITTER(PREFIX ## 15, 21, __VA_ARGS__) \ - EMITTER(PREFIX ## 16, 22, __VA_ARGS__) \ - EMITTER(PREFIX ## 17, 23, __VA_ARGS__) \ - EMITTER(PREFIX ## 18, 24, __VA_ARGS__) \ - EMITTER(PREFIX ## 19, 25, __VA_ARGS__) \ - EMITTER(PREFIX ## 1A, 26, __VA_ARGS__) \ - EMITTER(PREFIX ## 1B, 27, __VA_ARGS__) \ - EMITTER(PREFIX ## 1C, 28, __VA_ARGS__) \ - EMITTER(PREFIX ## 1D, 29, __VA_ARGS__) \ - EMITTER(PREFIX ## 1E, 30, __VA_ARGS__) \ - EMITTER(PREFIX ## 1F, 31, __VA_ARGS__) \ - -#define COUNT_1(EMITTER, PREFIX) \ - EMITTER(PREFIX ## 0) \ - EMITTER(PREFIX ## 1) - -#define COUNT_2(EMITTER, PREFIX) \ - COUNT_1(EMITTER, PREFIX) \ - EMITTER(PREFIX ## 2) \ - EMITTER(PREFIX ## 3) - -#define COUNT_3(EMITTER, PREFIX) \ - COUNT_2(EMITTER, PREFIX) \ - EMITTER(PREFIX ## 4) \ - EMITTER(PREFIX ## 5) \ - EMITTER(PREFIX ## 6) \ - EMITTER(PREFIX ## 7) - -#define COUNT_4(EMITTER, PREFIX) \ - COUNT_3(EMITTER, PREFIX) \ - EMITTER(PREFIX ## 8) \ - EMITTER(PREFIX ## 9) \ - EMITTER(PREFIX ## A) \ - EMITTER(PREFIX ## B) \ - EMITTER(PREFIX ## C) \ - EMITTER(PREFIX ## D) \ - EMITTER(PREFIX ## E) \ - EMITTER(PREFIX ## F) - -#define COUNT_5(EMITTER, PREFIX) \ - COUNT_4(EMITTER, PREFIX ## 0) \ - EMITTER(PREFIX ## 10) \ - EMITTER(PREFIX ## 11) \ - EMITTER(PREFIX ## 12) \ - EMITTER(PREFIX ## 13) \ - EMITTER(PREFIX ## 14) \ - EMITTER(PREFIX ## 15) \ - EMITTER(PREFIX ## 16) \ - EMITTER(PREFIX ## 17) \ - EMITTER(PREFIX ## 18) \ - EMITTER(PREFIX ## 19) \ - EMITTER(PREFIX ## 1A) \ - EMITTER(PREFIX ## 1B) \ - EMITTER(PREFIX ## 1C) \ - EMITTER(PREFIX ## 1D) \ - EMITTER(PREFIX ## 1E) \ - EMITTER(PREFIX ## 1F) \ - -#define ECHO(...) __VA_ARGS__, -#define ECHO_4(...) \ - ECHO(__VA_ARGS__) \ - ECHO(__VA_ARGS__) \ - ECHO(__VA_ARGS__) \ - ECHO(__VA_ARGS__) - #endif
M src/arm/emitter-thumb.hsrc/arm/emitter-thumb.h

@@ -18,17 +18,17 @@ DECLARE_INSTRUCTION_THUMB(EMITTER, NAME ## 10), \

DECLARE_INSTRUCTION_THUMB(EMITTER, NAME ## 11) #define DECLARE_THUMB_EMITTER_BLOCK(EMITTER) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LSL1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LSR1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, ASR1_)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, ADD3_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, SUB3_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, ADD1_)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, SUB1_)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, MOV1_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, CMP1_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, ADD2_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, SUB2_R)) \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LSL1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LSR1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, ASR1))), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, ADD3)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, SUB3)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, ADD1)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, SUB1)), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, MOV1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, CMP1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, ADD2))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, SUB2))), \ DECLARE_INSTRUCTION_THUMB(EMITTER, AND), \ DECLARE_INSTRUCTION_THUMB(EMITTER, EOR), \ DECLARE_INSTRUCTION_THUMB(EMITTER, LSL2), \

@@ -52,25 +52,25 @@ DECLARE_INSTRUCTION_THUMB(EMITTER, BX), \

DECLARE_INSTRUCTION_THUMB(EMITTER, BX), \ DECLARE_INSTRUCTION_THUMB(EMITTER, ILL), \ DECLARE_INSTRUCTION_THUMB(EMITTER, ILL), \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, LDR3_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, STR2_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, STRH2_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, STRB2_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDRSB_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDR2_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDRH2_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDRB2_R)) \ - APPLY(COUNT_3, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDRSH_R)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, STR1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDR1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, STRB1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDRB1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, STRH1_)) \ - APPLY(COUNT_5, ECHO, DECLARE_INSTRUCTION_THUMB(EMITTER, LDRH1_)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, STR3_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, LDR4_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, ADD5_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, ADD6_R)) \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LDR3))), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, STR2)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, STRH2)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, STRB2)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, LDRSB)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, LDR2)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, LDRH2)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, LDRB2)), \ + DO_8(DECLARE_INSTRUCTION_THUMB(EMITTER, LDRSH)), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, STR1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LDR1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, STRB1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LDRB1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, STRH1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LDRH1))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, STR3))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LDR4))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, ADD5))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, ADD6))), \ DECLARE_INSTRUCTION_THUMB(EMITTER, ADD7), \ DECLARE_INSTRUCTION_THUMB(EMITTER, ADD7), \ DECLARE_INSTRUCTION_THUMB(EMITTER, SUB4), \

@@ -87,8 +87,8 @@ DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, POP)), \

DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, POPR)), \ DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, BKPT)), \ DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, ILL)), \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, STMIA_R)) \ - APPLY(COUNT_3, ECHO_4, DECLARE_INSTRUCTION_THUMB(EMITTER, LDMIA_R)) \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, STMIA))), \ + DO_8(DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, LDMIA))), \ DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, BEQ)), \ DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, BNE)), \ DO_4(DECLARE_INSTRUCTION_THUMB(EMITTER, BCS)), \
M src/arm/isa-arm.csrc/arm/isa-arm.c

@@ -259,7 +259,7 @@

#define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address; #define ARM_LOAD_POST_BODY \ - currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; \ + currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; \ if (rd == ARM_PC) { \ ARM_WRITE_PC; \ }

@@ -567,7 +567,7 @@ ARM_STORE_POST_BODY;)

DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(LDM, load, - currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; + currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; if (rs & 0x8000) { ARM_WRITE_PC; })
M src/arm/isa-arm.hsrc/arm/isa-arm.h

@@ -15,5 +15,4 @@

typedef void (*ARMInstruction)(struct ARMCore*, uint32_t opcode); const ARMInstruction _armTable[0x1000]; - #endif
M src/arm/isa-inlines.hsrc/arm/isa-inlines.h

@@ -35,35 +35,39 @@ #define ARM_BORROW_FROM(M, N, D) (((uint32_t) (M)) >= ((uint32_t) (N)))

#define ARM_V_ADDITION(M, N, D) (!(ARM_SIGN((M) ^ (N))) && (ARM_SIGN((M) ^ (D))) && (ARM_SIGN((N) ^ (D)))) #define ARM_V_SUBTRACTION(M, N, D) ((ARM_SIGN((M) ^ (N))) && (ARM_SIGN((M) ^ (D)))) -#define ARM_WAIT_MUL(R) \ - if ((R & 0xFFFFFF00) == 0xFFFFFF00 || !(R & 0xFFFFFF00)) { \ - currentCycles += 1; \ - } else if ((R & 0xFFFF0000) == 0xFFFF0000 || !(R & 0xFFFF0000)) { \ - currentCycles += 2; \ - } else if ((R & 0xFF000000) == 0xFF000000 || !(R & 0xFF000000)) { \ - currentCycles += 3; \ - } else { \ - currentCycles += 4; \ +#define ARM_WAIT_MUL(R) \ + { \ + int32_t wait; \ + if ((R & 0xFFFFFF00) == 0xFFFFFF00 || !(R & 0xFFFFFF00)) { \ + wait = 1; \ + } else if ((R & 0xFFFF0000) == 0xFFFF0000 || !(R & 0xFFFF0000)) { \ + wait = 2; \ + } else if ((R & 0xFF000000) == 0xFF000000 || !(R & 0xFF000000)) { \ + wait = 3; \ + } else { \ + wait = 4; \ + } \ + currentCycles += cpu->memory.stall(cpu, wait); \ } #define ARM_STUB cpu->irqh.hitStub(cpu, opcode) #define ARM_ILL cpu->irqh.hitIllegal(cpu, opcode) -#define ARM_WRITE_PC \ - cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_ARM); \ - cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \ +#define ARM_WRITE_PC \ + cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_ARM); \ + cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \ LOAD_32(cpu->prefetch[0], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ - cpu->gprs[ARM_PC] += WORD_SIZE_ARM; \ + cpu->gprs[ARM_PC] += WORD_SIZE_ARM; \ LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ - currentCycles += 2 + cpu->memory.activeUncachedCycles32 + cpu->memory.activeSeqCycles32; + currentCycles += 2 + cpu->memory.activeNonseqCycles32 + cpu->memory.activeSeqCycles32; -#define THUMB_WRITE_PC \ - cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_THUMB); \ - cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \ +#define THUMB_WRITE_PC \ + cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_THUMB); \ + cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \ LOAD_16(cpu->prefetch[0], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ - cpu->gprs[ARM_PC] += WORD_SIZE_THUMB; \ + cpu->gprs[ARM_PC] += WORD_SIZE_THUMB; \ LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ - currentCycles += 2 + cpu->memory.activeUncachedCycles16 + cpu->memory.activeSeqCycles16; + currentCycles += 2 + cpu->memory.activeNonseqCycles16 + cpu->memory.activeSeqCycles16; static inline int _ARMModeHasSPSR(enum PrivilegeMode mode) { return mode != MODE_SYSTEM && mode != MODE_USER;
M src/arm/isa-thumb.csrc/arm/isa-thumb.c

@@ -42,7 +42,7 @@

#define THUMB_PREFETCH_CYCLES (1 + cpu->memory.activeSeqCycles16) #define THUMB_LOAD_POST_BODY \ - currentCycles += 1 + cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; + currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; #define THUMB_STORE_POST_BODY \ currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16;

@@ -54,15 +54,12 @@ BODY; \

cpu->cycles += currentCycles; \ } -#define DEFINE_IMMEDIATE_5_INSTRUCTION_EX_THUMB(NAME, IMMEDIATE, BODY) \ +#define DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(NAME, BODY) \ DEFINE_INSTRUCTION_THUMB(NAME, \ - int immediate = IMMEDIATE; \ + int immediate = (opcode >> 6) & 0x001F; \ int rd = opcode & 0x0007; \ int rm = (opcode >> 3) & 0x0007; \ BODY;) - -#define DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(NAME, BODY) \ - COUNT_CALL_5(DEFINE_IMMEDIATE_5_INSTRUCTION_EX_THUMB, NAME ## _, BODY) DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LSL1, if (!immediate) {

@@ -104,41 +101,32 @@ DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STR1, cpu->memory.store32(cpu, cpu->gprs[rm] + immediate * 4, cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;)

DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STRB1, cpu->memory.store8(cpu, cpu->gprs[rm] + immediate, cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;) DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STRH1, cpu->memory.store16(cpu, cpu->gprs[rm] + immediate * 2, cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;) -#define DEFINE_DATA_FORM_1_INSTRUCTION_EX_THUMB(NAME, RM, BODY) \ +#define DEFINE_DATA_FORM_1_INSTRUCTION_THUMB(NAME, BODY) \ DEFINE_INSTRUCTION_THUMB(NAME, \ - int rm = RM; \ + int rm = (opcode >> 6) & 0x0007; \ int rd = opcode & 0x0007; \ int rn = (opcode >> 3) & 0x0007; \ BODY;) -#define DEFINE_DATA_FORM_1_INSTRUCTION_THUMB(NAME, BODY) \ - COUNT_CALL_3(DEFINE_DATA_FORM_1_INSTRUCTION_EX_THUMB, NAME ## 3_R, BODY) +DEFINE_DATA_FORM_1_INSTRUCTION_THUMB(ADD3, THUMB_ADDITION(cpu->gprs[rd], cpu->gprs[rn], cpu->gprs[rm])) +DEFINE_DATA_FORM_1_INSTRUCTION_THUMB(SUB3, THUMB_SUBTRACTION(cpu->gprs[rd], cpu->gprs[rn], cpu->gprs[rm])) -DEFINE_DATA_FORM_1_INSTRUCTION_THUMB(ADD, THUMB_ADDITION(cpu->gprs[rd], cpu->gprs[rn], cpu->gprs[rm])) -DEFINE_DATA_FORM_1_INSTRUCTION_THUMB(SUB, THUMB_SUBTRACTION(cpu->gprs[rd], cpu->gprs[rn], cpu->gprs[rm])) - -#define DEFINE_DATA_FORM_2_INSTRUCTION_EX_THUMB(NAME, IMMEDIATE, BODY) \ +#define DEFINE_DATA_FORM_2_INSTRUCTION_THUMB(NAME, BODY) \ DEFINE_INSTRUCTION_THUMB(NAME, \ - int immediate = IMMEDIATE; \ + int immediate = (opcode >> 6) & 0x0007; \ int rd = opcode & 0x0007; \ int rn = (opcode >> 3) & 0x0007; \ BODY;) -#define DEFINE_DATA_FORM_2_INSTRUCTION_THUMB(NAME, BODY) \ - COUNT_CALL_3(DEFINE_DATA_FORM_2_INSTRUCTION_EX_THUMB, NAME ## 1_, BODY) - -DEFINE_DATA_FORM_2_INSTRUCTION_THUMB(ADD, THUMB_ADDITION(cpu->gprs[rd], cpu->gprs[rn], immediate)) -DEFINE_DATA_FORM_2_INSTRUCTION_THUMB(SUB, THUMB_SUBTRACTION(cpu->gprs[rd], cpu->gprs[rn], immediate)) +DEFINE_DATA_FORM_2_INSTRUCTION_THUMB(ADD1, THUMB_ADDITION(cpu->gprs[rd], cpu->gprs[rn], immediate)) +DEFINE_DATA_FORM_2_INSTRUCTION_THUMB(SUB1, THUMB_SUBTRACTION(cpu->gprs[rd], cpu->gprs[rn], immediate)) -#define DEFINE_DATA_FORM_3_INSTRUCTION_EX_THUMB(NAME, RD, BODY) \ +#define DEFINE_DATA_FORM_3_INSTRUCTION_THUMB(NAME, BODY) \ DEFINE_INSTRUCTION_THUMB(NAME, \ - int rd = RD; \ + int rd = (opcode >> 8) & 0x0007; \ int immediate = opcode & 0x00FF; \ BODY;) -#define DEFINE_DATA_FORM_3_INSTRUCTION_THUMB(NAME, BODY) \ - COUNT_CALL_3(DEFINE_DATA_FORM_3_INSTRUCTION_EX_THUMB, NAME ## _R, BODY) - DEFINE_DATA_FORM_3_INSTRUCTION_THUMB(ADD2, THUMB_ADDITION(cpu->gprs[rd], cpu->gprs[rd], immediate)) DEFINE_DATA_FORM_3_INSTRUCTION_THUMB(CMP1, int aluOut = cpu->gprs[rd] - immediate; THUMB_SUBTRACTION_S(cpu->gprs[rd], immediate, aluOut)) DEFINE_DATA_FORM_3_INSTRUCTION_THUMB(MOV1, cpu->gprs[rd] = immediate; THUMB_NEUTRAL_S(, , cpu->gprs[rd]))

@@ -260,15 +248,12 @@ if (rd == ARM_PC) {

THUMB_WRITE_PC; }) -#define DEFINE_IMMEDIATE_WITH_REGISTER_EX_THUMB(NAME, RD, BODY) \ +#define DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(NAME, BODY) \ DEFINE_INSTRUCTION_THUMB(NAME, \ - int rd = RD; \ + int rd = (opcode >> 8) & 0x0007; \ int immediate = (opcode & 0x00FF) << 2; \ BODY;) -#define DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(NAME, BODY) \ - COUNT_CALL_3(DEFINE_IMMEDIATE_WITH_REGISTER_EX_THUMB, NAME ## _R, BODY) - DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(LDR3, cpu->gprs[rd] = cpu->memory.load32(cpu, (cpu->gprs[ARM_PC] & 0xFFFFFFFC) + immediate, &currentCycles); THUMB_LOAD_POST_BODY;) DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(LDR4, cpu->gprs[rd] = cpu->memory.load32(cpu, cpu->gprs[ARM_SP] + immediate, &currentCycles); THUMB_LOAD_POST_BODY;) DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(STR3, cpu->memory.store32(cpu, cpu->gprs[ARM_SP] + immediate, cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;)

@@ -276,15 +261,12 @@

DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(ADD5, cpu->gprs[rd] = (cpu->gprs[ARM_PC] & 0xFFFFFFFC) + immediate) DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(ADD6, cpu->gprs[rd] = cpu->gprs[ARM_SP] + immediate) -#define DEFINE_LOAD_STORE_WITH_REGISTER_EX_THUMB(NAME, RM, BODY) \ +#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, BODY) \ DEFINE_INSTRUCTION_THUMB(NAME, \ - int rm = RM; \ + int rm = (opcode >> 6) & 0x0007; \ int rd = opcode & 0x0007; \ int rn = (opcode >> 3) & 0x0007; \ BODY;) - -#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, BODY) \ - COUNT_CALL_3(DEFINE_LOAD_STORE_WITH_REGISTER_EX_THUMB, NAME ## _R, BODY) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, cpu->gprs[rd] = cpu->memory.load32(cpu, cpu->gprs[rn] + cpu->gprs[rm], &currentCycles); THUMB_LOAD_POST_BODY;) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], &currentCycles); THUMB_LOAD_POST_BODY;)

@@ -295,7 +277,7 @@ DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, cpu->memory.store32(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;)

DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, cpu->memory.store8(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, cpu->memory.store16(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], &currentCycles); THUMB_STORE_POST_BODY;) -#define DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(NAME, RN, LS, DIRECTION, PRE_BODY, WRITEBACK) \ +#define DEFINE_LOAD_STORE_MULTIPLE_THUMB(NAME, RN, LS, DIRECTION, PRE_BODY, WRITEBACK) \ DEFINE_INSTRUCTION_THUMB(NAME, \ int rn = RN; \ UNUSED(rn); \

@@ -305,20 +287,21 @@ PRE_BODY; \

address = cpu->memory. LS ## Multiple(cpu, address, rs, LSM_ ## DIRECTION, &currentCycles); \ WRITEBACK;) -#define DEFINE_LOAD_STORE_MULTIPLE_THUMB(NAME, LS, DIRECTION, WRITEBACK) \ - COUNT_CALL_3(DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB, NAME ## _R, LS, DIRECTION, , WRITEBACK) - DEFINE_LOAD_STORE_MULTIPLE_THUMB(LDMIA, + (opcode >> 8) & 0x0007, load, IA, + , THUMB_LOAD_POST_BODY; if (!((1 << rn) & rs)) { cpu->gprs[rn] = address; }) DEFINE_LOAD_STORE_MULTIPLE_THUMB(STMIA, + (opcode >> 8) & 0x0007, store, IA, + , THUMB_STORE_POST_BODY; cpu->gprs[rn] = address;)

@@ -348,7 +331,7 @@

DEFINE_INSTRUCTION_THUMB(ADD7, cpu->gprs[ARM_SP] += (opcode & 0x7F) << 2) DEFINE_INSTRUCTION_THUMB(SUB4, cpu->gprs[ARM_SP] -= (opcode & 0x7F) << 2) -DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POP, +DEFINE_LOAD_STORE_MULTIPLE_THUMB(POP, ARM_SP, load, IA,

@@ -356,7 +339,7 @@ ,

THUMB_LOAD_POST_BODY; cpu->gprs[ARM_SP] = address) -DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POPR, +DEFINE_LOAD_STORE_MULTIPLE_THUMB(POPR, ARM_SP, load, IA,

@@ -365,7 +348,7 @@ THUMB_LOAD_POST_BODY;

cpu->gprs[ARM_SP] = address; THUMB_WRITE_PC;) -DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSH, +DEFINE_LOAD_STORE_MULTIPLE_THUMB(PUSH, ARM_SP, store, DB,

@@ -373,7 +356,7 @@ ,

THUMB_STORE_POST_BODY; cpu->gprs[ARM_SP] = address) -DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSHR, +DEFINE_LOAD_STORE_MULTIPLE_THUMB(PUSHR, ARM_SP, store, DB,
M src/debugger/cli-debugger.csrc/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 @@ { "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" },

{ "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" },

@@ -82,7 +86,7 @@ { "print", _print, CLIDVParse, "Print a value" },

{ "print/t", _printBin, CLIDVParse, "Print a value as binary" }, { "print/x", _printHex, CLIDVParse, "Print a value as hexadecimal" }, { "q", _quit, 0, "Quit the emulator" }, - { "quit", _quit, 0, "Quit the emulator" }, + { "quit", _quit, 0, "Quit the emulator" }, { "reset", _reset, 0, "Reset the emulation" }, { "r/1", _readByte, CLIDVParse, "Read a byte from a specified offset" }, { "r/2", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" },

@@ -97,21 +101,24 @@ { "w/r", _writeRegister, CLIDVParse, "Write a register" },

{ "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 } }; static inline void _printPSR(union PSR psr) { printf("%08X [%c%c%c%c%c%c%c]\n", psr.packed, - psr.n ? 'N' : '-', - psr.z ? 'Z' : '-', - psr.c ? 'C' : '-', - psr.v ? 'V' : '-', - psr.i ? 'I' : '-', - psr.f ? 'F' : '-', - psr.t ? 'T' : '-'); + psr.n ? 'N' : '-', + psr.z ? 'Z' : '-', + psr.c ? 'C' : '-', + psr.v ? 'V' : '-', + psr.i ? 'I' : '-', + psr.f ? 'F' : '-', + psr.t ? 'T' : '-'); } +#ifndef NDEBUG static void _handleDeath(int sig) { UNUSED(sig); printf("No debugger attached!\n");

@@ -133,6 +140,7 @@ kill(getpid(), SIGTRAP);

#endif sigaction(SIGTRAP, &osa, 0); } +#endif static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv);

@@ -196,7 +204,7 @@ }

static void _print(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(debugger); - for ( ; dv; dv = dv->next) { + for (; dv; dv = dv->next) { printf(" %u", dv->intValue); } printf("\n");

@@ -204,7 +212,7 @@ }

static void _printBin(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(debugger); - for ( ; dv; dv = dv->next) { + for (; dv; dv = dv->next) { printf(" 0b"); int i = 32; while (i--) {

@@ -216,7 +224,7 @@ }

static void _printHex(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(debugger); - for ( ; dv; dv = dv->next) { + for (; dv; dv = dv->next) { printf(" 0x%08X", dv->intValue); } printf("\n");

@@ -289,10 +297,10 @@ UNUSED(dv);

int r; for (r = 0; r < 4; ++r) { printf("%08X %08X %08X %08X\n", - debugger->d.cpu->gprs[r << 2], - debugger->d.cpu->gprs[(r << 2) + 1], - debugger->d.cpu->gprs[(r << 2) + 2], - debugger->d.cpu->gprs[(r << 2) + 3]); + debugger->d.cpu->gprs[r << 2], + debugger->d.cpu->gprs[(r << 2) + 1], + debugger->d.cpu->gprs[(r << 2) + 2], + debugger->d.cpu->gprs[(r << 2) + 3]); } _printPSR(debugger->d.cpu->cpsr); int instructionLength;
M src/debugger/debugger.csrc/debugger/debugger.c

@@ -155,6 +155,7 @@ next = &breakpoint->next;

if (breakpoint->address == address) { *previous = *next; free(breakpoint); + continue; } previous = next; }

@@ -179,6 +180,7 @@ next = &watchpoint->next;

if (watchpoint->address == address) { *previous = *next; free(watchpoint); + continue; } previous = next; }
M src/debugger/gdb-stub.csrc/debugger/gdb-stub.c

@@ -108,7 +108,7 @@ while (maxDigits--) {

letter = *hex - '0'; if (letter > 9) { letter = *hex - 'a'; - if (letter > 5) { + if (letter > 5) { break; } value *= 0x10;
M src/debugger/memory-debugger.csrc/debugger/memory-debugger.c

@@ -29,14 +29,14 @@ } \

} \ } -#define CREATE_SHIM(NAME, RETURN, TYPES, ARGS...) \ +#define CREATE_SHIM(NAME, RETURN, TYPES, ...) \ static RETURN ARMDebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - return debugger->originalMemory.NAME(cpu, ARGS); \ + return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } -#define CREATE_WATCHPOINT_SHIM(NAME, WIDTH, RETURN, TYPES, ARGS...) \ +#define CREATE_WATCHPOINT_SHIM(NAME, WIDTH, RETURN, TYPES, ...) \ static RETURN ARMDebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \

@@ -44,7 +44,7 @@ struct DebuggerEntryInfo info; \

if (_checkWatchpoints(debugger, address, &info, WIDTH)) { \ ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \ } \ - return debugger->originalMemory.NAME(cpu, ARGS); \ + return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } #define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME) \
M src/debugger/parser.csrc/debugger/parser.c

@@ -118,8 +118,8 @@ }

break; case LEX_EXPECT_BINARY: switch (token) { - case '0': - case '1': + case '0': + case '1': // TODO: handle overflow next <<= 1; next += token - '0';
M src/gba/audio.csrc/gba/audio.c

@@ -41,7 +41,7 @@ #else

audio->left = blip_new(BLIP_BUFFER_SIZE); audio->right = blip_new(BLIP_BUFFER_SIZE); // Guess too large; we hang producing extra samples if we guess too low - blip_set_rates(audio->left, GBA_ARM7TDMI_FREQUENCY, 96000); + blip_set_rates(audio->left, GBA_ARM7TDMI_FREQUENCY, 96000); blip_set_rates(audio->right, GBA_ARM7TDMI_FREQUENCY, 96000); #endif CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);

@@ -167,7 +167,7 @@

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

@@ -456,7 +456,12 @@ audio->chATimer = GBARegisterSOUNDCNT_HIGetChATimer(value);

audio->chBRight = GBARegisterSOUNDCNT_HIGetChBRight(value); audio->chBLeft = GBARegisterSOUNDCNT_HIGetChBLeft(value); audio->chBTimer = GBARegisterSOUNDCNT_HIGetChBTimer(value); - // TODO: Implement channel reset + if (GBARegisterSOUNDCNT_HIIsChAReset(value)) { + CircleBufferClear(&audio->chA.fifo); + } + if (GBARegisterSOUNDCNT_HIIsChBReset(value)) { + CircleBufferClear(&audio->chB.fifo); + } } void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {

@@ -699,15 +704,15 @@ } else {

start = 3; end = 0; } - uint32_t bitsCarry = ch->wavedata[end] & 0x0F000000; + uint32_t bitsCarry = ch->wavedata[end] & 0x000000F0; uint32_t bits; for (i = start; i >= end; --i) { - bits = ch->wavedata[i] & 0x0F000000; - ch->wavedata[i] = ((ch->wavedata[i] & 0xF0F0F0F0) >> 4) | ((ch->wavedata[i] & 0x000F0F0F) << 12); - ch->wavedata[i] |= bitsCarry >> 20; + bits = ch->wavedata[i] & 0x000000F0; + ch->wavedata[i] = ((ch->wavedata[i] & 0x0F0F0F0F) << 4) | ((ch->wavedata[i] & 0xF0F0F000) >> 12); + ch->wavedata[i] |= bitsCarry << 20; bitsCarry = bits; } - ch->sample = bitsCarry >> 24; + ch->sample = bitsCarry >> 4; ch->sample -= 8; ch->sample *= volume * 4; return 8 * (2048 - ch->control.rate);
M src/gba/audio.hsrc/gba/audio.h

@@ -279,7 +279,8 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);

#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples); -unsigned GBAAudioResampleNN(struct GBAAudio*, float ratio, float* drift, struct GBAStereoSample* output, unsigned nSamples); +unsigned GBAAudioResampleNN(struct GBAAudio*, float ratio, float* drift, struct GBAStereoSample* output, + unsigned nSamples); #endif struct GBASerializedState;
M src/gba/bios.csrc/gba/bios.c

@@ -175,7 +175,7 @@

void GBASwi16(struct ARMCore* cpu, int immediate) { struct GBA* gba = (struct GBA*) cpu->master; GBALog(gba, GBA_LOG_SWI, "SWI: %02X r0: %08X r1: %08X r2: %08X r3: %08X", - immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); + immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); if (gba->memory.fullBios) { ARMRaiseSWI(cpu);

@@ -191,9 +191,12 @@ break;

case 0x2: GBAHalt(gba); break; + case 0x3: + GBAStop(gba); + break; case 0x05: - // VBlankIntrWait - // Fall through: + // VBlankIntrWait + // Fall through: case 0x04: // IntrWait ARMRaiseSWI(cpu);

@@ -236,14 +239,14 @@ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 source");

break; } switch (cpu->gprs[1] >> BASE_OFFSET) { - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination"); - // Fall through - case REGION_WORKING_RAM: - case REGION_WORKING_IRAM: - case REGION_VRAM: - _unLz77(gba, immediate == 0x11 ? 1 : 2); - break; + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination"); + // Fall through + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unLz77(gba, immediate == 0x11 ? 1 : 2); + break; } break; case 0x13:

@@ -252,14 +255,14 @@ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman source");

break; } switch (cpu->gprs[1] >> BASE_OFFSET) { - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination"); - // Fall through - case REGION_WORKING_RAM: - case REGION_WORKING_IRAM: - case REGION_VRAM: - _unHuffman(gba); - break; + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination"); + // Fall through + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unHuffman(gba); + break; } break; case 0x14:

@@ -269,14 +272,14 @@ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL source");

break; } switch (cpu->gprs[1] >> BASE_OFFSET) { - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination"); - // Fall through - case REGION_WORKING_RAM: - case REGION_WORKING_IRAM: - case REGION_VRAM: - _unRl(gba, immediate == 0x14 ? 1 : 2); - break; + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination"); + // Fall through + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unRl(gba, immediate == 0x14 ? 1 : 2); + break; } break; case 0x16:

@@ -287,15 +290,19 @@ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter source");

break; } switch (cpu->gprs[1] >> BASE_OFFSET) { - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter destination"); - // Fall through - case REGION_WORKING_RAM: - case REGION_WORKING_IRAM: - case REGION_VRAM: - _unFilter(gba, immediate == 0x18 ? 2 : 1, immediate == 0x16 ? 1 : 2); - break; + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter destination"); + // Fall through + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unFilter(gba, immediate == 0x18 ? 2 : 1, immediate == 0x16 ? 1 : 2); + break; } + break; + case 0x19: + // SoundBias is mostly meaningless here + GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: SoundBias (19)"); break; case 0x1F: _MidiKey2Freq(gba);

@@ -457,7 +464,6 @@ remaining -= 4;

block = 0; } } - } cpu->gprs[0] = source; cpu->gprs[1] = dest;
M src/gba/cheats/gameshark.csrc/gba/cheats/gameshark.c

@@ -197,8 +197,9 @@ GBACheatRegisterLine(set, line);

switch (set->gsaVersion) { case 0: + case 3: GBACheatSetGameSharkVersion(set, 1); - // Fall through + // Fall through case 1: GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); return GBACheatAddGameSharkRaw(set, o1, o2);
M src/gba/cheats/parv3.csrc/gba/cheats/parv3.c

@@ -297,9 +297,10 @@ GBACheatRegisterLine(set, line);

switch (set->gsaVersion) { case 0: + case 1: GBACheatSetGameSharkVersion(set, 3); - // Fall through - case 1: + // Fall through + case 3: GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); return GBACheatAddProActionReplayRaw(set, o1, o2); }
M src/gba/gba.csrc/gba/gba.c

@@ -79,8 +79,10 @@ gba->romVf = 0;

gba->biosVf = 0; gba->logHandler = 0; - gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; + gba->logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; gba->stream = 0; + gba->keyCallback = 0; + gba->stopCallback = 0; gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);

@@ -91,19 +93,30 @@ gba->haltPending = false;

gba->idleDetectionStep = 0; gba->idleDetectionFailures = 0; - gba->realisticTiming = false; + gba->realisticTiming = true; + gba->hardCrash = true; gba->performingDMA = false; } -void GBADestroy(struct GBA* gba) { - if (gba->pristineRom == gba->memory.rom) { - gba->memory.rom = 0; +void GBAUnloadROM(struct GBA* gba) { + if (gba->memory.rom && gba->pristineRom != gba->memory.rom) { + if (gba->yankedRomSize) { + gba->yankedRomSize = 0; + } + mappedMemoryFree(gba->memory.rom, SIZE_CART0); } + gba->memory.rom = 0; if (gba->romVf) { gba->romVf->unmap(gba->romVf, gba->pristineRom, gba->pristineRomSize); + gba->pristineRom = 0; + gba->romVf = 0; } +} + +void GBADestroy(struct GBA* gba) { + GBAUnloadROM(gba); if (gba->biosVf) { gba->biosVf->unmap(gba->biosVf, gba->memory.bios, SIZE_BIOS);

@@ -140,13 +153,17 @@ struct GBA* gba = (struct GBA*) cpu->master;

if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) { GBASavedataUnmask(&gba->memory.savedata); } + + if (gba->yankedRomSize) { + gba->memory.romSize = gba->yankedRomSize; + gba->yankedRomSize = 0; + } GBAMemoryReset(gba); GBAVideoReset(&gba->video); GBAAudioReset(&gba->audio); GBAIOInit(gba); - GBASIODeinit(&gba->sio); - GBASIOInit(&gba->sio); + GBASIOReset(&gba->sio); gba->timersEnabled = 0; memset(gba->timers, 0, sizeof(gba->timers));

@@ -166,6 +183,11 @@ struct GBA* gba = (struct GBA*) cpu->master;

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

@@ -221,7 +243,7 @@ timer = &gba->timers[0];

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;

@@ -365,6 +387,7 @@ gba->cpu->components[GBA_COMPONENT_DEBUGGER] = 0;

} void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname) { + GBAUnloadROM(gba); gba->romVf = vf; gba->pristineRomSize = vf->size(vf); vf->seek(vf, 0, SEEK_SET);

@@ -376,6 +399,7 @@ if (!gba->pristineRom) {

GBALog(gba, GBA_LOG_WARN, "Couldn't map ROM"); return; } + gba->yankedRomSize = 0; gba->memory.rom = gba->pristineRom; gba->activeFile = fname; gba->memory.romSize = gba->pristineRomSize;

@@ -385,6 +409,12 @@ GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]);

// TODO: error check } +void GBAYankROM(struct GBA* gba) { + gba->yankedRomSize = gba->memory.romSize; + gba->memory.romSize = 0; + GBARaiseIRQ(gba, IRQ_GAMEPAK); +} + void GBALoadBIOS(struct GBA* gba, struct VFile* vf) { gba->biosVf = vf; uint32_t* bios = vf->map(vf, SIZE_BIOS, MAP_READ);

@@ -412,10 +442,10 @@ }

void GBAApplyPatch(struct GBA* gba, struct Patch* patch) { size_t patchedSize = patch->outputSize(patch, gba->memory.romSize); - if (!patchedSize) { + if (!patchedSize || patchedSize > SIZE_CART0) { return; } - gba->memory.rom = anonymousMemoryMap(patchedSize); + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); if (!patch->applyPatch(patch, gba->pristineRom, gba->pristineRomSize, gba->memory.rom, patchedSize)) { mappedMemoryFree(gba->memory.rom, patchedSize); gba->memory.rom = gba->pristineRom;

@@ -428,7 +458,12 @@

void GBATimerUpdateRegister(struct GBA* gba, int timer) { struct GBATimer* currentTimer = &gba->timers[timer]; if (currentTimer->enable && !currentTimer->countUp) { - gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent) >> currentTimer->prescaleBits); + int32_t prefetchSkew = 0; + if (gba->memory.lastPrefetchedPc - gba->memory.lastPrefetchedLoads * WORD_SIZE_THUMB >= (uint32_t) gba->cpu->gprs[ARM_PC]) { + prefetchSkew = (gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * (gba->cpu->memory.activeSeqCycles16 + 1) / WORD_SIZE_THUMB; + } + // Reading this takes two cycles (1N+1I), so let's remove them preemptively + gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent - 2 + prefetchSkew) >> currentTimer->prescaleBits); } }

@@ -491,10 +526,6 @@ if (value & (1 << IRQ_KEYPAD)) {

GBALog(gba, GBA_LOG_STUB, "Keypad interrupts not implemented"); } - if (value & (1 << IRQ_GAMEPAK)) { - GBALog(gba, GBA_LOG_STUB, "Gamepak interrupts not implemented"); - } - if (gba->memory.io[REG_IME >> 1] && value & gba->memory.io[REG_IF >> 1]) { ARMRaiseIRQ(gba->cpu); }

@@ -528,6 +559,14 @@ gba->cpu->nextEvent = 0;

gba->cpu->halted = 1; } +void GBAStop(struct GBA* gba) { + if (!gba->stopCallback) { + return; + } + gba->cpu->nextEvent = 0; + gba->stopCallback->stop(gba->stopCallback); +} + static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) { struct GBAThread* threadContext = GBAThreadGetContext(); enum GBALogLevel logLevel = GBA_LOG_ALL;

@@ -650,7 +689,7 @@ }

void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) { struct GBA* gba = (struct GBA*) cpu->master; - enum GBALogLevel level = GBA_LOG_FATAL; + enum GBALogLevel level = GBA_LOG_ERROR; if (gba->debugger) { level = GBA_LOG_STUB; struct DebuggerEntryInfo info = {

@@ -664,13 +703,17 @@ }

void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) { struct GBA* gba = (struct GBA*) cpu->master; - GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode); + if (!gba->yankedRomSize) { + GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode); + } if (gba->debugger) { struct DebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), .opcode = opcode }; ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info); + } else { + ARMRaiseUndefined(cpu); } }

@@ -728,6 +771,8 @@ }

} void GBAFrameEnded(struct GBA* gba) { + GBASavedataClean(&gba->memory.savedata, gba->video.frameCounter); + if (gba->rr) { gba->rr->nextFrame(gba->rr); }

@@ -745,6 +790,10 @@ }

if (gba->stream) { gba->stream->postVideoFrame(gba->stream, gba->video.renderer); + } + + if (gba->memory.hw.devices & (HW_GB_PLAYER | HW_GB_PLAYER_DETECTION)) { + GBAHardwarePlayerUpdate(gba); } struct GBAThread* thread = GBAThreadGetContext();
M src/gba/gba.hsrc/gba/gba.h

@@ -11,6 +11,7 @@

#include "arm.h" #include "debugger/debugger.h" +#include "gba/interface.h" #include "gba/memory.h" #include "gba/video.h" #include "gba/audio.h"

@@ -35,43 +36,6 @@ IRQ_KEYPAD = 0xC,

IRQ_GAMEPAK = 0xD }; -enum GBALogLevel { - GBA_LOG_FATAL = 0x01, - GBA_LOG_ERROR = 0x02, - GBA_LOG_WARN = 0x04, - GBA_LOG_INFO = 0x08, - GBA_LOG_DEBUG = 0x10, - GBA_LOG_STUB = 0x20, - - GBA_LOG_GAME_ERROR = 0x100, - GBA_LOG_SWI = 0x200, - GBA_LOG_STATUS = 0x400, - GBA_LOG_SIO = 0x800, - - GBA_LOG_ALL = 0xF3F, - -#ifdef NDEBUG - GBA_LOG_DANGER = GBA_LOG_ERROR -#else - GBA_LOG_DANGER = GBA_LOG_FATAL -#endif -}; - -enum GBAKey { - GBA_KEY_A = 0, - GBA_KEY_B = 1, - GBA_KEY_SELECT = 2, - GBA_KEY_START = 3, - GBA_KEY_RIGHT = 4, - GBA_KEY_LEFT = 5, - GBA_KEY_UP = 6, - GBA_KEY_DOWN = 7, - GBA_KEY_R = 8, - GBA_KEY_L = 9, - GBA_KEY_MAX, - GBA_KEY_NONE = -1 -}; - enum GBAComponent { GBA_COMPONENT_DEBUGGER, GBA_COMPONENT_CHEAT_DEVICE,

@@ -91,19 +55,10 @@ SP_BASE_SUPERVISOR = 0x03007FE0

}; struct GBA; -struct GBARotationSource; struct GBAThread; struct Patch; struct VFile; -typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args); - -struct GBAAVStream { - void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer); - void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right); - void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*); -}; - struct GBATimer { uint16_t reload; uint16_t oldReload;

@@ -146,6 +101,7 @@

struct GBARRContext* rr; void* pristineRom; size_t pristineRomSize; + size_t yankedRomSize; uint32_t romCrc32; struct VFile* romVf; struct VFile* biosVf;

@@ -155,6 +111,8 @@

GBALogHandler logHandler; enum GBALogLevel logLevel; struct GBAAVStream* stream; + struct GBAKeyCallback* keyCallback; + struct GBAStopCallback* stopCallback; enum GBAIdleLoopOptimization idleOptimization; uint32_t idleLoop;

@@ -166,6 +124,7 @@ int32_t cachedRegisters[16];

bool taintedRegisters[16]; bool realisticTiming; + bool hardCrash; }; struct GBACartridge {

@@ -198,14 +157,18 @@ void GBAWriteIME(struct GBA* gba, uint16_t value);

void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq); void GBATestIRQ(struct ARMCore* cpu); void GBAHalt(struct GBA* gba); +void GBAStop(struct GBA* gba); void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger); void GBADetachDebugger(struct GBA* gba); -void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); +void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, + uint32_t* opcode); void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode); void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname); +void GBAYankROM(struct GBA* gba); +void GBAUnloadROM(struct GBA* gba); void GBALoadBIOS(struct GBA* gba, struct VFile* vf); void GBAApplyPatch(struct GBA* gba, struct Patch* patch);

@@ -217,7 +180,7 @@

void GBAFrameStarted(struct GBA* gba); void GBAFrameEnded(struct GBA* gba); -ATTRIBUTE_FORMAT(printf,3,4) +ATTRIBUTE_FORMAT(printf, 3, 4) void GBALog(struct GBA* gba, enum GBALogLevel level, const char* format, ...); ATTRIBUTE_FORMAT(printf, 3, 4)
M src/gba/hardware.csrc/gba/hardware.c

@@ -5,7 +5,11 @@ * 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 "hardware.h" +#include "gba/io.h" #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);

@@ -16,12 +20,18 @@ static void _rtcProcessByte(struct GBACartridgeHardware* hw);

static void _rtcUpdateClock(struct GBACartridgeHardware* hw); static unsigned _rtcBCD(unsigned value); +static time_t _rtcGenericCallback(struct GBARTCSource* source); + static void _gyroReadPins(struct GBACartridgeHardware* hw); static void _rumbleReadPins(struct GBACartridgeHardware* hw); static void _lightReadPins(struct GBACartridgeHardware* hw); +static uint16_t _gbpRead(struct GBAKeyCallback*); +static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); +static int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles); + static const int RTC_BYTES[8] = { 0, // Force reset 0, // Empty

@@ -36,13 +46,27 @@

void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) { hw->gpioBase = base; GBAHardwareClear(hw); + + hw->gbpCallback.d.readKeys = _gbpRead; + hw->gbpCallback.p = hw; + hw->gbpDriver.d.init = 0; + hw->gbpDriver.d.deinit = 0; + hw->gbpDriver.d.load = 0; + hw->gbpDriver.d.unload = 0; + hw->gbpDriver.d.writeRegister = _gbpSioWriteRegister; + hw->gbpDriver.d.processEvents = _gbpSioProcessEvents; + hw->gbpDriver.p = hw; } void GBAHardwareClear(struct GBACartridgeHardware* hw) { - hw->devices = HW_NONE; + hw->devices = HW_NONE | (hw->devices & HW_GB_PLAYER_DETECTION); hw->direction = GPIO_WRITE_ONLY; hw->pinState = 0; hw->direction = 0; + + if (hw->p->sio.drivers.normal == &hw->gbpDriver.d) { + GBASIOSetDriver(&hw->p->sio, 0, SIO_NORMAL_32); + } } void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) {

@@ -245,7 +269,9 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {

time_t t; struct GBARTCSource* rtc = hw->p->rtcSource; if (rtc) { - rtc->sample(rtc); + if (rtc->sample) { + rtc->sample(rtc); + } t = rtc->unixTime(rtc); } else { t = time(0);

@@ -276,6 +302,27 @@ counter += (value % 10) << 4;

return counter; } +time_t _rtcGenericCallback(struct GBARTCSource* source) { + struct GBARTCGenericSource* rtc = (struct GBARTCGenericSource*) source; + switch (rtc->override) { + case RTC_NO_OVERRIDE: + default: + return time(0); + case RTC_FIXED: + return rtc->value; + case RTC_FAKE_EPOCH: + return rtc->value + rtc->p->video.frameCounter * (int64_t) VIDEO_TOTAL_LENGTH / GBA_ARM7TDMI_FREQUENCY; + } +} + +void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba) { + rtc->p = gba; + rtc->override = RTC_NO_OVERRIDE; + rtc->value = 0; + rtc->d.sample = 0; + rtc->d.unixTime = _rtcGenericCallback; +} + // == Gyro void GBAHardwareInitGyro(struct GBACartridgeHardware* hw) {

@@ -420,6 +467,130 @@ }

return 0xFF; } +// == Game Boy Player + +static const uint16_t _logoPalette[] = { + 0xFFDF, 0x640C, 0xE40C, 0xE42D, 0x644E, 0xE44E, 0xE46E, 0x68AF, + 0xE8B0, 0x68D0, 0x68F0, 0x6911, 0xE911, 0x6D32, 0xED32, 0xED73, + 0x6D93, 0xED94, 0x6DB4, 0xF1D5, 0x71F5, 0xF1F6, 0x7216, 0x7257, + 0xF657, 0x7678, 0xF678, 0xF699, 0xF6B9, 0x76D9, 0xF6DA, 0x7B1B, + 0xFB1B, 0xFB3C, 0x7B5C, 0x7B7D, 0xFF7D, 0x7F9D, 0x7FBE, 0x7FFF, + 0x642D, 0x648E, 0xE88F, 0xE8F1, 0x6D52, 0x6D73, 0xF1B4, 0xF216, + 0x7237, 0x7698, 0x7AFA, 0xFAFA, 0xFB5C, 0xFFBE, 0x7FDE, 0xFFFF, + 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +static const uint32_t _logoHash = 0xEEDA6963; + +static const uint32_t _gbpTxData[] = { + 0x0000494E, 0x0000494E, + 0xB6B1494E, 0xB6B1544E, + 0xABB1544E, 0xABB14E45, + 0xB1BA4E45, 0xB1BA4F44, + 0xB0BB4F44, 0xB0BB8002, + 0x10000010, 0x20000013, + 0x30000003, 0x30000003, + 0x30000003, 0x30000003, + 0x30000003, 0x00000000, +}; + +static const uint32_t _gbpRxData[] = { + 0x00000000, 0x494EB6B1, + 0x494EB6B1, 0x544EB6B1, + 0x544EABB1, 0x4E45ABB1, + 0x4E45B1BA, 0x4F44B1BA, + 0x4F44B0BB, 0x8000B0BB, + 0x10000010, 0x20000013, + 0x40000004, 0x40000004, + 0x40000004, 0x40000004, + 0x40000004, 0x40000004 +}; + +bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) { + if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) { + return false; + } + uint32_t hash = hash32(&video->renderer->vram[0x4000], 0x4000, 0); + return hash == _logoHash; +} + +void GBAHardwarePlayerUpdate(struct GBA* gba) { + if (gba->memory.hw.devices & HW_GB_PLAYER) { + if (GBAHardwarePlayerCheckScreen(&gba->video)) { + ++gba->memory.hw.gbpInputsPosted; + gba->memory.hw.gbpInputsPosted %= 3; + gba->keyCallback = &gba->memory.hw.gbpCallback.d; + } else { + // TODO: Save and restore + gba->keyCallback = 0; + } + gba->memory.hw.gbpTxPosition = 0; + return; + } + if (gba->keyCallback || gba->sio.drivers.normal) { + return; + } + if (GBAHardwarePlayerCheckScreen(&gba->video)) { + gba->memory.hw.devices |= HW_GB_PLAYER; + gba->memory.hw.gbpInputsPosted = 0; + gba->memory.hw.gbpNextEvent = INT_MAX; + gba->keyCallback = &gba->memory.hw.gbpCallback.d; + GBASIOSetDriver(&gba->sio, &gba->memory.hw.gbpDriver.d, SIO_NORMAL_32); + } +} + +uint16_t _gbpRead(struct GBAKeyCallback* callback) { + struct GBAGBPKeyCallback* gbpCallback = (struct GBAGBPKeyCallback*) callback; + if (gbpCallback->p->gbpInputsPosted == 2) { + return 0x30F; + } + return 0x3FF; +} + +uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { + struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver; + if (address == REG_SIOCNT) { + if (value & 0x0080) { + if (gbp->p->gbpTxPosition <= 16 && gbp->p->gbpTxPosition > 0) { + uint32_t rx = gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] | (gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] << 16); + uint32_t expected = _gbpRxData[gbp->p->gbpTxPosition]; + // TODO: Check expected + uint32_t mask = 0; + if (gbp->p->gbpTxPosition == 15) { + mask = 0x22; + if (gbp->p->p->rumble) { + gbp->p->p->rumble->setRumble(gbp->p->p->rumble, (rx & mask) == mask); + } + } + } + gbp->p->gbpNextEvent = 2048; + } + value &= 0x78FB; + } + return value; +} + +int32_t _gbpSioProcessEvents(struct GBASIODriver* driver, int32_t cycles) { + struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver; + gbp->p->gbpNextEvent -= cycles; + if (gbp->p->gbpNextEvent <= 0) { + uint32_t tx = 0; + if (gbp->p->gbpTxPosition <= 16) { + tx = _gbpTxData[gbp->p->gbpTxPosition]; + ++gbp->p->gbpTxPosition; + } + gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx; + gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16; + if (gbp->d.p->normalControl.irq) { + GBARaiseIRQ(gbp->p->p, IRQ_SIO); + } + gbp->d.p->normalControl.start = 0; + gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt; + gbp->p->gbpNextEvent = INT_MAX; + } + return gbp->p->gbpNextEvent; +} + // == Serialization void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {

@@ -436,13 +607,16 @@ state->hw.tiltState = hw->tiltState;

state->hw.lightCounter = hw->lightCounter; state->hw.lightSample = hw->lightSample; state->hw.lightEdge = hw->lightEdge; + state->hw.gbpInputsPosted = hw->gbpInputsPosted; + state->hw.gbpTxPosition = hw->gbpTxPosition; + state->hw.gbpNextEvent = hw->gbpNextEvent; } void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) { hw->readWrite = state->hw.readWrite; hw->pinState = state->hw.pinState; hw->direction = state->hw.pinDirection; - // TODO: Deterministic RTC + hw->devices = state->hw.devices; hw->rtc = state->hw.rtc; hw->gyroSample = state->hw.gyroSample; hw->gyroEdge = state->hw.gyroEdge;

@@ -452,4 +626,10 @@ hw->tiltState = state->hw.tiltState;

hw->lightCounter = state->hw.lightCounter; hw->lightSample = state->hw.lightSample; hw->lightEdge = state->hw.lightEdge; + 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); + } }
M src/gba/hardware.hsrc/gba/hardware.h

@@ -7,6 +7,7 @@ #ifndef GBA_HARDWARE_H

#define GBA_HARDWARE_H #include "util/common.h" +#include "gba/interface.h" #include "macros.h"

@@ -14,25 +15,15 @@ #include <time.h>

#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL) -struct GBARotationSource { - void (*sample)(struct GBARotationSource*); - - int32_t (*readTiltX)(struct GBARotationSource*); - int32_t (*readTiltY)(struct GBARotationSource*); - - int32_t (*readGyroZ)(struct GBARotationSource*); -}; - -struct GBALuminanceSource { - void (*sample)(struct GBALuminanceSource*); - - uint8_t (*readLuminance)(struct GBALuminanceSource*); -}; - -struct GBARTCSource { - void (*sample)(struct GBARTCSource*); - - time_t (*unixTime)(struct GBARTCSource*); +struct GBARTCGenericSource { + struct GBARTCSource d; + struct GBA* p; + enum { + RTC_NO_OVERRIDE, + RTC_FIXED, + RTC_FAKE_EPOCH + } override; + int64_t value; }; enum GBAHardwareDevice {

@@ -42,7 +33,9 @@ HW_RTC = 1,

HW_RUMBLE = 2, HW_LIGHT_SENSOR = 4, HW_GYRO = 8, - HW_TILT = 16 + HW_TILT = 16, + HW_GB_PLAYER = 32, + HW_GB_PLAYER_DETECTION = 64 }; enum GPIORegister {

@@ -91,6 +84,16 @@ struct GBARumble {

void (*setRumble)(struct GBARumble*, int enable); }; +struct GBAGBPKeyCallback { + struct GBAKeyCallback d; + struct GBACartridgeHardware* p; +}; + +struct GBAGBPSIODriver { + struct GBASIODriver d; + struct GBACartridgeHardware* p; +}; + DECL_BITFIELD(GPIOPin, uint16_t); struct GBACartridgeHardware {

@@ -114,6 +117,12 @@

uint16_t tiltX; uint16_t tiltY; int tiltState; + + unsigned gbpInputsPosted; + int gbpTxPosition; + int32_t gbpNextEvent; + struct GBAGBPKeyCallback gbpCallback; + struct GBAGBPSIODriver gbpDriver; }; void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase);

@@ -128,6 +137,12 @@

void GBAHardwareGPIOWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint16_t value); void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint8_t value); uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address); + +struct GBAVideo; +void GBAHardwarePlayerUpdate(struct GBA* gba); +bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video); + +void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba); struct GBASerializedState; void GBAHardwareSerialize(const struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
M src/gba/hle-bios.csrc/gba/hle-bios.c

@@ -3,49 +3,49 @@

#include "gba/memory.h" const uint8_t hleBios[SIZE_BIOS] = { - 0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x07, 0x00, 0x00, 0xea, - 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1, - 0x28, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0x00, 0x00, 0x5d, 0xe3, - 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02, 0x00, 0x58, 0x2d, 0xe9, - 0x02, 0xb0, 0x5e, 0xe5, 0x8c, 0xc0, 0xa0, 0xe3, 0x0b, 0xb1, 0x9c, 0xe7, - 0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1, 0x00, 0x10, 0x2d, 0xe9, - 0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3, 0x0c, 0xf0, 0x29, 0xe1, - 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1, 0x1b, 0xff, 0x2f, 0x11, - 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, 0x00, 0x10, 0xbd, 0xe8, - 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xe8, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, - 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, - 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, - 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5, 0x01, 0x00, 0xa0, 0xe3, - 0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0xc3, 0xa0, 0xe3, - 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3, - 0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0xc3, 0xe1, - 0xb8, 0x30, 0x4c, 0xe1, 0x01, 0x03, 0xcc, 0xe5, 0x08, 0x02, 0xcc, 0xe5, - 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0x13, 0xe0, 0x01, 0x30, 0x23, 0x10, - 0xb8, 0x30, 0x4c, 0x11, 0x08, 0x22, 0xcc, 0xe5, 0xf7, 0xff, 0xff, 0x0a, - 0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x02, 0x36, 0xa0, 0xe1, - 0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, - 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8, - 0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, - 0x16, 0x00, 0x00, 0xea, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, - 0xa3, 0x35, 0x81, 0xe0, 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1, - 0xb2, 0x20, 0xc1, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea, - 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, - 0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8, - 0xfb, 0xff, 0xff, 0xba, 0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0, - 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1, - 0xb2, 0x20, 0xd0, 0xb0, 0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba, - 0x00, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, 0x01, 0x04, 0x12, 0xe3, - 0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0, 0x0b, 0x00, 0x00, 0x0a, - 0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1, - 0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1, - 0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1, 0x02, 0x00, 0x51, 0xe1, - 0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x03, 0x00, 0x00, 0xea, - 0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8, 0xf8, 0x07, 0xa1, 0xb8, - 0xfb, 0xff, 0xff, 0xba, 0xf0, 0x87, 0xbd, 0xe8 + 0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x07, 0x00, 0x00, 0xea, + 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1, + 0x28, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0x00, 0x00, 0x5d, 0xe3, + 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02, 0x00, 0x58, 0x2d, 0xe9, + 0x02, 0xb0, 0x5e, 0xe5, 0x8c, 0xc0, 0xa0, 0xe3, 0x0b, 0xb1, 0x9c, 0xe7, + 0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1, 0x00, 0x10, 0x2d, 0xe9, + 0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3, 0x0c, 0xf0, 0x29, 0xe1, + 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1, 0x1b, 0xff, 0x2f, 0x11, + 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, 0x00, 0x10, 0xbd, 0xe8, + 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe8, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, + 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, + 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, + 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5, 0x01, 0x00, 0xa0, 0xe3, + 0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0xc3, 0xa0, 0xe3, + 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3, + 0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0xc3, 0xe1, + 0xb8, 0x30, 0x4c, 0xe1, 0x01, 0x03, 0xcc, 0xe5, 0x08, 0x02, 0xcc, 0xe5, + 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0x13, 0xe0, 0x01, 0x30, 0x23, 0x10, + 0xb8, 0x30, 0x4c, 0x11, 0x08, 0x22, 0xcc, 0xe5, 0xf7, 0xff, 0xff, 0x0a, + 0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x02, 0x36, 0xa0, 0xe1, + 0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, + 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8, + 0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, + 0x16, 0x00, 0x00, 0xea, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, + 0xa3, 0x35, 0x81, 0xe0, 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1, + 0xb2, 0x20, 0xc1, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea, + 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, + 0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8, + 0xfb, 0xff, 0xff, 0xba, 0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0, + 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1, + 0xb2, 0x20, 0xd0, 0xb0, 0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba, + 0x00, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, 0x01, 0x04, 0x12, 0xe3, + 0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0, 0x0b, 0x00, 0x00, 0x0a, + 0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1, + 0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1, + 0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1, 0x02, 0x00, 0x51, 0xe1, + 0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x03, 0x00, 0x00, 0xea, + 0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8, 0xf8, 0x07, 0xa1, 0xb8, + 0xfb, 0xff, 0xff, 0xba, 0xf0, 0x87, 0xbd, 0xe8 };
M src/gba/input.csrc/gba/input.c

@@ -257,7 +257,10 @@ description->lowDirection = GBA_KEY_NONE;

} } -static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) { +static bool _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) { + if (!ConfigurationHasSection(config, sectionName)) { + return false; + } _loadKey(map, type, sectionName, config, GBA_KEY_A, "A"); _loadKey(map, type, sectionName, config, GBA_KEY_B, "B"); _loadKey(map, type, sectionName, config, GBA_KEY_L, "L");

@@ -279,6 +282,7 @@ _loadAxis(map, type, sectionName, config, GBA_KEY_UP, "Up");

_loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down"); _loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left"); _loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right"); + return true; } static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {

@@ -348,6 +352,20 @@ }

return GBA_KEY_NONE; } +int GBAInputMapKeyBits(const struct GBAInputMap* map, uint32_t type, uint32_t bits, unsigned offset) { + int keys = 0; + for (; bits; bits >>= 1, ++offset) { + if (bits & 1) { + enum GBAKey key = GBAInputMapKey(map, type, offset); + if (key == GBA_KEY_NONE) { + continue; + } + keys |= 1 << key; + } + } + return keys; +} + void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) { struct GBAInputMapImpl* impl = _guaranteeMap(map, type); GBAInputUnbindKey(map, type, input);

@@ -362,7 +380,6 @@ }

if (impl) { impl->map[input] = GBA_NO_MAPPING; } - TableEnumerate(&impl->axes, _unbindAxis, &input); } int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) {

@@ -416,9 +433,10 @@ }

void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) { struct GBAInputMapImpl* impl = _guaranteeMap(map, type); + struct GBAAxis d2 = *description; + TableEnumerate(&impl->axes, _unbindAxis, &d2.highDirection); + TableEnumerate(&impl->axes, _unbindAxis, &d2.lowDirection); struct GBAAxis* dup = malloc(sizeof(struct GBAAxis)); - GBAInputUnbindKey(map, type, description->lowDirection); - GBAInputUnbindKey(map, type, description->highDirection); *dup = *description; TableInsert(&impl->axes, axis, dup); }

@@ -469,11 +487,11 @@ _makeSectionName(sectionName, SECTION_NAME_MAX, type);

_saveAll(map, type, sectionName, config); } -void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) { +bool GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) { char sectionName[SECTION_NAME_MAX]; snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); sectionName[SECTION_NAME_MAX - 1] = '\0'; - _loadAll(map, type, sectionName, config); + return _loadAll(map, type, sectionName, config); } void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
M src/gba/input.hsrc/gba/input.h

@@ -30,6 +30,7 @@ void GBAInputMapInit(struct GBAInputMap*);

void GBAInputMapDeinit(struct GBAInputMap*); enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key); +int GBAInputMapKeyBits(const struct GBAInputMap* map, uint32_t type, uint32_t bits, unsigned offset); void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input); void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, enum GBAKey input); int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);

@@ -45,13 +46,15 @@

void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*); void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*); -void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile); +bool GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile); void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile); const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId); void GBAInputSetPreferredDevice(struct Configuration*, uint32_t type, int playerId, const char* deviceName); -const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile); -void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile); +const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, + const char* profile); +void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, + const char* profile); #endif
A src/gba/interface.h

@@ -0,0 +1,107 @@

+/* 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 INTERFACE_H +#define INTERFACE_H + +#include "util/common.h" + +enum GBALogLevel { + GBA_LOG_FATAL = 0x01, + GBA_LOG_ERROR = 0x02, + GBA_LOG_WARN = 0x04, + GBA_LOG_INFO = 0x08, + GBA_LOG_DEBUG = 0x10, + GBA_LOG_STUB = 0x20, + + GBA_LOG_GAME_ERROR = 0x100, + GBA_LOG_SWI = 0x200, + GBA_LOG_STATUS = 0x400, + GBA_LOG_SIO = 0x800, + + GBA_LOG_ALL = 0xF3F, +}; + +enum GBAKey { + GBA_KEY_A = 0, + GBA_KEY_B = 1, + GBA_KEY_SELECT = 2, + GBA_KEY_START = 3, + GBA_KEY_RIGHT = 4, + GBA_KEY_LEFT = 5, + GBA_KEY_UP = 6, + GBA_KEY_DOWN = 7, + GBA_KEY_R = 8, + GBA_KEY_L = 9, + GBA_KEY_MAX, + GBA_KEY_NONE = -1 +}; + +enum GBASIOMode { + SIO_NORMAL_8 = 0, + SIO_NORMAL_32 = 1, + SIO_MULTI = 2, + SIO_UART = 3, + SIO_GPIO = 8, + SIO_JOYBUS = 12 +}; + +struct GBA; +struct GBAAudio; +struct GBASIO; +struct GBAThread; +struct GBAVideoRenderer; + +typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args); + +struct GBAAVStream { + void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer); + void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right); + void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*); +}; + +struct GBAKeyCallback { + uint16_t (*readKeys)(struct GBAKeyCallback*); +}; + +struct GBAStopCallback { + void (*stop)(struct GBAStopCallback*); +}; + +struct GBARotationSource { + void (*sample)(struct GBARotationSource*); + + int32_t (*readTiltX)(struct GBARotationSource*); + int32_t (*readTiltY)(struct GBARotationSource*); + + int32_t (*readGyroZ)(struct GBARotationSource*); +}; + +extern const int GBA_LUX_LEVELS[10]; + +struct GBALuminanceSource { + void (*sample)(struct GBALuminanceSource*); + + uint8_t (*readLuminance)(struct GBALuminanceSource*); +}; + +struct GBARTCSource { + void (*sample)(struct GBARTCSource*); + + time_t (*unixTime)(struct GBARTCSource*); +}; + +struct GBASIODriver { + struct GBASIO* p; + + bool (*init)(struct GBASIODriver* driver); + void (*deinit)(struct GBASIODriver* driver); + bool (*load)(struct GBASIODriver* driver); + bool (*unload)(struct GBASIODriver* driver); + uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); + int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles); +}; + +#endif
M src/gba/io.csrc/gba/io.c

@@ -333,7 +333,7 @@ break;

case REG_SOUND3CNT_X: GBAAudioWriteSOUND3CNT_X(&gba->audio, value); // TODO: The low bits need to not be readable, but still 8-bit writable - value &= 0x43FF; + value &= 0x47FF; break; case REG_SOUND4CNT_LO: GBAAudioWriteSOUND4CNT_LO(&gba->audio, value);

@@ -505,7 +505,7 @@ value &= 0x80;

if (!value) { GBAHalt(gba); } else { - GBALog(gba, GBA_LOG_STUB, "Stop unimplemented"); + GBAStop(gba); } return; }

@@ -584,14 +584,18 @@

case REG_KEYINPUT: if (gba->rr && gba->rr->isPlaying(gba->rr)) { return 0x3FF ^ gba->rr->queryInput(gba->rr); - } else if (gba->keySource) { - uint16_t input = *gba->keySource; + } else { + uint16_t input = 0x3FF; + if (gba->keyCallback) { + input = gba->keyCallback->readKeys(gba->keyCallback); + } else if (gba->keySource) { + input = *gba->keySource; + } if (gba->rr && gba->rr->isRecording(gba->rr)) { gba->rr->logInput(gba->rr, input); } return 0x3FF ^ input; } - break; case REG_SIOCNT: return gba->sio.siocnt;
M src/gba/memory.csrc/gba/memory.c

@@ -18,15 +18,16 @@ #define IDLE_LOOP_THRESHOLD 10000

static uint32_t _popcount32(unsigned bits); static void _pristineCow(struct GBA* gba); -static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE }; +static uint32_t _deadbeef[1] = { 0xE710B710 }; // Illegal instruction on both ARM and Thumb static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info); +static int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait); static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 }; -static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 0, 0, 0, 7, 7, 9, 9, 13, 13, 9 }; +static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 7, 7, 9, 9, 13, 13, 9 }; static const char GBA_BASE_WAITSTATES_SEQ[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 }; -static const char GBA_BASE_WAITSTATES_SEQ_32[16] = { 0, 0, 5, 0, 0, 0, 0, 0, 5, 5, 9, 9, 17, 17, 9 }; +static const char GBA_BASE_WAITSTATES_SEQ_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 5, 5, 9, 9, 17, 17, 9 }; static const char GBA_ROM_WAITSTATES[] = { 4, 3, 2, 8 }; static const char GBA_ROM_WAITSTATES_SEQ[] = { 2, 1, 4, 1, 8, 1 }; static const int DMA_OFFSET[] = { 1, -1, 0, 1 };

@@ -41,6 +42,7 @@ cpu->memory.store32 = GBAStore32;

cpu->memory.store16 = GBAStore16; cpu->memory.store8 = GBAStore8; cpu->memory.storeMultiple = GBAStoreMultiple; + cpu->memory.stall = GBAMemoryStall; gba->memory.bios = (uint32_t*) hleBios; gba->memory.fullBios = 0;

@@ -76,8 +78,6 @@ cpu->memory.activeSeqCycles32 = 0;

cpu->memory.activeSeqCycles16 = 0; cpu->memory.activeNonseqCycles32 = 0; cpu->memory.activeNonseqCycles16 = 0; - cpu->memory.activeUncachedCycles32 = 0; - cpu->memory.activeUncachedCycles16 = 0; gba->memory.biosPrefetch = 0; }

@@ -112,6 +112,9 @@ gba->memory.dma[3].count = 0x10000;

gba->memory.activeDMA = -1; gba->memory.nextDMA = INT_MAX; gba->memory.eventDiff = 0; + + gba->memory.prefetch = false; + gba->memory.lastPrefetchedPc = 0; if (!gba->memory.wram || !gba->memory.iwram) { GBAMemoryDeinit(gba);

@@ -232,6 +235,8 @@ }

} gba->lastJump = address; + memory->lastPrefetchedPc = 0; + memory->lastPrefetchedLoads = 0; if (newRegion == memory->activeRegion && (newRegion < REGION_CART0 || (address & (SIZE_CART0 - 1)) < memory->romSize)) { return; }

@@ -268,20 +273,22 @@ cpu->memory.activeMask = SIZE_CART0 - 1;

if ((address & (SIZE_CART0 - 1)) < memory->romSize) { break; } - // Fall through + // Fall through default: - memory->activeRegion = 0; + memory->activeRegion = -1; cpu->memory.activeRegion = _deadbeef; cpu->memory.activeMask = 0; - GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address"); - break; + 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->waitstatesPrefetchSeq32[memory->activeRegion]; - cpu->memory.activeSeqCycles16 = memory->waitstatesPrefetchSeq16[memory->activeRegion]; - cpu->memory.activeNonseqCycles32 = memory->waitstatesPrefetchNonseq32[memory->activeRegion]; - cpu->memory.activeNonseqCycles16 = memory->waitstatesPrefetchNonseq16[memory->activeRegion]; - cpu->memory.activeUncachedCycles32 = memory->waitstatesNonseq32[memory->activeRegion]; - cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[memory->activeRegion]; + cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion]; + cpu->memory.activeSeqCycles16 = memory->waitstatesSeq16[memory->activeRegion]; + cpu->memory.activeNonseqCycles32 = memory->waitstatesNonseq32[memory->activeRegion]; + cpu->memory.activeNonseqCycles16 = memory->waitstatesNonseq16[memory->activeRegion]; } #define LOAD_BAD \

@@ -334,7 +341,7 @@ #define LOAD_IO value = GBAIORead(gba, (address & (SIZE_IO - 1)) & ~2) | (GBAIORead(gba, (address & (SIZE_IO - 1)) | 2) << 16);

#define LOAD_PALETTE_RAM \ LOAD_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \ - ++wait; + wait += waitstatesRegion[REGION_PALETTE_RAM]; #define LOAD_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \

@@ -342,7 +349,7 @@ LOAD_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \

} else { \ LOAD_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ } \ - ++wait; + wait += waitstatesRegion[REGION_VRAM]; #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);

@@ -410,7 +417,11 @@ break;

} if (cycleCounter) { - *cycleCounter += 1 + wait; + wait += 2; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } + *cycleCounter += wait; } // Unaligned 32-bit loads are "rotated" so they make some semblance of sense int rotate = (address & 3) << 3;

@@ -435,8 +446,7 @@ }

} else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); LOAD_BAD; - uint32_t v2 = value; - LOAD_16(value, address & 2, &v2); + value = (value >> ((address & 2) * 8)) & 0xFFFF; } break; case REGION_WORKING_RAM:

@@ -472,7 +482,7 @@ if ((address & (SIZE_CART0 - 1)) < memory->romSize) {

LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); - value = (address >> 1) & 0xFFFF; \ + value = (address >> 1) & 0xFFFF; } break; case REGION_CART2_EX:

@@ -483,7 +493,7 @@ } else if ((address & (SIZE_CART0 - 1)) < memory->romSize) {

LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); - value = (address >> 1) & 0xFFFF; \ + value = (address >> 1) & 0xFFFF; } break; case REGION_CART_SRAM:

@@ -495,13 +505,16 @@ break;

default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); LOAD_BAD; - uint32_t v2 = value; - LOAD_16(value, address & 2, &v2); + value = (value >> ((address & 2) * 8)) & 0xFFFF; break; } if (cycleCounter) { - *cycleCounter += 1 + wait; + wait += 2; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } + *cycleCounter += wait; } // Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too. int rotate = (address & 1) << 3;

@@ -563,7 +576,7 @@ if ((address & (SIZE_CART0 - 1)) < memory->romSize) {

value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); - value = (address >> 1) & 0xFF; \ + value = (address >> 1) & 0xFF; } break; case REGION_CART_SRAM:

@@ -593,7 +606,11 @@ break;

} if (cycleCounter) { - *cycleCounter += 1 + wait; + wait += 2; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } + *cycleCounter += wait; } return value; }

@@ -611,16 +628,20 @@

#define STORE_PALETTE_RAM \ STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \ gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16); \ - ++wait; \ + wait += waitstatesRegion[REGION_PALETTE_RAM]; \ gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value); #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; + wait += waitstatesRegion[REGION_VRAM]; #define STORE_OAM \ STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); \

@@ -680,7 +701,11 @@ break;

} if (cycleCounter) { - *cycleCounter += 1 + wait; + ++wait; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } + *cycleCounter += wait; } }

@@ -707,8 +732,10 @@ break;

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:

@@ -740,7 +767,11 @@ break;

} if (cycleCounter) { - *cycleCounter += 1 + wait; + ++wait; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } + *cycleCounter += wait; } }

@@ -769,8 +800,8 @@ // TODO: check BG mode

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

@@ -793,6 +824,7 @@ if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) {

GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == SAVEDATA_SRAM) { memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value; + memory->savedata.dirty |= SAVEDATA_DIRT_NEW; } else if (memory->hw.devices & HW_TILT) { GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value); } else {

@@ -806,7 +838,11 @@ break;

} if (cycleCounter) { - *cycleCounter += 1 + wait; + ++wait; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } + *cycleCounter += wait; } }

@@ -1098,6 +1134,10 @@ break;

} if (cycleCounter) { + ++wait; + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } *cycleCounter += wait; }

@@ -1204,6 +1244,9 @@ break;

} if (cycleCounter) { + if (address >> BASE_OFFSET < REGION_CART0) { + wait = GBAMemoryStall(cpu, wait); + } *cycleCounter += wait; }

@@ -1230,7 +1273,7 @@ int ws2 = (parameters & 0x0300) >> 8;

int ws2seq = (parameters & 0x0400) >> 10; int prefetch = parameters & 0x4000; - memory->waitstatesNonseq16[REGION_CART_SRAM] = memory->waitstatesNonseq16[REGION_CART_SRAM_MIRROR] = GBA_ROM_WAITSTATES[sram]; + memory->waitstatesNonseq16[REGION_CART_SRAM] = memory->waitstatesNonseq16[REGION_CART_SRAM_MIRROR] = GBA_ROM_WAITSTATES[sram]; memory->waitstatesSeq16[REGION_CART_SRAM] = memory->waitstatesSeq16[REGION_CART_SRAM_MIRROR] = GBA_ROM_WAITSTATES[sram]; memory->waitstatesNonseq32[REGION_CART_SRAM] = memory->waitstatesNonseq32[REGION_CART_SRAM_MIRROR] = 2 * GBA_ROM_WAITSTATES[sram] + 1; memory->waitstatesSeq32[REGION_CART_SRAM] = memory->waitstatesSeq32[REGION_CART_SRAM_MIRROR] = 2 * GBA_ROM_WAITSTATES[sram] + 1;

@@ -1251,48 +1294,13 @@ memory->waitstatesSeq32[REGION_CART0] = memory->waitstatesSeq32[REGION_CART0_EX] = 2 * memory->waitstatesSeq16[REGION_CART0] + 1;

memory->waitstatesSeq32[REGION_CART1] = memory->waitstatesSeq32[REGION_CART1_EX] = 2 * memory->waitstatesSeq16[REGION_CART1] + 1; memory->waitstatesSeq32[REGION_CART2] = memory->waitstatesSeq32[REGION_CART2_EX] = 2 * memory->waitstatesSeq16[REGION_CART2] + 1; - if (!prefetch) { - memory->waitstatesPrefetchSeq16[REGION_CART0] = memory->waitstatesPrefetchSeq16[REGION_CART0_EX] = memory->waitstatesSeq16[REGION_CART0]; - memory->waitstatesPrefetchSeq16[REGION_CART1] = memory->waitstatesPrefetchSeq16[REGION_CART1_EX] = memory->waitstatesSeq16[REGION_CART1]; - memory->waitstatesPrefetchSeq16[REGION_CART2] = memory->waitstatesPrefetchSeq16[REGION_CART2_EX] = memory->waitstatesSeq16[REGION_CART2]; - - memory->waitstatesPrefetchSeq32[REGION_CART0] = memory->waitstatesPrefetchSeq32[REGION_CART0_EX] = memory->waitstatesSeq32[REGION_CART0]; - memory->waitstatesPrefetchSeq32[REGION_CART1] = memory->waitstatesPrefetchSeq32[REGION_CART1_EX] = memory->waitstatesSeq32[REGION_CART1]; - memory->waitstatesPrefetchSeq32[REGION_CART2] = memory->waitstatesPrefetchSeq32[REGION_CART2_EX] = memory->waitstatesSeq32[REGION_CART2]; + memory->prefetch = prefetch; - memory->waitstatesPrefetchNonseq16[REGION_CART0] = memory->waitstatesPrefetchNonseq16[REGION_CART0_EX] = memory->waitstatesNonseq16[REGION_CART0]; - memory->waitstatesPrefetchNonseq16[REGION_CART1] = memory->waitstatesPrefetchNonseq16[REGION_CART1_EX] = memory->waitstatesNonseq16[REGION_CART1]; - memory->waitstatesPrefetchNonseq16[REGION_CART2] = memory->waitstatesPrefetchNonseq16[REGION_CART2_EX] = memory->waitstatesNonseq16[REGION_CART2]; + cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion]; + cpu->memory.activeSeqCycles16 = memory->waitstatesSeq16[memory->activeRegion]; - memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = memory->waitstatesNonseq32[REGION_CART0]; - memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq32[REGION_CART1]; - memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq32[REGION_CART2]; - } else { - memory->waitstatesPrefetchSeq16[REGION_CART0] = memory->waitstatesPrefetchSeq16[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchSeq16[REGION_CART1] = memory->waitstatesPrefetchSeq16[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchSeq16[REGION_CART2] = memory->waitstatesPrefetchSeq16[REGION_CART2_EX] = 0; - - memory->waitstatesPrefetchSeq32[REGION_CART0] = memory->waitstatesPrefetchSeq32[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchSeq32[REGION_CART1] = memory->waitstatesPrefetchSeq32[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchSeq32[REGION_CART2] = memory->waitstatesPrefetchSeq32[REGION_CART2_EX] = 0; - - memory->waitstatesPrefetchNonseq16[REGION_CART0] = memory->waitstatesPrefetchNonseq16[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchNonseq16[REGION_CART1] = memory->waitstatesPrefetchNonseq16[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchNonseq16[REGION_CART2] = memory->waitstatesPrefetchNonseq16[REGION_CART2_EX] = 0; - - memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = 0; - } - - cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[memory->activeRegion]; - cpu->memory.activeSeqCycles16 = memory->waitstatesPrefetchSeq16[memory->activeRegion]; - - cpu->memory.activeNonseqCycles32 = memory->waitstatesPrefetchNonseq32[memory->activeRegion]; - cpu->memory.activeNonseqCycles16 = memory->waitstatesPrefetchNonseq16[memory->activeRegion]; - - cpu->memory.activeUncachedCycles32 = memory->waitstatesNonseq32[memory->activeRegion]; - cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[memory->activeRegion]; + cpu->memory.activeNonseqCycles32 = memory->waitstatesNonseq32[memory->activeRegion]; + cpu->memory.activeNonseqCycles16 = memory->waitstatesNonseq16[memory->activeRegion]; } void GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address) {

@@ -1395,7 +1403,7 @@ return INT_MAX;

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

@@ -1522,6 +1530,52 @@ if (info->nextEvent != INT_MAX) {

info->nextEvent += cycles; } cpu->cycles += cycles; +} + +int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { + struct GBA* gba = (struct GBA*) cpu->master; + struct GBAMemory* memory = &gba->memory; + + if (memory->activeRegion < REGION_CART0 || !memory->prefetch) { + // The wait is the stall + return wait; + } + + int32_t s = cpu->memory.activeSeqCycles16 + 1; + int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1; + + // Figure out how many sequential loads we can jam in + int32_t stall = s; + int32_t loads = 1; + int32_t previousLoads = 0; + + // Don't prefetch too much if we're overlapping with a previous prefetch + uint32_t dist = (memory->lastPrefetchedPc - cpu->gprs[ARM_PC]) >> 1; + if (dist < memory->lastPrefetchedLoads) { + previousLoads = dist; + } + while (stall < wait) { + stall += s; + ++loads; + } + if (loads + previousLoads > 8) { + int diff = (loads + previousLoads) - 8; + loads -= diff; + stall -= s * diff; + } else if (stall > wait && loads == 1) { + // We might need to stall a bit extra if we haven't finished the first S cycle + wait = stall; + } + // This instruction used to have an N, convert it to an S. + wait -= n2s; + + // TODO: Invalidate prefetch on branch + memory->lastPrefetchedLoads = loads; + memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * loads; + + // The next |loads|S waitstates disappear entirely, so long as they're all in a row + cpu->cycles -= (s - 1) * loads; + return wait; } void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state) {
M src/gba/memory.hsrc/gba/memory.h

@@ -86,7 +86,6 @@ DMA_TIMING_HBLANK = 2,

DMA_TIMING_CUSTOM = 3 }; - DECL_BITFIELD(GBADMARegister, uint16_t); DECL_BITS(GBADMARegister, DestControl, 5, 2); DECL_BITS(GBADMARegister, SrcControl, 7, 2);

@@ -131,6 +130,9 @@ char waitstatesPrefetchSeq16[16];

char waitstatesPrefetchNonseq32[16]; char waitstatesPrefetchNonseq16[16]; int activeRegion; + bool prefetch; + uint32_t lastPrefetchedPc; + uint32_t lastPrefetchedLoads; uint32_t biosPrefetch; struct GBADMA dma[4];

@@ -156,8 +158,10 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old);

void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old); void GBAPatch8(struct ARMCore* cpu, uint32_t address, int8_t value, int8_t* old); -uint32_t GBALoadMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter); -uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter); +uint32_t GBALoadMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, + int* cycleCounter); +uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, + int* cycleCounter); void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters);
A src/gba/renderers/software-bg.c

@@ -0,0 +1,195 @@

+/* 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 "software-private.h" + +#include "gba/gba.h" + +void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + int sizeAdjusted = 0x8000 << background->size; + + BACKGROUND_BITMAP_INIT; + + uint32_t screenBase = background->screenBase; + uint32_t charBase = background->charBase; + uint8_t mapData; + uint8_t tileData = 0; + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + x += background->dx; + y += background->dy; + + if (!mosaicWait) { + if (background->overflow) { + localX = x & (sizeAdjusted - 1); + localY = y & (sizeAdjusted - 1); + } else if ((x | y) & ~(sizeAdjusted - 1)) { + continue; + } else { + localX = x; + localY = y; + } + mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; + tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; + + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (tileData && IS_WRITABLE(current)) { + if (!objwinSlowPath) { + _compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); + } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { + color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); + } + } + } +} + +void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + BACKGROUND_BITMAP_INIT; + + uint32_t color = renderer->normalPalette[0]; + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + + if (!mosaicWait) { + LOAD_16(color, ((localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram); +#ifndef COLOR_16_BIT + unsigned color32; + color32 = 0; + color32 |= (color << 3) & 0xF8; + color32 |= (color << 6) & 0xF800; + color32 |= (color << 9) & 0xF80000; + color = color32; +#elif COLOR_5_6_5 + uint16_t color16 = 0; + color16 |= (color & 0x001F) << 11; + color16 |= (color & 0x03E0) << 1; + color16 |= (color & 0x7C00) >> 10; + color = color16; +#endif + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + if (!variant) { + _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_BRIGHTEN) { + _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_DARKEN) { + _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); + } + } + } +} + +void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + BACKGROUND_BITMAP_INIT; + + uint16_t color = renderer->normalPalette[0]; + uint32_t offset = 0; + if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { + offset = 0xA000; + } + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + + if (!mosaicWait) { + color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS]; + + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (color && IS_WRITABLE(current)) { + if (!objwinSlowPath) { + _compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current); + } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { + color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + _compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current); + } + } + } +} + +void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + BACKGROUND_BITMAP_INIT; + + uint32_t color = renderer->normalPalette[0]; + uint32_t offset = 0; + if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { + offset = 0xA000; + } + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + BACKGROUND_BITMAP_ITERATE(160, 128); + + if (!mosaicWait) { + LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram); +#ifndef COLOR_16_BIT + unsigned color32 = 0; + color32 |= (color << 9) & 0xF80000; + color32 |= (color << 3) & 0xF8; + color32 |= (color << 6) & 0xF800; + color = color32; +#elif COLOR_5_6_5 + uint16_t color16 = 0; + color16 |= (color & 0x001F) << 11; + color16 |= (color & 0x03E0) << 1; + color16 |= (color & 0x7C00) >> 10; + color = color16; +#endif + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + if (!variant) { + _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_BRIGHTEN) { + _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_DARKEN) { + _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); + } + } + } +}
A src/gba/renderers/software-mode0.c

@@ -0,0 +1,530 @@

+/* 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 "software-private.h" + +#include "gba/gba.h" + +#define BACKGROUND_TEXT_SELECT_CHARACTER \ + localX = tileX * 8 + inX; \ + xBase = localX & 0xF8; \ + if (background->size & 1) { \ + xBase += (localX & 0x100) << 5; \ + } \ + screenBase = yBase + (xBase >> 3); \ + LOAD_16(mapData, screenBase << 1, vram); \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= 4 * mod8; \ + for (; outX < end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + } else { \ + for (outX = end - 1; outX >= renderer->start; --outX) { \ + uint32_t* pixel = &renderer->row[outX]; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + LOAD_32(tileData, charBase, vram); \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + pixel = &renderer->row[outX]; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (outX < renderer->start) { \ + tileData >>= 4 * (renderer->start - outX); \ + outX = renderer->start; \ + pixel = &renderer->row[outX]; \ + } \ + for (; outX < renderer->end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + } else { \ + tileData >>= 4 * (0x8 - mod8); \ + int end = renderer->end - 8; \ + if (end < -1) { \ + end = -1; \ + } \ + outX = renderer->end - 1; \ + pixel = &renderer->row[outX]; \ + for (; outX > end; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + /* Needed for consistency checks */ \ + if (VIDEO_CHECKS) { \ + outX = renderer->end; \ + pixel = &renderer->row[outX]; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \ + x = inX & 7; \ + if (mosaicWait) { \ + int baseX = x - (mosaicH - mosaicWait); \ + if (baseX < 0) { \ + int disturbX = (16 + baseX) >> 3; \ + inX -= disturbX << 3; \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + baseX -= disturbX << 3; \ + inX += disturbX << 3; \ + } else { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + } \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ + } else { \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= 4 * baseX; \ + } else { \ + tileData >>= 4 * (7 - baseX); \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 16; \ + carryData = tileData; \ + } \ + } \ + for (; length; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + tileData = carryData; \ + for (; x < 8 && length; ++x, --length) { \ + if (!mosaicWait) { \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ + } else { \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= x * 4; \ + } else { \ + tileData >>= (7 - x) * 4; \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 16; \ + carryData = tileData; \ + } \ + mosaicWait = mosaicH; \ + } \ + --mosaicWait; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + } \ + x = 0; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + pixel += 8; \ + continue; \ + } \ + LOAD_32(tileData, charBase, vram); \ + if (tileData) { \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + } else { \ + pixel += 7; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + pixel += 8; \ + } \ + } else { \ + pixel += 8; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + int end2 = end - 4; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + int shift = inX & 0x3; \ + if (LIKELY(charBase < 0x10000)) { \ + if (end2 > outX) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= 8 * shift; \ + shift = 0; \ + for (; outX < end2; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } \ + } \ + \ + if (LIKELY(charBase < 0x10000)) { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= 8 * shift; \ + for (; outX < end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } \ + } else { \ + int start = outX; \ + outX = end - 1; \ + pixel = &renderer->row[outX]; \ + if (LIKELY(charBase < 0x10000)) { \ + if (end2 > start) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= end2; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + charBase += 4; \ + } \ + } \ + \ + if (LIKELY(charBase < 0x10000)) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= renderer->start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } \ + outX = end; \ + pixel = &renderer->row[outX]; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + return; \ + } \ + int end = mod8 - 4; \ + pixel = &renderer->row[outX]; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (end > 0) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX < renderer->end - end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + charBase += 4; \ + } \ + \ + LOAD_32(tileData, charBase, vram); \ + for (; outX < renderer->end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } else { \ + int shift = (8 - mod8) & 0x3; \ + int start = outX; \ + outX = renderer->end - 1; \ + pixel = &renderer->row[outX]; \ + if (end > 0) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= 8 * shift; \ + for (; outX >= start + 4; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + shift = 0; \ + } \ + \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= 8 * shift; \ + for (; outX >= start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + /* Needed for consistency checks */ \ + if (VIDEO_CHECKS) { \ + outX = renderer->end; \ + pixel = &renderer->row[outX]; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + pixel += 8; \ + continue; \ + } \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + LOAD_32(tileData, charBase, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + } else { \ + pixel += 4; \ + } \ + LOAD_32(tileData, charBase + 4, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + } else { \ + pixel += 4; \ + } \ + } else { \ + LOAD_32(tileData, charBase + 4, vram); \ + if (tileData) { \ + pixel += 3; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + pixel += 4; \ + LOAD_32(tileData, charBase, vram); \ + if (tileData) { \ + pixel += 3; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + pixel += 4; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + tileData = carryData; \ + for (x = 0; x < 8; ++x) { \ + if (!mosaicWait) { \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ + } else { \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (x >= 4) { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= (x - 4) * 8; \ + } else { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= x * 8; \ + } \ + } else { \ + if (x >= 4) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= (7 - x) * 8; \ + } else { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= (3 - x) * 8; \ + } \ + } \ + tileData &= 0xFF; \ + carryData = tileData; \ + } \ + mosaicWait = mosaicH; \ + } \ + tileData |= tileData << 8; \ + --mosaicWait; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \ + uint32_t* pixel = &renderer->row[outX]; \ + if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \ + int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \ + int x; \ + int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \ + int carryData = 0; \ + paletteData = 0; /* Quiets compiler warning */ \ + DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \ + return; \ + } \ + \ + if (inX & 0x7) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + \ + int mod8 = inX & 0x7; \ + int end = outX + 0x8 - mod8; \ + if (end > renderer->end) { \ + end = renderer->end; \ + } \ + if (UNLIKELY(end == outX)) { \ + return; \ + } \ + if (UNLIKELY(end < outX)) { \ + GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw!"); \ + return; \ + } \ + DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \ + outX = end; \ + if (tileX < tileEnd) { \ + ++tileX; \ + } else if (VIDEO_CHECKS && UNLIKELY(tileX > tileEnd)) { \ + GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw! tileX (%u) > tileEnd (%u)", tileX, tileEnd); \ + return; \ + } \ + length -= end - renderer->start; \ + } \ + /*! TODO: Make sure these lines can be removed */ \ + /*!*/ pixel = &renderer->row[outX]; \ + outX += (tileEnd - tileX) * 8; \ + /*!*/ if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ + /*!*/ GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw would occur!"); \ + /*!*/ return; \ + /*!*/ } \ + DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \ + if (length & 0x7) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + \ + int mod8 = length & 0x7; \ + if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \ + GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw!"); \ + return; \ + } \ + DRAW_BACKGROUND_MODE_0_TILE_PREFIX_ ## BPP (BLEND, OBJWIN) \ + } \ + if (VIDEO_CHECKS && UNLIKELY(&renderer->row[outX] != pixel)) { \ + GBALog(0, GBA_LOG_FATAL, "Background draw ended in the wrong place! Diff: %" PRIXPTR, &renderer->row[outX] - pixel); \ + } \ + if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ + GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw occurred!"); \ + return; \ + } + +void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) { + int inX = renderer->start + background->x; + int length = renderer->end - renderer->start; + if (background->mosaic) { + int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; + y -= y % mosaicV; + } + int inY = y + background->y; + uint16_t mapData; + + unsigned yBase = inY & 0xF8; + if (background->size == 2) { + yBase += inY & 0x100; + } else if (background->size == 3) { + yBase += (inY & 0x100) << 1; + } + yBase = (background->screenBase >> 1) + (yBase << 2); + + int localX; + int localY; + + unsigned xBase; + + int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; + flags |= FLAG_TARGET_2 * background->target2; + int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); + objwinFlags |= flags; + flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); + if (renderer->blda == 0x10 && renderer->bldb == 0) { + flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + } + + uint32_t screenBase; + uint32_t charBase; + int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); + color_t* mainPalette = renderer->normalPalette; + if (variant) { + mainPalette = renderer->variantPalette; + } + color_t* palette = mainPalette; + PREPARE_OBJWIN; + + int outX = renderer->start; + + uint32_t tileData; + uint32_t current; + int pixelData; + int paletteData; + int tileX = 0; + int tileEnd = ((length + inX) >> 3) - (inX >> 3); + uint16_t* vram = renderer->d.vram; + + if (!objwinSlowPath) { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN); + } + } else { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN); + } + } + } else { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN); + } + } else { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN); + } + } + } +}
A src/gba/renderers/software-obj.c

@@ -0,0 +1,272 @@

+/* 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 "software-private.h" + +#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \ + SPRITE_YBASE_ ## DEPTH(inY); \ + unsigned tileData; \ + for (; outX < condition; ++outX, inX += xOffset) { \ + if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ + continue; \ + } \ + SPRITE_XBASE_ ## DEPTH(inX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \ + } + +#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \ + SPRITE_YBASE_ ## DEPTH(inY); \ + unsigned tileData; \ + if (outX % mosaicH) { \ + if (!inX && xOffset > 0) { \ + inX = mosaicH - (outX % mosaicH); \ + outX += mosaicH - (outX % mosaicH); \ + } else if (inX == width - xOffset) { \ + inX = mosaicH + (outX % mosaicH); \ + outX += mosaicH - (outX % mosaicH); \ + } \ + } \ + for (; outX < condition; ++outX, inX += xOffset) { \ + if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ + continue; \ + } \ + int localX = inX - xOffset * (outX % mosaicH); \ + if (localX < 0 || localX > width - 1) { \ + continue; \ + } \ + SPRITE_XBASE_ ## DEPTH(localX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ + } + +#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \ + unsigned tileData; \ + for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \ + if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ + continue; \ + } \ + xAccum += mat.a; \ + yAccum += mat.c; \ + int localX = (xAccum >> 8) + (width >> 1); \ + int localY = (yAccum >> 8) + (height >> 1); \ + \ + if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \ + continue; \ + } \ + \ + SPRITE_YBASE_ ## DEPTH(localY); \ + SPRITE_XBASE_ ## DEPTH(localX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ + } + +#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2); +#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4; + +#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + +#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ + if (tileData) { \ + renderer->row[outX] |= FLAG_OBJWIN; \ + } + +#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6); +#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; + +#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + +#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ + if (tileData) { \ + renderer->row[outX] |= FLAG_OBJWIN; \ + } + +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { + int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0]; + int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1]; + int start = renderer->start; + int end = renderer->end; + uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; + flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); + flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN); + int32_t x = GBAObjAttributesBGetX(sprite->b) << 23; + 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; + target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); + target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); + target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); + target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); + if (GBAObjAttributesCGetPriority(sprite->c) < target2) { + variant = 0; + } + } + color_t* palette = &renderer->normalPalette[0x100]; + if (variant) { + palette = &renderer->variantPalette[0x100]; + } + + int inY = y - (int) GBAObjAttributesAGetY(sprite->a); + + uint32_t current; + if (GBAObjAttributesAIsTransformed(sprite->a)) { + int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); + int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); + struct GBAOAMMatrix mat; + LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); + LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b); + LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c); + LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d); + + if (inY < 0) { + inY += 256; + } + int outX = x >= start ? x : start; + int inX = outX - x; + int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)); + int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)); + + if (!GBAObjAttributesAIs256Color(sprite->a)) { + palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + if (flags & FLAG_OBJWIN) { + SPRITE_TRANSFORMED_LOOP(16, OBJWIN); + } else { + SPRITE_TRANSFORMED_LOOP(16, NORMAL); + } + } else { + if (flags & FLAG_OBJWIN) { + SPRITE_TRANSFORMED_LOOP(256, OBJWIN); + } else { + SPRITE_TRANSFORMED_LOOP(256, NORMAL); + } + } + } else { + int outX = x >= start ? x : start; + int condition = x + width; + int mosaicH = 1; + if (GBAObjAttributesAIsMosaic(sprite->a)) { + mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; + if (condition % mosaicH) { + condition += mosaicH - (condition % mosaicH); + } + } + if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) { + inY += 256; + } + if (GBAObjAttributesBIsVFlip(sprite->b)) { + inY = height - inY - 1; + } + if (end < condition) { + condition = end; + } + int inX = outX - x; + int xOffset = 1; + if (GBAObjAttributesBIsHFlip(sprite->b)) { + inX = width - inX - 1; + xOffset = -1; + } + if (!GBAObjAttributesAIs256Color(sprite->a)) { + palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + if (flags & FLAG_OBJWIN) { + SPRITE_NORMAL_LOOP(16, OBJWIN); + } else if (GBAObjAttributesAIsMosaic(sprite->a)) { + SPRITE_MOSAIC_LOOP(16, NORMAL); + } else { + SPRITE_NORMAL_LOOP(16, NORMAL); + } + } else { + if (flags & FLAG_OBJWIN) { + SPRITE_NORMAL_LOOP(256, OBJWIN); + } else if (GBAObjAttributesAIsMosaic(sprite->a)) { + SPRITE_MOSAIC_LOOP(256, NORMAL); + } else { + SPRITE_NORMAL_LOOP(256, NORMAL); + } + } + } + return 1; +} + +void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) { + int x; + uint32_t* pixel = &renderer->row[renderer->start]; + uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj; + + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); + bool objwinDisable = false; + bool objwinOnly = false; + if (objwinSlowPath) { + objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed); + objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed); + if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { + return; + } + + if (objwinDisable) { + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendObjwin(renderer, pixel, color | flags, current); + } + } + return; + } else if (objwinOnly) { + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendObjwin(renderer, pixel, color | flags, current); + } + } + return; + } else { + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendObjwin(renderer, pixel, color | flags, current); + } + } + return; + } + } else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { + return; + } + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendNoObjwin(renderer, pixel, color | flags, current); + } + } +}
A src/gba/renderers/software-private.h

@@ -0,0 +1,335 @@

+/* 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 SOFTWARE_PRIVATE_H +#define SOFTWARE_PRIVATE_H + +#include "video-software.h" + +#ifdef NDEBUG +#define VIDEO_CHECKS false +#else +#define VIDEO_CHECKS true +#endif + +void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, + struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, + struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, + struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, + struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, + struct GBAVideoSoftwareBackground* background, int y); + +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); +void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); + +static inline unsigned _brighten(unsigned color, int y); +static inline unsigned _darken(unsigned color, int y); +static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB); + + +// We stash the priority on the top bits so we can do a one-operator comparison +// The lower the number, the higher the priority, and sprites take precendence over backgrounds +// We want to do special processing if the color pixel is target 1, however + +static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { + if (color >= current) { + if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { + color = _mix(renderer->blda, current, renderer->bldb, color); + } else { + color = current & 0x00FFFFFF; + } + } else { + color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); + } + *pixel = color; +} + +static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { + if (color >= current) { + if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { + color = _mix(renderer->blda, current, renderer->bldb, color); + } else { + color = current & 0x00FFFFFF; + } + } else { + color = color & ~FLAG_TARGET_2; + } + *pixel = color; +} + +static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, + uint32_t current) { + UNUSED(renderer); + if (color < current) { + *pixel = color | (current & FLAG_OBJWIN); + } +} + +static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, + uint32_t current) { + UNUSED(renderer); + if (color < current) { + *pixel = color; + } +} + +#define COMPOSITE_16_OBJWIN(BLEND) \ + if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ + unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \ + unsigned mergedFlags = flags; \ + if (current & FLAG_OBJWIN) { \ + mergedFlags = objwinFlags; \ + } \ + _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ + } + +#define COMPOSITE_16_NO_OBJWIN(BLEND) \ + _composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current); + +#define COMPOSITE_256_OBJWIN(BLEND) \ + if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ + unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \ + unsigned mergedFlags = flags; \ + if (current & FLAG_OBJWIN) { \ + mergedFlags = objwinFlags; \ + } \ + _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ + } + +#define COMPOSITE_256_NO_OBJWIN(BLEND) \ + COMPOSITE_16_NO_OBJWIN(BLEND) + +#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \ + pixelData = tileData & 0xF; \ + current = *pixel; \ + if (pixelData && IS_WRITABLE(current)) { \ + COMPOSITE_16_ ## OBJWIN (BLEND); \ + } \ + tileData >>= 4; + +#define BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN) \ + pixelData = tileData & 0xFF; \ + current = *pixel; \ + if (pixelData && IS_WRITABLE(current)) { \ + COMPOSITE_256_ ## OBJWIN (BLEND); \ + } \ + tileData >>= 8; + +// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5 +#define PREPARE_OBJWIN \ + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \ + int objwinOnly = 0; \ + int objwinForceEnable = 0; \ + UNUSED(objwinForceEnable); \ + color_t* objwinPalette = renderer->normalPalette; \ + UNUSED(objwinPalette); \ + if (objwinSlowPath) { \ + if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && \ + (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ + objwinPalette = renderer->variantPalette; \ + } \ + switch (background->index) { \ + case 0: \ + objwinForceEnable = GBAWindowControlIsBg0Enable(renderer->objwin.packed) && \ + GBAWindowControlIsBg0Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg0Enable(renderer->objwin.packed); \ + break; \ + case 1: \ + objwinForceEnable = GBAWindowControlIsBg1Enable(renderer->objwin.packed) && \ + GBAWindowControlIsBg1Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg1Enable(renderer->objwin.packed); \ + break; \ + case 2: \ + objwinForceEnable = GBAWindowControlIsBg2Enable(renderer->objwin.packed) && \ + GBAWindowControlIsBg2Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg2Enable(renderer->objwin.packed); \ + break; \ + case 3: \ + objwinForceEnable = GBAWindowControlIsBg3Enable(renderer->objwin.packed) && \ + GBAWindowControlIsBg3Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg3Enable(renderer->objwin.packed); \ + break; \ + } \ + } + +#define BACKGROUND_BITMAP_INIT \ + int32_t x = background->sx + (renderer->start - 1) * background->dx; \ + int32_t y = background->sy + (renderer->start - 1) * background->dy; \ + int mosaicH = 0; \ + int mosaicWait = 0; \ + if (background->mosaic) { \ + int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; \ + y -= (inY % mosaicV) * background->dmy; \ + x -= (inY % mosaicV) * background->dmx; \ + mosaicH = GBAMosaicControlGetBgH(renderer->mosaic); \ + mosaicWait = renderer->start % (mosaicH + 1); \ + } \ + int32_t localX; \ + int32_t localY; \ + \ + int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \ + flags |= FLAG_TARGET_2 * background->target2; \ + int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && \ + GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \ + objwinFlags |= flags; \ + flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && \ + GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ + if (renderer->blda == 0x10 && renderer->bldb == 0) { \ + flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + } \ + int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && \ + (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ + color_t* palette = renderer->normalPalette; \ + if (variant) { \ + palette = renderer->variantPalette; \ + } \ + UNUSED(palette); \ + PREPARE_OBJWIN; + +#define BACKGROUND_BITMAP_ITERATE(W, H) \ + x += background->dx; \ + y += background->dy; \ + \ + if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \ + continue; \ + } else { \ + localX = x; \ + localY = y; \ + } + +static inline unsigned _brighten(unsigned color, int y) { + unsigned c = 0; + unsigned a; +#ifdef COLOR_16_BIT + a = color & 0x1F; + c |= (a + ((0x1F - a) * y) / 16) & 0x1F; + +#ifdef COLOR_5_6_5 + a = color & 0x7C0; + c |= (a + ((0x7C0 - a) * y) / 16) & 0x7C0; + + a = color & 0xF800; + c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; +#else + a = color & 0x3E0; + c |= (a + ((0x3E0 - a) * y) / 16) & 0x3E0; + + a = color & 0x7C00; + c |= (a + ((0x7C00 - a) * y) / 16) & 0x7C00; +#endif +#else + a = color & 0xF8; + c |= (a + ((0xF8 - a) * y) / 16) & 0xF8; + + a = color & 0xF800; + c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; + + a = color & 0xF80000; + c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000; +#endif + return c; +} + +static inline unsigned _darken(unsigned color, int y) { + unsigned c = 0; + unsigned a; +#ifdef COLOR_16_BIT + a = color & 0x1F; + c |= (a - (a * y) / 16) & 0x1F; + +#ifdef COLOR_5_6_5 + a = color & 0x7C0; + c |= (a - (a * y) / 16) & 0x7C0; + + a = color & 0xF800; + c |= (a - (a * y) / 16) & 0xF800; +#else + a = color & 0x3E0; + c |= (a - (a * y) / 16) & 0x3E0; + + a = color & 0x7C00; + c |= (a - (a * y) / 16) & 0x7C00; +#endif +#else + a = color & 0xF8; + c |= (a - (a * y) / 16) & 0xF8; + + a = color & 0xF800; + c |= (a - (a * y) / 16) & 0xF800; + + a = color & 0xF80000; + c |= (a - (a * y) / 16) & 0xF80000; +#endif + return c; +} + +static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB) { + unsigned c = 0; + unsigned a, b; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + a = colorA & 0xF81F; + b = colorB & 0xF81F; + a |= (colorA & 0x7C0) << 16; + b |= (colorB & 0x7C0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x08000000) { + c = (c & ~0x0FC00000) | 0x07C00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x10000) { + c = (c & ~0x1F800) | 0xF800; + } + c = (c & 0xF81F) | ((c >> 16) & 0x07C0); +#else + a = colorA & 0x7C1F; + b = colorB & 0x7C1F; + a |= (colorA & 0x3E0) << 16; + b |= (colorB & 0x3E0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x04000000) { + c = (c & ~0x07E00000) | 0x03E00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x10000) { + c = (c & ~0x1F800) | 0xF800; + } + c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); +#endif +#else + a = colorA & 0xF8; + b = colorB & 0xF8; + c |= ((a * weightA + b * weightB) / 16) & 0x1F8; + if (c & 0x00000100) { + c = 0x000000F8; + } + + a = colorA & 0xF800; + b = colorB & 0xF800; + c |= ((a * weightA + b * weightB) / 16) & 0x1F800; + if (c & 0x00010000) { + c = (c & 0x000000F8) | 0x0000F800; + } + + a = colorA & 0xF80000; + b = colorB & 0xF80000; + c |= ((a * weightA + b * weightB) / 16) & 0x1F80000; + if (c & 0x01000000) { + c = (c & 0x0000F8F8) | 0x00F80000; + } +#endif + return c; +} + +#endif
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -3,41 +3,17 @@ *

* 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 "video-software.h" +#include "software-private.h" #include "gba/gba.h" #include "gba/io.h" #include "util/arm-algo.h" -#ifdef NDEBUG -#define VIDEO_CHECKS false -#else -#define VIDEO_CHECKS true -#endif - -static const int _objSizes[32] = { - 8, 8, - 16, 16, - 32, 32, - 64, 64, - 16, 8, - 32, 8, - 32, 16, - 64, 32, - 8, 16, - 8, 32, - 16, 32, - 32, 64, - 0, 0, - 0, 0, - 0, 0, - 0, 0 -}; - 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);

@@ -58,20 +34,10 @@ static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);

static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value); static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value); -static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y); -static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer); -static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); -static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); +static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y); static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer); -static inline unsigned _brighten(unsigned color, int y); -static inline unsigned _darken(unsigned color, int y); -static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB); static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y); static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);

@@ -81,6 +47,7 @@ renderer->d.init = GBAVideoSoftwareRendererInit;

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;

@@ -362,6 +329,11 @@ }

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;

@@ -438,12 +410,10 @@ softwareRenderer->windows[activeWindow].endX = win->h.end;

if (win->h.end >= oldWindow.endX) { // Trim off extra windows we've overwritten for (++activeWindow; softwareRenderer->nWindows > activeWindow + 1 && win->h.end >= softwareRenderer->windows[activeWindow].endX; ++activeWindow) { -#ifdef DEBUG - if (activeWindow >= MAX_WINDOW) { - GBALog(0, GBA_LOG_DANGER, "Out of bounds window write will occur"); + if (VIDEO_CHECKS && activeWindow >= MAX_WINDOW) { + GBALog(0, GBA_LOG_FATAL, "Out of bounds window write will occur"); return; } -#endif softwareRenderer->windows[activeWindow] = softwareRenderer->windows[activeWindow + 1]; --softwareRenderer->nWindows; }

@@ -463,7 +433,7 @@ }

} #ifdef DEBUG if (softwareRenderer->nWindows > MAX_WINDOW) { - GBALog(0, GBA_LOG_ABORT, "Out of bounds window write occurred!"); + GBALog(0, GBA_LOG_FATAL, "Out of bounds window write occurred!"); } #endif }

@@ -477,7 +447,7 @@ LOAD_16(obj.a, 0, &renderer->d.oam->obj[i].a);

LOAD_16(obj.b, 0, &renderer->d.oam->obj[i].b); LOAD_16(obj.c, 0, &renderer->d.oam->obj[i].c); if (GBAObjAttributesAIsTransformed(obj.a) || !GBAObjAttributesAIsDisable(obj.a)) { - int height = _objSizes[GBAObjAttributesAGetShape(obj.a) * 8 + GBAObjAttributesBGetSize(obj.b) * 2 + 1]; + int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(obj.a) * 4 + GBAObjAttributesBGetSize(obj.b)][1]; if (GBAObjAttributesAIsTransformed(obj.a)) { height <<= GBAObjAttributesAGetDoubleSize(obj.a); }

@@ -492,7 +462,6 @@ }

renderer->oamMax = oamMax; renderer->oamDirty = 0; } - static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;

@@ -568,7 +537,7 @@ }

} #ifdef COLOR_16_BIT -#ifdef __ARM_NEON +#if defined(__ARM_NEON) && !defined(__APPLE__) _to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS); #else for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {

@@ -723,7 +692,7 @@ }

if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) { continue; } - drawn = _preprocessSprite(renderer, &sprite->obj, localY); + drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY); spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c); } }

@@ -737,41 +706,41 @@ renderer->start = renderer->end;

renderer->end = renderer->windows[w].endX; renderer->currentWindow = renderer->windows[w].control; if (spriteLayers & (1 << priority)) { - _postprocessSprite(renderer, priority); + GBAVideoSoftwareRendererPostprocessSprite(renderer, priority); } if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) { - _drawBackgroundMode0(renderer, &renderer->bg[0], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[0], y); } if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) { - _drawBackgroundMode0(renderer, &renderer->bg[1], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[1], y); } if (TEST_LAYER_ENABLED(2)) { switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) { case 0: - _drawBackgroundMode0(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[2], y); break; case 1: case 2: - _drawBackgroundMode2(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[2], y); break; case 3: - _drawBackgroundMode3(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode3(renderer, &renderer->bg[2], y); break; case 4: - _drawBackgroundMode4(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode4(renderer, &renderer->bg[2], y); break; case 5: - _drawBackgroundMode5(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode5(renderer, &renderer->bg[2], y); break; } } if (TEST_LAYER_ENABLED(3)) { switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) { case 0: - _drawBackgroundMode0(renderer, &renderer->bg[3], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[3], y); break; case 2: - _drawBackgroundMode2(renderer, &renderer->bg[3], y); + GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[3], y); break; } }

@@ -783,1150 +752,6 @@ renderer->bg[3].sx += renderer->bg[3].dmx;

renderer->bg[3].sy += renderer->bg[3].dmy; } -// We stash the priority on the top bits so we can do a one-operator comparison -// The lower the number, the higher the priority, and sprites take precendence over backgrounds -// We want to do special processing if the color pixel is target 1, however - -static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - if (color >= current) { - if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { - color = _mix(renderer->blda, current, renderer->bldb, color); - } else { - color = current & 0x00FFFFFF; - } - } else { - color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); - } - *pixel = color; -} - -static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - // We stash the priority on the top bits so we can do a one-operator comparison - // The lower the number, the higher the priority, and sprites take precendence over backgrounds - // We want to do special processing if the color pixel is target 1, however - if (color >= current) { - if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { - color = _mix(renderer->blda, current, renderer->bldb, color); - } else { - color = current & 0x00FFFFFF; - } - } else { - color = color & ~FLAG_TARGET_2; - } - *pixel = color; -} - -static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - UNUSED(renderer); - if (color < current) { - *pixel = color | (current & FLAG_OBJWIN); - } -} - -static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - UNUSED(renderer); - if (color < current) { - *pixel = color; - } -} - -#define COMPOSITE_16_OBJWIN(BLEND) \ - if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ - unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \ - unsigned mergedFlags = flags; \ - if (current & FLAG_OBJWIN) { \ - mergedFlags = objwinFlags; \ - } \ - _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ - } - -#define COMPOSITE_16_NO_OBJWIN(BLEND) \ - _composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current); - -#define COMPOSITE_256_OBJWIN(BLEND) \ - if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ - unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \ - unsigned mergedFlags = flags; \ - if (current & FLAG_OBJWIN) { \ - mergedFlags = objwinFlags; \ - } \ - _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ - } - -#define COMPOSITE_256_NO_OBJWIN(BLEND) \ - COMPOSITE_16_NO_OBJWIN(BLEND) - -#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \ - pixelData = tileData & 0xF; \ - current = *pixel; \ - if (pixelData && IS_WRITABLE(current)) { \ - COMPOSITE_16_ ## OBJWIN (BLEND); \ - } \ - tileData >>= 4; - -#define BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN) \ - pixelData = tileData & 0xFF; \ - current = *pixel; \ - if (pixelData && IS_WRITABLE(current)) { \ - COMPOSITE_256_ ## OBJWIN (BLEND); \ - } \ - tileData >>= 8; - -#define BACKGROUND_TEXT_SELECT_CHARACTER \ - localX = tileX * 8 + inX; \ - xBase = localX & 0xF8; \ - if (background->size & 1) { \ - xBase += (localX & 0x100) << 5; \ - } \ - screenBase = yBase + (xBase >> 3); \ - LOAD_16(mapData, screenBase << 1, vram); \ - localY = inY & 0x7; \ - if (GBA_TEXT_MAP_VFLIP(mapData)) { \ - localY = 7 - localY; \ - } - -// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5 -#define PREPARE_OBJWIN \ - int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \ - int objwinOnly = 0; \ - int objwinForceEnable = 0; \ - UNUSED(objwinForceEnable); \ - color_t* objwinPalette = renderer->normalPalette; \ - UNUSED(objwinPalette); \ - if (objwinSlowPath) { \ - if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ - objwinPalette = renderer->variantPalette; \ - } \ - switch (background->index) { \ - case 0: \ - objwinForceEnable = GBAWindowControlIsBg0Enable(renderer->objwin.packed) && GBAWindowControlIsBg0Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg0Enable(renderer->objwin.packed); \ - break; \ - case 1: \ - objwinForceEnable = GBAWindowControlIsBg1Enable(renderer->objwin.packed) && GBAWindowControlIsBg1Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg1Enable(renderer->objwin.packed); \ - break; \ - case 2: \ - objwinForceEnable = GBAWindowControlIsBg2Enable(renderer->objwin.packed) && GBAWindowControlIsBg2Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg2Enable(renderer->objwin.packed); \ - break; \ - case 3: \ - objwinForceEnable = GBAWindowControlIsBg3Enable(renderer->objwin.packed) && GBAWindowControlIsBg3Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg3Enable(renderer->objwin.packed); \ - break; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= 4 * mod8; \ - for (; outX < end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - } else { \ - for (outX = end - 1; outX >= renderer->start; --outX) { \ - uint32_t* pixel = &renderer->row[outX]; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - LOAD_32(tileData, charBase, vram); \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - pixel = &renderer->row[outX]; \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (outX < renderer->start) { \ - tileData >>= 4 * (renderer->start - outX); \ - outX = renderer->start; \ - pixel = &renderer->row[outX]; \ - } \ - for (; outX < renderer->end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - } else { \ - tileData >>= 4 * (0x8 - mod8); \ - int end = renderer->end - 8; \ - if (end < -1) { \ - end = -1; \ - } \ - outX = renderer->end - 1; \ - pixel = &renderer->row[outX]; \ - for (; outX > end; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - /* Needed for consistency checks */ \ - if (VIDEO_CHECKS) { \ - outX = renderer->end; \ - pixel = &renderer->row[outX]; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \ - x = inX & 7; \ - if (mosaicWait) { \ - int baseX = x - (mosaicH - mosaicWait); \ - if (baseX < 0) { \ - int disturbX = (16 + baseX) >> 3; \ - inX -= disturbX << 3; \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - baseX -= disturbX << 3; \ - inX += disturbX << 3; \ - } else { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - } \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - carryData = 0; \ - } else { \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= 4 * baseX; \ - } else { \ - tileData >>= 4 * (7 - baseX); \ - } \ - tileData &= 0xF; \ - tileData |= tileData << 4; \ - tileData |= tileData << 8; \ - tileData |= tileData << 12; \ - tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ - carryData = tileData; \ - } \ - } \ - for (; length; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - tileData = carryData; \ - for (; x < 8 && length; ++x, --length) { \ - if (!mosaicWait) { \ - if (UNLIKELY(charBase >= 0x10000)) { \ - carryData = 0; \ - } else { \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= x * 4; \ - } else { \ - tileData >>= (7 - x) * 4; \ - } \ - tileData &= 0xF; \ - tileData |= tileData << 4; \ - tileData |= tileData << 8; \ - tileData |= tileData << 12; \ - tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ - carryData = tileData; \ - } \ - mosaicWait = mosaicH; \ - } \ - --mosaicWait; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - } \ - x = 0; \ - } - -#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - pixel += 8; \ - continue; \ - } \ - LOAD_32(tileData, charBase, vram); \ - if (tileData) { \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - } else { \ - pixel += 7; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - pixel += 8; \ - } \ - } else { \ - pixel += 8; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256(BLEND, OBJWIN) \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - int end2 = end - 4; \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - int shift = inX & 0x3; \ - if (LIKELY(charBase < 0x10000)) { \ - if (end2 > outX) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= 8 * shift; \ - shift = 0; \ - for (; outX < end2; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } \ - } \ - \ - if (LIKELY(charBase < 0x10000)) { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= 8 * shift; \ - for (; outX < end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } \ - } else { \ - int start = outX; \ - outX = end - 1; \ - pixel = &renderer->row[outX]; \ - if (LIKELY(charBase < 0x10000)) { \ - if (end2 > start) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX >= end2; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - charBase += 4; \ - } \ - } \ - \ - if (LIKELY(charBase < 0x10000)) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX >= renderer->start; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } \ - outX = end; \ - pixel = &renderer->row[outX]; \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - return; \ - } \ - int end = mod8 - 4; \ - pixel = &renderer->row[outX]; \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (end > 0) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX < renderer->end - end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - charBase += 4; \ - } \ - \ - LOAD_32(tileData, charBase, vram); \ - for (; outX < renderer->end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } else { \ - int shift = (8 - mod8) & 0x3; \ - int start = outX; \ - outX = renderer->end - 1; \ - pixel = &renderer->row[outX]; \ - if (end > 0) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= 8 * shift; \ - for (; outX >= start + 4; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - shift = 0; \ - } \ - \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= 8 * shift; \ - for (; outX >= start; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - /* Needed for consistency checks */ \ - if (VIDEO_CHECKS) { \ - outX = renderer->end; \ - pixel = &renderer->row[outX]; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - pixel += 8; \ - continue; \ - } \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - LOAD_32(tileData, charBase, vram); \ - if (tileData) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - } else { \ - pixel += 4; \ - } \ - LOAD_32(tileData, charBase + 4, vram); \ - if (tileData) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - } else { \ - pixel += 4; \ - } \ - } else { \ - LOAD_32(tileData, charBase + 4, vram); \ - if (tileData) { \ - pixel += 3; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - pixel += 4; \ - LOAD_32(tileData, charBase, vram); \ - if (tileData) { \ - pixel += 3; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - pixel += 4; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - tileData = carryData; \ - for (x = 0; x < 8; ++x) { \ - if (!mosaicWait) { \ - if (UNLIKELY(charBase >= 0x10000)) { \ - carryData = 0; \ - } else { \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (x >= 4) { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= (x - 4) * 8; \ - } else { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= x * 8; \ - } \ - } else { \ - if (x >= 4) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= (7 - x) * 8; \ - } else { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= (3 - x) * 8; \ - } \ - } \ - tileData &= 0xFF; \ - carryData = tileData; \ - } \ - mosaicWait = mosaicH; \ - } \ - tileData |= tileData << 8; \ - --mosaicWait; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \ - uint32_t* pixel = &renderer->row[outX]; \ - if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \ - int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \ - int x; \ - int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \ - int carryData = 0; \ - paletteData = 0; /* Quiets compiler warning */ \ - DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \ - return; \ - } \ - \ - if (inX & 0x7) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ - int mod8 = inX & 0x7; \ - int end = outX + 0x8 - mod8; \ - if (end > renderer->end) { \ - end = renderer->end; \ - } \ - if (UNLIKELY(end == outX)) { \ - return; \ - } \ - if (UNLIKELY(end < outX)) { \ - GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \ - return; \ - } \ - DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \ - outX = end; \ - if (tileX < tileEnd) { \ - ++tileX; \ - } else if (VIDEO_CHECKS && UNLIKELY(tileX > tileEnd)) { \ - GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw! tileX (%u) > tileEnd (%u)", tileX, tileEnd); \ - return; \ - } \ - length -= end - renderer->start; \ - } \ - /*! TODO: Make sure these lines can be removed */ \ - /*!*/ pixel = &renderer->row[outX]; \ - outX += (tileEnd - tileX) * 8; \ - /*!*/ if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ - /*!*/ GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw would occur!"); \ - /*!*/ return; \ - /*!*/ } \ - DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \ - if (length & 0x7) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ - int mod8 = length & 0x7; \ - if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \ - GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw!"); \ - return; \ - } \ - DRAW_BACKGROUND_MODE_0_TILE_PREFIX_ ## BPP (BLEND, OBJWIN) \ - } \ - if (VIDEO_CHECKS && UNLIKELY(&renderer->row[outX] != pixel)) { \ - GBALog(0, GBA_LOG_FATAL, "Background draw ended in the wrong place! Diff: %" PRIXPTR, &renderer->row[outX] - pixel); \ - } \ - if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ - GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw occurred!"); \ - return; \ - } - -static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) { - int inX = renderer->start + background->x; - int length = renderer->end - renderer->start; - if (background->mosaic) { - int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; - y -= y % mosaicV; - } - int inY = y + background->y; - uint16_t mapData; - - unsigned yBase = inY & 0xF8; - if (background->size == 2) { - yBase += inY & 0x100; - } else if (background->size == 3) { - yBase += (inY & 0x100) << 1; - } - yBase = (background->screenBase >> 1) + (yBase << 2); - - int localX; - int localY; - - unsigned xBase; - - int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; - flags |= FLAG_TARGET_2 * background->target2; - int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); - objwinFlags |= flags; - flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); - if (renderer->blda == 0x10 && renderer->bldb == 0) { - flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - } - - uint32_t screenBase; - uint32_t charBase; - int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - color_t* mainPalette = renderer->normalPalette; - if (variant) { - mainPalette = renderer->variantPalette; - } - color_t* palette = mainPalette; - PREPARE_OBJWIN; - - int outX = renderer->start; - - uint32_t tileData; - uint32_t current; - int pixelData; - int paletteData; - int tileX = 0; - int tileEnd = ((length + inX) >> 3) - (inX >> 3); - uint16_t* vram = renderer->d.vram; - - if (!objwinSlowPath) { - if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN); - } - } else { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN); - } - } - } else { - if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN); - } - } else { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN); - } - } - } -} - -#define BACKGROUND_BITMAP_INIT \ - int32_t x = background->sx + (renderer->start - 1) * background->dx; \ - int32_t y = background->sy + (renderer->start - 1) * background->dy; \ - int mosaicH = 0; \ - int mosaicWait = 0; \ - if (background->mosaic) { \ - int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; \ - y -= (inY % mosaicV) * background->dmy; \ - x -= (inY % mosaicV) * background->dmx; \ - mosaicH = GBAMosaicControlGetBgH(renderer->mosaic); \ - mosaicWait = renderer->start % (mosaicH + 1); \ - } \ - int32_t localX; \ - int32_t localY; \ - \ - int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \ - flags |= FLAG_TARGET_2 * background->target2; \ - int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \ - objwinFlags |= flags; \ - flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ - if (renderer->blda == 0x10 && renderer->bldb == 0) { \ - flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - } \ - int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ - color_t* palette = renderer->normalPalette; \ - if (variant) { \ - palette = renderer->variantPalette; \ - } \ - UNUSED(palette); \ - PREPARE_OBJWIN; - -#define BACKGROUND_BITMAP_ITERATE(W, H) \ - x += background->dx; \ - y += background->dy; \ - \ - if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \ - continue; \ - } else { \ - localX = x; \ - localY = y; \ - } - -static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - int sizeAdjusted = 0x8000 << background->size; - - BACKGROUND_BITMAP_INIT; - - uint32_t screenBase = background->screenBase; - uint32_t charBase = background->charBase; - uint8_t mapData; - uint8_t tileData = 0; - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - x += background->dx; - y += background->dy; - - if (!mosaicWait) { - if (background->overflow) { - localX = x & (sizeAdjusted - 1); - localY = y & (sizeAdjusted - 1); - } else if ((x | y) & ~(sizeAdjusted - 1)) { - continue; - } else { - localX = x; - localY = y; - } - mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; - tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; - - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (tileData && IS_WRITABLE(current)) { - if (!objwinSlowPath) { - _compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); - } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { - color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); - } - } - } -} - -static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - BACKGROUND_BITMAP_INIT; - - uint32_t color = renderer->normalPalette[0]; - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - - if (!mosaicWait) { - LOAD_16(color, ((localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram); -#ifndef COLOR_16_BIT - unsigned color32; - color32 = 0; - color32 |= (color << 3) & 0xF8; - color32 |= (color << 6) & 0xF800; - color32 |= (color << 9) & 0xF80000; - color = color32; -#elif COLOR_5_6_5 - uint16_t color16 = 0; - color16 |= (color & 0x001F) << 11; - color16 |= (color & 0x03E0) << 1; - color16 |= (color & 0x7C00) >> 10; - color = color16; -#endif - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - if (!variant) { - _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_BRIGHTEN) { - _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_DARKEN) { - _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); - } - } - } -} - -static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - BACKGROUND_BITMAP_INIT; - - uint16_t color = renderer->normalPalette[0]; - uint32_t offset = 0; - if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { - offset = 0xA000; - } - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - - if (!mosaicWait) { - color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS]; - - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (color && IS_WRITABLE(current)) { - if (!objwinSlowPath) { - _compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current); - } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { - color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - _compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current); - } - } - } -} - -static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - BACKGROUND_BITMAP_INIT; - - uint32_t color = renderer->normalPalette[0]; - uint32_t offset = 0; - if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { - offset = 0xA000; - } - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - BACKGROUND_BITMAP_ITERATE(160, 128); - - if (!mosaicWait) { - LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram); -#ifndef COLOR_16_BIT - unsigned color32 = 0; - color32 |= (color << 9) & 0xF80000; - color32 |= (color << 3) & 0xF8; - color32 |= (color << 6) & 0xF800; - color = color32; -#elif COLOR_5_6_5 - uint16_t color16 = 0; - color16 |= (color & 0x001F) << 11; - color16 |= (color & 0x03E0) << 1; - color16 |= (color & 0x7C00) >> 10; - color = color16; -#endif - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - if (!variant) { - _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_BRIGHTEN) { - _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_DARKEN) { - _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); - } - } - } -} - -#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \ - SPRITE_YBASE_ ## DEPTH(inY); \ - unsigned tileData; \ - for (; outX < condition; ++outX, inX += xOffset) { \ - if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ - continue; \ - } \ - SPRITE_XBASE_ ## DEPTH(inX); \ - SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \ - } - -#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \ - SPRITE_YBASE_ ## DEPTH(inY); \ - unsigned tileData; \ - if (outX % mosaicH) { \ - if (!inX && xOffset > 0) { \ - inX = mosaicH - (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } else if (inX == width - xOffset) { \ - inX = mosaicH + (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } \ - } \ - for (; outX < condition; ++outX, inX += xOffset) { \ - if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ - continue; \ - } \ - int localX = inX - xOffset * (outX % mosaicH); \ - if (localX < 0 || localX > width - 1) { \ - continue; \ - } \ - SPRITE_XBASE_ ## DEPTH(localX); \ - SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ - } - -#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \ - unsigned tileData; \ - for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \ - if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ - continue; \ - } \ - xAccum += mat.a; \ - yAccum += mat.c; \ - int localX = (xAccum >> 8) + (width >> 1); \ - int localY = (yAccum >> 8) + (height >> 1); \ - \ - if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \ - continue; \ - } \ - \ - SPRITE_YBASE_ ## DEPTH(localY); \ - SPRITE_XBASE_ ## DEPTH(localX); \ - SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ - } - -#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2); -#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4; - -#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ - current = renderer->spriteLayer[outX]; \ - if ((current & FLAG_ORDER_MASK) > flags) { \ - if (tileData) { \ - renderer->spriteLayer[outX] = palette[tileData] | flags; \ - } else if (current != FLAG_UNWRITTEN) { \ - renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ - } \ - } - -#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ - if (tileData) { \ - renderer->row[outX] |= FLAG_OBJWIN; \ - } - -#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6); -#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; - -#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ - current = renderer->spriteLayer[outX]; \ - if ((current & FLAG_ORDER_MASK) > flags) { \ - if (tileData) { \ - renderer->spriteLayer[outX] = palette[tileData] | flags; \ - } else if (current != FLAG_UNWRITTEN) { \ - renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ - } \ - } - -#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ - if (tileData) { \ - renderer->row[outX] |= FLAG_OBJWIN; \ - } - -static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { - int width = _objSizes[GBAObjAttributesAGetShape(sprite->a) * 8 + GBAObjAttributesBGetSize(sprite->b) * 2]; - int height = _objSizes[GBAObjAttributesAGetShape(sprite->a) * 8 + GBAObjAttributesBGetSize(sprite->b) * 2 + 1]; - int start = renderer->start; - int end = renderer->end; - uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; - flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); - flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN); - int32_t x = GBAObjAttributesBGetX(sprite->b) << 23; - x >>= 23; - uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; - unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; - 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; - target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); - target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); - target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); - target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); - if (GBAObjAttributesCGetPriority(sprite->c) < target2) { - variant = 0; - } - } - color_t* palette = &renderer->normalPalette[0x100]; - if (variant) { - palette = &renderer->variantPalette[0x100]; - } - - int inY = y - (int) GBAObjAttributesAGetY(sprite->a); - - uint32_t current; - if (GBAObjAttributesAIsTransformed(sprite->a)) { - int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); - int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); - struct GBAOAMMatrix mat; - LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); - LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b); - LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c); - LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d); - - if (inY < 0) { - inY += 256; - } - int outX = x >= start ? x : start; - int inX = outX - x; - int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)); - int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)); - - if (!GBAObjAttributesAIs256Color(sprite->a)) { - palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; - if (flags & FLAG_OBJWIN) { - SPRITE_TRANSFORMED_LOOP(16, OBJWIN); - } else { - SPRITE_TRANSFORMED_LOOP(16, NORMAL); - } - } else { - if (flags & FLAG_OBJWIN) { - SPRITE_TRANSFORMED_LOOP(256, OBJWIN); - } else { - SPRITE_TRANSFORMED_LOOP(256, NORMAL); - } - } - } else { - int outX = x >= start ? x : start; - int condition = x + width; - int mosaicH = 1; - if (GBAObjAttributesAIsMosaic(sprite->a)) { - mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; - if (condition % mosaicH) { - condition += mosaicH - (condition % mosaicH); - } - } - if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) { - inY += 256; - } - if (GBAObjAttributesBIsVFlip(sprite->b)) { - inY = height - inY - 1; - } - if (end < condition) { - condition = end; - } - int inX = outX - x; - int xOffset = 1; - if (GBAObjAttributesBIsHFlip(sprite->b)) { - inX = width - inX - 1; - xOffset = -1; - } - if (!GBAObjAttributesAIs256Color(sprite->a)) { - palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; - if (flags & FLAG_OBJWIN) { - SPRITE_NORMAL_LOOP(16, OBJWIN); - } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(16, NORMAL); - } else { - SPRITE_NORMAL_LOOP(16, NORMAL); - } - } else { - if (flags & FLAG_OBJWIN) { - SPRITE_NORMAL_LOOP(256, OBJWIN); - } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(256, NORMAL); - } else { - SPRITE_NORMAL_LOOP(256, NORMAL); - } - } - } - return 1; -} - -static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) { - int x; - uint32_t* pixel = &renderer->row[renderer->start]; - uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj; - - int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); - bool objwinDisable = false; - bool objwinOnly = false; - if (objwinSlowPath) { - objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed); - objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed); - if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { - return; - } - - if (objwinDisable) { - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); - } - } - return; - } else if (objwinOnly) { - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); - } - } - return; - } else { - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); - } - } - return; - } - } else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { - return; - } - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendNoObjwin(renderer, pixel, color | flags, current); - } - } -} - static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) { int i; if (renderer->blendEffect == BLEND_BRIGHTEN) {

@@ -1943,131 +768,3 @@ renderer->variantPalette[i] = renderer->normalPalette[i];

} } } - -static inline unsigned _brighten(unsigned color, int y) { - unsigned c = 0; - unsigned a; -#ifdef COLOR_16_BIT - a = color & 0x1F; - c |= (a + ((0x1F - a) * y) / 16) & 0x1F; - -#ifdef COLOR_5_6_5 - a = color & 0x7C0; - c |= (a + ((0x7C0 - a) * y) / 16) & 0x7C0; - - a = color & 0xF800; - c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; -#else - a = color & 0x3E0; - c |= (a + ((0x3E0 - a) * y) / 16) & 0x3E0; - - a = color & 0x7C00; - c |= (a + ((0x7C00 - a) * y) / 16) & 0x7C00; -#endif -#else - a = color & 0xF8; - c |= (a + ((0xF8 - a) * y) / 16) & 0xF8; - - a = color & 0xF800; - c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; - - a = color & 0xF80000; - c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000; -#endif - return c; -} - -static inline unsigned _darken(unsigned color, int y) { - unsigned c = 0; - unsigned a; -#ifdef COLOR_16_BIT - a = color & 0x1F; - c |= (a - (a * y) / 16) & 0x1F; - -#ifdef COLOR_5_6_5 - a = color & 0x7C0; - c |= (a - (a * y) / 16) & 0x7C0; - - a = color & 0xF800; - c |= (a - (a * y) / 16) & 0xF800; -#else - a = color & 0x3E0; - c |= (a - (a * y) / 16) & 0x3E0; - - a = color & 0x7C00; - c |= (a - (a * y) / 16) & 0x7C00; -#endif -#else - a = color & 0xF8; - c |= (a - (a * y) / 16) & 0xF8; - - a = color & 0xF800; - c |= (a - (a * y) / 16) & 0xF800; - - a = color & 0xF80000; - c |= (a - (a * y) / 16) & 0xF80000; -#endif - return c; -} - -static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB) { - unsigned c = 0; - unsigned a, b; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - a = colorA & 0xF81F; - b = colorB & 0xF81F; - a |= (colorA & 0x7C0) << 16; - b |= (colorB & 0x7C0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x08000000) { - c = (c & ~0x0FC00000) | 0x07C00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x10000) { - c = (c & ~0x1F800) | 0xF800; - } - c = (c & 0xF81F) | ((c >> 16) & 0x07C0); -#else - a = colorA & 0x7C1F; - b = colorB & 0x7C1F; - a |= (colorA & 0x3E0) << 16; - b |= (colorB & 0x3E0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x04000000) { - c = (c & ~0x07E00000) | 0x03E00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x10000) { - c = (c & ~0x1F800) | 0xF800; - } - c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); -#endif -#else - a = colorA & 0xF8; - b = colorB & 0xF8; - c |= ((a * weightA + b * weightB) / 16) & 0x1F8; - if (c & 0x00000100) { - c = 0x000000F8; - } - - a = colorA & 0xF800; - b = colorB & 0xF800; - c |= ((a * weightA + b * weightB) / 16) & 0x1F800; - if (c & 0x00010000) { - c = (c & 0x000000F8) | 0x0000F800; - } - - a = colorA & 0xF80000; - b = colorB & 0xF80000; - c |= ((a * weightA + b * weightB) / 16) & 0x1F80000; - if (c & 0x01000000) { - c = (c & 0x0000F8F8) | 0x00F80000; - } -#endif - return c; -}
M src/gba/savedata.csrc/gba/savedata.c

@@ -14,7 +14,13 @@

#include <errno.h> #include <fcntl.h> +// Some testing was done here... +// Erase cycles can vary greatly. +// Some games may vary anywhere between about 2000 cycles to up to 30000 cycles. (Observed on a Macronix (09C2) chip). +// Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip). +// An average estimation is as follows. #define FLASH_SETTLE_CYCLES 18000 +#define CLEANUP_THRESHOLD 15 static void _flashSwitchBank(struct GBASavedata* savedata, int bank); static void _flashErase(struct GBASavedata* savedata);

@@ -28,6 +34,8 @@ savedata->flashState = FLASH_STATE_RAW;

savedata->vf = vf; savedata->realVf = vf; savedata->mapMode = MAP_WRITE; + savedata->dirty = 0; + savedata->dirtAge = 0; } void GBASavedataDeinit(struct GBASavedata* savedata) {

@@ -234,7 +242,9 @@ }

} } if (savedata->dust > 0 && (address >> 12) == savedata->settling) { - --savedata->dust; + // Give some overhead for waitstates and the comparison + // This estimation can probably be improved + savedata->dust -= 10; return 0x5F; } return savedata->currentBank[address];

@@ -245,6 +255,7 @@ switch (savedata->flashState) {

case FLASH_STATE_RAW: switch (savedata->command) { case FLASH_COMMAND_PROGRAM: + savedata->dirty |= SAVEDATA_DIRT_NEW; savedata->currentBank[address] = value; savedata->command = FLASH_COMMAND_NONE; break;

@@ -352,6 +363,7 @@ } else if ((savedata->writeAddress >> 3) < SIZE_CART_EEPROM) {

uint8_t current = savedata->data[savedata->writeAddress >> 3]; current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7))); current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7)); + savedata->dirty |= SAVEDATA_DIRT_NEW; savedata->data[savedata->writeAddress >> 3] = current; ++savedata->writeAddress; } else {

@@ -394,6 +406,41 @@ }

return 0; } +void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) { + if (!savedata->vf) { + return; + } + if (savedata->dirty & SAVEDATA_DIRT_NEW) { + savedata->dirty &= ~SAVEDATA_DIRT_NEW; + if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) { + savedata->dirtAge = frameCount; + savedata->dirty |= SAVEDATA_DIRT_SEEN; + } + } else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) { + size_t size; + switch (savedata->type) { + case SAVEDATA_EEPROM: + size = SIZE_CART_EEPROM; + break; + case SAVEDATA_SRAM: + size = SIZE_CART_SRAM; + break; + case SAVEDATA_FLASH512: + size = SIZE_CART_FLASH512; + break; + case SAVEDATA_FLASH1M: + size = SIZE_CART_FLASH1M; + break; + default: + size = 0; + break; + } + savedata->vf->sync(savedata->vf, savedata->data, size); + savedata->dirty = 0; + GBALog(0, GBA_LOG_INFO, "Savedata synced"); + } +} + void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) { state->savedata.type = savedata->type; state->savedata.command = savedata->command;

@@ -444,6 +491,7 @@ }

void _flashErase(struct GBASavedata* savedata) { GBALog(0, GBA_LOG_DEBUG, "Performing flash chip erase"); + savedata->dirty |= SAVEDATA_DIRT_NEW; size_t size = SIZE_CART_FLASH512; if (savedata->type == SAVEDATA_FLASH1M) { size = SIZE_CART_FLASH1M;

@@ -453,6 +501,7 @@ }

void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) { GBALog(0, GBA_LOG_DEBUG, "Performing flash sector erase at 0x%04x", sectorStart); + savedata->dirty |= SAVEDATA_DIRT_NEW; size_t size = 0x1000; if (savedata->type == SAVEDATA_FLASH1M) { GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
M src/gba/savedata.hsrc/gba/savedata.h

@@ -51,6 +51,11 @@ FLASH_MFG_PANASONIC = 0x1B32,

FLASH_MFG_SANYO = 0x1362 }; +enum SavedataDirty { + SAVEDATA_DIRT_NEW = 1, + SAVEDATA_DIRT_SEEN = 2 +}; + enum { SAVEDATA_FLASH_BASE = 0x0E005555,

@@ -77,6 +82,9 @@ bool realisticTiming;

unsigned settling; int dust; + enum SavedataDirty dirty; + uint32_t dirtAge; + enum FlashStateMachine flashState; };

@@ -97,6 +105,8 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8_t value);

uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata); void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize); + +void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount); struct GBASerializedState; void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData);
M src/gba/serialize.csrc/gba/serialize.c

@@ -61,67 +61,85 @@ gba->rr->stateSaved(gba->rr, state);

} } -void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { +bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { + bool error = false; if (state->versionMagic != GBA_SAVESTATE_MAGIC) { GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate"); - return; + error = true; } if (state->biosChecksum != gba->biosChecksum) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS"); if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) { - return; + error = true; } } if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game"); - return; + error = true; } else if (!gba->memory.rom && state->id != 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded"); - return; + error = true; } if (state->romCrc32 != gba->romCrc32) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game"); } if (state->cpu.cycles < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); - return; + 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"); - return; + error = true; } if (state->video.nextHblank - state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative"); - return; + error = true; } if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); - return; + 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"); - return; + error = true; } - if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) { + if (!state->audio.ch1Dead && (state->audio.ch1.envelopeNextStep < 0 || + state->audio.ch1.waveNextStep < 0 || + state->audio.ch1.sweepNextStep < 0 || + state->audio.ch1.nextEvent < 0)) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative"); - return; + error = true; } - if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) { + if (!state->audio.ch2Dead && (state->audio.ch2.envelopeNextStep < 0 || + state->audio.ch2.waveNextStep < 0 || + state->audio.ch2.nextEvent < 0)) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative"); - return; + error = true; } if (state->audio.ch3.nextEvent < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative"); - return; + error = true; } - if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) { + if (!state->audio.ch4Dead && (state->audio.ch4.envelopeNextStep < 0 || + state->audio.ch4.nextEvent < 0)) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative"); - return; + error = true; } int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET); if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM"); - return; + error = true; + } + if (error) { + return false; } memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs)); gba->cpu->cpsr = state->cpu.cpsr;

@@ -166,6 +184,7 @@

if (gba->rr) { gba->rr->stateLoaded(gba->rr, state); } + return true; } #ifndef _3DS

@@ -220,7 +239,9 @@ }

struct GBASerializedState state; uLongf len = sizeof(state); uncompress((Bytef*) &state, &len, chunk->data, chunk->size); - GBADeserialize(png_get_user_chunk_ptr(png), &state); + if (!GBADeserialize(png_get_user_chunk_ptr(png), &state)) { + longjmp(png_jmpbuf(png), 1); + } return 1; }

@@ -235,15 +256,17 @@ }

uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); PNGInstallChunkHandler(png, gba, _loadPNGChunkHandler, "gbAs"); - PNGReadHeader(png, info); - PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS); - PNGReadFooter(png, end); + bool success = PNGReadHeader(png, info); + success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS); + success = success && PNGReadFooter(png, end); PNGReadClose(png, info, end); - gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels); - GBASyncForceFrame(gba->sync); + if (success) { + gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels); + GBASyncForceFrame(gba->sync); + } free(pixels); - return true; + return success; } #endif

@@ -257,6 +280,8 @@ bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot);

vf->close(vf); if (success) { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot); + } else { + GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to save", slot); } return success; }

@@ -271,6 +296,8 @@ bool success = GBALoadStateNamed(threadContext->gba, vf);

vf->close(vf); if (success) { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot); + } else { + GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to load", slot); } return success; }

@@ -287,20 +314,20 @@ GBASerialize(gba, state);

vf->unmap(vf, state, sizeof(struct GBASerializedState)); return true; } - #ifdef USE_PNG +#ifdef USE_PNG else { return _savePNGState(gba, vf); } - #endif +#endif return false; } bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) { - #ifdef USE_PNG +#ifdef USE_PNG if (isPNG(vf)) { return _loadPNGState(gba, vf); } - #endif +#endif if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) { return false; }

@@ -308,9 +335,9 @@ struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ);

if (!state) { return false; } - GBADeserialize(gba, state); + bool success = GBADeserialize(gba, state); vf->unmap(vf, state, sizeof(struct GBASerializedState)); - return true; + return success; } struct GBASerializedState* GBAAllocateState(void) {
M src/gba/serialize.hsrc/gba/serialize.h

@@ -136,7 +136,8 @@ * | bit 1: Has rumble value (reserved)

* | bit 2: Has light sensor value * | bit 3: Has gyroscope value * | bit 4: Has tilt values - * | bits 5 - 7: Reserved + * | bit 5: Has Game Boy Player attached + * | bits 6 - 7: Reserved * | 0x002B8 - 0x002B9: Gyroscope sample * | 0x002BA - 0x002BB: Tilt x sample * | 0x002BC - 0x002BD: Tilt y sample

@@ -149,8 +150,11 @@ * | bits 4 - 15: Light counter

* | 0x002C0 - 0x002C0: Light sample * | 0x002C1 - 0x002C3: Flags * | bits 0 - 1: Tilt state machine - * | bits 2 - 31: Reserved - * 0x002C4 - 0x002DF: Reserved (leave zero) + * | bits 2 - 3: GB Player inputs posted + * | bits 4 - 8: GB Player transmit position + * | bits 9 - 23: Reserved + * 0x002C4 - 0x002C7: Game Boy Player next event + * 0x002C8 - 0x002DF: Reserved (leave zero) * 0x002E0 - 0x002EF: Savedata state * | 0x002E0 - 0x002E0: Savedata type * | 0x002E1 - 0x002E1: Savedata command (see savedata.h)

@@ -282,10 +286,13 @@ unsigned : 1;

unsigned lightCounter : 12; unsigned lightSample : 8; unsigned tiltState : 2; - unsigned : 22; + unsigned gbpInputsPosted : 2; + unsigned gbpTxPosition : 5; + unsigned : 15; + uint32_t gbpNextEvent : 32; } hw; - uint32_t reservedHardware[7]; + uint32_t reservedHardware[6]; struct { unsigned type : 8;

@@ -321,7 +328,7 @@ struct VDir;

struct GBAThread; void GBASerialize(struct GBA* gba, struct GBASerializedState* state); -void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); +bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot); bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
M src/gba/sharkport.csrc/gba/sharkport.c

@@ -123,7 +123,7 @@ case SAVEDATA_FLASH512:

if (copySize > SIZE_CART_FLASH512) { GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); } - // Fall through + // Fall through case SAVEDATA_FLASH1M: if (copySize > SIZE_CART_FLASH1M) { copySize = SIZE_CART_FLASH1M;

@@ -148,7 +148,6 @@ cleanup:

free(payload); return false; } - bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { union {
M src/gba/sio.csrc/gba/sio.c

@@ -48,14 +48,11 @@ }

} void GBASIOInit(struct GBASIO* sio) { - sio->rcnt = RCNT_INITIAL; - sio->siocnt = 0; - sio->mode = -1; - sio->activeDriver = 0; sio->drivers.normal = 0; sio->drivers.multiplayer = 0; sio->drivers.joybus = 0; - _switchMode(sio); + sio->activeDriver = 0; + GBASIOReset(sio); } void GBASIODeinit(struct GBASIO* sio) {

@@ -68,6 +65,17 @@ }

if (sio->drivers.joybus && sio->drivers.joybus->deinit) { sio->drivers.joybus->deinit(sio->drivers.joybus); } + if (sio->drivers.normal && sio->drivers.normal->deinit) { + sio->drivers.normal->deinit(sio->drivers.normal); + } +} + +void GBASIOReset(struct GBASIO* sio) { + GBASIODeinit(sio); + sio->rcnt = RCNT_INITIAL; + sio->siocnt = 0; + sio->mode = -1; + _switchMode(sio); } void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) {
M src/gba/sio.hsrc/gba/sio.h

@@ -8,34 +8,14 @@ #define GBA_SIO_H

#include "util/common.h" +#include "gba/interface.h" + #define MAX_GBAS 4 extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS]; -enum GBASIOMode { - SIO_NORMAL_8 = 0, - SIO_NORMAL_32 = 1, - SIO_MULTI = 2, - SIO_UART = 3, - SIO_GPIO = 8, - SIO_JOYBUS = 12 -}; - enum { RCNT_INITIAL = 0x8000 -}; - -struct GBASIO; - -struct GBASIODriver { - struct GBASIO* p; - - bool (*init)(struct GBASIODriver* driver); - void (*deinit)(struct GBASIODriver* driver); - bool (*load)(struct GBASIODriver* driver); - bool (*unload)(struct GBASIODriver* driver); - uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); - int32_t (*processEvents)(struct GBASIODriver* driver, int32_t cycles); }; struct GBASIODriverSet {

@@ -85,6 +65,7 @@ };

void GBASIOInit(struct GBASIO* sio); void GBASIODeinit(struct GBASIO* sio); +void GBASIOReset(struct GBASIO* sio); void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers); void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode);
M src/gba/supervisor/cli.csrc/gba/supervisor/cli.c

@@ -30,11 +30,9 @@ { "rewind", _rewind, CLIDVParse, "Rewind the emulation a number of recorded intervals" },

{ "save", _save, CLIDVParse, "Save a savestate" }, { 0, 0, 0, 0 } }; -#endif struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread* context) { struct GBACLIDebugger* debugger = malloc(sizeof(struct GBACLIDebugger)); -#ifdef USE_CLI_DEBUGGER debugger->d.init = _GBACLIDebuggerInit; debugger->d.deinit = _GBACLIDebuggerDeinit; debugger->d.custom = _GBACLIDebuggerCustom;

@@ -44,14 +42,10 @@ debugger->d.name = "Game Boy Advance";

debugger->d.commands = _GBACLIDebuggerCommands; debugger->context = context; -#else - UNUSED(context); -#endif return debugger; } -#ifdef USE_CLI_DEBUGGER static void _GBACLIDebuggerInit(struct CLIDebuggerSystem* debugger) { struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger;
M src/gba/supervisor/cli.hsrc/gba/supervisor/cli.h

@@ -6,21 +6,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef GBA_CLI_H #define GBA_CLI_H +#ifdef USE_CLI_DEBUGGER #include "debugger/cli-debugger.h" struct GBAThread; struct GBACLIDebugger { -#ifdef USE_CLI_DEBUGGER struct CLIDebuggerSystem d; struct GBAThread* context; bool frameAdvance; bool inVblank; -#endif }; struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread*); +#endif #endif
M src/gba/supervisor/config.csrc/gba/supervisor/config.c

@@ -13,6 +13,7 @@ #include <sys/stat.h>

#ifdef _WIN32 #include <windows.h> +#include <shlwapi.h> #include <shlobj.h> #include <strsafe.h> #endif

@@ -115,28 +116,86 @@ bool GBAConfigLoad(struct GBAConfig* config) {

char path[PATH_MAX]; GBAConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); - return ConfigurationRead(&config->configTable, path); + return GBAConfigLoadPath(config, path); } bool GBAConfigSave(const struct GBAConfig* config) { char path[PATH_MAX]; GBAConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); + return GBAConfigSavePath(config, path); +} + +bool GBAConfigLoadPath(struct GBAConfig* config, const char* path) { + return ConfigurationRead(&config->configTable, path); +} + +bool GBAConfigSavePath(const struct GBAConfig* config, const char* path) { return ConfigurationWrite(&config->configTable, path); +} + +void GBAConfigMakePortable(const struct GBAConfig* config) { + struct VFile* portable; +#ifndef _WIN32 + char out[PATH_MAX]; + getcwd(out, PATH_MAX); + strncat(out, PATH_SEP "portable.ini", PATH_MAX - strlen(out)); + portable = VFileOpen(out, O_WRONLY | O_CREAT); +#else + char out[MAX_PATH]; + wchar_t wpath[MAX_PATH]; + wchar_t wprojectName[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH); + HMODULE hModule = GetModuleHandleW(NULL); + GetModuleFileNameW(hModule, wpath, MAX_PATH); + PathRemoveFileSpecW(wpath); + WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, MAX_PATH, 0, 0); + StringCchCatA(out, MAX_PATH, "\\portable.ini"); + portable = VFileOpen(out, O_WRONLY | O_CREAT); +#endif + if (portable) { + portable->close(portable); + GBAConfigSave(config); + } } void GBAConfigDirectory(char* out, size_t outLength) { + struct VFile* portable; #ifndef _WIN32 + getcwd(out, outLength); + strncat(out, PATH_SEP "portable.ini", outLength - strlen(out)); + portable = VFileOpen(out, O_RDONLY); + if (portable) { + getcwd(out, outLength); + portable->close(portable); + return; + } + char* home = getenv("HOME"); snprintf(out, outLength, "%s/.config", home); mkdir(out, 0755); snprintf(out, outLength, "%s/.config/%s", home, binaryName); mkdir(out, 0755); #else - char home[MAX_PATH]; - SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home); - snprintf(out, outLength, "%s\\%s", home, projectName); - CreateDirectoryA(out, NULL); + wchar_t wpath[MAX_PATH]; + wchar_t wprojectName[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH); + HMODULE hModule = GetModuleHandleW(NULL); + GetModuleFileNameW(hModule, wpath, MAX_PATH); + PathRemoveFileSpecW(wpath); + WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0); + StringCchCatA(out, outLength, "\\portable.ini"); + portable = VFileOpen(out, O_RDONLY); + if (portable) { + portable->close(portable); + } else { + wchar_t* home; + SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &home); + StringCchPrintfW(wpath, MAX_PATH, L"%ws\\%ws", home, wprojectName); + CoTaskMemFree(home); + CreateDirectoryW(wpath, NULL); + } + WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0); #endif }

@@ -188,6 +247,7 @@ unsigned audioBuffers;

if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { opts->audioBuffers = audioBuffers; } + _lookupUIntValue(config, "sampleRate", &opts->sampleRate); int fakeBool; if (_lookupIntValue(config, "useBios", &fakeBool)) {

@@ -246,6 +306,7 @@ ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity);

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);
M src/gba/supervisor/config.hsrc/gba/supervisor/config.h

@@ -29,6 +29,7 @@ int rewindBufferCapacity;

int rewindBufferInterval; float fpsTarget; size_t audioBuffers; + unsigned sampleRate; int fullscreen; int width;

@@ -51,7 +52,10 @@ void GBAConfigDeinit(struct GBAConfig*);

bool GBAConfigLoad(struct GBAConfig*); bool GBAConfigSave(const struct GBAConfig*); +bool GBAConfigLoadPath(struct GBAConfig*, const char* path); +bool GBAConfigSavePath(const struct GBAConfig*, const char* path); +void GBAConfigMakePortable(const struct GBAConfig*); void GBAConfigDirectory(char* out, size_t outLength); const char* GBAConfigGetValue(const struct GBAConfig*, const char* key);
M src/gba/supervisor/overrides.csrc/gba/supervisor/overrides.c

@@ -8,7 +8,7 @@

#include "gba/gba.h" #include "gba/hardware.h" - #include "util/configuration.h" +#include "util/configuration.h" static const struct GBACartridgeOverride _overrides[] = { // Advance Wars

@@ -285,6 +285,12 @@

if (override->hardware & HW_TILT) { GBAHardwareInitTilt(&gba->memory.hw); } + + if (override->hardware & HW_GB_PLAYER_DETECTION) { + gba->memory.hw.devices |= HW_GB_PLAYER_DETECTION; + } else { + gba->memory.hw.devices &= ~HW_GB_PLAYER_DETECTION; + } } if (override->idleLoop != IDLE_LOOP_NONE) {

@@ -294,3 +300,12 @@ gba->idleOptimization = IDLE_LOOP_REMOVE;

} } } + +void GBAOverrideApplyDefaults(struct GBA* gba) { + struct GBACartridgeOverride override; + const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; + memcpy(override.id, &cart->id, sizeof(override.id)); + if (GBAOverrideFind(0, &override)) { + GBAOverrideApply(gba, &override); + } +}
M src/gba/supervisor/overrides.hsrc/gba/supervisor/overrides.h

@@ -25,5 +25,6 @@ void GBAOverrideSave(struct Configuration*, const struct GBACartridgeOverride* override);

struct GBA; void GBAOverrideApply(struct GBA*, const struct GBACartridgeOverride*); +void GBAOverrideApplyDefaults(struct GBA*); #endif
M src/gba/supervisor/thread.csrc/gba/supervisor/thread.c

@@ -23,6 +23,8 @@ #include "platform/commandline.h"

#include <signal.h> +static void _loadGameDir(struct GBAThread* threadContext); + static const float _defaultFPSTarget = 60.f; #ifndef DISABLE_THREADING

@@ -94,10 +96,22 @@ _waitUntilNotState(threadContext, THREAD_PAUSING);

} } +struct GBAThreadStop { + struct GBAStopCallback d; + struct GBAThread* p; +}; + +static void _stopCallback(struct GBAStopCallback* stop) { + struct GBAThreadStop* callback = (struct GBAThreadStop*) stop; + if (callback->p->stopCallback(callback->p)) { + _changeState(callback->p, THREAD_EXITING, false); + } +} + static THREAD_ENTRY _GBAThreadRun(void* context) { #ifdef USE_PTHREADS pthread_once(&_contextOnce, _createTLS); -#else +#elif _WIN32 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); #endif

@@ -106,7 +120,7 @@ struct ARMCore cpu;

struct Patch patch; struct GBACheatDevice cheatDevice; struct GBAThread* threadContext = context; - struct ARMComponent* components[GBA_COMPONENT_MAX] = {0}; + struct ARMComponent* components[GBA_COMPONENT_MAX] = { 0 }; struct GBARRContext* movie = 0; int numComponents = GBA_COMPONENT_MAX;

@@ -127,10 +141,18 @@ threadContext->cpu = &cpu;

gba.logLevel = threadContext->logLevel; gba.logHandler = threadContext->logHandler; gba.stream = threadContext->stream; + + struct GBAThreadStop stop; + if (threadContext->stopCallback) { + stop.d.stop = _stopCallback; + stop.p = threadContext; + gba.stopCallback = &stop.d; + } + gba.idleOptimization = threadContext->idleOptimization; #ifdef USE_PTHREADS pthread_setspecific(_contextKey, threadContext); -#else +#elif _WIN32 TlsSetValue(_contextKey, threadContext); #endif

@@ -203,7 +225,7 @@ movie->startPlaying(movie, false);

GBARRInitPlay(&gba); } - if (threadContext->skipBios) { + if (threadContext->skipBios && gba.memory.rom) { GBASkipBIOS(&cpu); }

@@ -283,7 +305,7 @@ }

MutexUnlock(&threadContext->stateMutex); if (resetScheduled) { ARMReset(&cpu); - if (threadContext->skipBios) { + if (threadContext->skipBios && gba.memory.rom) { GBASkipBIOS(&cpu); } }

@@ -352,18 +374,7 @@ if (args->dirmode) {

threadContext->gameDir = VDirOpen(args->fname); threadContext->stateDir = threadContext->gameDir; } else { - threadContext->rom = VFileOpen(args->fname, O_RDONLY); - threadContext->gameDir = 0; -#if USE_LIBZIP - if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpenZip(args->fname, 0); - } -#endif -#if USE_LZMA - if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpen7z(args->fname, 0); - } -#endif + GBAThreadLoadROM(threadContext, args->fname); } threadContext->fname = args->fname; threadContext->patch = VFileOpen(args->patch, O_RDONLY);

@@ -398,25 +409,7 @@ threadContext->rom = 0;

} if (threadContext->gameDir) { - threadContext->gameDir->rewind(threadContext->gameDir); - struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir); - while (dirent) { - struct Patch patchTemp; - struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY); - if (!vf) { - dirent = threadContext->gameDir->listNext(threadContext->gameDir); - continue; - } - if (!threadContext->rom && GBAIsROM(vf)) { - threadContext->rom = vf; - } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) { - threadContext->patch = vf; - } else { - vf->close(vf); - } - dirent = threadContext->gameDir->listNext(threadContext->gameDir); - } - + _loadGameDir(threadContext); } if (!threadContext->rom && !bootBios) {

@@ -426,6 +419,16 @@ }

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

@@ -437,7 +440,7 @@ ConditionInit(&threadContext->sync.audioRequiredCond);

threadContext->interruptDepth = 0; -#ifndef _WIN32 +#ifdef USE_PTHREADS sigset_t signals; sigemptyset(&signals); sigaddset(&signals, SIGINT);

@@ -678,6 +681,67 @@

GBASyncSetVideoSync(&threadContext->sync, frameOn); } +void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname) { + threadContext->rom = VFileOpen(fname, O_RDONLY); + threadContext->gameDir = 0; +#if USE_LIBZIP + if (!threadContext->gameDir) { + threadContext->gameDir = VDirOpenZip(fname, 0); + } +#endif +#if USE_LZMA + if (!threadContext->gameDir) { + threadContext->gameDir = VDirOpen7z(fname, 0); + } +#endif +} + +static void _loadGameDir(struct GBAThread* threadContext) { + threadContext->gameDir->rewind(threadContext->gameDir); + struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir); + while (dirent) { + struct Patch patchTemp; + struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY); + if (!vf) { + dirent = threadContext->gameDir->listNext(threadContext->gameDir); + continue; + } + if (!threadContext->rom && GBAIsROM(vf)) { + threadContext->rom = vf; + } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) { + threadContext->patch = vf; + } else { + vf->close(vf); + } + dirent = threadContext->gameDir->listNext(threadContext->gameDir); + } +} + +void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname) { + GBAUnloadROM(threadContext->gba); + + if (threadContext->rom) { + threadContext->rom->close(threadContext->rom); + threadContext->rom = 0; + } + + if (threadContext->save) { + threadContext->save->close(threadContext->save); + threadContext->save = 0; + } + + GBAThreadLoadROM(threadContext, fname); + if (threadContext->gameDir) { + _loadGameDir(threadContext); + } + + threadContext->fname = fname; + threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR); + + GBARaiseIRQ(threadContext->gba, IRQ_GAMEPAK); + GBALoadROM(threadContext->gba, threadContext->rom, threadContext->save, threadContext->fname); +} + #ifdef USE_PTHREADS struct GBAThread* GBAThreadGetContext(void) { pthread_once(&_contextOnce, _createTLS);

@@ -687,6 +751,10 @@ #elif _WIN32

struct GBAThread* GBAThreadGetContext(void) { InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); return TlsGetValue(_contextKey); +} +#else +struct GBAThread* GBAThreadGetContext(void) { + return 0; } #endif
M src/gba/supervisor/thread.hsrc/gba/supervisor/thread.h

@@ -21,6 +21,7 @@ struct GBACheatSet;

struct GBAOptions; typedef void (*ThreadCallback)(struct GBAThread* threadContext); +typedef bool (*ThreadStopCallback)(struct GBAThread* threadContext); enum ThreadState { THREAD_INITIALIZED = -1,

@@ -86,6 +87,7 @@ int logLevel;

ThreadCallback startCallback; ThreadCallback cleanCallback; ThreadCallback frameCallback; + ThreadStopCallback stopCallback; void* userData; void (*run)(struct GBAThread*);

@@ -125,6 +127,9 @@ bool GBAThreadIsPaused(struct GBAThread* threadContext);

void GBAThreadTogglePause(struct GBAThread* threadContext); void GBAThreadPauseFromThread(struct GBAThread* threadContext); struct GBAThread* GBAThreadGetContext(void); + +void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname); +void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname); #ifdef USE_PNG void GBAThreadTakeScreenshot(struct GBAThread* threadContext);
M src/gba/video.csrc/gba/video.c

@@ -17,17 +17,38 @@ 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); static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer); static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels); +const int GBAVideoObjSizes[16][2] = { + { 8, 8 }, + { 16, 16 }, + { 32, 32 }, + { 64, 64 }, + { 16, 8 }, + { 32, 8 }, + { 32, 16 }, + { 64, 32 }, + { 8, 16 }, + { 8, 32 }, + { 16, 32 }, + { 32, 64 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, +}; + static struct GBAVideoRenderer dummyRenderer = { .init = GBAVideoDummyRendererInit, .reset = GBAVideoDummyRendererReset, .deinit = GBAVideoDummyRendererDeinit, .writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister, + .writeVRAM = GBAVideoDummyRendererWriteVRAM, .writePalette = GBAVideoDummyRendererWritePalette, .writeOAM = GBAVideoDummyRendererWriteOAM, .drawScanline = GBAVideoDummyRendererDrawScanline,

@@ -46,7 +67,7 @@

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;

@@ -203,6 +224,12 @@ UNUSED(address);

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

@@ -233,7 +260,6 @@ UNUSED(stride);

UNUSED(pixels); // Nothing to do } - void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) { memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
M src/gba/video.hsrc/gba/video.h

@@ -26,19 +26,17 @@ #define GBA_G8(X) (((X) >> 2) & 0xF8)

#define GBA_B8(X) (((X) >> 7) & 0xF8) enum { - VIDEO_CYCLES_PER_PIXEL = 4, - VIDEO_HORIZONTAL_PIXELS = 240, VIDEO_HBLANK_PIXELS = 68, VIDEO_HDRAW_LENGTH = 1006, VIDEO_HBLANK_LENGTH = 226, - VIDEO_HORIZONTAL_LENGTH = 1232, + VIDEO_HORIZONTAL_LENGTH = VIDEO_HDRAW_LENGTH + VIDEO_HBLANK_LENGTH, VIDEO_VERTICAL_PIXELS = 160, VIDEO_VBLANK_PIXELS = 68, - VIDEO_VERTICAL_TOTAL_PIXELS = 228, + VIDEO_VERTICAL_TOTAL_PIXELS = VIDEO_VERTICAL_PIXELS + VIDEO_VBLANK_PIXELS, - VIDEO_TOTAL_LENGTH = 280896, + VIDEO_TOTAL_LENGTH = VIDEO_HORIZONTAL_LENGTH * VIDEO_VERTICAL_TOTAL_PIXELS, REG_DISPSTAT_MASK = 0xFF38,

@@ -66,7 +64,6 @@ DECL_BITS(GBAObjAttributesA, Mode, 10, 2);

DECL_BIT(GBAObjAttributesA, Mosaic, 12); DECL_BIT(GBAObjAttributesA, 256Color, 13); DECL_BITS(GBAObjAttributesA, Shape, 14, 2); - DECL_BITFIELD(GBAObjAttributesB, uint16_t); DECL_BITS(GBAObjAttributesB, X, 0, 9);

@@ -164,6 +161,7 @@ void (*reset)(struct GBAVideoRenderer* renderer);

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

@@ -214,5 +212,7 @@

struct GBASerializedState; void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state); void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state); + +extern const int GBAVideoObjSizes[16][2]; #endif
M src/platform/commandline.hsrc/platform/commandline.h

@@ -46,7 +46,8 @@ };

struct GBAThread; -bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser); +bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, + struct SubParser* subparser); void freeArguments(struct GBAArguments* opts); void usage(const char* arg0, const char* extraOptions);
M src/platform/ffmpeg/ffmpeg-encoder.csrc/platform/ffmpeg/ffmpeg-encoder.c

@@ -248,9 +248,9 @@ encoder->postaudioBuffer = av_malloc(encoder->postaudioBufferSize);

avcodec_fill_audio_frame(encoder->audioFrame, encoder->audio->channels, encoder->audio->sample_fmt, (const uint8_t*) encoder->postaudioBuffer, encoder->postaudioBufferSize, 0); if (encoder->audio->codec->id == AV_CODEC_ID_AAC && - (strcasecmp(encoder->containerFormat, "mp4") || - strcasecmp(encoder->containerFormat, "m4v") || - strcasecmp(encoder->containerFormat, "mov"))) { + (strcasecmp(encoder->containerFormat, "mp4") || + strcasecmp(encoder->containerFormat, "m4v") || + strcasecmp(encoder->containerFormat, "mov"))) { // MP4 container doesn't support the raw ADTS AAC format that the encoder spits out encoder->absf = av_bitstream_filter_init("aac_adtstoasc"); }

@@ -292,19 +292,19 @@ encoder->videoFrame->pts = 0;

encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - AV_PIX_FMT_RGB565, + AV_PIX_FMT_RGB565, #else - AV_PIX_FMT_BGR555, + AV_PIX_FMT_BGR555, #endif #else #ifndef USE_LIBAV - AV_PIX_FMT_0BGR32, + AV_PIX_FMT_0BGR32, #else - AV_PIX_FMT_BGR32, + AV_PIX_FMT_BGR32, #endif #endif - encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt, - SWS_POINT, 0, 0, 0); + encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt, + SWS_POINT, 0, 0, 0); av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32); avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE);

@@ -378,8 +378,8 @@ encoder->currentAudioSample = 0;

int channelSize = 2 * av_get_bytes_per_sample(encoder->audio->sample_fmt); avresample_convert(encoder->resampleContext, - 0, 0, 0, - (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); + 0, 0, 0, + (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); if (avresample_available(encoder->resampleContext) < encoder->audioFrame->nb_samples) { return; }

@@ -402,8 +402,8 @@ if (gotData) {

if (encoder->absf) { AVPacket tempPacket = packet; int success = av_bitstream_filter_filter(encoder->absf, encoder->audio, 0, - &tempPacket.data, &tempPacket.size, - packet.data, packet.size, 0); + &tempPacket.data, &tempPacket.size, + packet.data, packet.size, 0); if (success > 0) { #if LIBAVUTIL_VERSION_MAJOR >= 53 tempPacket.buf = av_buffer_create(tempPacket.data, tempPacket.size, av_buffer_default_free, 0, 0);
M src/platform/imagemagick/imagemagick-gif-encoder.csrc/platform/imagemagick/imagemagick-gif-encoder.c

@@ -58,7 +58,7 @@ unsigned stride;

renderer->getPixels(renderer, &stride, (void**) &pixels); size_t row; for (row = 0; row < VIDEO_VERTICAL_PIXELS; ++row) { - memcpy(&encoder->frame[row * VIDEO_HORIZONTAL_PIXELS], &pixels[row * 4 *stride], VIDEO_HORIZONTAL_PIXELS * 4); + memcpy(&encoder->frame[row * VIDEO_HORIZONTAL_PIXELS], &pixels[row * 4 * stride], VIDEO_HORIZONTAL_PIXELS * 4); } MagickConstituteImage(encoder->wand, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, "RGBP", CharPixel, encoder->frame);
M src/platform/libretro/libretro.csrc/platform/libretro/libretro.c

@@ -8,13 +8,18 @@

#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" #include "gba/video.h" +#include "util/circle-buffer.h" #include "util/vfs.h" #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;

@@ -22,11 +27,15 @@ static retro_audio_sample_batch_t audioCallback;

static retro_input_poll_t inputPollCallback; static retro_input_state_t inputCallback; static retro_log_printf_t logCallback; +static retro_set_rumble_state_t rumbleCallback; static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); 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;

@@ -35,7 +44,13 @@ static struct VFile* rom;

static void* data; static struct VFile* save; static void* savedata; +static struct VFile* bios; 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;

@@ -43,6 +58,13 @@ }

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

@@ -107,12 +129,27 @@ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },

{ 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); // TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported - // TODO: RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE + + struct retro_rumble_interface rumbleInterface; + if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) { + rumbleCallback = rumbleInterface.set_rumble_state; + CircleBufferInit(&rumbleHistory, RUMBLE_PWM); + rumble.setRumble = _setRumble; + } else { + 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)) {

@@ -132,7 +169,21 @@ gba.logLevel = 0; // TODO: Settings

gba.logHandler = GBARetroLog; gba.stream = &stream; gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings + if (rumbleCallback) { + gba.rumble = &rumble; + } + gba.luminanceSource = &lux; rom = 0; + + const char* sysDir = 0; + if (environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir)) { + char biosPath[PATH_MAX]; + snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, "gba_bios.bin"); + bios = VFileOpen(biosPath, O_RDONLY); + if (bios) { + GBALoadBIOS(&gba, bios); + } + } GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);

@@ -148,6 +199,10 @@ #endif

} void retro_deinit(void) { + if (bios) { + bios->close(bios); + bios = 0; + } GBADestroy(&gba); }

@@ -168,6 +223,26 @@ keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7;

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

@@ -176,6 +251,10 @@ }

void retro_reset(void) { ARMReset(&cpu); + + if (rumbleCallback) { + CircleBufferClear(&rumbleHistory); + } } bool retro_load_game(const struct retro_game_info* game) {

@@ -198,13 +277,7 @@ savedata = malloc(SIZE_CART_FLASH1M);

save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); GBALoadROM(&gba, rom, save, game->path); - - struct GBACartridgeOverride override; - const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom; - memcpy(override.id, &cart->id, sizeof(override.id)); - if (GBAOverrideFind(0, &override)) { - GBAOverrideApply(&gba, &override); - } + GBAOverrideApplyDefaults(&gba); ARMReset(&cpu); return true;

@@ -219,6 +292,7 @@ save->close(save);

save = 0; free(savedata); savedata = 0; + CircleBufferDeinit(&rumbleHistory); } size_t retro_serialize_size(void) {

@@ -317,10 +391,12 @@ break;

case GBA_LOG_INFO: case GBA_LOG_GAME_ERROR: case GBA_LOG_SWI: + case GBA_LOG_STATUS: retroLevel = RETRO_LOG_INFO; break; case GBA_LOG_DEBUG: case GBA_LOG_STUB: + case GBA_LOG_SIO: retroLevel = RETRO_LOG_DEBUG; break; }

@@ -352,3 +428,55 @@ unsigned stride;

renderer->getPixels(renderer, &stride, &pixels); videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride); } + +static void _setRumble(struct GBARumble* rumble, int enable) { + UNUSED(rumble); + if (!rumbleCallback) { + return; + } + rumbleLevel += enable; + if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) { + int8_t oldLevel; + CircleBufferRead8(&rumbleHistory, &oldLevel); + rumbleLevel -= oldLevel; + } + 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; +}
M src/platform/opengl/gl.csrc/platform/opengl/gl.c

@@ -78,7 +78,7 @@ glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_INT, 0, _glVertices); glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode (GL_PROJECTION); + glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, 0, 1); glMatrixMode(GL_MODELVIEW);
A 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; +}
A 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 <GLES2/gl2.h> + +#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
M src/platform/perf-main.csrc/platform/perf-main.c

@@ -186,7 +186,7 @@ }

} } GBASyncWaitFrameEnd(&context->sync); - if (*frames == duration) { + if (duration > 0 && *frames == duration) { _GBAPerfShutdown(0); } if (_dispatchExiting) {
M src/platform/posix/threading.hsrc/platform/posix/threading.h

@@ -10,7 +10,7 @@ #include "util/common.h"

#include <pthread.h> #include <sys/time.h> -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__OpenBSD__) #include <pthread_np.h> #endif

@@ -79,7 +79,7 @@

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
A src/platform/qt/AboutScreen.cpp

@@ -0,0 +1,41 @@

+/* 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 <QPixmap> + +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); + + QLatin1String tree(gitBranch); + if (tree == QLatin1String("(unknown)")) { + tree = QLatin1String(projectVersion); + } + + 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}", tree); + m_ui.extraLinks->setText(extraLinks); +}
A 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 <QDialog> + +#include "ui_AboutScreen.h" + +namespace QGBA { + +class AboutScreen : public QDialog { +Q_OBJECT + +public: + AboutScreen(QWidget* parent = nullptr); + +private: + Ui::AboutScreen m_ui; +}; + +} + +#endif
A src/platform/qt/AboutScreen.ui

@@ -0,0 +1,177 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AboutScreen</class> + <widget class="QWidget" name="AboutScreen"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>565</width> + <height>268</height> + </rect> + </property> + <property name="windowTitle"> + <string>About</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetFixedSize</enum> + </property> + <item row="0" column="1"> + <widget class="QLabel" name="projectName"> + <property name="font"> + <font> + <pointsize>36</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>{projectName}</string> + </property> + <property name="alignment"> + <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> + </property> + </widget> + </item> + <item row="6" column="0" colspan="4"> + <widget class="QLabel" name="copyright"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>© 2013 – 2015 Jeffrey Pfau — Game Boy Advance is a registered trademark of Nintendo Co., Ltd.</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="projectVersion"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>{projectVersion}</string> + </property> + <property name="alignment"> + <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="0" column="0" rowspan="6"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>8</number> + </property> + <property name="rightMargin"> + <number>16</number> + </property> + <property name="bottomMargin"> + <number>12</number> + </property> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="logo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>256</width> + <height>192</height> + </size> + </property> + <property name="text"> + <string>{logo}</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="description"> + <property name="text"> + <string>{projectName} is an open-source Game Boy Advance emulator</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="extraLinks"> + <property name="text"> + <string>&lt;a href=&quot;http://mgba.io/&quot;&gt;Website&lt;/a&gt; • &lt;a href=&quot;https://forums.mgba.io/&quot;&gt;Forums / Support&lt;/a&gt; • &lt;a href=&quot;https://github.com/mgba-emu/mgba/tree/{gitBranch}&quot;&gt;Source&lt;/a&gt; • &lt;a href=&quot;https://github.com/mgba-emu/mgba/blob/{gitBranch}/LICENSE&quot;&gt;License&lt;/a&gt;</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="gitInfo"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>Branch: &lt;tt&gt;{gitBranch}&lt;/tt&gt;&lt;br/&gt;Revision: &lt;tt&gt;{gitCommit}&lt;/tt&gt;</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
M src/platform/qt/AudioDevice.cppsrc/platform/qt/AudioDevice.cpp

@@ -5,6 +5,8 @@ * 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 "AudioDevice.h" +#include "LogController.h" + extern "C" { #include "gba/gba.h" #include "gba/audio.h"

@@ -24,6 +26,7 @@ }

void AudioDevice::setFormat(const QAudioFormat& format) { if (!m_context || !GBAThreadIsActive(m_context)) { + LOG(INFO) << tr("Can't set format of context-less audio device"); return; } #if RESAMPLE_LIBRARY == RESAMPLE_NN

@@ -49,6 +52,7 @@ maxSize = 0xFFFFFFFF;

} if (!m_context->gba) { + LOG(WARN) << tr("Audio device is missing its GBA"); return 0; }

@@ -68,5 +72,6 @@ #endif

} qint64 AudioDevice::writeData(const char*, qint64) { + LOG(WARN) << tr("Writing data to read-only audio device"); return 0; }
M src/platform/qt/AudioProcessor.cppsrc/platform/qt/AudioProcessor.cpp

@@ -38,10 +38,10 @@ return new AudioProcessorQt();

#endif default: -#ifdef BUILD_QT_MULTIMEDIA - return new AudioProcessorQt(); -#else +#ifdef BUILD_SDL return new AudioProcessorSDL(); +#else + return new AudioProcessorQt(); #endif } }
M src/platform/qt/AudioProcessor.hsrc/platform/qt/AudioProcessor.h

@@ -31,6 +31,7 @@ AudioProcessor(QObject* parent = nullptr);

virtual void setInput(GBAThread* input); int getBufferSamples() const { return m_samples; } + virtual unsigned sampleRate() const = 0; public slots: virtual void start() = 0;

@@ -38,6 +39,8 @@ virtual void pause() = 0;

virtual void setBufferSamples(int samples) = 0; virtual void inputParametersChanged() = 0; + + virtual void requestSampleRate(unsigned) = 0; protected: GBAThread* input() { return m_context; }
M src/platform/qt/AudioProcessorQt.cppsrc/platform/qt/AudioProcessorQt.cpp

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AudioProcessorQt.h" #include "AudioDevice.h" +#include "LogController.h" #include <QAudioOutput>

@@ -19,6 +20,7 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent)

: AudioProcessor(parent) , m_audioOutput(nullptr) , m_device(nullptr) + , m_sampleRate(44100) { }

@@ -34,6 +36,7 @@ }

void AudioProcessorQt::start() { if (!input()) { + LOG(WARN) << tr("Can't start an audio processor without input"); return; }

@@ -43,7 +46,7 @@ }

if (!m_audioOutput) { QAudioFormat format; - format.setSampleRate(44100); + format.setSampleRate(m_sampleRate); format.setChannelCount(2); format.setSampleSize(16); format.setCodec("audio/pcm");

@@ -81,3 +84,19 @@ if (m_device) {

m_device->setFormat(m_audioOutput->format()); } } + +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; + } + return m_audioOutput->format().sampleRate(); +}
M src/platform/qt/AudioProcessorQt.hsrc/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,9 +29,12 @@

virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); + virtual void requestSampleRate(unsigned) override; + private: QAudioOutput* m_audioOutput; AudioDevice* m_device; + unsigned m_sampleRate; }; }
M src/platform/qt/AudioProcessorSDL.cppsrc/platform/qt/AudioProcessorSDL.cpp

@@ -5,6 +5,8 @@ * 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 "AudioProcessorSDL.h" +#include "LogController.h" + extern "C" { #include "gba/supervisor/thread.h" }

@@ -13,7 +15,7 @@ using namespace QGBA;

AudioProcessorSDL::AudioProcessorSDL(QObject* parent) : AudioProcessor(parent) - , m_audio() + , m_audio{ 2048, 44100 } { }

@@ -23,6 +25,7 @@ }

void AudioProcessorSDL::start() { if (!input()) { + LOG(WARN) << tr("Can't start an audio processor without input"); return; }

@@ -51,3 +54,19 @@ }

void AudioProcessorSDL::inputParametersChanged() { } + +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; + } +}
M src/platform/qt/AudioProcessorSDL.hsrc/platform/qt/AudioProcessorSDL.h

@@ -22,12 +22,16 @@ public:

AudioProcessorSDL(QObject* parent = nullptr); ~AudioProcessorSDL(); + virtual unsigned sampleRate() const override; + public slots: virtual void start(); virtual void pause(); virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); + + virtual void requestSampleRate(unsigned) override; private: GBASDLAudio m_audio;
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -28,17 +28,31 @@

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 @@ add_definitions(-DQT_STATIC)

endif() set(SOURCE_FILES + AboutScreen.cpp AudioProcessor.cpp CheatsModel.cpp CheatsView.cpp

@@ -61,8 +76,10 @@ GameController.cpp

GamepadAxisEvent.cpp GamepadButtonEvent.cpp InputController.cpp + InputProfile.cpp KeyEditor.cpp LoadSaveState.cpp + LogController.cpp LogView.cpp MemoryModel.cpp MemoryView.cpp

@@ -81,6 +98,7 @@ VFileDevice.cpp

VideoView.cpp) qt5_wrap_ui(UI_FILES + AboutScreen.ui CheatsView.ui GIFView.ui LoadSaveState.ui

@@ -140,7 +158,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES})

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

@@ -151,6 +169,9 @@ find_program(DESKTOP_FILE_INSTALL desktop-file-install)

if(DESKTOP_FILE_INSTALL) 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})
M src/platform/qt/CheatsModel.cppsrc/platform/qt/CheatsModel.cpp

@@ -5,6 +5,7 @@ * 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 "CheatsModel.h" +#include "LogController.h" #include "VFileDevice.h" #include <QFont>

@@ -68,7 +69,7 @@ if (cheats->name) {

free(cheats->name); cheats->name = nullptr; } - cheats->name = strdup(value.toString().toLocal8Bit().constData()); + cheats->name = strdup(value.toString().toUtf8().constData()); emit dataChanged(index, index); return true; case Qt::CheckStateRole:

@@ -104,7 +105,7 @@ }

return QModelIndex(); } -Qt::ItemFlags CheatsModel::flags(const QModelIndex &index) const { +Qt::ItemFlags CheatsModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return 0; }

@@ -152,7 +153,6 @@ GBACheatRemoveSet(m_device, set);

GBACheatSetDeinit(set); delete set; endInsertRows(); - } QString CheatsModel::toString(const QModelIndexList& indices) const {

@@ -204,6 +204,7 @@

void CheatsModel::loadFile(const QString& path) { VFile* vf = VFileDevice::open(path, O_RDONLY); if (!vf) { + LOG(WARN) << tr("Failed to open cheats file: %1").arg(path); return; } beginResetModel();
M src/platform/qt/CheatsModel.hsrc/platform/qt/CheatsModel.h

@@ -27,7 +27,7 @@ virtual QModelIndex parent(const QModelIndex& index) const override;

virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; GBACheatSet* itemAt(const QModelIndex& index); void removeAt(const QModelIndex& index);
M src/platform/qt/CheatsView.cppsrc/platform/qt/CheatsView.cpp

@@ -99,20 +99,30 @@ m_controller->threadContinue();

} void CheatsView::enterCheat(std::function<bool(GBACheatSet*, const char*)> 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]); - callback(set, string.toLocal8Bit().constData()); + m_model.beginAppendRow(index); + callback(set, string.toUtf8().constData()); m_model.endAppendRow(); } m_controller->threadContinue();
M src/platform/qt/ConfigController.cppsrc/platform/qt/ConfigController.cpp

@@ -80,7 +80,7 @@ }

void ConfigOption::setValue(const QVariant& value) { QPair<QAction*, QVariant> action; - foreach(action, m_actions) { + foreach (action, m_actions) { bool signalsEnabled = action.first->blockSignals(true); action.first->setChecked(value == action.second); action.first->blockSignals(signalsEnabled);

@@ -107,7 +107,8 @@

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.sampleRate = 44100; 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 +116,8 @@ m_opts.rewindBufferInterval = 0;

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 +127,11 @@ GBAConfigFreeOpts(&m_opts);

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

@@ -214,7 +219,7 @@ setOption(key, value.toBool());

return; } QString stringValue(value.toString()); - setOption(key, stringValue.toLocal8Bit().constData()); + setOption(key, stringValue.toUtf8().constData()); } void ConfigController::setQtOption(const QString& key, const QVariant& value, const QString& group) {

@@ -258,3 +263,19 @@ void ConfigController::write() {

GBAConfigSave(&m_config); m_settings->sync(); } + +void ConfigController::makePortable() { + GBAConfigMakePortable(&m_config); + + char path[PATH_MAX]; + GBAConfigDirectory(path, sizeof(path)); + QString fileName(path); + fileName.append(QDir::separator()); + fileName.append("qt.ini"); + QSettings* settings2 = new QSettings(fileName, QSettings::IniFormat, this); + for (const auto& key : m_settings->allKeys()) { + settings2->setValue(key, m_settings->value(key)); + } + delete m_settings; + m_settings = settings2; +}
M src/platform/qt/ConfigController.hsrc/platform/qt/ConfigController.h

@@ -89,6 +89,7 @@ void setOption(const char* key, const char* value);

void setOption(const char* key, const QVariant& value); void setQtOption(const QString& key, const QVariant& value, const QString& group = QString()); + void makePortable(); void write(); private:
M src/platform/qt/Display.cppsrc/platform/qt/Display.cpp

@@ -22,7 +22,7 @@ #endif

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

@@ -46,7 +46,39 @@ }

Display::Display(QWidget* parent) : QWidget(parent) + , m_lockAspectRatio(false) + , m_filter(false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + connect(&m_mouseTimer, SIGNAL(timeout()), this, SIGNAL(hideCursor())); + m_mouseTimer.setSingleShot(true); + m_mouseTimer.setInterval(MOUSE_DISAPPEAR_TIMER); + setMouseTracking(true); +} + +void Display::resizeEvent(QResizeEvent*) { + m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); +} + +void Display::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); +} + +void Display::filter(bool filter) { + m_filter = filter; +} + +void Display::showMessage(const QString& message) { + m_messagePainter.showMessage(message); + if (!isDrawing()) { + forceDraw(); + } +} + +void Display::mouseMoveEvent(QMouseEvent*) { + emit showCursor(); + m_mouseTimer.stop(); + m_mouseTimer.start(); }
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -8,6 +8,8 @@ #define QGBA_DISPLAY

#include <QWidget> +#include "MessagePainter.h" + struct GBAThread; namespace QGBA {

@@ -28,20 +30,42 @@

static Display* create(QWidget* parent = nullptr); static void setDriver(Driver driver) { s_driver = driver; } + bool isAspectRatioLocked() const { return m_lockAspectRatio; } + bool isFiltered() const { return m_filter; } + + virtual bool isDrawing() const = 0; + +signals: + void showCursor(); + void hideCursor(); + public slots: virtual void startDrawing(GBAThread* context) = 0; virtual void stopDrawing() = 0; virtual void pauseDrawing() = 0; virtual void unpauseDrawing() = 0; virtual void forceDraw() = 0; - virtual void lockAspectRatio(bool lock) = 0; - virtual void filter(bool filter) = 0; + virtual void lockAspectRatio(bool lock); + virtual void filter(bool filter); virtual void framePosted(const uint32_t*) = 0; - virtual void showMessage(const QString& message) = 0; + void showMessage(const QString& message); + +protected: + void resizeEvent(QResizeEvent*); + virtual void mouseMoveEvent(QMouseEvent*) override; + + MessagePainter* messagePainter() { return &m_messagePainter; } + private: static Driver s_driver; + static const int MOUSE_DISAPPEAR_TIMER = 1000; + + MessagePainter m_messagePainter; + bool m_lockAspectRatio; + bool m_filter; + QTimer m_mouseTimer; }; }
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -16,13 +16,14 @@ 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) - , m_lockAspectRatio(false) - , m_filter(false) , m_context(nullptr) { + m_gl->setMouseTracking(true); + m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work? } DisplayGL::~DisplayGL() {

@@ -33,7 +34,9 @@ void DisplayGL::startDrawing(GBAThread* thread) {

if (m_drawThread) { return; } + m_isDrawing = true; m_painter->setContext(thread); + m_painter->setMessagePainter(messagePainter()); m_context = thread; m_painter->resize(size()); m_gl->move(0, 0);

@@ -46,13 +49,15 @@ connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));

m_drawThread->start(); GBASyncSetVideoSync(&m_context->sync, false); - lockAspectRatio(m_lockAspectRatio); - filter(m_filter); + lockAspectRatio(isAspectRatioLocked()); + filter(isFiltered()); + messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio()); resizePainter(); } void DisplayGL::stopDrawing() { if (m_drawThread) { + m_isDrawing = false; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); }

@@ -67,6 +72,7 @@ }

void DisplayGL::pauseDrawing() { if (m_drawThread) { + m_isDrawing = false; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); }

@@ -79,6 +85,7 @@ }

void DisplayGL::unpauseDrawing() { if (m_drawThread) { + m_isDrawing = true; if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); }

@@ -96,14 +103,14 @@ }

} void DisplayGL::lockAspectRatio(bool lock) { - m_lockAspectRatio = lock; + Display::lockAspectRatio(lock); if (m_drawThread) { QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock)); } } void DisplayGL::filter(bool filter) { - m_filter = filter; + Display::filter(filter); if (m_drawThread) { QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter)); }

@@ -111,17 +118,13 @@ }

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"); } } -void DisplayGL::showMessage(const QString& message) { - if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "showMessage", Q_ARG(const QString&, message)); - } -} - -void DisplayGL::resizeEvent(QResizeEvent*) { +void DisplayGL::resizeEvent(QResizeEvent* event) { + Display::resizeEvent(event); resizePainter(); }

@@ -138,7 +141,11 @@ , m_active(false)

, 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<PainterGL*>(v->user); painter->m_gl->swapBuffers();

@@ -146,25 +153,32 @@ };

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) { m_context = context; } -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::setMessagePainter(MessagePainter* messagePainter) { + m_messagePainter = messagePainter; } void PainterGL::resize(const QSize& size) { m_size = size; if (m_active) { - m_messagePainter->resize(size, m_backend.d.lockAspectRatio); forceDraw(); } }

@@ -172,7 +186,6 @@

void PainterGL::lockAspectRatio(bool lock) { m_backend.d.lockAspectRatio = lock; if (m_active) { - m_messagePainter->resize(m_size, m_backend.d.lockAspectRatio); forceDraw(); } }

@@ -185,8 +198,6 @@ }

} void PainterGL::start() { - m_messagePainter = new MessagePainter(this); - m_messagePainter->resize(m_size, m_backend.d.lockAspectRatio); m_gl->makeCurrent(); m_backend.d.init(&m_backend.d, reinterpret_cast<WHandle>(m_gl->winId())); m_gl->doneCurrent();

@@ -194,7 +205,11 @@ m_active = true;

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

@@ -203,6 +218,9 @@ m_backend.d.swap(&m_backend.d);

} else { GBASyncWaitFrameEnd(&m_context->sync); } + if (!m_queue.isEmpty()) { + QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); + } } void PainterGL::forceDraw() {

@@ -215,22 +233,17 @@

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); m_gl->doneCurrent(); m_gl->context()->moveToThread(m_gl->thread()); - m_messagePainter->clearMessage(); - delete m_messagePainter; - m_messagePainter = nullptr; moveToThread(m_gl->thread()); } void PainterGL::pause() { m_active = false; - // Make sure both buffers are filled - forceDraw(); - forceDraw(); } void PainterGL::unpause() {

@@ -243,9 +256,45 @@ float r = m_gl->devicePixelRatio();

m_backend.d.resized(&m_backend.d, m_size.width() * r, m_size.height() * r); m_backend.d.drawFrame(&m_backend.d); m_painter.endNativePainting(); - m_messagePainter->paint(&m_painter); + if (m_messagePainter) { + 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::showMessage(const QString& message) { - m_messagePainter->showMessage(message); +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(); }
M src/platform/qt/DisplayGL.hsrc/platform/qt/DisplayGL.h

@@ -8,14 +8,19 @@ #define QGBA_DISPLAY_GL

#include "Display.h" -#include "MessagePainter.h" - #include <QGLWidget> +#include <QList> +#include <QMouseEvent> +#include <QQueue> #include <QThread> #include <QTimer> extern "C" { +#ifdef BUILD_GL #include "platform/opengl/gl.h" +#elif defined(BUILD_GLES2) +#include "platform/opengl/gles2.h" +#endif } struct GBAThread;

@@ -29,6 +34,7 @@

protected: void paintEvent(QPaintEvent*) override {} void resizeEvent(QResizeEvent*) override {} + void mouseMoveEvent(QMouseEvent* event) override { event->ignore(); } }; class PainterGL;

@@ -38,6 +44,8 @@

public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); + + bool isDrawing() const override { return m_isDrawing; } public slots: void startDrawing(GBAThread* context) override;

@@ -49,8 +57,6 @@ void lockAspectRatio(bool lock) override;

void filter(bool filter) override; void framePosted(const uint32_t*) override; - void showMessage(const QString& message) override; - protected: virtual void paintEvent(QPaintEvent*) override {} virtual void resizeEvent(QResizeEvent*) override;

@@ -58,12 +64,11 @@

private: void resizePainter(); + bool m_isDrawing; QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread; GBAThread* m_context; - bool m_lockAspectRatio; - bool m_filter; }; class PainterGL : public QObject {

@@ -71,11 +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();

@@ -86,16 +93,23 @@ void resize(const QSize& size);

void lockAspectRatio(bool lock); void filter(bool filter); - void showMessage(const QString& message); - private: void performDraw(); + void dequeue(); + void dequeueAll(); + QList<uint32_t*> m_free; + QQueue<uint32_t*> 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; };
M src/platform/qt/DisplayQt.cppsrc/platform/qt/DisplayQt.cpp

@@ -7,26 +7,30 @@ #include "DisplayQt.h"

#include <QPainter> +extern "C" { +#include "gba/video.h" +} + using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) + , m_isDrawing(false) , m_backing(nullptr) - , m_lockAspectRatio(false) - , m_filter(false) { } void DisplayQt::startDrawing(GBAThread*) { + m_isDrawing = true; } void DisplayQt::lockAspectRatio(bool lock) { - m_lockAspectRatio = lock; + Display::lockAspectRatio(lock); update(); } void DisplayQt::filter(bool filter) { - m_filter = filter; + Display::filter(filter); update(); }

@@ -46,19 +50,15 @@ m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB32);

#endif } -void DisplayQt::showMessage(const QString& message) { - m_messagePainter.showMessage(message); -} - void DisplayQt::paintEvent(QPaintEvent*) { QPainter painter(this); painter.fillRect(QRect(QPoint(), size()), Qt::black); - if (m_filter) { + if (isFiltered()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } QSize s = size(); QSize ds = s; - if (m_lockAspectRatio) { + if (isAspectRatioLocked()) { if (s.width() * 2 > s.height() * 3) { ds.setWidth(s.height() * 3 / 2); } else if (s.width() * 2 < s.height() * 3) {

@@ -69,13 +69,9 @@ QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);

QRect full(origin, ds); #ifdef COLOR_5_6_5 - painter.drawImage(full, m_backing, QRect(0, 0, 240, 160)); + painter.drawImage(full, m_backing, QRect(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS)); #else - painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, 240, 160)); + painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS)); #endif - m_messagePainter.paint(&painter); -} - -void DisplayQt::resizeEvent(QResizeEvent*) { - m_messagePainter.resize(size(), m_lockAspectRatio); + messagePainter()->paint(&painter); }
M src/platform/qt/DisplayQt.hsrc/platform/qt/DisplayQt.h

@@ -7,7 +7,6 @@ #ifndef QGBA_DISPLAY_QT

#define QGBA_DISPLAY_QT #include "Display.h" -#include "MessagePainter.h" #include <QImage> #include <QTimer>

@@ -22,27 +21,24 @@

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; void framePosted(const uint32_t*) override; - void showMessage(const QString& message) override; - protected: virtual void paintEvent(QPaintEvent*) override; - virtual void resizeEvent(QResizeEvent*) override;; private: + bool m_isDrawing; QImage m_backing; - bool m_lockAspectRatio; - bool m_filter; - MessagePainter m_messagePainter; }; }
M src/platform/qt/GBAApp.cppsrc/platform/qt/GBAApp.cpp

@@ -15,6 +15,7 @@ #include <QFileOpenEvent>

#include <QIcon> extern "C" { +#include "gba/supervisor/thread.h" #include "platform/commandline.h" #include "util/socket.h" }

@@ -33,37 +34,47 @@ #ifdef BUILD_SDL

SDL_Init(SDL_INIT_NOPARACHUTE); #endif +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/res/mgba-1024.png")); +#endif SocketSubsystemInit(); qRegisterMetaType<const uint32_t*>("const uint32_t*"); + qRegisterMetaType<GBAThread*>("GBAThread*"); QApplication::setApplicationName(projectName); QApplication::setApplicationVersion(projectVersion); + if (!m_configController.getQtOption("displayDriver").isNull()) { + Display::setDriver(static_cast<Display::Driver>(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; + } + + if (!m_configController.getQtOption("audioDriver").isNull()) { + AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(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); - - Display::setDriver(static_cast<Display::Driver>(m_configController.getQtOption("videoDriver").toInt())); - AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(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) {

@@ -79,16 +90,15 @@ if (m_multiplayer.attached() >= MAX_GBAS) {

return nullptr; } Window* w = new Window(&m_configController, m_multiplayer.attached()); - m_windows[m_multiplayer.attached()] = w; + int windowId = m_multiplayer.attached(); + connect(w, &Window::destroyed, [this, windowId]() { + m_windows[windowId] = nullptr; + }); + 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; }
M src/platform/qt/GBAApp.hsrc/platform/qt/GBAApp.h

@@ -46,7 +46,8 @@

private: class FileDialog : public QFileDialog { public: - FileDialog(GBAApp* app, QWidget* parent = nullptr, const QString& caption = QString(), const QString& filter = QString()); + FileDialog(GBAApp* app, QWidget* parent = nullptr, const QString& caption = QString(), + const QString& filter = QString()); virtual int exec() override; private:
M src/platform/qt/GBAKeyEditor.cppsrc/platform/qt/GBAKeyEditor.cpp

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "GBAKeyEditor.h" #include <QComboBox> +#include <QHBoxLayout> #include <QPaintEvent> #include <QPainter> #include <QPushButton>

@@ -17,13 +18,14 @@

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) , m_profileSelect(nullptr) + , m_clear(nullptr) , m_type(type) , m_profile(profile) , m_controller(controller)

@@ -32,6 +34,7 @@ setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);

setMinimumSize(300, 300); const GBAInputMap* map = controller->map(); + controller->stealFocus(this); m_keyDU = new KeyEditor(this); m_keyDD = new KeyEditor(this);

@@ -64,30 +67,35 @@ m_profile = m_profileSelect->currentText();

m_controller->loadProfile(m_type, m_profile); refresh(); }); - } -#endif - connect(m_keyDU, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyDD, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyDL, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyDR, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keySelect, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyStart, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyA, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyB, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyL, SIGNAL(valueChanged(int)), this, SLOT(setNext())); - connect(m_keyR, SIGNAL(valueChanged(int)), this, SLOT(setNext())); + m_clear = new QWidget(this); + QHBoxLayout* layout = new QHBoxLayout; + m_clear->setLayout(layout); + layout->setSpacing(6); - connect(m_keyDU, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyDD, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyDL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyDR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keySelect, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyStart, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyA, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyB, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); - connect(m_keyR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + QPushButton* clearButton = new QPushButton(tr("Clear Button")); + layout->addWidget(clearButton); + connect(clearButton, &QAbstractButton::pressed, [this]() { + if (!findFocus()) { + return; + } + bool signalsBlocked = (*m_currentKey)->blockSignals(true); + (*m_currentKey)->clearButton(); + (*m_currentKey)->blockSignals(signalsBlocked); + }); + + QPushButton* clearAxis = new QPushButton(tr("Clear Analog")); + layout->addWidget(clearAxis); + connect(clearAxis, &QAbstractButton::pressed, [this]() { + if (!findFocus()) { + return; + } + bool signalsBlocked = (*m_currentKey)->blockSignals(true); + (*m_currentKey)->clearAxis(); + (*m_currentKey)->blockSignals(signalsBlocked); + }); + } +#endif m_buttons = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout;

@@ -115,6 +123,11 @@ m_keyL,

m_keyR }; + for (auto& key : m_keyOrder) { + connect(key, SIGNAL(valueChanged(int)), this, SLOT(setNext())); + connect(key, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + } + m_currentKey = m_keyOrder.end(); m_background.load(":/res/keymap.qpic");

@@ -135,13 +148,17 @@ setLocation(m_keyDL, DPAD_CENTER_X - DPAD_WIDTH, DPAD_CENTER_Y);

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); if (m_profileSelect) { - setLocation(m_profileSelect, 0.5, 0.7); + setLocation(m_profileSelect, 0.5, 0.67); + } + + if (m_clear) { + setLocation(m_clear, 0.5, 0.77); } }

@@ -151,6 +168,19 @@ painter.scale(width() / 480.0, height() / 480.0);

painter.drawPicture(0, 0, m_background); } +void GBAKeyEditor::closeEvent(QCloseEvent*) { + m_controller->releaseFocus(this); +} + +bool GBAKeyEditor::event(QEvent* event) { + if (event->type() == QEvent::WindowActivate) { + m_controller->stealFocus(this); + } else if (event->type() == QEvent::WindowDeactivate) { + m_controller->releaseFocus(this); + } + return QWidget::event(event); +} + void GBAKeyEditor::setNext() { findFocus();

@@ -167,6 +197,10 @@ }

} void GBAKeyEditor::save() { +#ifdef BUILD_SDL + m_controller->unbindAllAxes(m_type); +#endif + bindKey(m_keyDU, GBA_KEY_UP); bindKey(m_keyDD, GBA_KEY_DOWN); bindKey(m_keyDL, GBA_KEY_LEFT);

@@ -205,7 +239,7 @@ lookupBinding(map, m_keyR, GBA_KEY_R);

} void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) { - #ifdef BUILD_SDL +#ifdef BUILD_SDL if (m_type == SDL_BINDING_BUTTON) { int value = GBAInputQueryBinding(map, m_type, key); if (value != GBA_NO_MAPPING) {

@@ -213,7 +247,7 @@ keyEditor->setValueButton(value);

} return; } - #endif +#endif keyEditor->setValueKey(GBAInputQueryBinding(map, m_type, key)); }

@@ -239,14 +273,11 @@ #endif

void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { #ifdef BUILD_SDL - if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) { - m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key); - } else { -#endif - m_controller->bindKey(m_type, keyEditor->value(), key); -#ifdef BUILD_SDL + if (m_type == SDL_BINDING_BUTTON) { + m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key); } #endif + m_controller->bindKey(m_type, keyEditor->value(), key); } bool GBAKeyEditor::findFocus() {

@@ -304,5 +335,6 @@

void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) { QSize s = size(); QSize hint = widget->sizeHint(); - widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(), hint.height()); + widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(), + hint.height()); }
M src/platform/qt/GBAKeyEditor.hsrc/platform/qt/GBAKeyEditor.h

@@ -35,6 +35,8 @@

protected: virtual void resizeEvent(QResizeEvent*) override; virtual void paintEvent(QPaintEvent*) override; + virtual bool event(QEvent*) override; + virtual void closeEvent(QCloseEvent*) override; private slots: void setNext();

@@ -64,6 +66,7 @@

KeyEditor* keyById(GBAKey); QComboBox* m_profileSelect; + QWidget* m_clear; QWidget* m_buttons; KeyEditor* m_keyDU; KeyEditor* m_keyDD;
M src/platform/qt/GDBController.cppsrc/platform/qt/GDBController.cpp

@@ -44,7 +44,7 @@ if (m_gameController->isLoaded()) {

ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0); } else { QObject::disconnect(m_autoattach); - m_autoattach = connect(m_gameController, &GameController::gameStarted, [this] () { + m_autoattach = connect(m_gameController, &GameController::gameStarted, [this]() { QObject::disconnect(m_autoattach); ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0); });
M src/platform/qt/GDBWindow.cppsrc/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);

@@ -106,9 +106,8 @@ connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen()));

} void GDBWindow::failed() { - QMessageBox* failure = new QMessageBox(QMessageBox::Warning, tr("Crash"), - tr("Could not start GDB server"), - QMessageBox::Ok, this, Qt::Sheet); + QMessageBox* failure = new QMessageBox(QMessageBox::Warning, tr("Crash"), tr("Could not start GDB server"), + QMessageBox::Ok, this, Qt::Sheet); failure->setAttribute(Qt::WA_DeleteOnClose); failure->show(); }
M src/platform/qt/GDBWindow.hsrc/platform/qt/GDBWindow.h

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_GDB_WINDOW #define QGBA_GDB_WINDOW -#include <QWidget> +#include <QDialog> class QLineEdit; class QPushButton;

@@ -15,7 +15,7 @@ namespace QGBA {

class GDBController; -class GDBWindow : public QWidget { +class GDBWindow : public QDialog { Q_OBJECT public:
M src/platform/qt/GIFView.cppsrc/platform/qt/GIFView.cpp

@@ -8,6 +8,7 @@

#ifdef USE_MAGICK #include "GBAApp.h" +#include "LogController.h" #include <QMap>

@@ -33,7 +34,8 @@ stopRecording();

} void GIFView::startRecording() { - if (!ImageMagickGIFEncoderOpen(&m_encoder, m_filename.toLocal8Bit().constData())) { + if (!ImageMagickGIFEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) { + LOG(ERROR) << tr("Failed to open output GIF file: %1").arg(m_filename); return; } m_ui.start->setEnabled(false);
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -7,6 +7,7 @@ #include "GameController.h"

#include "AudioProcessor.h" #include "InputController.h" +#include "LogController.h" #include "MultiplayerController.h" #include "VFileDevice.h"

@@ -28,11 +29,10 @@

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)

@@ -48,9 +48,13 @@ , m_turbo(false)

, 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) + , m_backupLoadState(nullptr) + , m_backupSaveState(nullptr) { m_renderer = new GBAVideoSoftwareRenderer; GBAVideoSoftwareRendererCreate(m_renderer);

@@ -70,64 +74,84 @@ m_threadContext.cheats = &m_cheatDevice;

m_threadContext.logLevel = GBA_LOG_ALL; m_lux.p = this; - m_lux.sample = [] (GBALuminanceSource* context) { + m_lux.sample = [](GBALuminanceSource* context) { GameControllerLux* lux = static_cast<GameControllerLux*>(context); lux->value = 0xFF - lux->p->m_luxValue; }; - m_lux.readLuminance = [] (GBALuminanceSource* context) { + m_lux.readLuminance = [](GBALuminanceSource* context) { GameControllerLux* lux = static_cast<GameControllerLux*>(context); return lux->value; }; setLuminanceLevel(0); - m_rtc.p = this; - m_rtc.override = GameControllerRTC::NO_OVERRIDE; - m_rtc.sample = [] (GBARTCSource* context) { }; - m_rtc.unixTime = [] (GBARTCSource* context) -> time_t { - GameControllerRTC* rtc = static_cast<GameControllerRTC*>(context); - switch (rtc->override) { - case GameControllerRTC::NO_OVERRIDE: - default: - return time(nullptr); - case GameControllerRTC::FIXED: - return rtc->value; - case GameControllerRTC::FAKE_EPOCH: - return rtc->value + rtc->p->m_threadContext.gba->video.frameCounter * (int64_t) VIDEO_TOTAL_LENGTH / GBA_ARM7TDMI_FREQUENCY; + m_threadContext.startCallback = [](GBAThread* context) { + GameController* controller = static_cast<GameController*>(context->userData); + if (controller->m_audioProcessor) { + controller->m_audioProcessor->setInput(context); } - }; - - m_threadContext.startCallback = [] (GBAThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - controller->m_audioProcessor->setInput(context); context->gba->luminanceSource = &controller->m_lux; - context->gba->rtcSource = &controller->m_rtc; + 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; - controller->gameStarted(context); + + if (GBALoadState(context, context->stateDir, 0)) { + VFile* vf = GBAGetState(context->gba, context->stateDir, 0, true); + if (vf) { + vf->truncate(vf, 0); + } + } + QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(GBAThread*, context)); }; - m_threadContext.cleanCallback = [] (GBAThread* context) { + m_threadContext.cleanCallback = [](GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); - controller->gameStopped(context); + QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(GBAThread*, context)); }; - m_threadContext.frameCallback = [] (GBAThread* context) { + m_threadContext.frameCallback = [](GBAThread* context) { GameController* controller = static_cast<GameController*>(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); + QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(GBAThread*, context)); } - if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { - controller->frameAvailable(controller->m_drawContext); - } else { - controller->frameAvailable(nullptr); + }; + + m_threadContext.stopCallback = [](GBAThread* context) { + if (!context) { + return false; } + GameController* controller = static_cast<GameController*>(context->userData); + if (!GBASaveState(context, context->stateDir, 0, true)) { + return false; + } + QMetaObject::invokeMethod(controller, "closeGame"); + return true; }; - m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) { - static const char* stubMessage = "Stub software interrupt"; + m_threadContext.logHandler = [](GBAThread* context, enum GBALogLevel level, const char* format, va_list args) { + static const char* stubMessage = "Stub software interrupt: %02X"; + static const char* savestateMessage = "State %i loaded"; + static const char* savestateFailedMessage = "State %i failed to load"; if (!context) { return; }

@@ -136,7 +160,27 @@ if (level == GBA_LOG_STUB && strncmp(stubMessage, format, strlen(stubMessage)) == 0) {

va_list argc; va_copy(argc, args); int immediate = va_arg(argc, int); - controller->unimplementedBiosCall(immediate); + va_end(argc); + 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) { + va_list argc; + va_copy(argc, args); + int slot = va_arg(argc, int); + va_end(argc); + if (slot == 0) { + format = "Loaded suspend state"; + } + } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { + va_list argc; + va_copy(argc, args); + int slot = va_arg(argc, int); + va_end(argc); + if (slot == 0) { + return; + } + } } if (level == GBA_LOG_FATAL) { QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));

@@ -145,15 +189,15 @@ return;

} 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]() { GBARewind(&m_threadContext, 1); - emit rewound(&m_threadContext); emit frameAvailable(m_drawContext); + emit rewound(&m_threadContext); }); m_rewindTimer.setInterval(100);

@@ -176,6 +220,8 @@ closeGame();

GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; + delete[] m_frontBuffer; + delete m_backupLoadState; } void GameController::setMultiplayerController(MultiplayerController* controller) {

@@ -238,6 +284,7 @@ closeGame();

if (!dirmode) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { + postLog(GBA_LOG_ERROR, tr("Failed to open game file: %1").arg(path)); return; } file.close();

@@ -277,22 +324,12 @@ m_threadContext.bootBios = biosOnly;

if (biosOnly) { m_threadContext.fname = nullptr; } else { - m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); + m_threadContext.fname = strdup(m_fname.toUtf8().constData()); if (m_dirmode) { m_threadContext.gameDir = VDirOpen(m_threadContext.fname); m_threadContext.stateDir = m_threadContext.gameDir; } else { - m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); -#if USE_LIBZIP - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); - } -#endif -#if USE_LZMA - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); - } -#endif + GBAThreadLoadROM(&m_threadContext, m_threadContext.fname); } }

@@ -307,6 +344,7 @@ m_threadContext.patch = VFileDevice::open(m_patch, O_RDONLY);

} m_inputController->recalibrateAxes(); + memset(m_drawContext, 0xF8, 1024 * VIDEO_HORIZONTAL_PIXELS); if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false;

@@ -325,6 +363,27 @@ openGame();

} } +void GameController::yankPak() { + if (!m_gameOpen) { + return; + } + threadInterrupt(); + GBAYankROM(m_threadContext.gba); + threadContinue(); +} + +void GameController::replaceGame(const QString& path) { + if (!m_gameOpen) { + return; + } + + m_fname = path; + threadInterrupt(); + m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); + GBAThreadReplaceROM(&m_threadContext, m_threadContext.fname); + threadContinue(); +} + void GameController::loadPatch(const QString& path) { if (m_gameOpen) { closeGame();

@@ -341,6 +400,7 @@ return;

} VFile* vf = VFileDevice::open(path, O_RDONLY); if (!vf) { + postLog(GBA_LOG_ERROR, tr("Failed to open snapshot file for reading: %1").arg(path)); return; } threadInterrupt();

@@ -355,6 +415,7 @@ return;

} VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { + postLog(GBA_LOG_ERROR, tr("Failed to open snapshot file for writing: %1").arg(path)); return; } threadInterrupt();

@@ -409,8 +470,7 @@ if (!m_gameOpen || m_rewindTimer.isActive() || paused == GBAThreadIsPaused(&m_threadContext)) {

return; } if (paused) { - GBAThreadPause(&m_threadContext); - emit gamePaused(&m_threadContext); + m_pauseAfterFrame.testAndSetRelaxed(false, true); } else { GBAThreadUnpause(&m_threadContext); emit gameUnpaused(&m_threadContext);

@@ -466,8 +526,8 @@ } else {

GBARewind(&m_threadContext, states); } threadContinue(); - emit rewound(&m_threadContext); emit frameAvailable(m_drawContext); + emit rewound(&m_threadContext); } void GameController::startRewinding() {

@@ -475,7 +535,9 @@ if (!m_gameOpen || m_rewindTimer.isActive()) {

return; } m_wasPaused = isPaused(); - setPaused(true); + if (!GBAThreadIsPaused(&m_threadContext)) { + GBAThreadPause(&m_threadContext); + } m_rewindTimer.start(); }

@@ -484,7 +546,9 @@ if (!m_rewindTimer.isActive()) {

return; } m_rewindTimer.stop(); + bool signalsBlocked = blockSignals(true); setPaused(m_wasPaused); + blockSignals(signalsBlocked); } void GameController::keyPressed(int key) {

@@ -526,10 +590,67 @@ updateKeys();

} 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 (!rate) { + return; + } + 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) {

@@ -539,7 +660,9 @@ m_threadContext.fpsTarget = fps;

if (m_turbo && m_turboSpeed > 0) { m_threadContext.fpsTarget *= m_turboSpeed; } - redoSamples(m_audioProcessor->getBufferSamples()); + if (m_audioProcessor) { + redoSamples(m_audioProcessor->getBufferSamples()); + } threadContinue(); }

@@ -550,20 +673,31 @@ threadContinue();

} 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) { - if (slot > 0) { + if (slot > 0 && slot != m_stateSlot) { m_stateSlot = slot; + m_backupSaveState.clear(); } GBARunOnThread(&m_threadContext, [](GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); - GBALoadState(context, context->stateDir, controller->m_stateSlot); - controller->stateLoaded(context); - controller->frameAvailable(controller->m_drawContext); + if (!controller->m_backupLoadState) { + controller->m_backupLoadState = new GBASerializedState; + } + GBASerialize(context->gba, controller->m_backupLoadState); + if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) { + controller->frameAvailable(controller->m_drawContext); + controller->stateLoaded(context); + } }); }

@@ -573,10 +707,50 @@ m_stateSlot = slot;

} GBARunOnThread(&m_threadContext, [](GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); + VFile* vf = GBAGetState(context->gba, context->stateDir, controller->m_stateSlot, false); + if (vf) { + controller->m_backupSaveState.resize(vf->size(vf)); + vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); + vf->close(vf); + } GBASaveState(context, context->stateDir, controller->m_stateSlot, true); }); } +void GameController::loadBackupState() { + if (!m_backupLoadState) { + return; + } + + GBARunOnThread(&m_threadContext, [](GBAThread* context) { + GameController* controller = static_cast<GameController*>(context->userData); + if (GBADeserialize(context->gba, controller->m_backupLoadState)) { + GBALog(context->gba, GBA_LOG_STATUS, "Undid state load"); + controller->frameAvailable(controller->m_drawContext); + controller->stateLoaded(context); + } + delete controller->m_backupLoadState; + controller->m_backupLoadState = nullptr; + }); +} + +void GameController::saveBackupState() { + if (m_backupSaveState.isEmpty()) { + return; + } + + GBARunOnThread(&m_threadContext, [](GBAThread* context) { + GameController* controller = static_cast<GameController*>(context->userData); + VFile* vf = GBAGetState(context->gba, context->stateDir, controller->m_stateSlot, true); + if (vf) { + vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size()); + vf->close(vf); + GBALog(context->gba, GBA_LOG_STATUS, "Undid state save"); + } + controller->m_backupSaveState.clear(); + }); +} + void GameController::setVideoSync(bool set) { m_videoSync = set; if (!m_turbo) {

@@ -650,7 +824,9 @@ m_threadContext.fpsTarget = m_fpsTarget * m_turboSpeed;

m_threadContext.sync.audioWait = true; m_threadContext.sync.videoFrameWait = false; } - redoSamples(m_audioProcessor->getBufferSamples()); + if (m_audioProcessor) { + redoSamples(m_audioProcessor->getBufferSamples()); + } threadContinue(); }

@@ -679,11 +855,21 @@ }

#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()));

@@ -700,7 +886,7 @@ m_luxValue = value;

value = std::max<int>(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; }

@@ -712,22 +898,22 @@ 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); } void GameController::setRealTime() { - m_rtc.override = GameControllerRTC::NO_OVERRIDE; + m_rtc.override = GBARTCGenericSource::RTC_NO_OVERRIDE; } void GameController::setFixedTime(const QDateTime& time) { - m_rtc.override = GameControllerRTC::FIXED; + m_rtc.override = GBARTCGenericSource::RTC_FIXED; m_rtc.value = time.toMSecsSinceEpoch() / 1000; } void GameController::setFakeEpoch(const QDateTime& time) { - m_rtc.override = GameControllerRTC::FAKE_EPOCH; + m_rtc.override = GBARTCGenericSource::RTC_FAKE_EPOCH; m_rtc.value = time.toMSecsSinceEpoch() / 1000; }

@@ -745,7 +931,7 @@ float ratio;

if (m_threadContext.gba) { sampleRate = m_threadContext.gba->audio.sampleRate; } - ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, 44100); + ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, m_audioProcess->sampleRate()); m_threadContext.audioBuffers = ceil(samples / ratio); #else m_threadContext.audioBuffers = samples;
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -55,7 +55,7 @@ void threadInterrupt();

void threadContinue(); bool isPaused(); - bool isLoaded() { return m_gameOpen; } + bool isLoaded() { return m_gameOpen && GBAThreadIsActive(&m_threadContext); } bool audioSync() const { return m_audioSync; } bool videoSync() const { return m_videoSync; }

@@ -71,6 +71,8 @@ void setOverride(const GBACartridgeOverride& override);

void clearOverride() { m_threadContext.hasOverride = false; } void setOptions(const GBAOptions*); + + int stateSlot() const { return m_stateSlot; } #ifdef USE_GDB_STUB ARMDebugger* debugger();

@@ -97,6 +99,8 @@

public slots: void loadGame(const QString& path, bool dirmode = false); void loadBIOS(const QString& path); + void yankPak(); + void replaceGame(const QString& path); void setSkipBIOS(bool); void setUseBIOS(bool); void loadPatch(const QString& path);

@@ -115,9 +119,14 @@ void keyPressed(int key);

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); + void loadBackupState(); + void saveBackupState(); void setVideoSync(bool); void setAudioSync(bool); void setFrameskip(int);

@@ -159,6 +168,7 @@ void redoSamples(int samples);

void enableTurbo(); uint32_t* m_drawContext; + uint32_t* m_frontBuffer; GBAThread m_threadContext; GBAVideoSoftwareRenderer* m_renderer; GBACheatDevice m_cheatDevice;

@@ -188,8 +198,13 @@ bool m_turboForced;

float m_turboSpeed; QTimer m_rewindTimer; bool m_wasPaused; + + bool m_audioChannels[6]; + bool m_videoLayers[5]; int m_stateSlot; + GBASerializedState* m_backupLoadState; + QByteArray m_backupSaveState; InputController* m_inputController; MultiplayerController* m_multiplayer;

@@ -201,17 +216,7 @@ } m_lux;

uint8_t m_luxValue; int m_luxLevel; - static const int LUX_LEVELS[10]; - - struct GameControllerRTC : GBARTCSource { - GameController* p; - enum { - NO_OVERRIDE, - FIXED, - FAKE_EPOCH - } override; - int64_t value; - } m_rtc; + GBARTCGenericSource m_rtc; }; }
M src/platform/qt/GamepadAxisEvent.hsrc/platform/qt/GamepadAxisEvent.h

@@ -16,7 +16,7 @@ namespace QGBA {

class InputController; -class GamepadAxisEvent : public QEvent { +class GamepadAxisEvent : public QEvent { public: enum Direction { NEUTRAL = 0,
M src/platform/qt/GamepadButtonEvent.hsrc/platform/qt/GamepadButtonEvent.h

@@ -16,7 +16,7 @@ namespace QGBA {

class InputController; -class GamepadButtonEvent : public QEvent { +class GamepadButtonEvent : public QEvent { public: GamepadButtonEvent(Type pressType, int button, int type, InputController* controller = nullptr);
M src/platform/qt/InputController.cppsrc/platform/qt/InputController.cpp

@@ -8,6 +8,7 @@

#include "ConfigController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" +#include "InputProfile.h" #include <QApplication> #include <QTimer>

@@ -24,7 +25,7 @@ int InputController::s_sdlInited = 0;

GBASDLEvents InputController::s_sdlEvents; #endif -InputController::InputController(int playerId, QObject* parent) +InputController::InputController(int playerId, QWidget* topLevel, QObject* parent) : QObject(parent) , m_playerId(playerId) , m_config(nullptr)

@@ -33,6 +34,8 @@ #ifdef BUILD_SDL

, m_playerAttached(false) #endif , m_allowOpposing(false) + , m_topLevel(topLevel) + , m_focusParent(topLevel) { GBAInputMapInit(&m_inputMap);

@@ -106,8 +109,15 @@ #endif

} void InputController::loadProfile(uint32_t type, const QString& profile) { - GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData()); + bool loaded = GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData()); recalibrateAxes(); + if (!loaded) { + const InputProfile* ip = InputProfile::findProfile(profile); + if (ip) { + ip->apply(this); + } + } + emit profileLoaded(profile); } void InputController::saveConfiguration() {

@@ -128,7 +138,7 @@ m_config->write();

} void InputController::saveProfile(uint32_t type, const QString& profile) { - GBAInputProfileSave(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData()); + GBAInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData()); m_config->write(); }

@@ -193,14 +203,16 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device) {

if (!m_config) { return; } - GBAInputSetPreferredDevice(m_config->input(), type, m_playerId, device.toLocal8Bit().constData()); + GBAInputSetPreferredDevice(m_config->input(), type, m_playerId, device.toUtf8().constData()); } 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; }

@@ -403,6 +415,10 @@ }

GBAInputBindAxis(&m_inputMap, type, axis, &description); } +void InputController::unbindAllAxes(uint32_t type) { + GBAInputUnbindAllAxes(&m_inputMap, type); +} + void InputController::testGamepad(int type) { auto activeAxes = activeGamepadAxes(type); auto oldAxes = m_activeAxes;

@@ -424,7 +440,7 @@ bool newlyAboveThreshold = activeAxes.contains(axis);

if (newlyAboveThreshold) { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this); postPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); }

@@ -433,7 +449,7 @@ }

for (auto axis : oldAxes) { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this); clearPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); } if (!QApplication::focusWidget()) {

@@ -446,7 +462,7 @@

for (int button : activeButtons) { GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this); postPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); }

@@ -454,8 +470,21 @@ }

for (int button : oldButtons) { GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this); clearPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); + sendGamepadEvent(event); + } +} + +void InputController::sendGamepadEvent(QEvent* event) { + QWidget* focusWidget = nullptr; + if (m_focusParent) { + focusWidget = m_focusParent->focusWidget(); + if (!focusWidget) { + focusWidget = m_focusParent; + } + } else { + focusWidget = QApplication::focusWidget(); } + QApplication::sendEvent(focusWidget, event); } void InputController::postPendingEvent(GBAKey key) {

@@ -470,16 +499,36 @@ 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 } -#endif + +void InputController::stealFocus(QWidget* focus) { + m_focusParent = focus; +} + +void InputController::releaseFocus(QWidget* focus) { + if (focus == m_focusParent) { + m_focusParent = m_topLevel; + } +}
M src/platform/qt/InputController.hsrc/platform/qt/InputController.h

@@ -32,7 +32,7 @@

public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(int playerId = 0, QObject* parent = nullptr); + InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config);

@@ -60,6 +60,7 @@ QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes(int type);

void recalibrateAxes(); void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); + void unbindAllAxes(uint32_t type); QStringList connectedGamepads(uint32_t type) const; int gamepad(uint32_t type) const;

@@ -74,28 +75,35 @@

float gyroSensitivity() const; void setGyroSensitivity(float sensitivity); + void stealFocus(QWidget* focus); + void releaseFocus(QWidget* focus); + GBARumble* rumble(); GBARotationSource* rotationSource(); +signals: + void profileLoaded(const QString& profile); + 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); void clearPendingEvent(GBAKey); bool hasPendingEvent(GBAKey) const; + void sendGamepadEvent(QEvent*); GBAInputMap m_inputMap; ConfigController* m_config; int m_playerId; bool m_allowOpposing; + QWidget* m_topLevel; + QWidget* m_focusParent; #ifdef BUILD_SDL static int s_sdlInited;
A src/platform/qt/InputProfile.cpp

@@ -0,0 +1,222 @@

+/* 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 "InputProfile.h" + +#include "InputController.h" + +#include <QRegExp> + +using namespace QGBA; + +const InputProfile InputProfile::s_defaultMaps[] = { + { + "XInput Controller #\\d+", // XInput (Windows) + (int[GBA_KEY_MAX]) { + /*keyA */ 11, + /*keyB */ 10, + /*keySelect */ 5, + /*keyStart */ 4, + /*keyRight */ 3, + /*keyLeft */ 2, + /*keyUp */ 0, + /*keyDown */ 1, + /*keyR */ 9, + /*keyL */ 8 + }, + (ShortcutButton[]) { + {"loadState", 12}, + {"saveState", 13}, + {} + }, + (ShortcutAxis[]) { + {"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5}, + {"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 4}, + {} + } + }, + { + "(Microsoft X-Box 360 pad|Xbox Gamepad \\(userspace driver\\))", // Linux + (int[GBA_KEY_MAX]) { + /*keyA */ 1, + /*keyB */ 0, + /*keySelect */ 6, + /*keyStart */ 7, + /*keyRight */ -1, + /*keyLeft */ -1, + /*keyUp */ -1, + /*keyDown */ -1, + /*keyR */ 5, + /*keyL */ 4 + }, + (ShortcutButton[]) { + {"loadState", 2}, + {"saveState", 3}, + {} + }, + (ShortcutAxis[]) { + {"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5}, + {"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 2}, + {} + } + }, + { + "Controller", // The Xbox 360 controller drivers on OS X are vague... + (int[GBA_KEY_MAX]) { + /*keyA */ 1, + /*keyB */ 0, + /*keySelect */ 9, + /*keyStart */ 8, + /*keyRight */ 14, + /*keyLeft */ 13, + /*keyUp */ 11, + /*keyDown */ 12, + /*keyR */ 5, + /*keyL */ 4 + }, + (ShortcutButton[]) { + {"loadState", 2}, + {"saveState", 3}, + {} + }, + (ShortcutAxis[]) { + {"holdFastForward", GamepadAxisEvent::Direction::POSITIVE, 5}, + {"holdRewind", GamepadAxisEvent::Direction::POSITIVE, 2}, + {} + } + }, + { + "PLAYSTATION\\(R\\)3 Controller", // DualShock 3 (OS X) + (int[GBA_KEY_MAX]) { + /*keyA */ 13, + /*keyB */ 14, + /*keySelect */ 0, + /*keyStart */ 3, + /*keyRight */ 5, + /*keyLeft */ 7, + /*keyUp */ 4, + /*keyDown */ 6, + /*keyR */ 11, + /*keyL */ 10 + }, + (ShortcutButton[]) { + {"loadState", 15}, + {"saveState", 12}, + {"holdFastForward", 9}, + {"holdRewind", 8}, + {} + } + }, + { + "Wiimote \\(..-..-..-..-..-..\\)", // WJoy (OS X) + (int[GBA_KEY_MAX]) { + /*keyA */ 15, + /*keyB */ 16, + /*keySelect */ 7, + /*keyStart */ 6, + /*keyRight */ 14, + /*keyLeft */ 13, + /*keyUp */ 11, + /*keyDown */ 12, + /*keyR */ 20, + /*keyL */ 19 + }, + (ShortcutButton[]) { + {"loadState", 18}, + {"saveState", 17}, + {"holdFastForward", 22}, + {"holdRewind", 21}, + {} + } + }, +}; + +constexpr InputProfile::InputProfile(const char* name, + int keys[GBA_KEY_MAX], + const ShortcutButton* shortcutButtons, + const ShortcutAxis* shortcutAxes, + AxisValue axes[GBA_KEY_MAX], + const struct Coord& tiltAxis, + const struct Coord& gyroAxis, + float gyroSensitivity) + : m_profileName(name) + , m_keys { + keys[GBA_KEY_A], + keys[GBA_KEY_B], + keys[GBA_KEY_SELECT], + keys[GBA_KEY_START], + keys[GBA_KEY_RIGHT], + keys[GBA_KEY_LEFT], + keys[GBA_KEY_UP], + keys[GBA_KEY_DOWN], + keys[GBA_KEY_R], + keys[GBA_KEY_L] + } + , m_shortcutButtons(shortcutButtons) + , m_shortcutAxes(shortcutAxes) + , m_axes { + axes[GBA_KEY_A], + axes[GBA_KEY_B], + axes[GBA_KEY_SELECT], + axes[GBA_KEY_START], + axes[GBA_KEY_RIGHT], + axes[GBA_KEY_LEFT], + axes[GBA_KEY_UP], + axes[GBA_KEY_DOWN], + axes[GBA_KEY_R], + axes[GBA_KEY_L] + } + , m_tiltAxis(tiltAxis) + , m_gyroAxis(gyroAxis) + , m_gyroSensitivity(gyroSensitivity) +{ +} + +const InputProfile* InputProfile::findProfile(const QString& name) { + for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) { + QRegExp re(s_defaultMaps[i].m_profileName); + if (re.exactMatch(name)) { + return &s_defaultMaps[i]; + } + } + return nullptr; +} + +void InputProfile::apply(InputController* controller) const { + for (size_t i = 0; i < GBA_KEY_MAX; ++i) { +#ifdef BUILD_SDL + controller->bindKey(SDL_BINDING_BUTTON, m_keys[i], static_cast<GBAKey>(i)); + controller->bindAxis(SDL_BINDING_BUTTON, m_axes[i].axis, m_axes[i].direction, static_cast<GBAKey>(i)); +#endif + } + controller->registerTiltAxisX(m_tiltAxis.x); + controller->registerTiltAxisY(m_tiltAxis.y); + controller->registerGyroAxisX(m_gyroAxis.x); + controller->registerGyroAxisY(m_gyroAxis.y); + controller->setGyroSensitivity(m_gyroSensitivity); +} + +bool InputProfile::lookupShortcutButton(const QString& shortcutName, int* button) const { + for (size_t i = 0; m_shortcutButtons[i].shortcut; ++i) { + const ShortcutButton& shortcut = m_shortcutButtons[i]; + if (QLatin1String(shortcut.shortcut) == shortcutName) { + *button = shortcut.button; + return true; + } + } + return false; +} + +bool InputProfile::lookupShortcutAxis(const QString& shortcutName, int* axis, GamepadAxisEvent::Direction* direction) const { + for (size_t i = 0; m_shortcutAxes[i].shortcut; ++i) { + const ShortcutAxis& shortcut = m_shortcutAxes[i]; + if (QLatin1String(shortcut.shortcut) == shortcutName) { + *axis = shortcut.axis; + *direction = shortcut.direction; + return true; + } + } + return false; +}
A src/platform/qt/InputProfile.h

@@ -0,0 +1,78 @@

+/* 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 QGBA_INPUT_PROFILE +#define QGBA_INPUT_PROFILE + +#include "GamepadAxisEvent.h" + +extern "C" { +#include "gba/interface.h" +} + +namespace QGBA { + +class InputController; + +class InputProfile { +public: + static const InputProfile* findProfile(const QString& name); + + void apply(InputController*) const; + bool lookupShortcutButton(const QString& shortcut, int* button) const; + bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const; + +private: + struct Coord { + int x; + int y; + }; + + struct AxisValue { + GamepadAxisEvent::Direction direction; + int axis; + }; + + struct ShortcutButton { + const char* shortcut; + int button; + }; + + struct ShortcutAxis { + const char* shortcut; + GamepadAxisEvent::Direction direction; + int axis; + }; + + constexpr InputProfile(const char* name, + int keys[GBA_KEY_MAX], + const ShortcutButton* shortcutButtons = (ShortcutButton[]) {{}}, + const ShortcutAxis* shortcutAxes = (ShortcutAxis[]) {{}}, + AxisValue axes[GBA_KEY_MAX] = (AxisValue[GBA_KEY_MAX]) { + {}, {}, {}, {}, + { GamepadAxisEvent::Direction::POSITIVE, 0 }, + { GamepadAxisEvent::Direction::NEGATIVE, 0 }, + { GamepadAxisEvent::Direction::NEGATIVE, 1 }, + { GamepadAxisEvent::Direction::POSITIVE, 1 }, + {}, {}}, + const struct Coord& tiltAxis = { 2, 3 }, + const struct Coord& gyroAxis = { 0, 1 }, + float gyroSensitivity = 2e+09f); + + static const InputProfile s_defaultMaps[]; + + const char* m_profileName; + const int m_keys[GBA_KEY_MAX]; + const AxisValue m_axes[GBA_KEY_MAX]; + const ShortcutButton* m_shortcutButtons; + const ShortcutAxis* m_shortcutAxes; + Coord m_tiltAxis; + Coord m_gyroAxis; + float m_gyroSensitivity; +}; + +} + +#endif
M src/platform/qt/KeyEditor.cppsrc/platform/qt/KeyEditor.cpp

@@ -15,22 +15,20 @@

KeyEditor::KeyEditor(QWidget* parent) : QLineEdit(parent) , m_direction(GamepadAxisEvent::NEUTRAL) + , m_key(-1) + , m_axis(-1) , m_button(false) { setAlignment(Qt::AlignCenter); } void KeyEditor::setValue(int key) { + m_key = key; if (m_button) { - if (key < 0) { - clear(); - } else { - setText(QString::number(key)); - } + updateButtonText(); } else { setText(QKeySequence(key).toString(QKeySequence::NativeText)); } - m_key = key; emit valueChanged(key); }

@@ -41,18 +39,30 @@ }

void KeyEditor::setValueButton(int button) { m_button = true; - m_direction = GamepadAxisEvent::NEUTRAL; setValue(button); } void KeyEditor::setValueAxis(int axis, int32_t value) { m_button = true; - m_key = axis; + m_axis = axis; m_direction = value < 0 ? GamepadAxisEvent::NEGATIVE : GamepadAxisEvent::POSITIVE; - setText((value < 0 ? "-" : "+") + QString::number(axis)); + updateButtonText(); emit axisChanged(axis, m_direction); } +void KeyEditor::clearButton() { + m_button = true; + setValue(-1); +} + +void KeyEditor::clearAxis() { + m_button = true; + m_axis = -1; + m_direction = GamepadAxisEvent::NEUTRAL; + updateButtonText(); + emit axisChanged(m_axis, m_direction); +} + QSize KeyEditor::sizeHint() const { QSize hint = QLineEdit::sizeHint(); hint.setWidth(40);

@@ -85,3 +95,18 @@ return true;

} return QWidget::event(event); } + +void KeyEditor::updateButtonText() { + QStringList text; + if (m_key >= 0) { + text.append(QString::number(m_key)); + } + if (m_direction != GamepadAxisEvent::NEUTRAL) { + text.append((m_direction == GamepadAxisEvent::NEGATIVE ? "-" : "+") + QString::number(m_axis)); + } + if (text.isEmpty()) { + setText(tr("---")); + } else { + setText(text.join("/")); + } +}
M src/platform/qt/KeyEditor.hsrc/platform/qt/KeyEditor.h

@@ -20,6 +20,7 @@

int value() const { return m_key; } GamepadAxisEvent::Direction direction() const { return m_direction; } + int axis() const { return m_axis; } virtual QSize sizeHint() const override;

@@ -28,6 +29,8 @@ void setValue(int key);

void setValueKey(int key); void setValueButton(int button); void setValueAxis(int axis, int32_t value); + void clearButton(); + void clearAxis(); signals: void valueChanged(int key);

@@ -38,7 +41,10 @@ virtual void keyPressEvent(QKeyEvent* event) override;

virtual bool event(QEvent* event) override; private: + void updateButtonText(); + int m_key; + int m_axis; bool m_button; GamepadAxisEvent::Direction m_direction; };
M src/platform/qt/LoadSaveState.cppsrc/platform/qt/LoadSaveState.cpp

@@ -23,9 +23,10 @@

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;

@@ -43,6 +44,13 @@ for (i = 0; i < NUM_SLOTS; ++i) {

loadState(i + 1); m_slots[i]->installEventFilter(this); 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);

@@ -201,6 +209,5 @@

void LoadSaveState::paintEvent(QPaintEvent*) { QPainter painter(this); QRect full(QPoint(), size()); - painter.drawPixmap(full, m_currentImage); painter.fillRect(full, QColor(0, 0, 0, 128)); }
A src/platform/qt/LogController.cpp

@@ -0,0 +1,93 @@

+/* 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 "LogController.h" + +using namespace QGBA; + +LogController LogController::s_global(GBA_LOG_ALL); + +LogController::LogController(int levels, QObject* parent) + : QObject(parent) + , m_logLevel(levels) +{ + if (this != &s_global) { + connect(&s_global, SIGNAL(logPosted(int, const QString&)), this, SLOT(postLog(int, const QString&))); + connect(this, SIGNAL(levelsSet(int)), &s_global, SLOT(setLevels(int))); + connect(this, SIGNAL(levelsEnabled(int)), &s_global, SLOT(enableLevels(int))); + connect(this, SIGNAL(levelsDisabled(int)), &s_global, SLOT(disableLevels(int))); + } +} + +LogController::Stream LogController::operator()(int level) { + return Stream(this, level); +} + +void LogController::postLog(int level, const QString& string) { + if (!(m_logLevel & level)) { + return; + } + emit logPosted(level, string); +} + +void LogController::setLevels(int levels) { + m_logLevel = levels; + emit levelsSet(levels); +} + +void LogController::enableLevels(int levels) { + m_logLevel |= levels; + emit levelsEnabled(levels); +} + +void LogController::disableLevels(int levels) { + m_logLevel &= ~levels; + emit levelsDisabled(levels); +} + +LogController* LogController::global() { + return &s_global; +} + +QString LogController::toString(int level) { + switch (level) { + case GBA_LOG_DEBUG: + return tr("DEBUG"); + case GBA_LOG_STUB: + return tr("STUB"); + case GBA_LOG_INFO: + return tr("INFO"); + case GBA_LOG_WARN: + return tr("WARN"); + case GBA_LOG_ERROR: + return tr("ERROR"); + case GBA_LOG_FATAL: + return tr("FATAL"); + case GBA_LOG_GAME_ERROR: + return tr("GAME ERROR"); + case GBA_LOG_SWI: + return tr("SWI"); + case GBA_LOG_STATUS: + return tr("STATUS"); + case GBA_LOG_SIO: + return tr("SIO"); + } + return QString(); +} + +LogController::Stream::Stream(LogController* controller, int level) + : m_log(controller) + , m_level(level) +{ +} + +LogController::Stream::~Stream() { + m_log->postLog(m_level, m_queue.join(" ")); +} + +LogController::Stream& LogController::Stream::operator<<(const QString& string) { + m_queue.append(string); + return *this; +}
A src/platform/qt/LogController.h

@@ -0,0 +1,68 @@

+/* 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 QGBA_LOG_CONTROLLER +#define QGBA_LOG_CONTROLLER + +#include <QObject> +#include <QStringList> + +extern "C" { +#include "gba/gba.h" +} + +namespace QGBA { + +class LogController : public QObject { +Q_OBJECT + +private: + class Stream { + public: + Stream(LogController* controller, int level); + ~Stream(); + + Stream& operator<<(const QString&); + + private: + int m_level; + LogController* m_log; + + QStringList m_queue; + }; + +public: + LogController(int levels, QObject* parent = nullptr); + + int levels() const { return m_logLevel; } + + Stream operator()(int level); + + static LogController* global(); + static QString toString(int level); + +signals: + void logPosted(int level, const QString& log); + void levelsSet(int levels); + void levelsEnabled(int levels); + void levelsDisabled(int levels); + +public slots: + void postLog(int level, const QString& string); + void setLevels(int levels); + void enableLevels(int levels); + void disableLevels(int levels); + +private: + int m_logLevel; + + static LogController s_global; +}; + +#define LOG(L) (*LogController::global())(GBA_LOG_ ## L) + +} + +#endif
M src/platform/qt/LogView.cppsrc/platform/qt/LogView.cpp

@@ -5,38 +5,71 @@ * 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 "LogView.h" +#include "LogController.h" + #include <QTextBlock> #include <QTextCursor> using namespace QGBA; -LogView::LogView(QWidget* parent) +LogView::LogView(LogController* log, QWidget* parent) : QWidget(parent) - , m_logLevel(0) , m_lines(0) , m_lineLimit(DEFAULT_LINE_LIMIT) { m_ui.setupUi(this); - connect(m_ui.levelDebug, SIGNAL(toggled(bool)), this, SLOT(setLevelDebug(bool))); - connect(m_ui.levelStub, SIGNAL(toggled(bool)), this, SLOT(setLevelStub(bool))); - connect(m_ui.levelInfo, SIGNAL(toggled(bool)), this, SLOT(setLevelInfo(bool))); - connect(m_ui.levelWarn, SIGNAL(toggled(bool)), this, SLOT(setLevelWarn(bool))); - connect(m_ui.levelError, SIGNAL(toggled(bool)), this, SLOT(setLevelError(bool))); - connect(m_ui.levelFatal, SIGNAL(toggled(bool)), this, SLOT(setLevelFatal(bool))); - connect(m_ui.levelGameError, SIGNAL(toggled(bool)), this, SLOT(setLevelGameError(bool))); - connect(m_ui.levelSWI, SIGNAL(toggled(bool)), this, SLOT(setLevelSWI(bool))); - connect(m_ui.levelStatus, SIGNAL(toggled(bool)), this, SLOT(setLevelStatus(bool))); - connect(m_ui.levelSIO, SIGNAL(toggled(bool)), this, SLOT(setLevelSIO(bool))); + connect(m_ui.levelDebug, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_DEBUG, set); + }); + connect(m_ui.levelStub, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_STUB, set); + }); + connect(m_ui.levelInfo, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_INFO, set); + }); + connect(m_ui.levelWarn, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_WARN, set); + }); + connect(m_ui.levelError, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_ERROR, set); + }); + connect(m_ui.levelFatal, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_FATAL, set); + }); + connect(m_ui.levelGameError, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_GAME_ERROR, set); + }); + connect(m_ui.levelSWI, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_SWI, set); + }); + connect(m_ui.levelStatus, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_STATUS, set); + }); + connect(m_ui.levelSIO, &QAbstractButton::toggled, [this](bool set) { + setLevel(GBA_LOG_SIO, set); + }); connect(m_ui.clear, SIGNAL(clicked()), this, SLOT(clear())); connect(m_ui.maxLines, SIGNAL(valueChanged(int)), this, SLOT(setMaxLines(int))); m_ui.maxLines->setValue(DEFAULT_LINE_LIMIT); + + connect(log, SIGNAL(logPosted(int, const QString&)), this, SLOT(postLog(int, const QString&))); + connect(log, SIGNAL(levelsSet(int)), this, SLOT(setLevels(int))); + connect(log, &LogController::levelsEnabled, [this](int level) { + bool s = blockSignals(true); + setLevel(level, true); + blockSignals(s); + }); + connect(log, &LogController::levelsDisabled, [this](int level) { + bool s = blockSignals(true); + setLevel(level, false); + blockSignals(s); + }); + connect(this, SIGNAL(levelsEnabled(int)), log, SLOT(enableLevels(int))); + connect(this, SIGNAL(levelsDisabled(int)), log, SLOT(disableLevels(int))); } void LogView::postLog(int level, const QString& log) { - if (!(level & m_logLevel)) { - return; - } - m_ui.view->appendPlainText(QString("%1:\t%2").arg(toString(level)).arg(log)); + m_ui.view->appendPlainText(QString("%1:\t%2").arg(LogController::toString(level)).arg(log)); ++m_lines; if (m_lines > m_lineLimit) { clearLine();

@@ -49,8 +82,6 @@ m_lines = 0;

} void LogView::setLevels(int levels) { - m_logLevel = levels; - m_ui.levelDebug->setCheckState(levels & GBA_LOG_DEBUG ? Qt::Checked : Qt::Unchecked); m_ui.levelStub->setCheckState(levels & GBA_LOG_STUB ? Qt::Checked : Qt::Unchecked); m_ui.levelInfo->setCheckState(levels & GBA_LOG_INFO ? Qt::Checked : Qt::Unchecked);

@@ -61,87 +92,44 @@ m_ui.levelGameError->setCheckState(levels & GBA_LOG_GAME_ERROR ? Qt::Checked : Qt::Unchecked);

m_ui.levelSWI->setCheckState(levels & GBA_LOG_SWI ? Qt::Checked : Qt::Unchecked); m_ui.levelStatus->setCheckState(levels & GBA_LOG_STATUS ? Qt::Checked : Qt::Unchecked); m_ui.levelSIO->setCheckState(levels & GBA_LOG_SIO ? Qt::Checked : Qt::Unchecked); - - emit levelsSet(levels); } -void LogView::setLevelDebug(bool set) { - if (set) { - setLevel(GBA_LOG_DEBUG); - } else { - clearLevel(GBA_LOG_DEBUG); +void LogView::setLevel(int level, bool set) { + if (level & GBA_LOG_DEBUG) { + m_ui.levelDebug->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelStub(bool set) { - if (set) { - setLevel(GBA_LOG_STUB); - } else { - clearLevel(GBA_LOG_STUB); + if (level & GBA_LOG_STUB) { + m_ui.levelStub->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelInfo(bool set) { - if (set) { - setLevel(GBA_LOG_INFO); - } else { - clearLevel(GBA_LOG_INFO); + if (level & GBA_LOG_INFO) { + m_ui.levelInfo->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelWarn(bool set) { - if (set) { - setLevel(GBA_LOG_WARN); - } else { - clearLevel(GBA_LOG_WARN); + if (level & GBA_LOG_WARN) { + m_ui.levelWarn->setCheckState(set ? Qt::Checked : Qt::Unchecked); + } + if (level & GBA_LOG_ERROR) { + m_ui.levelError->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelError(bool set) { - if (set) { - setLevel(GBA_LOG_ERROR); - } else { - clearLevel(GBA_LOG_ERROR); + if (level & GBA_LOG_FATAL) { + m_ui.levelFatal->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelFatal(bool set) { - if (set) { - setLevel(GBA_LOG_FATAL); - } else { - clearLevel(GBA_LOG_FATAL); + if (level & GBA_LOG_GAME_ERROR) { + m_ui.levelGameError->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelGameError(bool set) { - if (set) { - setLevel(GBA_LOG_GAME_ERROR); - } else { - clearLevel(GBA_LOG_GAME_ERROR); + if (level & GBA_LOG_SWI) { + m_ui.levelSWI->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelSWI(bool set) { - if (set) { - setLevel(GBA_LOG_SWI); - } else { - clearLevel(GBA_LOG_SWI); + if (level & GBA_LOG_STATUS) { + m_ui.levelStatus->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} - -void LogView::setLevelStatus(bool set) { - if (set) { - setLevel(GBA_LOG_STATUS); - } else { - clearLevel(GBA_LOG_STATUS); + if (level & GBA_LOG_SIO) { + m_ui.levelSIO->setCheckState(set ? Qt::Checked : Qt::Unchecked); } -} -void LogView::setLevelSIO(bool set) { if (set) { - setLevel(GBA_LOG_SIO); + emit levelsEnabled(level); } else { - clearLevel(GBA_LOG_SIO); + emit levelsDisabled(level); } }

@@ -150,42 +138,6 @@ m_lineLimit = limit;

while (m_lines > m_lineLimit) { clearLine(); } -} - -QString LogView::toString(int level) { - switch (level) { - case GBA_LOG_DEBUG: - return tr("DEBUG"); - case GBA_LOG_STUB: - return tr("STUB"); - case GBA_LOG_INFO: - return tr("INFO"); - case GBA_LOG_WARN: - return tr("WARN"); - case GBA_LOG_ERROR: - return tr("ERROR"); - case GBA_LOG_FATAL: - return tr("FATAL"); - case GBA_LOG_GAME_ERROR: - return tr("GAME ERROR"); - case GBA_LOG_SWI: - return tr("SWI"); - case GBA_LOG_STATUS: - return tr("STATUS"); - case GBA_LOG_SIO: - return tr("SIO"); - } - return QString(); -} - -void LogView::setLevel(int level) { - m_logLevel |= level; - emit levelsEnabled(level); -} - -void LogView::clearLevel(int level) { - m_logLevel &= ~level; - emit levelsDisabled(level); } void LogView::clearLine() {
M src/platform/qt/LogView.hsrc/platform/qt/LogView.h

@@ -16,14 +16,15 @@ }

namespace QGBA { +class LogController; + class LogView : public QWidget { Q_OBJECT public: - LogView(QWidget* parent = nullptr); + LogView(LogController* log, QWidget* parent = nullptr); signals: - void levelsSet(int levels); void levelsEnabled(int levels); void levelsDisabled(int levels);

@@ -32,30 +33,17 @@ void postLog(int level, const QString& log);

void setLevels(int levels); void clear(); - void setLevelDebug(bool); - void setLevelStub(bool); - void setLevelInfo(bool); - void setLevelWarn(bool); - void setLevelError(bool); - void setLevelFatal(bool); - void setLevelGameError(bool); - void setLevelSWI(bool); - void setLevelStatus(bool); - void setLevelSIO(bool); - +private slots: void setMaxLines(int); private: static const int DEFAULT_LINE_LIMIT = 1000; Ui::LogView m_ui; - int m_logLevel; int m_lines; int m_lineLimit; - static QString toString(int level); - void setLevel(int level); - void clearLevel(int level); + void setLevel(int level, bool); void clearLine(); };
M src/platform/qt/MemoryModel.cppsrc/platform/qt/MemoryModel.cpp

@@ -7,6 +7,7 @@ #include "MemoryModel.h"

#include "GBAApp.h" #include "GameController.h" +#include "LogController.h" #include <QAction> #include <QApplication>

@@ -153,7 +154,7 @@ return;

} QFile outfile(filename); if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - // TODO: Log + LOG(WARN) << tr("Failed to open output file: %1").arg(filename); return; } QDataStream stream(&outfile);

@@ -195,9 +196,12 @@ static QChar c0('0');

static QString arg("%0"); QSizeF letterSize = QSizeF(m_letterWidth, m_cellHeight); painter.drawStaticText(QPointF((m_margins.left() - m_regionName.size().width() - 1) / 2.0, 0), m_regionName); - painter.drawText(QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())), Qt::AlignHCenter, tr("ASCII")); + painter.drawText( + QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())), + Qt::AlignHCenter, tr("ASCII")); for (int x = 0; x < 16; ++x) { - painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter, QString::number(x, 16).toUpper()); + painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter, + QString::number(x, 16).toUpper()); } int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight; for (int y = 0; y < height; ++y) {

@@ -209,38 +213,56 @@ case 2:

for (int x = 0; x < 16; x += 2) { uint32_t address = (y + m_top) * 16 + x + m_base; if (isInSelection(address)) { - painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 2, m_cellSize.height())), palette.highlight()); + painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), + QSizeF(m_cellSize.width() * 2, m_cellSize.height())), + palette.highlight()); painter.setPen(palette.color(QPalette::HighlightedText)); if (isEditing(address)) { - drawEditingText(painter, QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp)); + drawEditingText( + painter, + QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp)); continue; } } else { painter.setPen(palette.color(QPalette::WindowText)); } uint16_t b = m_cpu->memory.load16(m_cpu, address, nullptr); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp), m_staticNumbers[b & 0xFF]); + painter.drawStaticText( + QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp), + m_staticNumbers[(b >> 8) & 0xFF]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp), + m_staticNumbers[b & 0xFF]); } break; case 4: for (int x = 0; x < 16; x += 4) { uint32_t address = (y + m_top) * 16 + x + m_base; if (isInSelection(address)) { - painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), QSizeF(m_cellSize.width() * 4, m_cellSize.height())), palette.highlight()); + painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), + QSizeF(m_cellSize.width() * 4, m_cellSize.height())), + palette.highlight()); painter.setPen(palette.color(QPalette::HighlightedText)); if (isEditing(address)) { - drawEditingText(painter, QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp)); + drawEditingText( + painter, + QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp)); continue; } } else { painter.setPen(palette.color(QPalette::WindowText)); } uint32_t b = m_cpu->memory.load32(m_cpu, address, nullptr); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 24) & 0xFF]); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[(b >> 16) & 0xFF]); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp), m_staticNumbers[(b >> 8) & 0xFF]); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp), m_staticNumbers[b & 0xFF]); + painter.drawStaticText( + QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp), + m_staticNumbers[(b >> 24) & 0xFF]); + painter.drawStaticText( + QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp), + m_staticNumbers[(b >> 16) & 0xFF]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp), + m_staticNumbers[(b >> 8) & 0xFF]); + painter.drawStaticText( + QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp), + m_staticNumbers[b & 0xFF]); } break; case 1:

@@ -248,28 +270,34 @@ default:

for (int x = 0; x < 16; ++x) { uint32_t address = (y + m_top) * 16 + x + m_base; if (isInSelection(address)) { - painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize), palette.highlight()); + painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize), + palette.highlight()); painter.setPen(palette.color(QPalette::HighlightedText)); if (isEditing(address)) { - drawEditingText(painter, QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp)); + drawEditingText(painter, + QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp)); continue; } } else { painter.setPen(palette.color(QPalette::WindowText)); } uint8_t b = m_cpu->memory.load8(m_cpu, address, nullptr); - painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp), m_staticNumbers[b]); + painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp), + m_staticNumbers[b]); } break; } painter.setPen(palette.color(QPalette::WindowText)); for (int x = 0; x < 16; ++x) { uint8_t b = m_cpu->memory.load8(m_cpu, (y + m_top) * 16 + x + m_base, nullptr); - painter.drawStaticText(QPointF(viewport()->size().width() - (16 - x) * m_margins.right() / 17.0 - m_letterWidth * 0.5, yp), b < 0x80 ? m_staticAscii[b] : m_staticAscii[0]); + painter.drawStaticText( + QPointF(viewport()->size().width() - (16 - x) * m_margins.right() / 17.0 - m_letterWidth * 0.5, yp), + b < 0x80 ? m_staticAscii[b] : m_staticAscii[0]); } } painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height()); - painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(), viewport()->size().height()); + painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(), + viewport()->size().height()); painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top()); }

@@ -282,18 +310,28 @@ update();

} void MemoryModel::mousePressEvent(QMouseEvent* event) { - if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) { + if (event->x() < m_margins.left() || event->y() < m_margins.top() || + event->x() > size().width() - m_margins.right()) { m_selection = qMakePair(0, 0); return; } QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top())); - uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base; + uint32_t address = int(position.x() / m_cellSize.width()) + + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base; if (event->button() == Qt::RightButton && isInSelection(address)) { return; } - m_selectionAnchor = address & ~(m_align - 1); - m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align); + if (event->modifiers() & Qt::ShiftModifier) { + if ((address & ~(m_align - 1)) < m_selectionAnchor) { + m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align); + } else { + m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align); + } + } else { + m_selectionAnchor = address & ~(m_align - 1); + m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align); + } m_buffer = 0; m_bufferedNybbles = 0; emit selectionChanged(m_selection.first, m_selection.second);

@@ -301,12 +339,14 @@ viewport()->update();

} void MemoryModel::mouseMoveEvent(QMouseEvent* event) { - if (event->x() < m_margins.left() || event->y() < m_margins.top() || event->x() > size().width() - m_margins.right()) { + if (event->x() < m_margins.left() || event->y() < m_margins.top() || + event->x() > size().width() - m_margins.right()) { return; } QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top())); - uint32_t address = int(position.x() / m_cellSize.width()) + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base; + uint32_t address = int(position.x() / m_cellSize.width()) + + (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base; if ((address & ~(m_align - 1)) < m_selectionAnchor) { m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align); } else {

@@ -419,7 +459,11 @@ uint8_t b = m_buffer >> ((nybbles - 2) * 4);

painter.drawStaticText(o, m_staticNumbers[b]); } else { int b = m_buffer & 0xF; - painter.drawStaticText(o, m_staticAscii[b + '0']); + if (b < 10) { + painter.drawStaticText(o, m_staticAscii[b + '0']); + } else { + painter.drawStaticText(o, m_staticAscii[b - 10 + 'A']); + } } o += QPointF(m_letterWidth * 2, 0); }
M src/platform/qt/MessagePainter.cppsrc/platform/qt/MessagePainter.cpp

@@ -18,6 +18,7 @@

MessagePainter::MessagePainter(QObject* parent) : QObject(parent) , m_messageTimer(this) + , m_scaleFactor(1) { m_messageFont.setFamily("Source Code Pro"); m_messageFont.setStyleHint(QFont::Monospace);

@@ -29,7 +30,7 @@

clearMessage(); } -void MessagePainter::resize(const QSize& size, bool lockAspectRatio) { +void MessagePainter::resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor) { int w = size.width(); int h = size.height(); int drawW = w;

@@ -42,36 +43,63 @@ drawH = w * 2 / 3;

} } m_world.reset(); - m_world.translate((w - drawW) / 2, (h - drawH) / 2); m_world.scale(qreal(drawW) / VIDEO_HORIZONTAL_PIXELS, qreal(drawH) / VIDEO_VERTICAL_PIXELS); + m_scaleFactor = scaleFactor; + m_local = QPoint(1, VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1); + m_local = m_world.map(m_local); + m_local += QPoint((w - drawW) / 2, (h - drawH) / 2); + m_pixmapBuffer = QPixmap(drawW * m_scaleFactor, + (m_messageFont.pixelSize() + 2) * m_world.m22() * m_scaleFactor); + m_pixmapBuffer.setDevicePixelRatio(m_scaleFactor); + m_mutex.lock(); m_message.prepare(m_world, m_messageFont); + redraw(); + m_mutex.unlock(); } -void MessagePainter::paint(QPainter* painter) { - painter->setWorldTransform(m_world); - painter->setRenderHint(QPainter::Antialiasing); - painter->setFont(m_messageFont); - painter->setPen(Qt::black); - painter->translate(1, VIDEO_VERTICAL_PIXELS - m_messageFont.pixelSize() - 1); +void MessagePainter::redraw() { + m_pixmapBuffer.fill(Qt::transparent); + if (m_message.text().isEmpty()) { + m_pixmap = m_pixmapBuffer; + m_pixmap.setDevicePixelRatio(m_scaleFactor); + return; + } + QPainter painter(&m_pixmapBuffer); + painter.setWorldTransform(m_world); + painter.setRenderHint(QPainter::Antialiasing); + painter.setFont(m_messageFont); + painter.setPen(Qt::black); const static int ITERATIONS = 11; for (int i = 0; i < ITERATIONS; ++i) { - painter->save(); - painter->translate(cos(i * 2.0 * M_PI / ITERATIONS) * 0.8, sin(i * 2.0 * M_PI / ITERATIONS) * 0.8); - painter->drawStaticText(0, 0, m_message); - painter->restore(); + painter.save(); + painter.translate(cos(i * 2.0 * M_PI / ITERATIONS) * 0.8, sin(i * 2.0 * M_PI / ITERATIONS) * 0.8); + painter.drawStaticText(0, 0, m_message); + painter.restore(); } - painter->setPen(Qt::white); - painter->drawStaticText(0, 0, m_message); + painter.setPen(Qt::white); + painter.drawStaticText(0, 0, m_message); + m_pixmap = m_pixmapBuffer; + m_pixmap.setDevicePixelRatio(m_scaleFactor); +} + +void MessagePainter::paint(QPainter* painter) { + painter->drawPixmap(m_local, m_pixmap); } + void MessagePainter::showMessage(const QString& message) { + m_mutex.lock(); m_message.setText(message); - m_message.prepare(m_world, m_messageFont); + redraw(); + m_mutex.unlock(); m_messageTimer.stop(); m_messageTimer.start(); } void MessagePainter::clearMessage() { + m_mutex.lock(); m_message.setText(QString()); + redraw(); + m_mutex.unlock(); m_messageTimer.stop(); }
M src/platform/qt/MessagePainter.hsrc/platform/qt/MessagePainter.h

@@ -6,7 +6,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_MESSAGE_PAINTER #define QGBA_MESSAGE_PAINTER +#include <QMutex> #include <QObject> +#include <QPixmap> #include <QStaticText> #include <QTimer>

@@ -18,18 +20,26 @@

public: MessagePainter(QObject* parent = nullptr); - void resize(const QSize& size, bool lockAspectRatio); + void resize(const QSize& size, bool lockAspectRatio, qreal scaleFactor); void paint(QPainter* painter); + void setScaleFactor(qreal factor); public slots: void showMessage(const QString& message); void clearMessage(); private: + void redraw(); + + QMutex m_mutex; QStaticText m_message; + QPixmap m_pixmap; + QPixmap m_pixmapBuffer; QTimer m_messageTimer; + QPoint m_local; QTransform m_world; QFont m_messageFont; + qreal m_scaleFactor; }; }
M src/platform/qt/OverrideView.cppsrc/platform/qt/OverrideView.cpp

@@ -15,7 +15,7 @@

using namespace QGBA; OverrideView::OverrideView(GameController* controller, ConfigController* config, QWidget* parent) - : QWidget(parent) + : QDialog(parent) , m_controller(controller) , m_config(config) {

@@ -39,6 +39,7 @@ connect(m_ui.hwGyro, SIGNAL(clicked()), this, SLOT(updateOverrides()));

connect(m_ui.hwLight, SIGNAL(clicked()), this, SLOT(updateOverrides())); connect(m_ui.hwTilt, SIGNAL(clicked()), this, SLOT(updateOverrides())); connect(m_ui.hwRumble, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.hwGBPlayer, SIGNAL(clicked()), this, SLOT(updateOverrides())); connect(m_ui.save, SIGNAL(clicked()), this, SLOT(saveOverride()));

@@ -79,6 +80,9 @@ }

if (m_ui.hwRumble->isChecked()) { m_override.hardware |= HW_RUMBLE; } + } + if (m_ui.hwGBPlayer->isChecked()) { + m_override.hardware |= HW_GB_PLAYER_DETECTION; } bool ok;

@@ -87,7 +91,8 @@ if (ok) {

m_override.idleLoop = parsedIdleLoop; } - if (m_override.savetype != SAVEDATA_AUTODETECT || m_override.hardware != HW_NO_OVERRIDE || m_override.idleLoop != IDLE_LOOP_NONE) { + if (m_override.savetype != SAVEDATA_AUTODETECT || m_override.hardware != HW_NO_OVERRIDE || + m_override.idleLoop != IDLE_LOOP_NONE) { m_controller->setOverride(m_override); } else { m_controller->clearOverride();

@@ -114,12 +119,12 @@ m_ui.hwGyro->setChecked(thread->gba->memory.hw.devices & HW_GYRO);

m_ui.hwLight->setChecked(thread->gba->memory.hw.devices & HW_LIGHT_SENSOR); m_ui.hwTilt->setChecked(thread->gba->memory.hw.devices & HW_TILT); m_ui.hwRumble->setChecked(thread->gba->memory.hw.devices & HW_RUMBLE); + m_ui.hwGBPlayer->setChecked(thread->gba->memory.hw.devices & HW_GB_PLAYER_DETECTION); if (thread->gba->idleLoop != IDLE_LOOP_NONE) { m_ui.idleLoop->setText(QString::number(thread->gba->idleLoop, 16)); } else { m_ui.idleLoop->clear(); - } GBAGetGameCode(thread->gba, m_override.id);
M src/platform/qt/OverrideView.hsrc/platform/qt/OverrideView.h

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_OVERRIDE_VIEW #define QGBA_OVERRIDE_VIEW -#include <QWidget> +#include <QDialog> #include "ui_OverrideView.h"

@@ -21,7 +21,7 @@

class ConfigController; class GameController; -class OverrideView : public QWidget { +class OverrideView : public QDialog { Q_OBJECT public:
M src/platform/qt/OverrideView.uisrc/platform/qt/OverrideView.ui

@@ -6,8 +6,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>409</width> - <height>228</height> + <width>401</width> + <height>203</height> </rect> </property> <property name="sizePolicy">

@@ -23,13 +23,19 @@ <layout class="QGridLayout" name="gridLayout_3">

<property name="sizeConstraint"> <enum>QLayout::SetFixedSize</enum> </property> - <item row="2" column="1"> + <item row="4" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> </spacer> </item> <item>

@@ -44,7 +50,135 @@ </widget>

</item> </layout> </item> - <item row="0" column="0" rowspan="3"> + <item row="3" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Save type</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="savetype"> + <item> + <property name="text"> + <string>Autodetect</string> + </property> + </item> + <item> + <property name="text"> + <string>None</string> + </property> + </item> + <item> + <property name="text"> + <string>SRAM</string> + </property> + </item> + <item> + <property name="text"> + <string>Flash 512kb</string> + </property> + </item> + <item> + <property name="text"> + <string>Flash 1Mb</string> + </property> + </item> + <item> + <property name="text"> + <string>EEPROM</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Idle loop</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="idleLoop"/> + </item> + <item row="1" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="hwGBPlayer"> + <property name="text"> + <string>Game Boy Player features</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" rowspan="3"> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string/>

@@ -112,83 +246,6 @@ </widget>

</item> </layout> </widget> - </item> - <item row="0" column="1"> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string/> - </property> - <layout class="QFormLayout" name="formLayout_5"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::AllNonFixedFieldsGrow</enum> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Save type</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="savetype"> - <item> - <property name="text"> - <string>Autodetect</string> - </property> - </item> - <item> - <property name="text"> - <string>None</string> - </property> - </item> - <item> - <property name="text"> - <string>SRAM</string> - </property> - </item> - <item> - <property name="text"> - <string>Flash 512kb</string> - </property> - </item> - <item> - <property name="text"> - <string>Flash 1Mb</string> - </property> - </item> - <item> - <property name="text"> - <string>EEPROM</string> - </property> - </item> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Idle loop</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="idleLoop"/> - </item> - <item row="1" column="1"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="1" column="1"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - </spacer> </item> </layout> </widget>
M src/platform/qt/PaletteView.cppsrc/platform/qt/PaletteView.cpp

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "PaletteView.h" #include "GBAApp.h" +#include "LogController.h" #include "VFileDevice.h" #include <QFileDialog>

@@ -84,7 +85,8 @@ if (start + length > 512) {

length = 512 - start; } m_controller->threadInterrupt(); - QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export palette"), tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); + QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export palette"), + tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); if (!dialog->exec()) { m_controller->threadContinue(); return;

@@ -92,6 +94,7 @@ }

QString filename = dialog->selectedFiles()[0]; VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { + LOG(ERROR) << tr("Failed to open output palette file: %1").arg(filename); m_controller->threadContinue(); return; }
M src/platform/qt/SavestateButton.cppsrc/platform/qt/SavestateButton.cpp

@@ -13,7 +13,7 @@

SavestateButton::SavestateButton(QWidget* parent) : QAbstractButton(parent) { - // Nothing to do + setAttribute(Qt::WA_TranslucentBackground); } void SavestateButton::paintEvent(QPaintEvent*) {
M src/platform/qt/SensorView.cppsrc/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())

@@ -61,6 +61,7 @@ m_ui.gyroSensitivity->setValue(m_input->gyroSensitivity() / 1e8f);

connect(m_ui.gyroSensitivity, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this](double value) { m_input->setGyroSensitivity(value * 1e8f); }); + m_input->stealFocus(this); } void SensorView::jiggerer(QAbstractButton* button, void (InputController::*setter)(int)) {

@@ -68,15 +69,26 @@ connect(button, &QAbstractButton::toggled, [this, button, setter](bool checked) {

if (!checked) { m_jiggered = nullptr; } else { + button->setFocus(); m_jiggered = [this, button, setter](int axis) { (m_input->*setter)(axis); button->setChecked(false); + button->clearFocus(); }; } }); button->installEventFilter(this); } +bool SensorView::event(QEvent* event) { + if (event->type() == QEvent::WindowActivate) { + m_input->stealFocus(this); + } else if (event->type() == QEvent::WindowDeactivate) { + m_input->releaseFocus(this); + } + return QWidget::event(event); +} + bool SensorView::eventFilter(QObject*, QEvent* event) { if (event->type() == GamepadAxisEvent::Type()) { GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event);

@@ -91,7 +103,8 @@ }

void SensorView::updateSensors() { m_controller->threadInterrupt(); - if (m_rotation->sample && (!m_controller->isLoaded() || !(m_controller->thread()->gba->memory.hw.devices & (HW_GYRO | HW_TILT)))) { + if (m_rotation->sample && + (!m_controller->isLoaded() || !(m_controller->thread()->gba->memory.hw.devices & (HW_GYRO | HW_TILT)))) { m_rotation->sample(m_rotation); m_rotation->sample(m_rotation); m_rotation->sample(m_rotation);
M src/platform/qt/SensorView.hsrc/platform/qt/SensorView.h

@@ -7,7 +7,7 @@ #ifndef QGBA_SENSOR_VIEW

#define QGBA_SENSOR_VIEW #include <QTimer> -#include <QWidget> +#include <QDialog> #include <functional>

@@ -22,7 +22,7 @@ class GameController;

class GamepadAxisEvent; class InputController; -class SensorView : public QWidget { +class SensorView : public QDialog { Q_OBJECT public:

@@ -30,6 +30,7 @@ SensorView(GameController* controller, InputController* input, QWidget* parent = nullptr);

protected: bool eventFilter(QObject*, QEvent* event) override; + bool event(QEvent* event); private slots: void updateSensors();
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -7,13 +7,14 @@ #include "SettingsView.h"

#include "AudioProcessor.h" #include "ConfigController.h" +#include "Display.h" #include "GBAApp.h" using namespace QGBA; SettingsView::SettingsView(ConfigController* controller, QWidget* parent) - : QWidget(parent) - , m_controller(controller) + : QDialog(parent) + , m_controller(controller) { m_ui.setupUi(this);

@@ -21,6 +22,7 @@ loadSetting("bios", m_ui.bios);

loadSetting("useBios", m_ui.useBios); loadSetting("skipBios", m_ui.skipBios); loadSetting("audioBuffers", m_ui.audioBufferSize); + loadSetting("sampleRate", m_ui.sampleRate); loadSetting("videoSync", m_ui.videoSync); loadSetting("audioSync", m_ui.audioSync); loadSetting("frameskip", m_ui.frameskip);

@@ -35,6 +37,19 @@ loadSetting("resampleVideo", m_ui.resampleVideo);

loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); loadSetting("suspendScreensaver", m_ui.suspendScreensaver); + double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); + if (fastForwardRatio <= 0) { + m_ui.fastForwardUnbounded->setChecked(true); + m_ui.fastForwardRatio->setEnabled(false); + } else { + m_ui.fastForwardUnbounded->setChecked(false); + m_ui.fastForwardRatio->setEnabled(true); + m_ui.fastForwardRatio->setValue(fastForwardRatio); + } + connect(m_ui.fastForwardUnbounded, &QAbstractButton::toggled, [this](bool checked) { + m_ui.fastForwardRatio->setEnabled(!checked); + }); + QString idleOptimization = loadSetting("idleOptimization"); if (idleOptimization == "ignore") { m_ui.idleOptimization->setCurrentIndex(0);

@@ -59,6 +74,19 @@ m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1);

} #endif + QVariant displayDriver = m_controller->getQtOption("displayDriver"); + m_ui.displayDriver->addItem(tr("Software (Qt)"), static_cast<int>(Display::Driver::QT)); + if (!displayDriver.isNull() && displayDriver.toInt() == static_cast<int>(Display::Driver::QT)) { + m_ui.displayDriver->setCurrentIndex(m_ui.displayDriver->count() - 1); + } + +#ifdef BUILD_GL + m_ui.displayDriver->addItem(tr("OpenGL"), static_cast<int>(Display::Driver::OPENGL)); + if (displayDriver.isNull() || displayDriver.toInt() == static_cast<int>(Display::Driver::OPENGL)) { + m_ui.displayDriver->setCurrentIndex(m_ui.displayDriver->count() - 1); + } +#endif + connect(m_ui.biosBrowse, SIGNAL(clicked()), this, SLOT(selectBios())); connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); }

@@ -75,6 +103,7 @@ saveSetting("bios", m_ui.bios);

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

@@ -89,6 +118,12 @@ saveSetting("resampleVideo", m_ui.resampleVideo);

saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); saveSetting("suspendScreensaver", m_ui.suspendScreensaver); + if (m_ui.fastForwardUnbounded->isChecked()) { + saveSetting("fastForwardRatio", "-1"); + } else { + saveSetting("fastForwardRatio", m_ui.fastForwardRatio); + } + switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) { case IDLE_LOOP_IGNORE: saveSetting("idleOptimization", "ignore");

@@ -108,6 +143,13 @@ AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(audioDriver.toInt()));

emit audioDriverChanged(); } + QVariant displayDriver = m_ui.displayDriver->itemData(m_ui.displayDriver->currentIndex()); + if (displayDriver != m_controller->getQtOption("displayDriver")) { + m_controller->setQtOption("displayDriver", displayDriver); + Display::setDriver(static_cast<Display::Driver>(displayDriver.toInt())); + emit displayDriverChanged(); + } + m_controller->write(); emit biosLoaded(m_ui.bios->text());

@@ -123,7 +165,7 @@ saveSetting(key, field->lineEdit());

} void SettingsView::saveSetting(const char* key, const QDoubleSpinBox* field) { - saveSetting(key, field->cleanText()); + saveSetting(key, field->value()); } void SettingsView::saveSetting(const char* key, const QLineEdit* field) {

@@ -131,14 +173,14 @@ saveSetting(key, field->text());

} void SettingsView::saveSetting(const char* key, const QSlider* field) { - saveSetting(key, QString::number(field->value())); + saveSetting(key, field->value()); } void SettingsView::saveSetting(const char* key, const QSpinBox* field) { - saveSetting(key, field->cleanText()); + saveSetting(key, field->value()); } -void SettingsView::saveSetting(const char* key, const QString& field) { +void SettingsView::saveSetting(const char* key, const QVariant& field) { m_controller->setOption(key, field); m_controller->updateOption(key); }
M src/platform/qt/SettingsView.hsrc/platform/qt/SettingsView.h

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_SETTINGS_VIEW #define QGBA_SETTINGS_VIEW -#include <QWidget> +#include <QDialog> #include "ui_SettingsView.h"

@@ -14,7 +14,7 @@ namespace QGBA {

class ConfigController; -class SettingsView : public QWidget { +class SettingsView : public QDialog { Q_OBJECT public:

@@ -23,6 +23,7 @@

signals: void biosLoaded(const QString&); void audioDriverChanged(); + void displayDriverChanged(); private slots: void selectBios();

@@ -39,7 +40,7 @@ void saveSetting(const char* key, const QDoubleSpinBox*);

void saveSetting(const char* key, const QLineEdit*); void saveSetting(const char* key, const QSlider*); void saveSetting(const char* key, const QSpinBox*); - void saveSetting(const char* key, const QString&); + void saveSetting(const char* key, const QVariant&); void loadSetting(const char* key, QAbstractButton*); void loadSetting(const char* key, QComboBox*);
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -6,8 +6,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>673</width> - <height>366</height> + <width>707</width> + <height>420</height> </rect> </property> <property name="sizePolicy">

@@ -62,10 +62,10 @@ <property name="editable">

<bool>true</bool> </property> <property name="currentText"> - <string>2048</string> + <string>1536</string> </property> <property name="currentIndex"> - <number>2</number> + <number>3</number> </property> <item> <property name="text">

@@ -74,12 +74,27 @@ </property>

</item> <item> <property name="text"> + <string>768</string> + </property> + </item> + <item> + <property name="text"> <string>1024</string> </property> </item> <item> <property name="text"> + <string>1536</string> + </property> + </item> + <item> + <property name="text"> <string>2048</string> + </property> + </item> + <item> + <property name="text"> + <string>3072</string> </property> </item> <item>

@@ -99,13 +114,20 @@ </item>

</layout> </item> <item row="2" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Sample rate:</string> + </property> + </widget> + </item> + <item row="3" column="0"> <widget class="QLabel" name="label_17"> <property name="text"> <string>Volume:</string> </property> </widget> </item> - <item row="2" column="1"> + <item row="3" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> <widget class="QSlider" name="volume">

@@ -132,46 +154,38 @@ </widget>

</item> </layout> </item> - <item row="3" column="0" colspan="2"> + <item row="4" column="0" colspan="2"> <widget class="Line" name="line"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_2"> + <item row="5" column="0"> + <widget class="QLabel" name="label_10"> <property name="text"> - <string>Sync:</string> + <string>Display driver:</string> </property> </widget> </item> - <item row="4" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_10"> - <item> - <widget class="QCheckBox" name="videoSync"> - <property name="text"> - <string>Video</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="audioSync"> - <property name="text"> - <string>Audio</string> - </property> - </widget> - </item> - </layout> + <item row="5" column="1"> + <widget class="QComboBox" name="displayDriver"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> </item> - <item row="5" column="0"> + <item row="6" column="0"> <widget class="QLabel" name="label_9"> <property name="text"> <string>Frameskip:</string> </property> </widget> </item> - <item row="5" column="1"> + <item row="6" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_16"> <item> <widget class="QLabel" name="label_12">

@@ -192,14 +206,14 @@ </widget>

</item> </layout> </item> - <item row="6" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="label_3"> <property name="text"> <string>FPS target:</string> </property> </widget> </item> - <item row="6" column="1"> + <item row="7" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QDoubleSpinBox" name="fpsTarget">

@@ -223,36 +237,95 @@ </widget>

</item> </layout> </item> - <item row="7" column="0" colspan="2"> + <item row="8" column="0" colspan="2"> <widget class="Line" name="line_4"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="8" column="1"> + <item row="9" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Sync:</string> + </property> + </widget> + </item> + <item row="9" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QCheckBox" name="videoSync"> + <property name="text"> + <string>Video</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="audioSync"> + <property name="text"> + <string>Audio</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="10" column="1"> <widget class="QCheckBox" name="lockAspectRatio"> <property name="text"> <string>Lock aspect ratio</string> </property> </widget> </item> - <item row="9" column="1"> + <item row="11" column="1"> <widget class="QCheckBox" name="resampleVideo"> <property name="text"> <string>Resample video</string> </property> </widget> </item> - <item row="10" column="1"> - <widget class="QCheckBox" name="suspendScreensaver"> - <property name="text"> - <string>Suspend screensaver</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <item> + <widget class="QComboBox" name="sampleRate"> + <property name="editable"> + <bool>true</bool> + </property> + <property name="currentText"> + <string>44100</string> + </property> + <property name="currentIndex"> + <number>2</number> + </property> + <item> + <property name="text"> + <string>22050</string> + </property> + </item> + <item> + <property name="text"> + <string>32000</string> + </property> + </item> + <item> + <property name="text"> + <string>44100</string> + </property> + </item> + <item> + <property name="text"> + <string>48000</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Hz</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item>

@@ -373,28 +446,38 @@ </widget>

</item> </layout> </item> - <item row="7" column="0" colspan="2"> + <item row="10" column="0" colspan="2"> <widget class="Line" name="line_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="8" column="1"> + <item row="11" column="1"> <widget class="QCheckBox" name="allowOpposingDirections"> <property name="text"> <string>Allow opposing input directions</string> </property> </widget> </item> - <item row="9" column="0"> + <item row="12" column="1"> + <widget class="QCheckBox" name="suspendScreensaver"> + <property name="text"> + <string>Suspend screensaver</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="13" column="0"> <widget class="QLabel" name="label_15"> <property name="text"> <string>Idle loops</string> </property> </widget> </item> - <item row="9" column="1"> + <item row="13" column="1"> <widget class="QComboBox" name="idleOptimization"> <item> <property name="text">

@@ -411,6 +494,52 @@ <property name="text">

<string>Detect and remove</string> </property> </item> + </widget> + </item> + <item row="8" column="1"> + <widget class="QDoubleSpinBox" name="fastForwardRatio"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="suffix"> + <string>×</string> + </property> + <property name="minimum"> + <double>0.010000000000000</double> + </property> + <property name="maximum"> + <double>20.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.500000000000000</double> + </property> + <property name="value"> + <double>5.000000000000000</double> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>Fast forward speed</string> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QCheckBox" name="fastForwardUnbounded"> + <property name="text"> + <string>Unbounded</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="0" colspan="2"> + <widget class="Line" name="line_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> </widget> </item> </layout>
M src/platform/qt/ShortcutController.cppsrc/platform/qt/ShortcutController.cpp

@@ -7,6 +7,7 @@ #include "ShortcutController.h"

#include "ConfigController.h" #include "GamepadButtonEvent.h" +#include "InputProfile.h" #include <QAction> #include <QKeyEvent>

@@ -18,6 +19,7 @@ ShortcutController::ShortcutController(QObject* parent)

: QAbstractItemModel(parent) , m_rootMenu(nullptr) , m_config(nullptr) + , m_profile(nullptr) { }

@@ -118,10 +120,12 @@ ShortcutItem* item = &smenu->items().last();

if (m_config) { loadShortcuts(item); } - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), + createIndex(smenu->items().count() - 1, 2, item)); } -void ShortcutController::addFunctions(QMenu* menu, std::function<void ()> press, std::function<void ()> release, const QKeySequence& shortcut, const QString& visibleName, const QString& name) { +void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + const QKeySequence& shortcut, const QString& visibleName, const QString& name) { ShortcutItem* smenu = m_menuMap[menu]; if (!smenu) { return;

@@ -137,7 +141,8 @@ if (m_config) {

loadShortcuts(item); } m_heldKeys[shortcut] = item; - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), + createIndex(smenu->items().count() - 1, 2, item)); } void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) {

@@ -155,7 +160,8 @@ beginInsertRows(parent, smenu->items().count(), smenu->items().count());

smenu->addSubmenu(menu); endInsertRows(); ShortcutItem* item = &smenu->items().last(); - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), + createIndex(smenu->items().count() - 1, 2, item)); m_menuMap[menu] = item; }

@@ -211,7 +217,8 @@ item->setShortcut(keySequence);

if (m_config) { m_config->setQtOption(item->name(), keySequence.toString(), KEY_SECTION); } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), + createIndex(index.row(), 2, index.internalPointer())); } void ShortcutController::updateButton(const QModelIndex& index, int button) {

@@ -234,8 +241,12 @@ m_buttons[button] = item;

} if (m_config) { m_config->setQtOption(item->name(), button, BUTTON_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName); + } } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), + createIndex(index.row(), 2, index.internalPointer())); } void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {

@@ -266,8 +277,12 @@ if (direction == GamepadAxisEvent::NEGATIVE) {

d = '-'; } m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); + } } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), + createIndex(index.row(), 2, index.internalPointer())); } void ShortcutController::clearKey(const QModelIndex& index) {

@@ -358,6 +373,9 @@ return false;

} void ShortcutController::loadShortcuts(ShortcutItem* item) { + if (item->name().isNull()) { + return; + } QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); if (!shortcut.isNull()) { QKeySequence keySequence(shortcut.toString());

@@ -370,19 +388,45 @@ m_heldKeys[keySequence] = item;

} item->setShortcut(keySequence); } - QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION); + loadGamepadShortcuts(item); +} + +void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) { + if (item->name().isNull()) { + return; + } + QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION); + int oldButton = item->button(); + if (oldButton >= 0) { + m_buttons.take(oldButton); + item->setButton(-1); + } + if (button.isNull() && m_profile) { + int buttonInt; + if (m_profile->lookupShortcutButton(item->name(), &buttonInt)) { + button = buttonInt; + } + } if (!button.isNull()) { - int oldButton = item->button(); item->setButton(button.toInt()); - if (oldButton >= 0) { - m_buttons.take(oldButton); - } m_buttons[button.toInt()] = item; } - QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION); + + QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION); + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + item->setAxis(-1, GamepadAxisEvent::NEUTRAL); + } + if (axis.isNull() && m_profile) { + int axisInt; + GamepadAxisEvent::Direction direction; + if (m_profile->lookupShortcutAxis(item->name(), &axisInt, &direction)) { + axis = QLatin1String(direction == GamepadAxisEvent::Direction::NEGATIVE ? "-" : "+") + QString::number(axisInt); + } + } if (!axis.isNull()) { - int oldAxis = item->axis(); - GamepadAxisEvent::Direction oldDirection = item->direction(); QString axisDesc = axis.toString(); if (axisDesc.size() >= 2) { GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;

@@ -396,9 +440,6 @@ bool ok;

int axis = axisDesc.mid(1).toInt(&ok); if (ok) { item->setAxis(axis, direction); - if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); - } m_axes[qMakePair(axis, direction)] = item; } }

@@ -425,6 +466,21 @@ QString key = QKeySequence(event->key()).toString();

return QKeySequence(modifier + key); } +void ShortcutController::loadProfile(const QString& profile) { + m_profileName = profile; + m_profile = InputProfile::findProfile(profile); + onSubitems(&m_rootMenu, [this](ShortcutItem* item) { + loadGamepadShortcuts(item); + }); +} + +void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) { + for (ShortcutItem& subitem : item->items()) { + func(&subitem); + onSubitems(&subitem, func); + } +} + ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent) : m_action(action) , m_shortcut(action->shortcut())

@@ -473,7 +529,9 @@ void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) {

m_items.append(ShortcutItem(action, name, this)); } -void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name) { +void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions, + const QKeySequence& shortcut, const QString& visibleName, + const QString& name) { m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this)); }
M src/platform/qt/ShortcutController.hsrc/platform/qt/ShortcutController.h

@@ -21,6 +21,7 @@

namespace QGBA { class ConfigController; +class InputProfile; class ShortcutController : public QAbstractItemModel { Q_OBJECT

@@ -29,13 +30,16 @@ private:

constexpr static const char* const KEY_SECTION = "shortcutKey"; constexpr static const char* const BUTTON_SECTION = "shortcutButton"; constexpr static const char* const AXIS_SECTION = "shortcutAxis"; + constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; + constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; class ShortcutItem { public: typedef QPair<std::function<void ()>, std::function<void ()>> Functions; ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent = nullptr); - ShortcutItem(Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent = nullptr); + ShortcutItem(Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, + ShortcutItem* parent = nullptr); ShortcutItem(QMenu* action, ShortcutItem* parent = nullptr); QAction* action() { return m_action; }

@@ -51,7 +55,8 @@ const QList<ShortcutItem>& items() const { return m_items; }

ShortcutItem* parent() { return m_parent; } const ShortcutItem* parent() const { return m_parent; } void addAction(QAction* action, const QString& name); - void addFunctions(Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name); + void addFunctions(Functions functions, const QKeySequence& shortcut, const QString& visibleName, + const QString& name); void addSubmenu(QMenu* menu); int button() const { return m_button; } void setShortcut(const QKeySequence& sequence);

@@ -60,7 +65,9 @@ int axis() const { return m_axis; }

GamepadAxisEvent::Direction direction() const { return m_direction; } void setAxis(int axis, GamepadAxisEvent::Direction direction); - bool operator==(const ShortcutItem& other) const { return m_menu == other.m_menu && m_action == other.m_action; } + bool operator==(const ShortcutItem& other) const { + return m_menu == other.m_menu && m_action == other.m_action; + } private: QAction* m_action;

@@ -80,6 +87,7 @@ public:

ShortcutController(QObject* parent = nullptr); void setConfigController(ConfigController* controller); + void setProfile(const QString& profile); virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

@@ -91,9 +99,12 @@ virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;

virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; void addAction(QMenu* menu, QAction* action, const QString& name); - void addFunctions(QMenu* menu, std::function<void ()> press, std::function<void()> release, const QKeySequence& shortcut, const QString& visibleName, const QString& name); + void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + 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;

@@ -106,6 +117,9 @@ void clearButton(const QModelIndex& index);

static QKeySequence keyEventToSequence(const QKeyEvent*); +public slots: + void loadProfile(const QString& profile); + protected: bool eventFilter(QObject*, QEvent*) override;

@@ -113,6 +127,8 @@ private:

ShortcutItem* itemAt(const QModelIndex& index); const ShortcutItem* itemAt(const QModelIndex& index) const; void loadShortcuts(ShortcutItem*); + void loadGamepadShortcuts(ShortcutItem*); + void onSubitems(ShortcutItem*, std::function<void(ShortcutItem*)> func); ShortcutItem m_rootMenu; QMap<QMenu*, ShortcutItem*> m_menuMap;

@@ -120,6 +136,8 @@ QMap<int, ShortcutItem*> m_buttons;

QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes; QMap<QKeySequence, ShortcutItem*> m_heldKeys; ConfigController* m_config; + QString m_profileName; + const InputProfile* m_profile; }; }
M src/platform/qt/ShortcutView.cppsrc/platform/qt/ShortcutView.cpp

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ShortcutView.h" #include "GamepadButtonEvent.h" +#include "InputController.h" #include "ShortcutController.h" #include <QKeyEvent>

@@ -15,6 +16,7 @@

ShortcutView::ShortcutView(QWidget* parent) : QWidget(parent) , m_controller(nullptr) + , m_input(nullptr) { m_ui.setupUi(this); m_ui.keyEdit->setValueButton(-1);

@@ -30,6 +32,14 @@

void ShortcutView::setController(ShortcutController* controller) { m_controller = controller; m_ui.shortcutTable->setModel(controller); +} + +void ShortcutView::setInputController(InputController* controller) { + if (m_input) { + m_input->releaseFocus(this); + } + m_input = controller; + m_input->stealFocus(this); } bool ShortcutView::eventFilter(QObject*, QEvent* event) {

@@ -104,10 +114,27 @@ }

m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); } - void ShortcutView::updateAxis(int axis, int direction) { if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { return; } - m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, static_cast<GamepadAxisEvent::Direction>(direction)); + m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, + static_cast<GamepadAxisEvent::Direction>(direction)); +} + +void ShortcutView::closeEvent(QCloseEvent*) { + if (m_input) { + m_input->releaseFocus(this); + } +} + +bool ShortcutView::event(QEvent* event) { + if (m_input) { + if (event->type() == QEvent::WindowActivate) { + m_input->stealFocus(this); + } else if (event->type() == QEvent::WindowDeactivate) { + m_input->releaseFocus(this); + } + } + return QWidget::event(event); }
M src/platform/qt/ShortcutView.hsrc/platform/qt/ShortcutView.h

@@ -14,6 +14,7 @@ #include "ui_ShortcutView.h"

namespace QGBA { +class InputController; class ShortcutController; class ShortcutView : public QWidget {

@@ -23,9 +24,12 @@ public:

ShortcutView(QWidget* parent = nullptr); void setController(ShortcutController* controller); + void setInputController(InputController* input); protected: virtual bool eventFilter(QObject* obj, QEvent* event) override; + virtual bool event(QEvent*) override; + virtual void closeEvent(QCloseEvent*) override; private slots: void load(const QModelIndex&);

@@ -38,6 +42,7 @@ private:

Ui::ShortcutView m_ui; ShortcutController* m_controller; + InputController* m_input; }; }
M src/platform/qt/VFileDevice.cppsrc/platform/qt/VFileDevice.cpp

@@ -27,5 +27,5 @@ return m_vf->size(m_vf);

} VFile* VFileDevice::open(QString path, int mode) { - return VFileOpen(path.toLocal8Bit().constData(), mode); + return VFileOpen(path.toUtf8().constData(), mode); }
M src/platform/qt/VideoView.cppsrc/platform/qt/VideoView.cpp

@@ -8,6 +8,7 @@

#ifdef USE_FFMPEG #include "GBAApp.h" +#include "LogController.h" #include <QMap>

@@ -131,8 +132,8 @@ .vcodec = QString(),

.acodec = QString(), .vbr = 0, .abr = 0, - .width = 240, - .height = 160 + .width = VIDEO_HORIZONTAL_PIXELS, + .height = VIDEO_VERTICAL_PIXELS }); addPreset(m_ui.presetHQ, (Preset) {

@@ -169,8 +170,8 @@ .vcodec = "PNG",

.acodec = "FLAC", .vbr = 0, .abr = 0, - .width = 240, - .height = 160, + .width = VIDEO_HORIZONTAL_PIXELS, + .height = VIDEO_VERTICAL_PIXELS, }); setPreset((Preset) {

@@ -179,8 +180,8 @@ .vcodec = "PNG",

.acodec = "FLAC", .vbr = 0, .abr = 0, - .width = 240, - .height = 160, + .width = VIDEO_HORIZONTAL_PIXELS, + .height = VIDEO_VERTICAL_PIXELS, }); showAdvanced(false);

@@ -197,7 +198,8 @@ void VideoView::startRecording() {

if (!validateSettings()) { return; } - if (!FFmpegEncoderOpen(&m_encoder, m_filename.toLocal8Bit().constData())) { + if (!FFmpegEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) { + LOG(ERROR) << tr("Failed to open output video file: %1").arg(m_filename); return; } m_ui.start->setEnabled(false);

@@ -230,7 +232,7 @@ m_audioCodec = sanitizeCodec(codec, s_acodecMap);

if (m_audioCodec == "none") { m_audioCodecCstr = nullptr; } else { - m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData()); + m_audioCodecCstr = strdup(m_audioCodec.toUtf8().constData()); } if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) { free(m_audioCodecCstr);

@@ -246,7 +248,7 @@

void VideoView::setVideoCodec(const QString& codec, bool manual) { free(m_videoCodecCstr); m_videoCodec = sanitizeCodec(codec, s_vcodecMap); - m_videoCodecCstr = strdup(m_videoCodec.toLocal8Bit().constData()); + m_videoCodecCstr = strdup(m_videoCodec.toUtf8().constData()); if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) { free(m_videoCodecCstr); m_videoCodecCstr = nullptr;

@@ -261,7 +263,7 @@

void VideoView::setContainer(const QString& container, bool manual) { free(m_containerCstr); m_container = sanitizeCodec(container, s_containerMap); - m_containerCstr = strdup(m_container.toLocal8Bit().constData()); + m_containerCstr = strdup(m_container.toUtf8().constData()); if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) { free(m_containerCstr); m_containerCstr = nullptr;
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -13,6 +13,7 @@ #include <QMimeData>

#include <QPainter> #include <QStackedLayout> +#include "AboutScreen.h" #include "CheatsView.h" #include "ConfigController.h" #include "Display.h"

@@ -40,19 +41,20 @@ }

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 Window::Window(ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) - , m_logView(new LogView()) + , m_log(0) + , m_logView(new LogView(&m_log)) , m_stateWindow(nullptr) , m_screenWidget(new WindowBackground()) , m_logo(":/res/mgba-1024.png") , m_config(config) - , m_inputController(playerId) + , m_inputController(playerId, this) #ifdef USE_FFMPEG , m_videoView(nullptr) #endif

@@ -68,6 +70,7 @@ , m_playerId(playerId)

{ setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); + setAttribute(Qt::WA_DeleteOnClose); m_controller = new GameController(this); m_controller->setInputController(&m_inputController); m_controller->setOverrides(m_config->overrides());

@@ -97,6 +100,14 @@ connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped()));

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<const uchar*>(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()));

@@ -109,25 +120,35 @@ #endif

connect(m_controller, SIGNAL(gamePaused(GBAThread*)), &m_inputController, SLOT(resumeScreensaver())); connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), m_display, SLOT(unpauseDrawing())); connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), &m_inputController, SLOT(suspendScreensaver())); - connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&))); + connect(m_controller, SIGNAL(postLog(int, const QString&)), &m_log, SLOT(postLog(int, const QString&))); connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(recordFrame())); connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), m_display, SLOT(framePosted(const uint32_t*))); connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&))); connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed())); connect(m_controller, SIGNAL(unimplementedBiosCall(int)), this, SLOT(unimplementedBiosCall(int))); connect(m_controller, SIGNAL(statusPosted(const QString&)), m_display, SLOT(showMessage(const QString&))); - connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); - connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); - connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int))); + connect(&m_log, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); + connect(&m_log, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); + connect(&m_log, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int))); connect(this, SIGNAL(startDrawing(GBAThread*)), m_display, SLOT(startDrawing(GBAThread*)), Qt::QueuedConnection); connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing())); 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<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display) { + m_screenWidget->setCursor(Qt::BlankCursor); + } + }); + connect(m_display, &Display::showCursor, [this]() { + m_screenWidget->unsetCursor(); + }); + connect(&m_inputController, SIGNAL(profileLoaded(const QString&)), m_shortcutController, SLOT(loadProfile(const QString&))); - m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS); + m_log.setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_shortcutController->setConfigController(m_config);

@@ -160,6 +181,7 @@ }

void Window::resizeFrame(int width, int height) { QSize newSize(width, height); + m_screenWidget->setSizeHint(newSize); newSize -= m_screenWidget->size(); newSize += size(); resize(newSize);

@@ -172,7 +194,7 @@

void Window::loadConfig() { const GBAOptions* opts = m_config->options(); - m_logView->setLevels(opts->logLevel); + m_log.setLevels(opts->logLevel); m_controller->setOptions(opts); m_display->lockAspectRatio(opts->lockAspectRatio);

@@ -182,12 +204,17 @@ if (opts->bios) {

m_controller->loadBIOS(opts->bios); } + // TODO: Move these to ConfigController if (opts->fpsTarget) { emit fpsTargetChanged(opts->fpsTarget); } if (opts->audioBuffers) { emit audioBufferSamplesChanged(opts->audioBuffers); + } + + if (opts->sampleRate) { + emit sampleRateChanged(opts->sampleRate); } if (opts->width && opts->height) {

@@ -226,6 +253,24 @@ QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' ')));

QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); if (!filename.isEmpty()) { m_controller->loadGame(filename); + } +} + +void Window::replaceROM() { + QStringList formats{ + "*.gba", +#ifdef USE_LIBZIP + "*.zip", +#endif +#ifdef USE_LZMA + "*.7z", +#endif + "*.rom", + "*.bin"}; + QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' '))); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); + if (!filename.isEmpty()) { + m_controller->replaceGame(filename); } }

@@ -276,6 +321,7 @@ void Window::openSettingsWindow() {

SettingsView* settingsWindow = new SettingsView(m_config); connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); + connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart())); openView(settingsWindow); }

@@ -285,6 +331,7 @@ m_inputController.recalibrateAxes();

#endif ShortcutView* shortcutView = new ShortcutView(); shortcutView->setController(m_shortcutController); + shortcutView->setInputController(&m_inputController); openView(shortcutView); }

@@ -311,6 +358,11 @@

void Window::openMemoryWindow() { MemoryView* memoryWindow = new MemoryView(m_controller); openView(memoryWindow); +} + +void Window::openAboutScreen() { + AboutScreen* about = new AboutScreen(); + openView(about); } #ifdef BUILD_SDL

@@ -355,9 +407,7 @@ if (!m_gdbController) {

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

@@ -389,12 +439,32 @@ m_controller->keyReleased(key);

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<int, QAction*>::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) {

@@ -404,6 +474,10 @@ saveConfig();

QMainWindow::closeEvent(event); } +void Window::focusInEvent(QFocusEvent*) { + m_display->forceDraw(); +} + void Window::focusOutEvent(QFocusEvent*) { m_controller->setTurbo(false, false); m_controller->stopRewinding();

@@ -444,7 +518,6 @@ if (isFullScreen()) {

return; } showFullScreen(); - setCursor(Qt::BlankCursor); #ifndef Q_OS_MAC if (m_controller->isLoaded() && !m_controller->isPaused()) { menuBar()->hide();

@@ -456,9 +529,9 @@ void Window::exitFullScreen() {

if (!isFullScreen()) { return; } - unsetCursor(); - showNormal(); + m_screenWidget->unsetCursor(); menuBar()->show(); + showNormal(); } void Window::toggleFullScreen() {

@@ -484,13 +557,14 @@ foreach (QAction* action, m_gameActions) {

action->setDisabled(false); } if (context->fname) { + setWindowFilePath(context->fname); appendMRU(context->fname); } updateTitle(); attachWidget(m_display); #ifndef Q_OS_MAC - if(isFullScreen()) { + if (isFullScreen()) { menuBar()->hide(); } #endif

@@ -503,26 +577,28 @@ 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(); } void Window::gameCrashed(const QString& errorMessage) { QMessageBox* crash = new QMessageBox(QMessageBox::Critical, tr("Crash"), - tr("The game has crashed with the following error:\n\n%1").arg(errorMessage), - QMessageBox::Ok, this, Qt::Sheet); + tr("The game has crashed with the following error:\n\n%1").arg(errorMessage), + QMessageBox::Ok, this, Qt::Sheet); crash->setAttribute(Qt::WA_DeleteOnClose); crash->show(); } void Window::gameFailed() { QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Couldn't Load"), - tr("Could not load game. Are you sure it's in the correct format?"), - QMessageBox::Ok, this, Qt::Sheet); + tr("Could not load game. Are you sure it's in the correct format?"), + QMessageBox::Ok, this, Qt::Sheet); fail->setAttribute(Qt::WA_DeleteOnClose); fail->show(); }

@@ -533,13 +609,31 @@ return;

} m_hitUnimplementedBiosCall = true; - QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Unimplemented BIOS call"), - tr("This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience."), - QMessageBox::Ok, this, Qt::Sheet); + QMessageBox* fail = new QMessageBox( + QMessageBox::Warning, tr("Unimplemented BIOS call"), + tr("This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience."), + QMessageBox::Ok, this, Qt::Sheet); fail->setAttribute(Qt::WA_DeleteOnClose); fail->show(); } +void Window::tryMakePortable() { + QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"), + tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"), + QMessageBox::Yes | QMessageBox::Cancel, this, Qt::Sheet); + confirm->setAttribute(Qt::WA_DeleteOnClose); + connect(confirm->button(QMessageBox::Yes), SIGNAL(clicked()), m_config, SLOT(makePortable())); + confirm->show(); +} + +void Window::mustRestart() { + QMessageBox* dialog = new QMessageBox(QMessageBox::Warning, tr("Restart needed"), + tr("Some changes will not take effect until the emulator is restarted."), + QMessageBox::Ok, this, Qt::Sheet); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + void Window::recordFrame() { m_frameList.append(QDateTime::currentDateTime()); while (m_frameList.count() > FRAME_LIST_SIZE) {

@@ -591,7 +685,7 @@ m_stateWindow = new LoadSaveState(m_controller);

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

@@ -609,12 +703,19 @@ menubar->clear();

QMenu* fileMenu = menubar->addMenu(tr("&File")); m_shortcutController->addMenu(fileMenu); installEventFilter(m_shortcutController); - addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), + "loadROM"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS"); + + addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM"); m_mruMenu = fileMenu->addMenu(tr("Recent")); + + fileMenu->addSeparator(); + + addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable"); fileMenu->addSeparator();

@@ -648,6 +749,21 @@

quickLoadMenu->addSeparator(); quickSaveMenu->addSeparator(); + QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu); + undoLoadState->setShortcut(tr("F11")); + connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState())); + m_gameActions.append(undoLoadState); + addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); + + QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); + undoSaveState->setShortcut(tr("Shift+F11")); + connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState())); + m_gameActions.append(undoSaveState); + addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); + + quickLoadMenu->addSeparator(); + quickSaveMenu->addSeparator(); + int i; for (i = 1; i < 10; ++i) { quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);

@@ -682,6 +798,14 @@ });

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

@@ -697,6 +821,11 @@ QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu);

connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame())); m_gameActions.append(shutdown); addControlledAction(emulationMenu, shutdown, "shutdown"); + + QAction* yank = new QAction(tr("Yank game pak"), emulationMenu); + connect(yank, SIGNAL(triggered()), m_controller, SLOT(yankPak())); + m_gameActions.append(yank); + addControlledAction(emulationMenu, yank, "yank"); emulationMenu->addSeparator(); QAction* pause = new QAction(tr("&Pause"), emulationMenu);

@@ -706,12 +835,6 @@ pause->setShortcut(tr("Ctrl+P"));

connect(pause, SIGNAL(triggered(bool)), m_controller, SLOT(setPaused(bool))); connect(m_controller, &GameController::gamePaused, [this, pause]() { pause->setChecked(true); - - QImage currentImage(reinterpret_cast<const uchar*>(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);

@@ -820,15 +943,20 @@ QMenu* frameMenu = avMenu->addMenu(tr("Frame size"));

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; #ifdef Q_OS_WIN - fullscreenKeys = QKeySequence("Alt+Enter"); + fullscreenKeys = QKeySequence("Alt+Return"); #else fullscreenKeys = QKeySequence("Ctrl+F"); #endif

@@ -860,20 +988,6 @@ m_config->updateOption("frameskip");

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

@@ -903,14 +1017,12 @@ #endif

#ifdef USE_FFMPEG QAction* recordOutput = new QAction(tr("Record output..."), avMenu); - recordOutput->setShortcut(tr("F11")); connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow())); addControlledAction(avMenu, recordOutput, "recordOutput"); #endif #ifdef USE_MAGICK QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu); - recordGIF->setShortcut(tr("Shift+F11")); connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow())); addControlledAction(avMenu, recordGIF, "recordGIF"); #endif

@@ -922,16 +1034,14 @@ for (int i = 0; i < 4; ++i) {

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

@@ -940,23 +1050,20 @@ for (int i = 0; i < 4; ++i) {

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

@@ -984,8 +1091,10 @@ addControlledAction(toolsMenu, gdbWindow, "gdbWindow");

#endif toolsMenu->addSeparator(); - addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings"); - addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), "shortcuts"); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), + "settings"); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), + "shortcuts"); QAction* keymap = new QAction(tr("Remap keyboard..."), toolsMenu); connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow()));

@@ -1014,6 +1123,21 @@ skipBios->connect([this](const QVariant& value) {

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

@@ -1056,6 +1180,7 @@ }

void Window::attachWidget(QWidget* widget) { m_screenWidget->layout()->addWidget(widget); + m_screenWidget->unsetCursor(); static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget); }

@@ -1137,10 +1262,10 @@ painter.setRenderHint(QPainter::SmoothPixmapTransform);

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);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -20,6 +20,7 @@

#include "GDBController.h" #include "InputController.h" #include "LoadSaveState.h" +#include "LogController.h" struct GBAOptions; struct GBAArguments;

@@ -53,6 +54,7 @@ signals:

void startDrawing(GBAThread*); void shutdown(); void audioBufferSamplesChanged(int samples); + void sampleRateChanged(unsigned samples); void fpsTargetChanged(float target); public slots:

@@ -64,6 +66,8 @@ void exitFullScreen();

void toggleFullScreen(); void loadConfig(); void saveConfig(); + + void replaceROM(); void importSharkport(); void exportSharkport();

@@ -79,6 +83,8 @@

void openPaletteWindow(); void openMemoryWindow(); + void openAboutScreen(); + #ifdef BUILD_SDL void openGamepadWindow(); #endif

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

@@ -111,6 +119,9 @@ void gameStopped();

void gameCrashed(const QString&); void gameFailed(); void unimplementedBiosCall(int); + + void tryMakePortable(); + void mustRestart(); void recordFrame(); void showFPS();

@@ -138,6 +149,8 @@

GameController* m_controller; Display* m_display; QList<QAction*> m_gameActions; + QMap<int, QAction*> m_frameSizes; + LogController m_log; LogView* m_logView; LoadSaveState* m_stateWindow; WindowBackground* m_screenWidget;
M src/platform/qt/main.cppsrc/platform/qt/main.cpp

@@ -9,12 +9,14 @@

#ifdef QT_STATIC #include <QtPlugin> #ifdef _WIN32 -Q_IMPORT_PLUGIN (QWindowsIntegrationPlugin); -Q_IMPORT_PLUGIN (QWindowsAudioPlugin); +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); - return application.exec(); + QGBA::GBAApp application(argc, argv); + return application.exec(); }
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -41,7 +41,7 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VERSION_DEBIAN}" PARENT_SCOPE)

file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c) set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${PIXMAN-1_LIBRARIES}) -include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${PIXMAN-1_INCLUDE_DIR} ${SDL_INCLUDE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${PIXMAN-1_INCLUDE_DIRS} ${SDL_INCLUDE_DIR}) set(SDL_INCLUDE_DIR "${SDL_INCLUDE_DIR}" PARENT_SCOPE) set(SDL_LIBRARY "${SDL_LIBRARY}" PARENT_SCOPE)

@@ -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()
D src/platform/sdl/egl-sdl.c

@@ -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(); -}
A 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 +}
A 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
M src/platform/sdl/gl-sdl.csrc/platform/sdl/gl-sdl.c

@@ -5,17 +5,10 @@ * 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 "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);

@@ -35,34 +28,7 @@ renderer->runloop = GBASDLGLRunloop;

} 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)); - 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 @@ GBAGLContextCreate(&renderer->gl);

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

@@ -109,4 +75,7 @@ if (renderer->gl.d.deinit) {

renderer->gl.d.deinit(&renderer->gl.d); } free(renderer->d.outputBuffer); +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_DeleteContext(renderer->glCtx); +#endif }
A 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 <malloc.h> + +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); +}
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -86,6 +86,8 @@ renderer.filter = opts.resampleVideo;

#ifdef BUILD_GL GBASDLGLCreate(&renderer); +#elif defined(BUILD_GLES2) + GBASDLGLES2Create(&renderer); #else GBASDLSWCreate(&renderer); #endif

@@ -110,6 +112,10 @@

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

@@ -176,5 +182,4 @@

renderer->deinit(renderer); SDL_Quit(); - }
M src/platform/sdl/main.hsrc/platform/sdl/main.h

@@ -20,11 +20,14 @@ #pragma GCC diagnostic push

#pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #include <SDL/SDL.h> -#include <GLES2/gl2.h> #include <EGL/egl.h> #include <bcm_host.h> #pragma GCC diagnostic pop +#endif + +#ifdef BUILD_GLES2 +#include "platform/opengl/gles2.h" #endif #ifdef USE_PIXMAN

@@ -45,6 +48,7 @@ #if SDL_VERSION_ATLEAST(2, 0, 0)

SDL_Window* window; SDL_Texture* sdlTex; SDL_Renderer* sdlRenderer; + SDL_GLContext* glCtx; #endif int viewportWidth;

@@ -56,6 +60,8 @@ bool filter;

#ifdef BUILD_GL struct GBAGLContext gl; +#elif BUILD_GLES2 + struct GBAGLES2Context gl; #endif #ifdef USE_PIXMAN

@@ -68,13 +74,6 @@ EGLDisplay display;

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

@@ -88,5 +87,9 @@ void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer);

#ifdef BUILD_GL void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer); +#endif + +#ifdef BUILD_GLES2 +void GBASDLGLES2Create(struct SDLSoftwareRenderer* renderer); #endif #endif
M src/platform/sdl/sdl-audio.csrc/platform/sdl/sdl-audio.c

@@ -22,7 +22,7 @@ GBALog(0, GBA_LOG_ERROR, "Could not initialize SDL sound system: %s", SDL_GetError());

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;

@@ -76,7 +76,6 @@ #else

UNUSED(context); SDL_PauseAudio(1); #endif - } void GBASDLResumeAudio(struct GBASDLAudio* context) {
M src/platform/sdl/sdl-audio.hsrc/platform/sdl/sdl-audio.h

@@ -15,6 +15,7 @@

struct GBASDLAudio { // Input size_t samples; + unsigned sampleRate; // State SDL_AudioSpec desiredSpec;
M src/platform/sdl/sdl-events.csrc/platform/sdl/sdl-events.c

@@ -22,7 +22,7 @@ #define GUI_MOD KMOD_CTRL

#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);
M src/platform/sdl/sw-sdl.csrc/platform/sdl/sw-sdl.c

@@ -65,7 +65,7 @@ #else

pixman_format_code_t format = PIXMAN_x8b8g8r8; #endif renderer->pix = pixman_image_create_bits(format, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, - renderer->d.outputBuffer, renderer->d.outputBufferStride * BYTES_PER_PIXEL); + renderer->d.outputBuffer, renderer->d.outputBufferStride * BYTES_PER_PIXEL); renderer->screenpix = pixman_image_create_bits(format, renderer->viewportWidth, renderer->viewportHeight, surface->pixels, surface->pitch); pixman_transform_t transform;

@@ -104,17 +104,17 @@ #else

#ifdef USE_PIXMAN if (renderer->ratio > 1) { pixman_image_composite32(PIXMAN_OP_SRC, renderer->pix, 0, renderer->screenpix, - 0, 0, 0, 0, 0, 0, - renderer->viewportWidth, renderer->viewportHeight); + 0, 0, 0, 0, 0, 0, + renderer->viewportWidth, renderer->viewportHeight); } #else switch (renderer->ratio) { #if defined(__ARM_NEON) && COLOR_16_BIT case 2: - _neon2x(surface->pixels, renderer->d.outputBuffer, 240, 160); + _neon2x(surface->pixels, renderer->d.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); break; case 4: - _neon4x(surface->pixels, renderer->d.outputBuffer, 240, 160); + _neon4x(surface->pixels, renderer->d.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); break; #endif case 1:
M src/util/common.hsrc/util/common.h

@@ -22,12 +22,14 @@

#include "version.h" #ifdef _MSC_VER -typedef intptr_t off_t; +#include <sys/types.h> typedef intptr_t ssize_t; +#define inline __inline #define restrict __restrict #define strcasecmp _stricmp #define strncasecmp _strnicmp #define ftruncate _chsize +#define snprintf _snprintf #else #include <strings.h> #include <unistd.h>
M src/util/configuration.csrc/util/configuration.c

@@ -100,6 +100,10 @@ }

HashTableRemove(currentSection, key); } +bool ConfigurationHasSection(const struct Configuration* configuration, const char* section) { + return HashTableLookup(&configuration->sections, section); +} + const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) { const struct Table* currentSection = &configuration->root; if (section) {
M src/util/configuration.hsrc/util/configuration.h

@@ -23,6 +23,7 @@ void ConfigurationSetIntValue(struct Configuration*, const char* section, const char* key, int value);

void ConfigurationSetUIntValue(struct Configuration*, const char* section, const char* key, unsigned value); void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value); +bool ConfigurationHasSection(const struct Configuration*, const char* section); const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key); void ConfigurationClearValue(struct Configuration*, const char* section, const char* key);
M src/util/formatting.csrc/util/formatting.c

@@ -9,17 +9,20 @@ #include <float.h>

int ftostr_l(char* restrict str, size_t size, float f, locale_t locale) { #ifdef HAVE_SNPRINTF_L - return snprintf_l(str, size, locale, "%*.g", FLT_DIG, f); + return snprintf_l(str, size, locale, "%.*g", FLT_DIG, f); #elif defined(HAVE_LOCALE) locale_t old = uselocale(locale); - int res = snprintf(str, size, "%*.g", FLT_DIG, f); + int res = snprintf(str, size, "%.*g", FLT_DIG, f); uselocale(old); return res; -#else +#elif defined(HAVE_SETLOCALE) char* old = setlocale(LC_NUMERIC, locale); - int res = snprintf(str, size, "%*.g", FLT_DIG, f); + int res = snprintf(str, size, "%.*g", FLT_DIG, f); setlocale(LC_NUMERIC, old); return res; +#else + UNUSED(locale); + return snprintf(str, size, "%.*g", FLT_DIG, f); #endif }

@@ -30,11 +33,14 @@ locale_t old = uselocale(locale);

float res = strtof(str, end); uselocale(old); return res; -#else +#elif defined(HAVE_SETLOCALE) char* old = setlocale(LC_NUMERIC, locale); float res = strtof(str, end); setlocale(LC_NUMERIC, old); return res; +#else + UNUSED(locale); + return strtof(str, end); #endif } #endif
M src/util/patch-ips.csrc/util/patch-ips.c

@@ -39,7 +39,8 @@ }

size_t _IPSOutputSize(struct Patch* patch, size_t inSize) { UNUSED(patch); - return inSize; + UNUSED(inSize); + return 16 * 1024 * 1024; // IPS patches can grow up to 16MiB, but not beyond } bool _IPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) {
M src/util/socket.hsrc/util/socket.h

@@ -110,7 +110,6 @@ bindInfo.sin6_family = AF_INET6;

bindInfo.sin6_port = htons(port); memcpy(bindInfo.sin6_addr.s6_addr, bindAddress->ipv6, sizeof(bindInfo.sin6_addr.s6_addr)); err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); - } if (err) { close(sock);
M src/util/string.hsrc/util/string.h

@@ -8,12 +8,12 @@ #define UTIL_STRING_H

#include "util/common.h" -#ifndef strndup +#ifndef HAVE_STRNDUP // This is sometimes a macro char* strndup(const char* start, size_t len); #endif -#ifndef strdup +#ifndef HAVE_STRDUP char* strdup(const char* str); #endif
M src/util/vfs.csrc/util/vfs.c

@@ -5,8 +5,38 @@ * 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 "vfs.h" +struct VFile* VFileOpen(const char* path, int flags) { +#ifdef USE_VFS_FILE + const char* chflags; + switch (flags & O_ACCMODE) { + case O_WRONLY: + if (flags & O_APPEND) { + chflags = "ab"; + } else { + chflags = "wb"; + } + break; + case O_RDWR: + if (flags & O_APPEND) { + chflags = "a+b"; + } else if (flags & O_TRUNC) { + chflags = "w+b"; + } else { + chflags = "r+b"; + } + break; + case O_RDONLY: + chflags = "rb"; + break; + } + return VFileFOpen(path, chflags); +#else + return VFileOpenFD(path, flags); +#endif +} + ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { - ssize_t bytesRead = 0; + size_t bytesRead = 0; while (bytesRead < size - 1) { ssize_t newRead = vf->read(vf, &buffer[bytesRead], 1); if (newRead <= 0) {

@@ -50,3 +80,31 @@ STORE_16LE(lehword, 0, hword);

} return r; } + +struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + struct VFile* vf; + if (!dir) { + if (!realPath) { + return 0; + } + char* dotPoint = strrchr(realPath, '.'); + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + if (dotPoint > strrchr(realPath, '/')) { + int len = dotPoint - realPath; + strncpy(path, realPath, len); + path[len] = 0; + strncat(path + len, suffix, PATH_MAX - len - 1); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); + } + vf = VFileOpen(path, mode); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); + vf = dir->openFile(dir, path, mode); + } + return vf; +}
M src/util/vfs.hsrc/util/vfs.h

@@ -39,6 +39,7 @@ void* (*map)(struct VFile* vf, size_t size, int flags);

void (*unmap)(struct VFile* vf, void* memory, size_t size); void (*truncate)(struct VFile* vf, size_t size); ssize_t (*size)(struct VFile* vf); + bool (*sync)(struct VFile* vf, const void* buffer, size_t size); }; struct VDirEntry {

@@ -53,6 +54,8 @@ struct VFile* (*openFile)(struct VDir* vd, const char* name, int mode);

}; struct VFile* VFileOpen(const char* path, int flags); + +struct VFile* VFileOpenFD(const char* path, int flags); struct VFile* VFileFOpen(const char* path, const char* mode); struct VFile* VFileFromFD(int fd); struct VFile* VFileFromMemory(void* mem, size_t size);

@@ -68,8 +71,10 @@ #ifdef USE_LZMA

struct VDir* VDirOpen7z(const char* path, int flags); #endif -struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode); -struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode); +struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, + int mode); +struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, + const char* infix, const char* suffix, int mode); ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size);
M src/util/vfs/vfs-dirent.csrc/util/vfs/vfs-dirent.c

@@ -190,31 +190,3 @@ return vdede->ent->d_name;

} return 0; } - -struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - struct VFile* vf; - if (!dir) { - if (!realPath) { - return 0; - } - char* dotPoint = strrchr(realPath, '.'); - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - if (dotPoint > strrchr(realPath, '/')) { - int len = dotPoint - realPath; - strncpy(path, realPath, len); - path[len] = 0; - strncat(path + len, suffix, PATH_MAX - len - 1); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); - } - vf = VFileOpen(path, mode); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); - vf = dir->openFile(dir, path, mode); - } - return vf; -}
M src/util/vfs/vfs-fd.csrc/util/vfs/vfs-fd.c

@@ -9,6 +9,8 @@ #include <fcntl.h>

#include <sys/stat.h> #ifndef _WIN32 #include <sys/mman.h> +#else +#include <windows.h> #endif struct VFileFD {

@@ -28,15 +30,20 @@ static void* _vfdMap(struct VFile* vf, size_t size, int flags);

static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); static void _vfdTruncate(struct VFile* vf, size_t size); static ssize_t _vfdSize(struct VFile* vf); +static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size); -struct VFile* VFileOpen(const char* path, int flags) { +struct VFile* VFileOpenFD(const char* path, int flags) { if (!path) { return 0; } #ifdef _WIN32 flags |= O_BINARY; -#endif + wchar_t wpath[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath) / sizeof(*wpath)); + int fd = _wopen(wpath, flags, 0666); +#else int fd = open(path, flags, 0666); +#endif return VFileFromFD(fd); }

@@ -60,6 +67,7 @@ vfd->d.map = _vfdMap;

vfd->d.unmap = _vfdUnmap; vfd->d.truncate = _vfdTruncate; vfd->d.size = _vfdSize; + vfd->d.sync = _vfdSync; return &vfd->d; }

@@ -160,3 +168,14 @@ return -1;

} return stat.st_size; } + +static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size) { + UNUSED(buffer); + UNUSED(size); + struct VFileFD* vfd = (struct VFileFD*) vf; +#ifndef _WIN32 + return fsync(vfd->fd) == 0; +#else + return FlushFileBuffers((HANDLE) _get_osfhandle(vfd->fd)); +#endif +}
M src/util/vfs/vfs-file.csrc/util/vfs/vfs-file.c

@@ -7,11 +7,13 @@ #include "util/vfs.h"

#include "util/memory.h" +#include <errno.h> #include <stdio.h> struct VFileFILE { struct VFile d; FILE* file; + bool writable; }; static bool _vffClose(struct VFile* vf);

@@ -22,12 +24,16 @@ static void* _vffMap(struct VFile* vf, size_t size, int flags);

static void _vffUnmap(struct VFile* vf, void* memory, size_t size); static void _vffTruncate(struct VFile* vf, size_t size); static ssize_t _vffSize(struct VFile* vf); +static bool _vffSync(struct VFile* vf, const void* buffer, size_t size); struct VFile* VFileFOpen(const char* path, const char* mode) { if (!path && !mode) { return 0; } FILE* file = fopen(path, mode); + if (!file && errno == ENOENT && strcmp(mode, "r+b") == 0) { + file = fopen(path, "w+b"); + } return VFileFromFILE(file); }

@@ -42,6 +48,7 @@ return 0;

} vff->file = file; + vff->writable = false; vff->d.close = _vffClose; vff->d.seek = _vffSeek; vff->d.read = _vffRead;

@@ -51,6 +58,7 @@ vff->d.map = _vffMap;

vff->d.unmap = _vffUnmap; vff->d.truncate = _vffTruncate; vff->d.size = _vffSize; + vff->d.sync = _vffSync; return &vff->d; }

@@ -80,8 +88,10 @@ return fwrite(buffer, size, 1, vff->file);

} static void* _vffMap(struct VFile* vf, size_t size, int flags) { - UNUSED(flags); struct VFileFILE* vff = (struct VFileFILE*) vf; + if (flags & MAP_WRITE) { + vff->writable = true; + } void* mem = anonymousMemoryMap(size); if (!mem) { return 0;

@@ -95,10 +105,12 @@ }

static void _vffUnmap(struct VFile* vf, void* memory, size_t size) { struct VFileFILE* vff = (struct VFileFILE*) vf; - long pos = ftell(vff->file); - fseek(vff->file, 0, SEEK_SET); - fwrite(memory, size, 1, vff->file); - fseek(vff->file, pos, SEEK_SET); + if (vff->writable) { + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_SET); + fwrite(memory, size, 1, vff->file); + fseek(vff->file, pos, SEEK_SET); + } mappedMemoryFree(memory, size); }

@@ -130,3 +142,14 @@ ssize_t size = ftell(vff->file);

fseek(vff->file, pos, SEEK_SET); return size; } + +static bool _vffSync(struct VFile* vf, const void* buffer, size_t size) { + struct VFileFILE* vff = (struct VFileFILE*) vf; + if (buffer && size) { + long pos = ftell(vff->file); + fseek(vff->file, 0, SEEK_SET); + fwrite(buffer, size, 1, vff->file); + fseek(vff->file, pos, SEEK_SET); + } + return fflush(vff->file) == 0; +}
M src/util/vfs/vfs-lzma.csrc/util/vfs/vfs-lzma.c

@@ -56,6 +56,7 @@ static void* _vf7zMap(struct VFile* vf, size_t size, int flags);

static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size); static void _vf7zTruncate(struct VFile* vf, size_t size); static ssize_t _vf7zSize(struct VFile* vf); +static bool _vf7zSync(struct VFile* vf, const void* buffer, size_t size); static bool _vd7zClose(struct VDir* vd); static void _vd7zRewind(struct VDir* vd);

@@ -94,6 +95,7 @@

SzArEx_Init(&vd->db); SRes res = SzArEx_Open(&vd->db, &vd->lookStream.s, &vd->allocImp, &vd->allocTempImp); if (res != SZ_OK) { + File_Close(&vd->archiveStream.file); free(vd); return 0; }

@@ -114,6 +116,7 @@

bool _vf7zClose(struct VFile* vf) { struct VFile7z* vf7z = (struct VFile7z*) vf; IAlloc_Free(&vf7z->vd->allocImp, vf7z->outBuffer); + File_Close(&vf7z->vd->archiveStream.file); return true; }

@@ -291,6 +294,7 @@ vf->d.map = _vf7zMap;

vf->d.unmap = _vf7zUnmap; vf->d.truncate = _vf7zTruncate; vf->d.size = _vf7zSize; + vf->d.sync = _vf7zSync; return &vf->d; }

@@ -306,6 +310,13 @@ free(name);

} return vde7z->utf8; +} + +bool _vf7zSync(struct VFile* vf, const void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); + return false; } #endif
M src/util/vfs/vfs-mem.csrc/util/vfs/vfs-mem.c

@@ -20,6 +20,7 @@ static void* _vfmMap(struct VFile* vf, size_t size, int flags);

static void _vfmUnmap(struct VFile* vf, void* memory, size_t size); static void _vfmTruncate(struct VFile* vf, size_t size); static ssize_t _vfmSize(struct VFile* vf); +static bool _vfmSync(struct VFile* vf, const void* buffer, size_t size); struct VFile* VFileFromMemory(void* mem, size_t size) { if (!mem || !size) {

@@ -43,6 +44,7 @@ vfm->d.map = _vfmMap;

vfm->d.unmap = _vfmUnmap; vfm->d.truncate = _vfmTruncate; vfm->d.size = _vfmSize; + vfm->d.sync = _vfmSync; return &vfm->d; }

@@ -137,3 +139,10 @@ ssize_t _vfmSize(struct VFile* vf) {

struct VFileMem* vfm = (struct VFileMem*) vf; return vfm->size; } + +bool _vfmSync(struct VFile* vf, const void* buffer, size_t size) { + UNUSED(vf); + UNUSED(buffer); + UNUSED(size); + return true; +}
M src/util/vfs/vfs-zip.csrc/util/vfs/vfs-zip.c

@@ -43,6 +43,7 @@ static void* _vfzMap(struct VFile* vf, size_t size, int flags);

static void _vfzUnmap(struct VFile* vf, void* memory, size_t size); static void _vfzTruncate(struct VFile* vf, size_t size); static ssize_t _vfzSize(struct VFile* vf); +static bool _vfzSync(struct VFile* vf, const void* buffer, size_t size); static bool _vdzClose(struct VDir* vd); static void _vdzRewind(struct VDir* vd);

@@ -289,6 +290,7 @@ vfz->d.map = _vfzMap;

vfz->d.unmap = _vfzUnmap; vfz->d.truncate = _vfzTruncate; vfz->d.size = _vfzSize; + vfz->d.sync = _vfzSync; return &vfz->d; }

@@ -300,6 +302,13 @@ if (zip_stat_index(vdez->z, vdez->index, 0, &s) < 0) {

return 0; } return s.name; +} + +bool _vfzSync(struct VFile* vf, const void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); + return false; } #endif
A tools/debian/changelog

@@ -0,0 +1,5 @@

+mgba (0.3.0-1) UNRELEASED; urgency=low + + * Initial release (closes: Bug#787470). + + -- Sérgio Benjamim <sergio_br2@yahoo.com.br> Mon, 17 Aug 2015 18:40:00 -0300
A tools/debian/clean

@@ -0,0 +1,2 @@

+debian/libmgba.install +debian/libretro-mgba.install
A tools/debian/compat

@@ -0,0 +1,1 @@

+9
A tools/debian/control

@@ -0,0 +1,78 @@

+Source: mgba +Section: otherosfs +Priority: extra +Maintainer: Sérgio Benjamim <sergio_br2@yahoo.com.br> +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.
A 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) <jeffrey@endrift.com> +Source: https://github.com/mgba-emu/mgba +Comment: This package was debianized by + Sergio Benjamim (sergio-br2) <sergio_br2@yahoo.com.br> 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) <sergio_br2@yahoo.com.br> +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'.
A tools/debian/docs

@@ -0,0 +1,2 @@

+README.md +CHANGES
A tools/debian/libmgba.install.in

@@ -0,0 +1,1 @@

+obj/libmgba.so* usr/lib/@DEB_HOST_MULTIARCH@/
A tools/debian/libretro-mgba.install.in

@@ -0,0 +1,1 @@

+obj/mgba_libretro.so usr/lib/@DEB_HOST_MULTIARCH@/libretro
A tools/debian/mgba-qt.install

@@ -0,0 +1,3 @@

+usr/bin/mgba-qt +res/mgba-qt.desktop usr/share/applications +usr/share/icons
A tools/debian/mgba-qt.manpages

@@ -0,0 +1,1 @@

+doc/mgba-qt.6
A tools/debian/mgba-sdl.install

@@ -0,0 +1,1 @@

+usr/bin/mgba
A tools/debian/mgba-sdl.manpages

@@ -0,0 +1,1 @@

+doc/mgba.6
A 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
A tools/debian/source/format

@@ -0,0 +1,1 @@

+3.0 (quilt)
A 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
M tools/sanitize-deb.shtools/sanitize-deb.sh

@@ -56,8 +56,8 @@ sed -i~ "s/,,*/,/g" deb-temp/DEBIAN/control

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
M version.cmakeversion.cmake

@@ -1,5 +1,47 @@

-if(NOT ${BINARY_NAME}_SOURCE_DIR) - set(${BINARY_NAME}_SOURCE_DIR ${CMAKE_SOURCE_DIR}) +if(NOT PROJECT_NAME) + set(PROJECT_NAME "mGBA") endif() +set(LIB_VERSION_MAJOR 0) +set(LIB_VERSION_MINOR 4) +set(LIB_VERSION_PATCH 0) +set(LIB_VERSION_ABI 0.4) +set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) -configure_file("${${BINARY_NAME}_SOURCE_DIR}/src/util/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c") +execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND git describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + +if(GIT_REV STREQUAL "") + set(GIT_REV -1) +endif() +if(NOT GIT_TAG STREQUAL "") + set(VERSION_STRING ${GIT_TAG}) +elseif(GIT_BRANCH STREQUAL "") + set(VERSION_STRING ${LIB_VERSION_STRING}) +else() + if(GIT_BRANCH STREQUAL "master") + set(VERSION_STRING ${GIT_REV}-${GIT_COMMIT_SHORT}) + else() + set(VERSION_STRING ${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT}) + endif() + + if(NOT LIB_VERSION_ABI STREQUAL GIT_BRANCH) + set(VERSION_STRING ${LIB_VERSION_ABI}-${VERSION_STRING}) + endif() +endif() + +if(NOT GIT_COMMIT) + set(GIT_COMMIT "(unknown)") +endif() +if(NOT GIT_COMMIT_SHORT) + set(GIT_COMMIT_SHORT "(unknown)") +endif() +if(NOT GIT_BRANCH) + set(GIT_BRANCH "(unknown)") +endif() + +if(CONFIG_FILE AND OUT_FILE) + configure_file("${CONFIG_FILE}" "${OUT_FILE}") +endif()