Merge branch 'master' into port/3ds

This commit is contained in:
Jeffrey Pfau 2015-08-17 00:02:36 -07:00
commit e17e4fd190
186 changed files with 7327 additions and 3223 deletions

34
.clang-format Normal file
View File

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

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/build
*.user*
*~
*.swp

114
CHANGES
View File

@ -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 @@ Features:
- 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
- GBA Audio: Force audio FIFOs to 32-bit
- GBA Memory: Improve Thumb open bus behavior
- VFS: Fix resource leaks if some allocations fail
- Video: Fix an issue with very long filenames
- 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
- 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
- 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 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
- 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: 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
- 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 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
- Qt: Cap the maximum number of multiplayer windows
- Qt: Fix maximum year in sensor override
- 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
- 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:

View File

@ -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 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
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 @@ find_feature(USE_MAGICK "MagickWand")
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 @@ if(APPLE)
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 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
endif()
endif()
if(BUILD_RASPI)
set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE)
endif()
if(BUILD_PANDORA)
add_definitions(-DBUILD_PANDORA)
endif()
@ -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 @@ if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE)
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 @@ endif()
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})
@ -420,6 +419,10 @@ if(BUILD_GL)
add_definitions(-DBUILD_GL)
endif()
if(BUILD_GLES2)
add_definitions(-DBUILD_GLES2)
endif()
if(BUILD_LIBRETRO)
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})

41
PORTING.md Normal file
View File

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

View File

@ -15,6 +15,7 @@ Features
- 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 @@ Features
- 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 @@ Controls are configurable in the menu. The default gamepad controls are mapped s
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 @@ Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to
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 @@ Footnotes
- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5))
- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141))
- 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.

118
doc/mgba-qt.6 Normal file
View File

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

259
doc/mgba.6 Normal file
View File

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

Binary file not shown.

View File

@ -7,130 +7,266 @@
<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"/>
<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.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_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"/>
<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>

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 KiB

After

Width:  |  Height:  |  Size: 518 KiB

View File

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

View File

@ -42,30 +42,29 @@ void ARMSetPrivilegeMode(struct ARMCore* cpu, enum PrivilegeMode mode) {
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 @@ void ARMRaiseSWI(struct ARMCore* cpu) {
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 @@ void ARMRunLoop(struct ARMCore* 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;

View File

@ -101,8 +101,10 @@ struct ARMMemory {
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 @@ struct ARMMemory {
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);

View File

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

View File

@ -16,9 +16,9 @@
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_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,71 +40,53 @@
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_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_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 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(ADD3, ADD)
DEFINE_DATA_FORM_1_DECODER_THUMB(SUB3, SUB)
DEFINE_DATA_FORM_1_DECODER_THUMB(ADD)
DEFINE_DATA_FORM_1_DECODER_THUMB(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(ADD1, ADD)
DEFINE_DATA_FORM_2_DECODER_THUMB(SUB1, SUB)
DEFINE_DATA_FORM_2_DECODER_THUMB(ADD)
DEFINE_DATA_FORM_2_DECODER_THUMB(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)
DEFINE_DATA_FORM_3_DECODER_THUMB(MOV1, MOV, ARM_OPERAND_AFFECTED_1)
@ -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 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0)
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 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0)
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_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_MEM_STORE_THUMB(NAME, RD, MNEMONIC, REG) \
DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, RD, MNEMONIC, REG, STORE_CYCLES)
DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD5, ADD, ARM_PC)
DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD6, ADD, ARM_SP)
#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_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; \
@ -211,9 +184,6 @@ DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(ADD6, ADD, DATA, ARM_SP)
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)
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, LDR, LOAD_CYCLES, ARM_ACCESS_HALFWORD)
@ -237,7 +207,7 @@ DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFW
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)

View File

@ -209,7 +209,8 @@ struct ARMInstructionInfo {
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

View File

@ -29,105 +29,4 @@
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

View File

@ -18,17 +18,17 @@
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, 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, 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)), \

View File

@ -259,7 +259,7 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
#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 @@ DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(STRT,
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;
})

View File

@ -15,5 +15,4 @@ struct ARMCore;
typedef void (*ARMInstruction)(struct ARMCore*, uint32_t opcode);
const ARMInstruction _armTable[0x1000];
#endif

View File

@ -35,35 +35,39 @@
#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;

View File

@ -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,16 +54,13 @@
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) {
cpu->gprs[rd] = cpu->gprs[rm];
@ -104,40 +101,31 @@ DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STR1, cpu->memory.store32(cpu, cpu->gprs[rm
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_INSTRUCTION_THUMB(NAME, \
int rm = RM; \
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(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_INSTRUCTION_THUMB(NAME, \
int immediate = IMMEDIATE; \
int rm = (opcode >> 6) & 0x0007; \
int rd = opcode & 0x0007; \
int rn = (opcode >> 3) & 0x0007; \
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 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 DEFINE_DATA_FORM_3_INSTRUCTION_EX_THUMB(NAME, RD, BODY) \
DEFINE_INSTRUCTION_THUMB(NAME, \
int rd = RD; \
int immediate = opcode & 0x00FF; \
int immediate = (opcode >> 6) & 0x0007; \
int rd = opcode & 0x0007; \
int rn = (opcode >> 3) & 0x0007; \
BODY;)
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_THUMB(NAME, BODY) \
COUNT_CALL_3(DEFINE_DATA_FORM_3_INSTRUCTION_EX_THUMB, NAME ## _R, BODY)
DEFINE_INSTRUCTION_THUMB(NAME, \
int rd = (opcode >> 8) & 0x0007; \
int immediate = opcode & 0x00FF; \
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))
@ -260,15 +248,12 @@ DEFINE_INSTRUCTION_WITH_HIGH_THUMB(MOV3,
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,16 +261,13 @@ DEFINE_IMMEDIATE_WITH_REGISTER_THUMB(STR3, cpu->memory.store32(cpu, cpu->gprs[AR
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;)
DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.load16(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[r
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 @@ DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, cpu->memory.store16(cpu, cpu->gprs[
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_CONDITIONAL_BRANCH_THUMB(LE)
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 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POP,
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 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POPR,
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 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSH,
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,

View File

@ -18,7 +18,9 @@ static const char* ERROR_OVERFLOW = "Arguments overflow";
static struct CLIDebugger* _activeDebugger;
#ifndef NDEBUG
static void _breakInto(struct CLIDebugger*, struct CLIDebugVector*);
#endif
static void _continue(struct CLIDebugger*, struct CLIDebugVector*);
static void _disassemble(struct CLIDebugger*, struct CLIDebugVector*);
static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*);
@ -56,6 +58,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
{ "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
{ "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
{ "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" },
{ "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" },
{ "c", _continue, 0, "Continue execution" },
{ "continue", _continue, 0, "Continue execution" },
{ "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" },
@ -82,7 +86,7 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "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 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" },
{ "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" },
{ "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" },
#ifndef NDEBUG
{ "!", _breakInto, 0, "Break into attached debugger (for developers)" },
#endif
{ 0, 0, 0, 0 }
};
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 @@ static void _breakInto(struct CLIDebugger* debugger, struct CLIDebugVector* dv)
#endif
sigaction(SIGTRAP, &osa, 0);
}
#endif
static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
UNUSED(dv);
@ -196,7 +204,7 @@ static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector
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 _print(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
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 _printBin(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
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 @@ static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* 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;

View File

@ -155,6 +155,7 @@ void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address)
if (breakpoint->address == address) {
*previous = *next;
free(breakpoint);
continue;
}
previous = next;
}
@ -179,6 +180,7 @@ void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address)
if (watchpoint->address == address) {
*previous = *next;
free(watchpoint);
continue;
}
previous = next;
}

View File

@ -108,7 +108,7 @@ static uint32_t _hex2int(const char* hex, int maxDigits) {
letter = *hex - '0';
if (letter > 9) {
letter = *hex - 'a';
if (letter > 5) {
if (letter > 5) {
break;
}
value *= 0x10;

View File

@ -29,14 +29,14 @@ static uint32_t _popcount32(unsigned bits) {
} \
}
#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 @@ static uint32_t _popcount32(unsigned bits) {
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) \

View File

@ -118,8 +118,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) {
break;
case LEX_EXPECT_BINARY:
switch (token) {
case '0':
case '1':
case '0':
case '1':
// TODO: handle overflow
next <<= 1;
next += token - '0';

View File

@ -41,7 +41,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
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 @@ void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
int32_t GBAAudioProcessEvents(struct GBAAudio* audio, int32_t cycles) {
audio->nextEvent -= cycles;
audio->eventDiff += cycles;
if (audio->nextEvent <= 0) {
while (audio->nextEvent <= 0) {
audio->nextEvent = INT_MAX;
if (audio->enable) {
if (audio->playingCh1 && !audio->ch1.envelope.dead) {
@ -456,7 +456,12 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t 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 @@ static int32_t _updateChannel3(struct GBAAudioChannel3* ch) {
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);

View File

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

View File

@ -175,7 +175,7 @@ static void _Div(struct GBA* gba, int32_t num, int32_t denom) {
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 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
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 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
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 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
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 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
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,16 +290,20 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
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);
break;
@ -457,7 +464,6 @@ static void _unHuffman(struct GBA* gba) {
block = 0;
}
}
}
cpu->gprs[0] = source;
cpu->gprs[1] = dest;

View File

@ -197,8 +197,9 @@ bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
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);

View File

@ -297,9 +297,10 @@ bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t
switch (set->gsaVersion) {
case 0:
GBACheatSetGameSharkVersion(set, 3);
// Fall through
case 1:
GBACheatSetGameSharkVersion(set, 3);
// Fall through
case 3:
GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
return GBACheatAddProActionReplayRaw(set, o1, o2);
}

View File

@ -79,8 +79,10 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
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 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
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 @@ void GBAReset(struct ARMCore* cpu) {
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 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
int32_t cycles = cpu->nextEvent;
int32_t nextEvent = INT_MAX;
int32_t testEvent;
#ifndef NDEBUG
if (cycles < 0) {
GBALog(gba, GBA_LOG_FATAL, "Negative cycles passed: %i", cycles);
}
#endif
gba->bus = cpu->prefetch[1];
if (cpu->executionMode == MODE_THUMB) {
@ -221,7 +243,7 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
if (timer->enable) {
timer->nextEvent -= cycles;
timer->lastEvent -= cycles;
if (timer->nextEvent <= 0) {
while (timer->nextEvent <= 0) {
timer->lastEvent = timer->nextEvent;
timer->nextEvent += timer->overflowInterval;
gba->memory.io[REG_TM0CNT_LO >> 1] = timer->reload;
@ -365,6 +387,7 @@ void GBADetachDebugger(struct GBA* gba) {
}
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 @@ void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char
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 @@ void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char
// 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 GBALoadBIOS(struct GBA* gba, struct VFile* vf) {
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 GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
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 @@ void GBAWriteIE(struct GBA* gba, uint16_t value) {
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 @@ void GBAHalt(struct GBA* gba) {
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 GBAGetGameTitle(struct GBA* gba, char* out) {
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 GBAHitStub(struct ARMCore* cpu, uint32_t opcode) {
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 GBAFrameStarted(struct GBA* gba) {
}
void GBAFrameEnded(struct GBA* gba) {
GBASavedataClean(&gba->memory.savedata, gba->video.frameCounter);
if (gba->rr) {
gba->rr->nextFrame(gba->rr);
}
@ -747,6 +792,10 @@ void GBAFrameEnded(struct GBA* gba) {
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();
if (!thread) {
return;

View File

@ -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 @@ enum GBAIRQ {
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 @@ enum {
};
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 GBA {
struct GBARRContext* rr;
void* pristineRom;
size_t pristineRomSize;
size_t yankedRomSize;
uint32_t romCrc32;
struct VFile* romVf;
struct VFile* biosVf;
@ -155,6 +111,8 @@ struct GBA {
GBALogHandler logHandler;
enum GBALogLevel logLevel;
struct GBAAVStream* stream;
struct GBAKeyCallback* keyCallback;
struct GBAStopCallback* stopCallback;
enum GBAIdleLoopOptimization idleOptimization;
uint32_t idleLoop;
@ -166,6 +124,7 @@ struct GBA {
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 GBAGetGameTitle(struct GBA* gba, char* out);
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)

View File

@ -5,7 +5,11 @@
* 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 @@ static const int RTC_BYTES[8] = {
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 @@ unsigned _rtcBCD(unsigned value) {
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 @@ uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) {
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 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
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 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
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);
}
}

View File

@ -7,6 +7,7 @@
#define GBA_HARDWARE_H
#include "util/common.h"
#include "gba/interface.h"
#include "macros.h"
@ -14,25 +15,15 @@
#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 @@ enum GBAHardwareDevice {
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 @@ struct GBACartridgeHardware {
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);
@ -129,6 +138,12 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* gpio, uint32_t address, u
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);
void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, const struct GBASerializedState* state);

View File

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

View File

@ -257,7 +257,10 @@ void _unbindAxis(uint32_t axis, void* dp, void* user) {
}
}
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 @@ static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* section
_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 @@ enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key
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 @@ void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input
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 @@ int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, in
void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) {
struct GBAInputMapImpl* impl = _guaranteeMap(map, type);
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 @@ void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Config
_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) {

View File

@ -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 GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl
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

107
src/gba/interface.h Normal file
View File

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

View File

@ -333,7 +333,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
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 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) {
if (!value) {
GBAHalt(gba);
} else {
GBALog(gba, GBA_LOG_STUB, "Stop unimplemented");
GBAStop(gba);
}
return;
}
@ -584,14 +584,18 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
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;

View File

@ -18,15 +18,16 @@
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 @@ void GBAMemoryInit(struct GBA* gba) {
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 @@ void GBAMemoryInit(struct GBA* gba) {
cpu->memory.activeSeqCycles16 = 0;
cpu->memory.activeNonseqCycles32 = 0;
cpu->memory.activeNonseqCycles16 = 0;
cpu->memory.activeUncachedCycles32 = 0;
cpu->memory.activeUncachedCycles16 = 0;
gba->memory.biosPrefetch = 0;
}
@ -113,6 +113,9 @@ void GBAMemoryReset(struct GBA* gba) {
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);
GBALog(gba, GBA_LOG_FATAL, "Could not map memory");
@ -232,6 +235,8 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
}
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 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
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 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
#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 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
} 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 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
}
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 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
} 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 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
}
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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
#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 @@ void GBAStore32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycle
}
if (cycleCounter) {
*cycleCounter += 1 + wait;
++wait;
if (address >> BASE_OFFSET < REGION_CART0) {
wait = GBAMemoryStall(cpu, wait);
}
*cycleCounter += wait;
}
}
@ -707,8 +732,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
case REGION_VRAM:
if ((address & 0x0001FFFF) < SIZE_VRAM) {
STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
} else {
STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
}
break;
case REGION_OAM:
@ -740,7 +767,11 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
}
if (cycleCounter) {
*cycleCounter += 1 + wait;
++wait;
if (address >> BASE_OFFSET < REGION_CART0) {
wait = GBAMemoryStall(cpu, wait);
}
*cycleCounter += wait;
}
}
@ -769,8 +800,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OBJ: 0x%08X", address);
break;
}
((int8_t*) gba->video.renderer->vram)[address & 0x1FFFE] = value;
((int8_t*) gba->video.renderer->vram)[(address & 0x1FFFE) | 1] = value;
gba->video.renderer->vram[(address & 0x1FFFE) >> 1] = ((uint8_t) value) | (value << 8);
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
break;
case REGION_OAM:
GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot Store8 to OAM: 0x%08X", address);
@ -793,6 +824,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
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 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
}
if (cycleCounter) {
*cycleCounter += 1 + wait;
++wait;
if (address >> BASE_OFFSET < REGION_CART0) {
wait = GBAMemoryStall(cpu, wait);
}
*cycleCounter += wait;
}
}
@ -1098,6 +1134,10 @@ uint32_t GBALoadMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum L
}
if (cycleCounter) {
++wait;
if (address >> BASE_OFFSET < REGION_CART0) {
wait = GBAMemoryStall(cpu, wait);
}
*cycleCounter += wait;
}
@ -1204,6 +1244,9 @@ uint32_t GBAStoreMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum
}
if (cycleCounter) {
if (address >> BASE_OFFSET < REGION_CART0) {
wait = GBAMemoryStall(cpu, wait);
}
*cycleCounter += wait;
}
@ -1230,7 +1273,7 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
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 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
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->prefetch = prefetch;
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];
cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion];
cpu->memory.activeSeqCycles16 = memory->waitstatesSeq16[memory->activeRegion];
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];
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 @@ int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles) {
}
memory->nextDMA -= cycles;
memory->eventDiff += cycles;
if (memory->nextDMA <= 0) {
while (memory->nextDMA <= 0) {
struct GBADMA* dma = &memory->dma[memory->activeDMA];
GBAMemoryServiceDMA(gba, memory->activeDMA, dma);
GBAMemoryUpdateDMAs(gba, memory->eventDiff);
@ -1524,6 +1532,52 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
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) {
memcpy(state->wram, memory->wram, SIZE_WORKING_RAM);
memcpy(state->iwram, memory->iwram, SIZE_WORKING_IRAM);

View File

@ -86,7 +86,6 @@ enum DMATiming {
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 @@ struct GBAMemory {
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* o
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);

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
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 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
}
}
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 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
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 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
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 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
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 _flashSwitchBank(struct GBASavedata* savedata, int bank) {
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 _flashErase(struct GBASavedata* savedata) {
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);

View File

@ -51,6 +51,11 @@ enum FlashManufacturer {
FLASH_MFG_SANYO = 0x1362
};
enum SavedataDirty {
SAVEDATA_DIRT_NEW = 1,
SAVEDATA_DIRT_SEEN = 2
};
enum {
SAVEDATA_FLASH_BASE = 0x0E005555,
@ -77,6 +82,9 @@ struct GBASavedata {
unsigned settling;
int dust;
enum SavedataDirty dirty;
uint32_t dirtAge;
enum FlashStateMachine flashState;
};
@ -98,6 +106,8 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
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);
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData);

View File

@ -61,67 +61,85 @@ 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 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 @@ void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
if (gba->rr) {
gba->rr->stateLoaded(gba->rr, state);
}
return true;
}
#ifndef _3DS
@ -220,7 +239,9 @@ static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
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 @@ static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
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 GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
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 GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
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 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) {
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 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
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) {

View File

@ -136,7 +136,8 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
* | 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 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
* | 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 @@ struct GBASerializedState {
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);

View File

@ -123,7 +123,7 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
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;
@ -149,7 +149,6 @@ cleanup:
return false;
}
bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {
union {
char c[0x1C];

View File

@ -48,14 +48,11 @@ static void _switchMode(struct GBASIO* sio) {
}
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 @@ void GBASIODeinit(struct GBASIO* sio) {
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) {

View File

@ -8,36 +8,16 @@
#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 {
struct GBASIODriver* normal;
struct GBASIODriver* multiplayer;
@ -85,6 +65,7 @@ struct GBASIO {
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);

View File

@ -30,11 +30,9 @@ struct CLIDebuggerCommandSummary _GBACLIDebuggerCommands[] = {
{ "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 @@ struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread* context) {
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;

View File

@ -6,21 +6,21 @@
#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

View File

@ -13,6 +13,7 @@
#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 GBAConfigDirectory(char* out, size_t outLength) {
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 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
opts->audioBuffers = audioBuffers;
}
_lookupUIntValue(config, "sampleRate", &opts->sampleRate);
int fakeBool;
if (_lookupIntValue(config, "useBios", &fakeBool)) {
@ -246,6 +306,7 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval);
ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);

View File

@ -29,6 +29,7 @@ struct GBAOptions {
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);

View File

@ -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 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
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 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
}
}
}
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);
}
}

View File

@ -25,5 +25,6 @@ void GBAOverrideSave(struct Configuration*, const struct GBACartridgeOverride* o
struct GBA;
void GBAOverrideApply(struct GBA*, const struct GBACartridgeOverride*);
void GBAOverrideApplyDefaults(struct GBA*);
#endif

View File

@ -23,6 +23,8 @@
#include <signal.h>
static void _loadGameDir(struct GBAThread* threadContext);
static const float _defaultFPSTarget = 60.f;
#ifndef DISABLE_THREADING
@ -94,10 +96,22 @@ static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
}
}
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 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
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 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
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 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
GBARRInitPlay(&gba);
}
if (threadContext->skipBios) {
if (threadContext->skipBios && gba.memory.rom) {
GBASkipBIOS(&cpu);
}
@ -283,7 +305,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
MutexUnlock(&threadContext->stateMutex);
if (resetScheduled) {
ARMReset(&cpu);
if (threadContext->skipBios) {
if (threadContext->skipBios && gba.memory.rom) {
GBASkipBIOS(&cpu);
}
}
@ -352,18 +374,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
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 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
}
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 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
if (!threadContext->patch) {
threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ups", O_RDONLY);
}
if (!threadContext->patch) {
threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".ips", O_RDONLY);
}
if (!threadContext->patch) {
threadContext->patch = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "patch", ".bps", O_RDONLY);
}
MutexInit(&threadContext->stateMutex);
ConditionInit(&threadContext->stateCond);
@ -437,7 +440,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
threadContext->interruptDepth = 0;
#ifndef _WIN32
#ifdef USE_PTHREADS
sigset_t signals;
sigemptyset(&signals);
sigaddset(&signals, SIGINT);
@ -678,6 +681,67 @@ void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
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);
@ -688,6 +752,10 @@ struct GBAThread* GBAThreadGetContext(void) {
InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
return TlsGetValue(_contextKey);
}
#else
struct GBAThread* GBAThreadGetContext(void) {
return 0;
}
#endif
#ifdef USE_PNG

View File

@ -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 @@ struct GBAThread {
ThreadCallback startCallback;
ThreadCallback cleanCallback;
ThreadCallback frameCallback;
ThreadStopCallback stopCallback;
void* userData;
void (*run)(struct GBAThread*);
@ -126,6 +128,9 @@ 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);
#endif

View File

@ -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 @@ void GBAVideoReset(struct GBAVideo* video) {
video->lastHblank = 0;
video->nextHblank = VIDEO_HDRAW_LENGTH;
video->nextEvent = video->nextHblank;
video->eventDiff = video->nextEvent;
video->eventDiff = 0;
video->nextHblankIRQ = 0;
video->nextVblankIRQ = 0;
@ -203,6 +224,12 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer*
return value;
}
static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
UNUSED(renderer);
UNUSED(address);
// Nothing to do
}
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
UNUSED(renderer);
UNUSED(address);
@ -234,7 +261,6 @@ static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, un
// Nothing to do
}
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) {
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
memcpy(state->oam, video->oam.raw, SIZE_OAM);

View File

@ -26,19 +26,17 @@
#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,
@ -67,7 +65,6 @@ 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);
DECL_BITS(GBAObjAttributesB, MatIndex, 9, 5);
@ -164,6 +161,7 @@ struct GBAVideoRenderer {
void (*deinit)(struct GBAVideoRenderer* renderer);
uint16_t (*writeVideoRegister)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
void (*writeVRAM)(struct GBAVideoRenderer* renderer, uint32_t address);
void (*writePalette)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
void (*writeOAM)(struct GBAVideoRenderer* renderer, uint32_t oam);
void (*drawScanline)(struct GBAVideoRenderer* renderer, int y);
@ -215,4 +213,6 @@ 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

View File

@ -46,7 +46,8 @@ struct GraphicsOpts {
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);

View File

@ -248,9 +248,9 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
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 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
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 @@ void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t rig
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 @@ void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t rig
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);

View File

@ -58,7 +58,7 @@ static void _magickPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRen
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);

View File

@ -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 @@ unsigned retro_api_version(void) {
void retro_set_environment(retro_environment_t env) {
environCallback = env;
struct retro_variable vars[] = {
{ SOLAR_SENSOR_LEVEL, "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" },
{ 0, 0 }
};
environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars);
}
void retro_set_video_refresh(retro_video_refresh_t video) {
@ -107,12 +129,27 @@ void retro_init(void) {
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" }
};
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
// 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,8 +169,22 @@ void retro_init(void) {
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);
renderer.outputBufferStride = 256;
@ -148,6 +199,10 @@ void retro_init(void) {
}
void retro_deinit(void) {
if (bios) {
bios->close(bios);
bios = 0;
}
GBADestroy(&gba);
}
@ -168,6 +223,26 @@ void retro_run(void) {
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
static bool wasAdjustingLux = false;
if (wasAdjustingLux) {
wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
} else {
if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
++luxLevel;
if (luxLevel > 10) {
luxLevel = 10;
}
wasAdjustingLux = true;
} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
--luxLevel;
if (luxLevel < 0) {
luxLevel = 0;
}
wasAdjustingLux = true;
}
}
int frameCount = gba.video.frameCounter;
while (gba.video.frameCounter == frameCount) {
ARMRunLoop(&cpu);
@ -176,6 +251,10 @@ void retro_run(void) {
void retro_reset(void) {
ARMReset(&cpu);
if (rumbleCallback) {
CircleBufferClear(&rumbleHistory);
}
}
bool retro_load_game(const struct retro_game_info* game) {
@ -198,13 +277,7 @@ bool retro_load_game(const struct retro_game_info* game) {
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 @@ void retro_unload_game(void) {
save = 0;
free(savedata);
savedata = 0;
CircleBufferDeinit(&rumbleHistory);
}
size_t retro_serialize_size(void) {
@ -317,10 +391,12 @@ void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* f
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 @@ static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
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;
}

View File

@ -78,7 +78,7 @@ void GBAGLContextDrawFrame(struct VideoBackend* v) {
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);

136
src/platform/opengl/gles2.c Normal file
View File

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

View File

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

View File

@ -186,7 +186,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet)
}
}
GBASyncWaitFrameEnd(&context->sync);
if (*frames == duration) {
if (duration > 0 && *frames == duration) {
_GBAPerfShutdown(0);
}
if (_dispatchExiting) {

View File

@ -10,7 +10,7 @@
#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 ThreadJoin(Thread thread) {
static inline int ThreadSetName(const char* name) {
#ifdef __APPLE__
return pthread_setname_np(name);
#elif defined(__FreeBSD__)
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
return 0;
#else

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@
* 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 @@ AudioDevice::AudioDevice(QObject* parent)
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 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
}
if (!m_context->gba) {
LOG(WARN) << tr("Audio device is missing its GBA");
return 0;
}
@ -68,5 +72,6 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
}
qint64 AudioDevice::writeData(const char*, qint64) {
LOG(WARN) << tr("Writing data to read-only audio device");
return 0;
}

View File

@ -38,10 +38,10 @@ AudioProcessor* AudioProcessor::create() {
#endif
default:
#ifdef BUILD_QT_MULTIMEDIA
return new AudioProcessorQt();
#else
#ifdef BUILD_SDL
return new AudioProcessorSDL();
#else
return new AudioProcessorQt();
#endif
}
}

View File

@ -31,6 +31,7 @@ public:
virtual void setInput(GBAThread* input);
int getBufferSamples() const { return m_samples; }
virtual unsigned sampleRate() const = 0;
public slots:
virtual void start() = 0;
@ -39,6 +40,8 @@ public slots:
virtual void setBufferSamples(int samples) = 0;
virtual void inputParametersChanged() = 0;
virtual void requestSampleRate(unsigned) = 0;
protected:
GBAThread* input() { return m_context; }

View File

@ -6,6 +6,7 @@
#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::setInput(GBAThread* input) {
void AudioProcessorQt::start() {
if (!input()) {
LOG(WARN) << tr("Can't start an audio processor without input");
return;
}
@ -43,7 +46,7 @@ void AudioProcessorQt::start() {
if (!m_audioOutput) {
QAudioFormat format;
format.setSampleRate(44100);
format.setSampleRate(m_sampleRate);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
@ -81,3 +84,19 @@ void AudioProcessorQt::inputParametersChanged() {
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();
}

View File

@ -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 @@ public slots:
virtual void setBufferSamples(int samples);
virtual void inputParametersChanged();
virtual void requestSampleRate(unsigned) override;
private:
QAudioOutput* m_audioOutput;
AudioDevice* m_device;
unsigned m_sampleRate;
};
}

View File

@ -5,6 +5,8 @@
* 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 @@ AudioProcessorSDL::~AudioProcessorSDL() {
void AudioProcessorSDL::start() {
if (!input()) {
LOG(WARN) << tr("Can't start an audio processor without input");
return;
}
@ -51,3 +54,19 @@ void AudioProcessorSDL::setBufferSamples(int samples) {
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;
}
}

View File

@ -22,6 +22,8 @@ public:
AudioProcessorSDL(QObject* parent = nullptr);
~AudioProcessorSDL();
virtual unsigned sampleRate() const override;
public slots:
virtual void start();
virtual void pause();
@ -29,6 +31,8 @@ public slots:
virtual void setBufferSamples(int samples);
virtual void inputParametersChanged();
virtual void requestSampleRate(unsigned) override;
private:
GBASDLAudio m_audio;
};

View File

@ -28,17 +28,31 @@ endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(Qt5Multimedia)
if(NOT WIN32 OR NOT BUILD_SDL)
find_package(Qt5Multimedia)
endif()
find_package(Qt5OpenGL)
find_package(Qt5Widgets)
if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
if(NOT BUILD_GL AND NOT BUILD_GLES2)
message(WARNING "OpenGL is required to build the Qt port")
set(BUILD_QT OFF PARENT_SCOPE)
return()
endif()
if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND)
message(WARNING "Cannot find Qt modules")
set(BUILD_QT OFF PARENT_SCOPE)
return()
endif()
if(BUILD_GL)
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
endif()
if(BUILD_GLES2)
list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c)
endif()
get_target_property(QT_TYPE Qt5::Core TYPE)
if(QT_TYPE STREQUAL STATIC_LIBRARY)
@ -47,6 +61,7 @@ if(QT_TYPE STREQUAL STATIC_LIBRARY)
endif()
set(SOURCE_FILES
AboutScreen.cpp
AudioProcessor.cpp
CheatsModel.cpp
CheatsView.cpp
@ -61,8 +76,10 @@ set(SOURCE_FILES
GamepadAxisEvent.cpp
GamepadButtonEvent.cpp
InputController.cpp
InputProfile.cpp
KeyEditor.cpp
LoadSaveState.cpp
LogController.cpp
LogView.cpp
MemoryModel.cpp
MemoryView.cpp
@ -81,6 +98,7 @@ set(SOURCE_FILES
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
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
@ -152,6 +170,9 @@ if(UNIX AND NOT APPLE)
install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/applications/\")")
endif()
endif()
if(UNIX)
install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba-qt.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-qt)
endif()
if(APPLE OR WIN32)
set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
endif()

View File

@ -5,6 +5,7 @@
* 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 @@ bool CheatsModel::setData(const QModelIndex& index, const QVariant& value, int r
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 @@ QModelIndex CheatsModel::parent(const QModelIndex& index) const {
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 @@ void CheatsModel::removeAt(const QModelIndex& index) {
GBACheatSetDeinit(set);
delete set;
endInsertRows();
}
QString CheatsModel::toString(const QModelIndexList& indices) const {
@ -204,6 +204,7 @@ void CheatsModel::endAppendRow() {
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();

View File

@ -27,7 +27,7 @@ public:
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);

View File

@ -99,20 +99,30 @@ void CheatsView::removeSet() {
}
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();

View File

@ -80,7 +80,7 @@ void ConfigOption::setValue(const char* value) {
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 @@ ConfigController::ConfigController(QObject* parent)
m_opts.audioSync = GameController::AUDIO_SYNC;
m_opts.videoSync = GameController::VIDEO_SYNC;
m_opts.fpsTarget = 60;
m_opts.audioBuffers = 2048;
m_opts.audioBuffers = 1536;
m_opts.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 @@ ConfigController::ConfigController(QObject* parent)
m_opts.rewindBufferCapacity = 0;
m_opts.useBios = true;
m_opts.suspendScreensaver = true;
GBAConfigLoadDefaults(&m_config, &m_opts);
GBAConfigLoad(&m_config);
GBAConfigLoadDefaults(&m_config, &m_opts);
GBAConfigMap(&m_config, &m_opts);
}
@ -126,7 +127,11 @@ ConfigController::~ConfigController() {
}
bool ConfigController::parseArguments(GBAArguments* args, int argc, char* argv[]) {
return ::parseArguments(args, &m_config, argc, argv, 0);
if (::parseArguments(args, &m_config, argc, argv, 0)) {
GBAConfigMap(&m_config, &m_opts);
return true;
}
return false;
}
ConfigOption* ConfigController::addOption(const char* key) {
@ -214,7 +219,7 @@ void ConfigController::setOption(const char* key, const QVariant& value) {
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;
}

View File

@ -89,6 +89,7 @@ public slots:
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:

View File

@ -22,7 +22,7 @@ Display::Driver Display::s_driver = Display::Driver::QT;
Display* Display::create(QWidget* parent) {
#ifdef BUILD_GL
QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
QGLFormat format(QGLFormat(QGL::Rgba | QGL::SingleBuffer));
format.setSwapInterval(1);
#endif
@ -46,7 +46,39 @@ Display* Display::create(QWidget* parent) {
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();
}

View File

@ -8,6 +8,8 @@
#include <QWidget>
#include "MessagePainter.h"
struct GBAThread;
namespace QGBA {
@ -28,20 +30,42 @@ public:
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;
};
}

View File

@ -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 @@ void DisplayGL::startDrawing(GBAThread* thread) {
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::stopDrawing() {
void DisplayGL::pauseDrawing() {
if (m_drawThread) {
m_isDrawing = false;
if (GBAThreadIsActive(m_context)) {
GBAThreadInterrupt(m_context);
}
@ -79,6 +85,7 @@ void DisplayGL::pauseDrawing() {
void DisplayGL::unpauseDrawing() {
if (m_drawThread) {
m_isDrawing = true;
if (GBAThreadIsActive(m_context)) {
GBAThreadInterrupt(m_context);
}
@ -96,14 +103,14 @@ void DisplayGL::forceDraw() {
}
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::filter(bool filter) {
void DisplayGL::framePosted(const uint32_t* buffer) {
if (m_drawThread && buffer) {
QMetaObject::invokeMethod(m_painter, "setBacking", Q_ARG(const uint32_t*, buffer));
m_painter->enqueue(buffer);
QMetaObject::invokeMethod(m_painter, "draw");
}
}
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 @@ PainterGL::PainterGL(QGLWidget* parent)
, m_context(nullptr)
, m_messagePainter(nullptr)
{
#ifdef BUILD_GL
GBAGLContextCreate(&m_backend);
#elif defined(BUILD_GLES2)
GBAGLES2ContextCreate(&m_backend);
#endif
m_backend.d.swap = [](VideoBackend* v) {
PainterGL* painter = static_cast<PainterGL*>(v->user);
painter->m_gl->swapBuffers();
@ -146,25 +153,32 @@ PainterGL::PainterGL(QGLWidget* parent)
m_backend.d.user = this;
m_backend.d.filter = false;
m_backend.d.lockAspectRatio = false;
for (int i = 0; i < 2; ++i) {
m_free.append(new uint32_t[256 * 256]);
}
}
PainterGL::~PainterGL() {
while (!m_queue.isEmpty()) {
delete[] m_queue.dequeue();
}
for (auto item : m_free) {
delete[] item;
}
}
void PainterGL::setContext(GBAThread* context) {
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::resize(const QSize& size) {
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::filter(bool filter) {
}
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 @@ void PainterGL::start() {
}
void PainterGL::draw() {
if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) {
if (m_queue.isEmpty()) {
return;
}
if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip) || !m_queue.isEmpty()) {
dequeue();
m_painter.begin(m_gl->context()->device());
performDraw();
m_painter.end();
@ -203,6 +218,9 @@ void PainterGL::draw() {
} else {
GBASyncWaitFrameEnd(&m_context->sync);
}
if (!m_queue.isEmpty()) {
QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
}
}
void PainterGL::forceDraw() {
@ -215,22 +233,17 @@ void PainterGL::forceDraw() {
void PainterGL::stop() {
m_active = false;
m_gl->makeCurrent();
dequeueAll();
m_backend.d.clear(&m_backend.d);
m_backend.d.swap(&m_backend.d);
m_backend.d.deinit(&m_backend.d);
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 @@ void PainterGL::performDraw() {
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::showMessage(const QString& message) {
m_messagePainter->showMessage(message);
void PainterGL::enqueue(const uint32_t* backing) {
m_mutex.lock();
uint32_t* buffer;
if (m_free.isEmpty()) {
buffer = m_queue.dequeue();
} else {
buffer = m_free.takeLast();
}
memcpy(buffer, backing, 256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
m_queue.enqueue(buffer);
m_mutex.unlock();
}
void PainterGL::dequeue() {
m_mutex.lock();
if (m_queue.isEmpty()) {
m_mutex.unlock();
return;
}
uint32_t* buffer = m_queue.dequeue();
m_backend.d.postFrame(&m_backend.d, buffer);
m_free.append(buffer);
m_mutex.unlock();
}
void PainterGL::dequeueAll() {
uint32_t* buffer = 0;
m_mutex.lock();
while (!m_queue.isEmpty()) {
buffer = m_queue.dequeue();
m_free.append(buffer);
}
if (buffer) {
m_backend.d.postFrame(&m_backend.d, buffer);
}
m_mutex.unlock();
}

View File

@ -8,14 +8,19 @@
#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 @@ public:
protected:
void paintEvent(QPaintEvent*) override {}
void resizeEvent(QResizeEvent*) override {}
void mouseMoveEvent(QMouseEvent* event) override { event->ignore(); }
};
class PainterGL;
@ -39,6 +45,8 @@ public:
DisplayGL(const QGLFormat& format, QWidget* parent = nullptr);
~DisplayGL();
bool isDrawing() const override { return m_isDrawing; }
public slots:
void startDrawing(GBAThread* context) override;
void stopDrawing() override;
@ -49,8 +57,6 @@ public slots:
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 @@ protected:
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 @@ public slots:
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;
};

View File

@ -7,26 +7,30 @@
#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 @@ void DisplayQt::framePosted(const uint32_t* buffer) {
#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 @@ void DisplayQt::paintEvent(QPaintEvent*) {
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);
}

View File

@ -7,7 +7,6 @@
#define QGBA_DISPLAY_QT
#include "Display.h"
#include "MessagePainter.h"
#include <QImage>
#include <QTimer>
@ -22,27 +21,24 @@ Q_OBJECT
public:
DisplayQt(QWidget* parent = nullptr);
bool isDrawing() const override { return m_isDrawing; }
public slots:
void startDrawing(GBAThread* context) override;
void stopDrawing() override {}
void pauseDrawing() override {}
void unpauseDrawing() override {}
void stopDrawing() override { m_isDrawing = false; }
void pauseDrawing() override { m_isDrawing = false; }
void unpauseDrawing() override { m_isDrawing = true; }
void forceDraw() override { update(); }
void lockAspectRatio(bool lock) override;
void filter(bool filter) override;
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;
};
}

View File

@ -15,6 +15,7 @@
#include <QIcon>
extern "C" {
#include "gba/supervisor/thread.h"
#include "platform/commandline.h"
#include "util/socket.h"
}
@ -33,37 +34,47 @@ GBAApp::GBAApp(int& argc, char* argv[])
SDL_Init(SDL_INIT_NOPARACHUTE);
#endif
#ifndef Q_OS_MAC
setWindowIcon(QIcon(":/res/mgba-1024.png"));
#endif
SocketSubsystemInit();
qRegisterMetaType<const uint32_t*>("const uint32_t*");
qRegisterMetaType<GBAThread*>("GBAThread*");
QApplication::setApplicationName(projectName);
QApplication::setApplicationVersion(projectVersion);
Window* w = new Window(&m_configController);
m_windows[0] = w;
#ifndef Q_OS_MAC
w->show();
#endif
if (!m_configController.getQtOption("displayDriver").isNull()) {
Display::setDriver(static_cast<Display::Driver>(m_configController.getQtOption("displayDriver").toInt()));
}
GBAArguments args;
if (m_configController.parseArguments(&args, argc, argv)) {
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;
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 @@ Window* GBAApp::newWindow() {
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;
}

View File

@ -46,7 +46,8 @@ protected:
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:

View File

@ -6,6 +6,7 @@
#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 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
setMinimumSize(300, 300);
const GBAInputMap* map = controller->map();
controller->stealFocus(this);
m_keyDU = new KeyEditor(this);
m_keyDD = new KeyEditor(this);
@ -64,31 +67,36 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
m_controller->loadProfile(m_type, m_profile);
refresh();
});
m_clear = new QWidget(this);
QHBoxLayout* layout = new QHBoxLayout;
m_clear->setLayout(layout);
layout->setSpacing(6);
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
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()));
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()));
m_buttons = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout;
m_buttons->setLayout(layout);
@ -115,6 +123,11 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
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 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y);
setLocation(m_keySelect, 0.415, 0.93);
setLocation(m_keyStart, 0.585, 0.93);
setLocation(m_keyA, 0.826, 0.451);
setLocation(m_keyB, 0.667, 0.490);
setLocation(m_keyA, 0.826, 0.475);
setLocation(m_keyB, 0.667, 0.514);
setLocation(m_keyL, 0.1, 0.1);
setLocation(m_keyR, 0.9, 0.1);
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 @@ void GBAKeyEditor::paintEvent(QPaintEvent* event) {
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::setNext() {
}
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 @@ void GBAKeyEditor::refresh() {
}
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 @@ void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, G
}
return;
}
#endif
#endif
keyEditor->setValueKey(GBAInputQueryBinding(map, m_type, key));
}
@ -239,14 +273,11 @@ void GBAKeyEditor::lookupAxes(const GBAInputMap* map) {
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 @@ KeyEditor* GBAKeyEditor::keyById(GBAKey key) {
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());
}

Some files were not shown because too many files have changed in this diff Show More