all repos — mgba @ 4d137e7f85e1e0a1f17f53aa1ac126a855559b37

mGBA Game Boy Advance Emulator

Merge branch 'feature/input-revamp' into medusa
Vicki Pfau vi@endrift.com
Tue, 30 Jan 2018 18:35:15 -0800
commit

4d137e7f85e1e0a1f17f53aa1ac126a855559b37

parent

d1e96a04dc5ceb352de358a1241d5dbbd0002e09

599 files changed, 5874 insertions(+), 2017 deletions(-)

jump to
M .travis-deps.sh.travis-deps.sh

@@ -2,11 +2,6 @@ #!/bin/sh

if [ $TRAVIS_OS_NAME = "osx" ]; then brew update brew install qt5 ffmpeg imagemagick sdl2 libzip libpng - if [ "$CC" == "gcc" ]; then - brew install gcc@5 - export CC=gcc-5 - export CXX=g++-5 - fi else sudo apt-get clean sudo add-apt-repository -y ppa:george-edison55/cmake-3.x
M .travis.yml.travis.yml

@@ -7,8 +7,6 @@ dist: trusty

compiler: gcc - os: osx compiler: clang - - os: osx - compiler: gcc before_install: - source ./.travis-deps.sh
M CHANGESCHANGES

@@ -28,6 +28,13 @@ - Super Game Boy support

- Customizable autofire speed - Ability to set default Game Boy model - Map viewer + - Automatic cheat loading and saving + - GameShark and Action Replay button support + - AGBPrint support + - Debugger: Conditional breakpoints and watchpoints + - Ability to select GB/GBC/SGB BIOS on console ports + - Optional automatic state saving/loading + - Access to ur0 and uma0 partitions on the Vita Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading

@@ -36,6 +43,28 @@ - Qt: Fix GL display when loading a game from CLI (fixes mgba.io/i/843)

- ARM: Fix MSR when T bit is set - GB Serialize: Fix game title check - GB: Revamp IRQ handling based on new information + - GBA Video: Don't mask out high bits of BLDY (fixes mgba.io/i/899) + - GBA Video: Force align 256-color tiles + - GBA DMA: ROM reads are forced to increment + - GB Video: Fix loading states while in mode 3 + - GB Video: Only trigger STAT write IRQs when screen is on (fixes mgba.io/i/912) + - GBA Cheats: Fix PARv3 slide codes (fixes mgba.io/i/919) + - GBA Video: OBJWIN can change blend params after OBJ is drawn (fixes mgba.io/i/921) + - GBA DMA: Fix invalid DMA reads (fixes mgba.io/i/142) + - GBA Savedata: Fix crash when resizing flash + - GBA Video: Add delay when enabling BGs (fixes mgba.io/i/744, mgba.io/i/752) + - GB Memory: HDMAs should not start when LCD is off (fixes mgba.io/i/310) + - GBA Cheats: Fix slide codes not initializing properly + - Qt: Fix locale being set to English on settings save (fixes mgba.io/i/906) + - LR35902: Fix watchpoints not reporting new value + - GBA Audio: Increase PSG volume (fixes mgba.io/i/932) + - 3DS: Fix opening files in directory names with trailing slashes + - GB MBC: Fix MBC2 saves (fixes mgba.io/i/954) + - GBA Memory: Fix copy-on-write memory leak + - Core: Fix ROM patches not being unloaded when disabled (fixes mgba.io/i/962) + - GBA I/O: Fix writing to DISPCNT CGB flag (fixes mgba.io/i/902) + - GBA Memory: Partially revert prefetch changes (fixes mgba.io/i/840) + - PSP2: Fix issues causing poor audio Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)

@@ -44,6 +73,18 @@ - Qt: Redo GameController into multiple classes

- Test: Restructure test suite into multiple executables - Python: Integrate tests from cinema test suite - Util: Don't build crc32 if the function already exists + - GBA: Implement display start DMAs + - Qt: Prevent window from being created off-screen + - Qt: Add option to disable FPS display + - GBA: Improve multiboot image detection + - GB MBC: Remove erroneous bank 0 wrapping + - GBA Cheats: Allow multiple ROM patches in the same slot + - GB: Skip BIOS option now works + - Libretro: Add frameskip option + - GBA Memory: 64 MiB GBA Video cartridge support + - 3DS: Scale font based on glyph heights (fixes mgba.io/i/961) + - PSP2: Use system enter key by default + - 3DS: Remove deprecated CSND interface 0.6.1: (2017-10-01) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -2,7 +2,11 @@ cmake_minimum_required(VERSION 3.1)

project(medusa) set(BINARY_NAME medusa-emu CACHE INTERNAL "Name of output binaries") if(NOT MSVC) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=c99") + set(GCC_STD "c99") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.3") + set(GCC_STD "gnu99") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=${GCC_STD}") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146") endif()

@@ -40,6 +44,10 @@ set(BUILD_GL ON CACHE STRING "Build with OpenGL")

set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2") set(USE_EPOXY ON CACHE STRING "Build with libepoxy") set(DISABLE_DEPS OFF CACHE BOOL "Build without dependencies") +if(WIN32) + set(WIN32_UNIX_PATHS OFF CACHE BOOL "Use Unix-like paths") + mark_as_advanced(WIN32_UNIX_PATHS) +endif() file(GLOB ARM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/*.c) file(GLOB ARM_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/test/*.c) file(GLOB LR35902_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/*.c)

@@ -57,7 +65,7 @@ file(GLOB UTIL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/*.[cSs])

file(GLOB UTIL_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/test/*.c) file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c) file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c) -file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c) +file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/*.c) file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c) file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/*.c) file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c)

@@ -80,8 +88,16 @@ if(NOT CMAKE_BUILD_TYPE)

set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) endif() -include(GNUInstallDirs) -string(REPLACE "${PROJECT_NAME}" "${BINARY_NAME}" CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DOCDIR}") +if(NOT WIN32 OR WIN32_UNIX_PATHS) + include(GNUInstallDirs) + string(REPLACE "${PROJECT_NAME}" "${BINARY_NAME}" CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DOCDIR}") +else() + set(CMAKE_INSTALL_LIBDIR ".") + set(CMAKE_INSTALL_BINDIR ".") + set(CMAKE_INSTALL_DATADIR ".") + set(CMAKE_INSTALL_DOCDIR ".") + set(CMAKE_INSTALL_INCLUDEDIR "include") +endif() set(LIBDIR "${CMAKE_INSTALL_LIBDIR}" CACHE PATH "Installed library directory") mark_as_advanced(LIBDIR)

@@ -168,7 +184,7 @@ list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c)

source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c) # Advanced settings -if(NOT DEFINED 3DS) +if(NOT DEFINED 3DS AND NOT (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.5")) # LTO appears to make 3DS binary slower set(DEFAULT_LTO ON) else()

@@ -214,6 +230,7 @@ add_definitions(-D_GNU_SOURCE)

endif() if(NOT APPLE AND NOT HAIKU) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)

@@ -223,7 +240,9 @@ endif()

if(APPLE) add_definitions(-D_DARWIN_C_SOURCE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") + if(CMAKE_SYSTEM_VERSION VERSION_GREATER "10.5.8") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") + endif() endif() if(NOT HAIKU AND NOT MSVC AND NOT PSP2)

@@ -414,6 +433,8 @@ ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/parser.c

${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/symbols.c ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/cli-debugger.c) +file(GLOB DEBUGGER_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/test/*.c) + set(FEATURE_SRC) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")

@@ -520,6 +541,7 @@ include_directories(AFTER ${ZLIB_INCLUDE_DIRS})

list(APPEND DEPENDENCY_LIB ${ZLIB_LIBRARIES}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},zlib1g") set(HAVE_CRC32 ON) + list(APPEND OS_LIB ${ZLIB_LIBRARIES}) else() # zlib pulls in crc32 check_function_exists(crc32 HAVE_CRC32)

@@ -596,7 +618,7 @@ endif()

endif() if (USE_LZMA) - include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/third-party/lzma) + include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma) add_definitions(-D_7ZIP_PPMD_SUPPPORT) list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-lzma.c) set(LZMA_SRC

@@ -710,6 +732,7 @@ endif()

if(USE_DEBUGGERS) list(APPEND FEATURE_SRC ${DEBUGGER_SRC}) + list(APPEND TEST_SRC ${DEBUGGER_TEST_SRC}) list(APPEND FEATURES DEBUGGERS) endif()

@@ -850,7 +873,7 @@ if(BUILD_OPENEMU)

find_library(FOUNDATION Foundation) find_library(OPENEMUBASE OpenEmuBase) file(GLOB OE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/openemu/*.m) - add_library(${BINARY_NAME}-openemu MODULE ${CORE_SRC} ${OE_SRC}) + add_library(${BINARY_NAME}-openemu MODULE ${CORE_SRC} ${OS_SRC}) set_target_properties(${BINARY_NAME}-openemu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/openemu/Info.plist.in BUNDLE TRUE

@@ -951,7 +974,8 @@ SET(CPACK_DEB_COMPONENT_INSTALL ON)

set(CPACK_STRIP_FILES ${BINARY_NAME}) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME}) +file(GLOB READMES ${CMAKE_CURRENT_SOURCE_DIR}/README*.md) +install(FILES ${READMES} ${CMAKE_CURRENT_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME}) include(CPack)
M README.mdREADME.md

@@ -128,11 +128,11 @@ 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 ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) 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):

For x86 (32 bit) builds: - pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} For x86_64 (64 bit) builds: - pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} Check out the source code by running this command:

@@ -146,7 +146,7 @@ cd build

cmake .. -G "MSYS Makefiles" make -Please note that this build of medusa for Windows is not suitable for distribution, due to the scattering of DLLs it needs to run, but is perfect for development. However, if distributing such a build is desired (e.g. for testing on machines that don't have the MSYS2 environment installed), a tool called "[Dependency Walker](http://dependencywalker.com)" can be used to see which additional DLL files need to be shipped with the medusa executable. +Please note that this build of medusa for Windows is not suitable for distribution, due to the scattering of DLLs it needs to run, but is perfect for development. However, if distributing such a build is desired (e.g. for testing on machines that don't have the MSYS2 environment installed), running `cpack -G ZIP` will prepare a zip file with all of the necessary DLLs. ### Dependencies
A README_DE.md

@@ -0,0 +1,178 @@

+mGBA +==== + +mGBA ist ein Emulator für Game Boy Advance-Spiele. Das Ziel von mGBA ist, schneller und genauer als viele existierende Game Boy Advance-Emulatoren zu sein. Außerdem verfügt mGBA über Funktionen, die anderen Emulatoren fehlen. Zusätzlich werden auch Game Boy- und Game Boy Color-Spiele unterstützt. + +Aktuelle Neuigkeiten und Downloads findest Du auf [mgba.io](https://mgba.io). + +[![Build-Status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)](https://travis-ci.org/mgba-emu/mgba) + +Features +-------- + +- Nahzu vollständige Unterstützung der Game Boy Advance-Hardware[<sup>[1]</sup>](#missing). +- Unterstützung der Game Boy-/Game Boy Color-Hardware. +- Schnelle Emulation. mGBA ist dafür bekannt, auch auf schwacher Hardware wie Netbooks mit voller Geschwindigkeit zu laufen. +- Qt- und SDL-Portierungen für eine vollwertige und eine "leichtgewichtige" Benutzeroberfläche. +- Lokale (gleicher Computer) Unterstützung für Link-Kabel. +- Erkennung des Speichertypes, einschließlich der Größe des Flash-Speichers[<sup>[2]</sup>](#flashdetect). +- Unterstützung für Spielmodule mit Bewegungssensoren und Rüttel-Effekten (nur verwendbar mit Spiele-Controllern). +- Unterstützung für Echtzeituhren, selbst ohne Konfiguration. +- Unterstützung für Game Boy Printer und Game Boy Camera. +- Eingebaute BIOS-Implementierung mit der Möglichkeit, externe BIOS-Dateien zu laden. +- Turbo/Vorlauf-Unterstützung durch drücken der Tab-Taste. +- Rücklauf-Unterstützung durch drücken der Akzent-Taste. +- Frameskip von bis zu 10 Bildern. +- Unterstützung für Screenshots. +- Unterstützung für Cheat-Codes. +- 9 Speicherstände für Savestates/Spielzustände. Savestates können auch als Screenshots dargestellt werden. +- Video- und GIF-Aufzeichnung. +- Frei wählbare Tastenbelegungen für Tastaturen und Controller. +- Unterstützung für ZIP- und 7z-Archive. +- Unterstützung für Patches im IPS-, UPS- und BPS-Format. +- Spiele-Debugging über ein Kommandozeilen-Interface und IDA Pro-kompatible GDB-Unterstützung. +- Einstellbare Rücklauf-Funktion. +- Unterstützung für das Laden und Exportieren von GameShark- und Action Replay-Abbildern. +- Verfügbare Cores für RetroArch/Libretro und OpenEmu. +- Viele, viele kleinere Dinge. + +### Geplante Features + +- Unterstützung für Link-Kabel-Multiplayer über ein Netzwerk. +- Unterstützung für Link-Kabel über Dolphin/JOY-Bus. +- M4A-Audio-Abmischung für höhere Audio-Qualität. +- Unterstützung für Tool-Assisted Speedruns. +- Lua-Unterstützung für Scripting. +- Eine umfangreiche Debugging-Suite. +- e-Reader-Unterstützung. +- Unterstützung für Drahtlosadapter. + +Unterstützte Plattformen +------------------------ + +- Windows Vista oder neuer +- OS X 10.7 (Lion)[<sup>[3]</sup>](#osxver) oder neuer +- Linux +- FreeBSD +- Nintendo 3DS +- Wii +- PlayStation Vita + +Andere Unix-ähnliche Plattformen wie OpenBSD sind ebenfalls dafür bekannt, mit mGBA kompatibel zu sein. Sie sind jedoch nicht getestet und werden nicht voll unterstützt. + +### Systemvoraussetzungen + +Die Systemvoraussetzungen sind minimal. Jeder Computer, der mit Windows Vista oder neuer läuft, sollte in der Lage sein, die Emulation zu bewältigen. Unterstützung für OpenGL 1.1 oder neuer ist ebenfalls voraussgesetzt. + +Downloads +--------- + +Download-Links befinden sich in der [Downloads][downloads]-Sektion auf der offizielle Website. Der Quellcode befindet sich auf [GitHub][source]. + +Steuerung +--------- + +Die Steuerung kann im Einstellungs-Menü konfiguriert werden. Viele Spiele-Controller werden automatisch erkannt und entsprechend belegt. Für Tastaturen wird standardmäßig folgende Belegung verwendet: + +- **A**: X +- **B**: Z +- **L**: A +- **R**: S +- **Start**: Enter +- **Select**: Rücktaste + +Kompilieren +----------- + +Um mGBA kompilieren zu können, wird CMake 2.8.11 oder neuer benötigt. GCC und Clang sind beide dafür bekannt, mGBA kompilieren zu können. Visual Studio 2013 und älter funktionieren nicht. Unterstützung für Visual Studio 2015 und neuer wird bald hinzugefügt. Um CMake auf einem Unix-basierten System zu verwenden, werden folgende Kommandos empfohlen: + + mkdir build + cd build + cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .. + make + sudo make install + +Damit wird mGBA gebaut und in `/usr/bin` und `/usr/lib` installiert. Installierte Abhängigkeiten werden automatisch erkannt. Features, die aufgrund fehlender Abhängigkeiten deaktiviert werden, werden nach dem `cmake`-Kommando aufgelistet. + +Wenn Du macOS verwendest, sind die einzelnen Schritte etwas anders. Angenommen, dass Du eine Homebrew-Paketverwaltung verwendest, werden folgende Schritte zum installieren der Abhängigkeiten und anschließenden bauen von mGBA empfohlen: + + brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit + mkdir build + cd build + cmake -DCMAKE_PREFIX_PATH='brew --prefix qt5' .. + make + +Bitte beachte, dass Du unter macOS nicht 'make install' verwenden solltest, da dies nicht korrekt funktionieren wird. + +### Für Entwickler: Kompilieren unter Windows + +Um mGBA auf Windows zu kompilieren, wird MSYS2 empfohlen. Befolge die Installationsschritte auf der [MSYS2-Website](https://msys2.github.io). Stelle sicher, dass Du die 32-Bit-Version ("MSYS2 MinGW 32-bit") (oder die 64-Bit-Version "MSYS2 MinGW 64-bit", wenn Du mGBA für x86_64 kompilieren willst) verwendest und führe folgendes Kommando (einschließlich der Klammern) aus, um alle benötigten Abhängigkeiten zu installieren. Bitte beachte, dass dafür über 500MiB an Paketen heruntergeladen werden, was eine Weile dauern kann): + +Für x86 (32 Bit): + + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} + +Für x86_64 (64 Bit): + + pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} + +Lade den aktuellen mGBA-Quellcode mithilfe des folgenden Kommandos herunter: + + git clone https://github.com/mgba-emu/mgba.git + +Abschließend wird mGBA über folgende Kommandos kompiliert: + + cd mgba + mkdir build + cd build + cmake .. -G "MSYS Makefiles" + make + +Bitte beachte, dass mGBA für Windows aufgrund der Vielzahl an benötigten DLLs nicht für die weitere Verteilung geeignet ist, wenn es auf diese Weise gebaut wurde. Es ist jedoch perfekt für Entwickler geeignet. Soll mGBA dennoch weiter verteilt werden (beispielsweise zu Testzwecken auf Systemen, auf denen keine MSYS2-Umgebung installiert ist), kann mithilfe des Befehls 'cpack -G ZIP' ein ZIP-Archiv mit allen benötigten DLLs erstellt werden. + +### Abhängigkeiten + +mGBA hat keine "harten" Abhängigkeiten. Dennoch werden die folgenden optionalen Abhängigkeiten für einige Features benötigt. Diese Features werden automatisch deaktiviert, wenn die benötigten Abhängigkeiten nicht gefunden werden. + +- Qt 5: Für die Benutzeroberfläche. Qt Multimedia oder SDL werden für Audio-Ausgabe benötigt. +- SDL: Für eine einfachere Benutzeroberfläche und Spiele-Controller-Unterstützung in der Qt-Oberfläche. SDL 2 ist empfohlen, SDL 1.2 wird jedoch auch unterstützt. +- zlib und libpng: Für die Unterstützung von Bildschirmfotos und Savestates-in-PNG-Unterstützung. +- libedit: Für die Unterstützung des Kommandozeilen-Debuggers. +- ffmpeg oder libav: Für Videoaufzeichnungen. +- libzip oder zlib: Um ROMs aus ZIP-Dateien zu laden. +- ImageMagick: Für GIF-Aufzeichnungen. +- SQLite3: Für Spiele-Datenbanken. +- libelf: Für das Laden von ELF-Dateien. + +SQLite3, libpng und zlib werden mit dem Emulator mitgeliefert, sodass sie nicht zuerst kompiliert werden müssen. + +Fußnoten +-------- + +<a name="missing">[1]</a> Zurzeit fehlende Features sind + +- OBJ-Fenster für die Modi 3, 4 und 5 ([Bug #5](http://mgba.io/b/5)) +- Mosaik-Effekt für umgewandelte OBJs ([Bug #9](http://mgba.io/b/9)) + +<a name="flashdetect">[2]</a> In manchen Fällen ist es nicht möglich, die Größe des Flash-Speichers automatisch zu ermitteln. Diese kann dann zur Laufzeit konfiguriert werden, es wird jedoch empfohlen, den Fehler zu melden. + +<a name="osxver">[3]</a> 10.7 wird nur für die Qt-Portierung benötigt. Die SDL-Portierung ist dafür bekannt, mit 10.5 und möglicherweise auf älteren Versionen zu funktionieren. + +[downloads]: http://mgba.io/downloads.html +[source]: https://github.com/mgba-emu/mgba/ + +Copyright +--------- + +Copyright für mGBA © 2013 – 2017 Jeffrey Pfau. mGBA wird unter der [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/) veröffentlicht. Eine Kopie der Lizenz ist in der mitgelieferten Datei LICENSE verfügbar. + +mGBA beinhaltet die folgenden Bibliotheken von Drittanbietern: + +- [inih](https://github.com/benhoyt/inih), Copyright © 2009 Ben Hoyt, verwendet unter einer BSD 3-clause-Lizenz. +- [blip-buf](https://code.google.com/archive/b/blip-buf), Copyright © 2003 - 2009 Shay Green, verwendet unter einer Lesser GNU Public License. +- [LZMA SDK](http://www.7-zip.org/sdk.html), Public Domain. +- [MurmurHash3](https://github.com/aappleby/smhasher), Implementierung von Austin Appleby, Public Domain. +- [getopt fot MSVC](https://github.com/skandhurkat/Getopt-for-Visual-Studio/), Public Domain. +- [SQLite3](https://www.sqlite.org), Public Domain. + +Wenn Du ein Spiele-Publisher bist und mGBA für kommerzielle Verwendung lizenzieren möchtest, schreibe bitte eine e-Mail an [licensing@mgba.io](mailto:licensing@mgba.io) für weitere Informationen.
A cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml

@@ -0,0 +1,2 @@

+config: + gb.model: CGB
M include/mgba-util/common.hinclude/mgba-util/common.h

@@ -73,7 +73,7 @@ #ifndef M_PI

#define M_PI 3.141592654f #endif -#ifndef _MSC_VER +#if !defined(_MSC_VER) && (defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) #define ATOMIC_STORE(DST, SRC) __atomic_store_n(&DST, SRC, __ATOMIC_RELEASE) #define ATOMIC_LOAD(DST, SRC) DST = __atomic_load_n(&SRC, __ATOMIC_ACQUIRE) #define ATOMIC_ADD(DST, OP) __atomic_add_fetch(&DST, OP, __ATOMIC_RELEASE)

@@ -101,6 +101,8 @@ #else

#define PRIz "z" #endif +#if defined __BIG_ENDIAN__ +#define LOAD_32BE(DEST, ADDR, ARR) DEST = *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) #if defined(__PPC__) || defined(__POWERPC__) #define LOAD_32LE(DEST, ADDR, ARR) { \ uint32_t _addr = (ADDR); \

@@ -126,10 +128,39 @@ void* _ptr = (ARR); \

__asm__("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ } -#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3]) -#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = __builtin_bswap64(SRC) -#elif defined __BIG_ENDIAN__ -#if defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#define LOAD_64LE(DEST, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + union { \ + struct { \ + uint32_t hi; \ + uint32_t lo; \ + }; \ + uint64_t b64; \ + } *bswap = (void*) &DEST; \ + const void* _ptr = (ARR); \ + __asm__( \ + "lwbrx %0, %2, %3 \n" \ + "lwbrx %1, %2, %4 \n" \ + : "=r"(bswap->lo), "=r"(bswap->hi) : "b"(_ptr), "r"(_addr), "r"(_addr + 4)); \ +} + +#define STORE_64LE(SRC, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + union { \ + struct { \ + uint32_t hi; \ + uint32_t lo; \ + }; \ + uint64_t b64; \ + } *bswap = (void*) &SRC; \ + const void* _ptr = (ARR); \ + __asm__( \ + "stwbrx %0, %2, %3 \n" \ + "stwbrx %1, %2, %4 \n" \ + : : "r"(bswap->hi), "r"(bswap->lo), "b"(_ptr), "r"(_addr), "r"(_addr + 4)); \ +} + +#elif defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) #define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3]) #define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) #define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(((uint16_t*) ARR)[(ADDR) >> 1])

@@ -146,6 +177,7 @@ #define LOAD_16LE(DEST, ADDR, ARR) DEST = *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))

#define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#define LOAD_32BE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) #endif #define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START))
M include/mgba-util/platform/3ds/threading.hinclude/mgba-util/platform/3ds/threading.h

@@ -11,18 +11,9 @@

#include <3ds.h> #include <malloc.h> -#ifdef _3DS -// ctrulib already has a type called Thread -#define Thread CustomThread -#endif - #define THREAD_ENTRY void typedef ThreadFunc ThreadEntry; -typedef struct { - Handle handle; - u8* stack; -} Thread; typedef LightLock Mutex; typedef struct { Mutex mutex;

@@ -103,22 +94,12 @@ static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) {

if (!entry || !thread) { return 1; } - thread->stack = memalign(8, 0x8000); - if (!thread->stack) { - return 1; - } - bool isNew3DS; - APT_CheckNew3DS(&isNew3DS); - if (isNew3DS && svcCreateThread(&thread->handle, entry, (u32) context, (u32*) &thread->stack[0x8000], 0x18, 2) == 0) { - return 0; - } - return svcCreateThread(&thread->handle, entry, (u32) context, (u32*) &thread->stack[0x8000], 0x18, -1); + *thread = threadCreate(entry, context, 0x8000, 0x18, 2, true); + return !*thread; } static inline int ThreadJoin(Thread thread) { - svcWaitSynchronization(thread.handle, U64_MAX); - free(thread.stack); - return 0; + return threadJoin(thread, U64_MAX); } static inline void ThreadSetName(const char* name) {
M include/mgba-util/platform/posix/threading.hinclude/mgba-util/platform/posix/threading.h

@@ -86,7 +86,12 @@ }

static inline int ThreadSetName(const char* name) { #ifdef __APPLE__ +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 return pthread_setname_np(name); +#else + UNUSED(name); + return 0; +#endif #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); return 0;
M include/mgba-util/vector.hinclude/mgba-util/vector.h

@@ -10,6 +10,10 @@ #include <mgba-util/common.h>

CXX_GUARD_START +#ifdef vector +#undef vector +#endif + #define DECLARE_VECTOR(NAME, TYPE) \ struct NAME { \ TYPE* vector; \
M include/mgba-util/vfs.hinclude/mgba-util/vfs.h

@@ -87,7 +87,7 @@ #ifdef USE_LZMA

struct VDir* VDirOpen7z(const char* path, int flags); #endif -#if defined(__wii__) || defined(_3DS) +#if defined(__wii__) || defined(_3DS) || defined(PSP2) struct VDir* VDeviceList(void); #endif
M include/mgba/core/cheats.hinclude/mgba/core/cheats.h

@@ -14,8 +14,6 @@ #include <mgba/core/cpu.h>

#include <mgba/core/log.h> #include <mgba-util/vector.h> -#define MAX_ROM_PATCHES 4 - enum mCheatType { CHEAT_ASSIGN, CHEAT_ASSIGN_INDIRECT,

@@ -30,7 +28,8 @@ CHEAT_IF_ULT,

CHEAT_IF_UGT, CHEAT_IF_AND, CHEAT_IF_LAND, - CHEAT_IF_NAND + CHEAT_IF_NAND, + CHEAT_IF_BUTTON, }; struct mCheat {

@@ -79,6 +78,8 @@

struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name); struct mCheatSets cheats; + bool autosave; + bool buttonDown; }; struct VFile;

@@ -99,7 +100,12 @@

bool mCheatParseFile(struct mCheatDevice*, struct VFile*); bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 +void mCheatAutosave(struct mCheatDevice*); +#endif + void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*); +void mCheatPressButton(struct mCheatDevice*, bool down); CXX_GUARD_END
M include/mgba/core/config.hinclude/mgba/core/config.h

@@ -51,6 +51,7 @@ char* savegamePath;

char* savestatePath; char* screenshotPath; char* patchPath; + char* cheatsPath; int volume; bool mute;
M include/mgba/core/core.hinclude/mgba/core/core.h

@@ -148,6 +148,7 @@ void (*attachDebugger)(struct mCore*, struct mDebugger*);

void (*detachDebugger)(struct mCore*); void (*loadSymbols)(struct mCore*, struct VFile*); + bool (*lookupIdentifier)(struct mCore*, const char* name, int32_t* value, int* segment); #endif struct mCheatDevice* (*cheatDevice)(struct mCore*);

@@ -175,6 +176,7 @@ bool mCorePreloadFile(struct mCore* core, const char* path);

bool mCoreAutoloadSave(struct mCore* core); bool mCoreAutoloadPatch(struct mCore* core); +bool mCoreAutoloadCheats(struct mCore* core); bool mCoreSaveState(struct mCore* core, int slot, int flags); bool mCoreLoadState(struct mCore* core, int slot, int flags);
M include/mgba/core/directories.hinclude/mgba/core/directories.h

@@ -21,6 +21,7 @@ struct VDir* save;

struct VDir* patch; struct VDir* state; struct VDir* screenshot; + struct VDir* cheats; }; void mDirectorySetInit(struct mDirectorySet* dirs);
M include/mgba/core/mem-search.hinclude/mgba/core/mem-search.h

@@ -13,29 +13,38 @@

#include <mgba-util/vector.h> enum mCoreMemorySearchType { - mCORE_MEMORY_SEARCH_32, - mCORE_MEMORY_SEARCH_16, - mCORE_MEMORY_SEARCH_8, + mCORE_MEMORY_SEARCH_INT, mCORE_MEMORY_SEARCH_STRING, mCORE_MEMORY_SEARCH_GUESS, }; +enum mCoreMemorySearchOp { + mCORE_MEMORY_SEARCH_EQUAL, + mCORE_MEMORY_SEARCH_GREATER, + mCORE_MEMORY_SEARCH_LESS, + mCORE_MEMORY_SEARCH_DELTA, +}; + struct mCoreMemorySearchParams { int memoryFlags; enum mCoreMemorySearchType type; + enum mCoreMemorySearchOp op; + int align; + int width; union { const char* valueStr; - uint32_t value32; - uint32_t value16; - uint32_t value8; + int32_t valueInt; }; }; struct mCoreMemorySearchResult { uint32_t address; int segment; - uint64_t guessDivisor; + uint32_t guessDivisor; + uint32_t guessMultiplier; enum mCoreMemorySearchType type; + int width; + int32_t oldValue; }; DECLARE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult);
M include/mgba/debugger/debugger.hinclude/mgba/debugger/debugger.h

@@ -60,16 +60,17 @@ uint32_t oldValue;

uint32_t newValue; enum mWatchpointType watchType; enum mWatchpointType accessType; - }; + } wp; struct { uint32_t opcode; enum mBreakpointType breakType; - }; - }; + } bp; + } type; }; struct mDebugger; +struct ParseTree; struct mDebuggerPlatform { struct mDebugger* p;

@@ -79,14 +80,19 @@ void (*entered)(struct mDebuggerPlatform*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);

bool (*hasBreakpoints)(struct mDebuggerPlatform*); void (*setBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); + void (*setConditionalBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); + void (*setConditionalWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition); void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*checkBreakpoints)(struct mDebuggerPlatform*); void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); + + bool (*getRegister)(struct mDebuggerPlatform*, const char* name, int32_t* value); + bool (*setRegister)(struct mDebuggerPlatform*, const char* name, int32_t value); + bool (*lookupIdentifier)(struct mDebuggerPlatform*, const char* name, int32_t* value, int* segment); }; -struct mDebuggerSymbols; struct mDebugger { struct mCPUComponent d; struct mDebuggerPlatform* platform;

@@ -108,6 +114,8 @@ void mDebuggerAttach(struct mDebugger*, struct mCore*);

void mDebuggerRun(struct mDebugger*); void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); + +bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment); CXX_GUARD_END
M include/mgba/gba/interface.hinclude/mgba/gba/interface.h

@@ -21,6 +21,13 @@ SIO_GPIO = 8,

SIO_JOYBUS = 12 }; +enum GBASIOJOYCommand { + JOY_RESET = 0xFF, + JOY_POLL = 0x00, + JOY_TRANS = 0x14, + JOY_RECV = 0x15 +}; + struct GBA; struct GBAAudio; struct GBASIO;

@@ -47,6 +54,10 @@ bool (*load)(struct GBASIODriver* driver);

bool (*unload)(struct GBASIODriver* driver); uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); }; + +void GBASIOJOYCreate(struct GBASIODriver* sio); +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); + CXX_GUARD_END
M include/mgba/internal/arm/debugger/debugger.hinclude/mgba/internal/arm/debugger/debugger.h

@@ -15,8 +15,10 @@

#include <mgba/internal/arm/arm.h> #include <mgba-util/vector.h> +struct ParseTree; struct ARMDebugBreakpoint { uint32_t address; + struct ParseTree* condition; bool isSw; struct { uint32_t opcode;

@@ -27,6 +29,7 @@

struct ARMDebugWatchpoint { uint32_t address; enum mWatchpointType type; + struct ParseTree* condition; }; DECLARE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint);
M include/mgba/internal/debugger/cli-debugger.hinclude/mgba/internal/debugger/cli-debugger.h

@@ -14,6 +14,7 @@ #include <mgba/debugger/debugger.h>

extern const char* ERROR_MISSING_ARGS; extern const char* ERROR_OVERFLOW; +extern const char* ERROR_INVALID_ARGS; struct CLIDebugger;

@@ -24,22 +25,17 @@ CLIDV_ERROR_TYPE,

CLIDV_INT_TYPE, CLIDV_CHAR_TYPE, } type; - union { - char* charValue; - struct { - int32_t intValue; - int segmentValue; - }; - }; + char* charValue; + int32_t intValue; + int segmentValue; }; typedef void (*CLIDebuggerCommand)(struct CLIDebugger*, struct CLIDebugVector*); -typedef struct CLIDebugVector* (*CLIDVParser)(struct CLIDebugger* debugger, const char* string, size_t length); struct CLIDebuggerCommandSummary { const char* name; CLIDebuggerCommand command; - CLIDVParser parser; + const char* format; const char* summary; };

@@ -51,8 +47,6 @@ void (*deinit)(struct CLIDebuggerSystem*);

bool (*custom)(struct CLIDebuggerSystem*); void (*disassemble)(struct CLIDebuggerSystem*, struct CLIDebugVector* dv); - uint32_t (*lookupIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); - uint32_t (*lookupPlatformIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); void (*printStatus)(struct CLIDebuggerSystem*); struct CLIDebuggerCommandSummary* commands;

@@ -81,9 +75,6 @@

struct CLIDebuggerSystem* system; struct CLIDebuggerBackend* backend; }; - -struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* string, size_t length); -struct CLIDebugVector* CLIDVStringParse(struct CLIDebugger* debugger, const char* string, size_t length); void CLIDebuggerCreate(struct CLIDebugger*); void CLIDebuggerAttachSystem(struct CLIDebugger*, struct CLIDebuggerSystem*);
M include/mgba/internal/debugger/parser.hinclude/mgba/internal/debugger/parser.h

@@ -8,27 +8,36 @@ #define PARSER_H

#include <mgba-util/common.h> +#include <mgba-util/vector.h> + CXX_GUARD_START -#include <mgba/debugger/debugger.h> - -enum LexState { - LEX_ERROR = -1, - LEX_ROOT = 0, - LEX_EXPECT_IDENTIFIER, - LEX_EXPECT_BINARY, - LEX_EXPECT_DECIMAL, - LEX_EXPECT_HEX, - LEX_EXPECT_PREFIX, - LEX_EXPECT_OPERATOR -}; +struct Token; +DECLARE_VECTOR(LexVector, struct Token); enum Operation { OP_ASSIGN, OP_ADD, OP_SUBTRACT, OP_MULTIPLY, - OP_DIVIDE + OP_DIVIDE, + OP_MODULO, + OP_AND, + OP_OR, + OP_XOR, + OP_LESS, + OP_GREATER, + OP_EQUAL, + OP_NOT_EQUAL, + OP_LOGICAL_AND, + OP_LOGICAL_OR, + OP_LE, + OP_GE, + OP_NEGATE, + OP_FLIP, + OP_NOT, + OP_SHIFT_L, + OP_SHIFT_R, }; struct Token {

@@ -48,22 +57,20 @@ enum Operation operatorValue;

}; }; -struct LexVector { - struct LexVector* next; - struct Token token; -}; - struct ParseTree { struct Token token; struct ParseTree* lhs; struct ParseTree* rhs; }; -size_t lexExpression(struct LexVector* lv, const char* string, size_t length); +size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol); void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv); void lexFree(struct LexVector* lv); void parseFree(struct ParseTree* tree); + +struct mDebugger; +bool mDebuggerEvaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, int32_t* value, int* segment); CXX_GUARD_END
M include/mgba/internal/gb/gb.hinclude/mgba/internal/gb/gb.h

@@ -145,6 +145,8 @@ void GBCreate(struct GB* gb);

void GBDestroy(struct GB* gb); void GBReset(struct LR35902Core* cpu); +void GBSkipBIOS(struct GB* gb); +void GBUnmapBIOS(struct GB* gb); void GBDetectModel(struct GB* gb); void GBUpdateIRQs(struct GB* gb);
M include/mgba/internal/gb/io.hinclude/mgba/internal/gb/io.h

@@ -84,6 +84,7 @@ REG_WY = 0x4A,

REG_WX = 0x4B, // CGB + REG_UNK4C = 0x4C, REG_KEY1 = 0x4D, REG_VBK = 0x4F, REG_HDMA1 = 0x51,

@@ -101,8 +102,8 @@ REG_SVBK = 0x70,

REG_UNK72 = 0x72, REG_UNK73 = 0x73, REG_UNK74 = 0x74, - REG_UNK75 = 0x75, - REG_UNK76 = 0x76, + REG_PCM12 = 0x75, + REG_PCM34 = 0x76, REG_UNK77 = 0x77, REG_MAX = 0x100 };
M include/mgba/internal/gb/memory.hinclude/mgba/internal/gb/memory.h

@@ -207,7 +207,7 @@

uint8_t GBView8(struct LR35902Core* cpu, uint16_t address, int segment); void GBMemoryDMA(struct GB* gb, uint16_t base); -void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value); +uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value); void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old, int segment);
M include/mgba/internal/gb/renderers/software.hinclude/mgba/internal/gb/renderers/software.h

@@ -32,6 +32,8 @@ uint8_t scx;

uint8_t wy; uint8_t wx; uint8_t currentWy; + int lastY; + bool hasWindow; GBRegisterLCDC lcdc; enum GBModel model;

@@ -43,6 +45,9 @@ int sgbPacketId;

int sgbDataSets; uint8_t sgbPartialDataSet[15]; bool sgbBorders; + int sgbAttrX; + int sgbAttrY; + int sgbAttrDirection; }; void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*);
M include/mgba/internal/gb/serialize.hinclude/mgba/internal/gb/serialize.h

@@ -169,7 +169,9 @@ * | 0x0C7DB: Current bit count

* | 0x0C7DC - 0x0C7DF: Flags * | bits 0 - 1: Current P1 bits * | bits 2 - 3: Current render mode - * | bits 4 - 31: Reserved (leave 0) + * | bit 4: Is a mode event not scheduled? + * | bit 5: Is a frame event not scheduled? + * | bits 6 - 31: Reserved (leave 0) * | 0x0C7E0 - 0x0C7EF: Current packet * | 0x0C7F0 - 0x0C7FF: Reserved * | 0x0C800 - 0x0E7FF: Character VRAM
M include/mgba/internal/gb/video.hinclude/mgba/internal/gb/video.h

@@ -159,7 +159,7 @@ void GBVideoInit(struct GBVideo* video);

void GBVideoReset(struct GBVideo* video); void GBVideoDeinit(struct GBVideo* video); void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* renderer); -void GBVideoProcessDots(struct GBVideo* video); +void GBVideoProcessDots(struct GBVideo* video, uint32_t cyclesLate); void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value); void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value);
M include/mgba/internal/gba/cheats.hinclude/mgba/internal/gba/cheats.h

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

#include <mgba/internal/arm/arm.h> #include <mgba/core/cheats.h> -#define MAX_ROM_PATCHES 4 +#define MAX_ROM_PATCHES 10 #define COMPLETE ((size_t) -1) enum GBACheatType {

@@ -63,7 +63,7 @@ PAR3_COND_LT = 0x18000000,

PAR3_COND_GT = 0x20000000, PAR3_COND_ULT = 0x28000000, PAR3_COND_UGT = 0x30000000, - PAR3_COND_LAND = 0x38000000, + PAR3_COND_AND = 0x38000000, }; enum GBAActionReplay3Width {
M include/mgba/internal/gba/dma.hinclude/mgba/internal/gba/dma.h

@@ -10,18 +10,18 @@ #include <mgba-util/common.h>

CXX_GUARD_START -enum DMAControl { - DMA_INCREMENT = 0, - DMA_DECREMENT = 1, - DMA_FIXED = 2, - DMA_INCREMENT_RELOAD = 3 +enum GBADMAControl { + GBA_DMA_INCREMENT = 0, + GBA_DMA_DECREMENT = 1, + GBA_DMA_FIXED = 2, + GBA_DMA_INCREMENT_RELOAD = 3 }; -enum DMATiming { - DMA_TIMING_NOW = 0, - DMA_TIMING_VBLANK = 1, - DMA_TIMING_HBLANK = 2, - DMA_TIMING_CUSTOM = 3 +enum GBADMATiming { + GBA_DMA_TIMING_NOW = 0, + GBA_DMA_TIMING_VBLANK = 1, + GBA_DMA_TIMING_HBLANK = 2, + GBA_DMA_TIMING_CUSTOM = 3 }; DECL_BITFIELD(GBADMARegister, uint16_t);

@@ -59,6 +59,7 @@ struct GBADMA;

void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info); void GBADMARunHblank(struct GBA* gba, int32_t cycles); void GBADMARunVblank(struct GBA* gba, int32_t cycles); +void GBADMARunDisplayStart(struct GBA* gba, int32_t cycles); void GBADMAUpdate(struct GBA* gba); CXX_GUARD_END
A include/mgba/internal/gba/matrix.h

@@ -0,0 +1,28 @@

+/* Copyright (c) 2013-2018 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 GBA_MATRIX_H +#define GBA_MATRIX_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +struct GBAMatrix { + uint32_t cmd; + uint32_t paddr; + uint32_t vaddr; + uint32_t size; +}; + +struct GBA; +struct GBAMemory; +void GBAMatrixReset(struct GBA*); +void GBAMatrixWrite(struct GBA*, uint32_t address, uint32_t value); +void GBAMatrixWrite16(struct GBA*, uint32_t address, uint16_t value); + +CXX_GUARD_END + +#endif
M include/mgba/internal/gba/memory.hinclude/mgba/internal/gba/memory.h

@@ -17,6 +17,7 @@ #include <mgba/internal/gba/dma.h>

#include <mgba/internal/gba/hardware.h> #include <mgba/internal/gba/savedata.h> #include <mgba/internal/gba/vfame.h> +#include <mgba/internal/gba/matrix.h> mLOG_DECLARE_CATEGORY(GBA_MEM);

@@ -71,7 +72,9 @@ SIZE_CART_SRAM = 0x00008000,

SIZE_CART_FLASH512 = 0x00010000, SIZE_CART_FLASH1M = 0x00020000, SIZE_CART_EEPROM = 0x00002000, - SIZE_CART_EEPROM512 = 0x00000200 + SIZE_CART_EEPROM512 = 0x00000200, + + SIZE_AGB_PRINT = 0x10000 }; enum {

@@ -79,6 +82,21 @@ OFFSET_MASK = 0x00FFFFFF,

BASE_OFFSET = 24 }; +enum { + AGB_PRINT_BASE = 0x00FD0000, + AGB_PRINT_TOP = 0x00FE0000, + AGB_PRINT_PROTECT = 0x00FE2FFE, + AGB_PRINT_STRUCT = 0x01FE20F8, + AGB_PRINT_FLUSH_ADDR = 0x01FE209C, +}; + +struct GBAPrintContext { + uint16_t request; + uint16_t bank; + uint16_t get; + uint16_t put; +}; + struct GBAMemory { uint32_t* bios; uint32_t* wram;

@@ -89,6 +107,7 @@

struct GBACartridgeHardware hw; struct GBASavedata savedata; struct GBAVFameCart vfame; + struct GBAMatrix matrix; size_t romSize; uint32_t romMask; uint16_t romID;

@@ -106,6 +125,11 @@

struct GBADMA dma[4]; struct mTimingEvent dmaEvent; int activeDMA; + uint32_t dmaTransferRegister; + + uint16_t agbPrint; + struct GBAPrintContext agbPrintCtx; + uint16_t* agbPrintBuffer; bool mirroring; };

@@ -144,6 +168,8 @@

struct GBASerializedState; void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state); void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state); + +void GBAPrintFlush(struct GBA* gba); CXX_GUARD_END
M include/mgba/internal/gba/renderers/video-software.hinclude/mgba/internal/gba/renderers/video-software.h

@@ -43,6 +43,8 @@ int16_t dy;

int16_t dmy; int32_t sx; int32_t sy; + int yCache; + uint16_t mapCache[64]; color_t* extPalette; color_t* variantPalette; };
M include/mgba/internal/gba/serialize.hinclude/mgba/internal/gba/serialize.h

@@ -169,7 +169,8 @@ * | 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) + * 0x002C8 - 0x002CB: Current DMA transfer word + * 0x002CC - 0x002DF: Reserved (leave zero) * 0x002E0 - 0x002EF: Savedata state * | 0x002E0 - 0x002E0: Savedata type * | 0x002E1 - 0x002E1: Savedata command (see savedata.h)

@@ -293,7 +294,9 @@ GBASerializedHWFlags3 flags3;

uint32_t gbpNextEvent; } hw; - uint32_t reservedHardware[6]; + uint32_t dmaTransferRegister; + + uint32_t reservedHardware[5]; struct { uint8_t type;
M include/mgba/internal/lr35902/debugger/debugger.hinclude/mgba/internal/lr35902/debugger/debugger.h

@@ -15,16 +15,18 @@

#include <mgba/internal/lr35902/lr35902.h> #include <mgba-util/vector.h> - +struct ParseTree; struct LR35902DebugBreakpoint { uint16_t address; int segment; + struct ParseTree* condition; }; struct LR35902DebugWatchpoint { uint16_t address; int segment; enum mWatchpointType type; + struct ParseTree* condition; }; struct LR35902Segment {
M src/arm/debugger/cli-debugger.csrc/arm/debugger/cli-debugger.c

@@ -17,23 +17,21 @@ static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*);

static void _disassembleThumb(struct CLIDebugger*, struct CLIDebugVector*); static void _setBreakpointARM(struct CLIDebugger*, struct CLIDebugVector*); static void _setBreakpointThumb(struct CLIDebugger*, struct CLIDebugVector*); -static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*); static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum ExecutionMode mode); static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode); static struct CLIDebuggerCommandSummary _armCommands[] = { - { "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, - { "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, - { "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, - { "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, - { "dis/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" }, - { "dis/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" }, - { "disasm/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" }, - { "disasm/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" }, - { "disassemble/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" }, - { "disassemble/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" }, - { "w/r", _writeRegister, CLIDVParse, "Write a register" }, + { "b/a", _setBreakpointARM, "I", "Set a software breakpoint as ARM" }, + { "b/t", _setBreakpointThumb, "I", "Set a software breakpoint as Thumb" }, + { "break/a", _setBreakpointARM, "I", "Set a software breakpoint as ARM" }, + { "break/t", _setBreakpointThumb, "I", "Set a software breakpoint as Thumb" }, + { "dis/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, + { "dis/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, + { "disasm/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, + { "disasm/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, + { "disassemble/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, + { "disassemble/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, { 0, 0, 0, 0 } };

@@ -145,25 +143,6 @@ }

_printLine(debugger->p, cpu->gprs[ARM_PC] - instructionLength, mode); } -static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct CLIDebuggerBackend* be = debugger->backend; - struct ARMCore* cpu = debugger->d.core->cpu; - if (!dv || dv->type != CLIDV_INT_TYPE) { - be->printf(be, "%s\n", ERROR_MISSING_ARGS); - return; - } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - be->printf(be, "%s\n", ERROR_MISSING_ARGS); - return; - } - uint32_t regid = dv->intValue; - uint32_t value = dv->next->intValue; - if (regid >= ARM_PC) { - return; - } - cpu->gprs[regid] = value; -} - static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { struct CLIDebuggerBackend* be = debugger->backend; if (!dv || dv->type != CLIDV_INT_TYPE) {

@@ -184,38 +163,9 @@ uint32_t address = dv->intValue;

ARMDebuggerSetSoftwareBreakpoint(debugger->d.platform, address, MODE_THUMB); } -static uint32_t _lookupPlatformIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - struct ARMCore* cpu = debugger->p->d.core->cpu; - if (strcmp(name, "sp") == 0) { - return cpu->gprs[ARM_SP]; - } - if (strcmp(name, "lr") == 0) { - return cpu->gprs[ARM_LR]; - } - if (strcmp(name, "pc") == 0) { - return cpu->gprs[ARM_PC]; - } - if (strcmp(name, "cpsr") == 0) { - return cpu->cpsr.packed; - } - // TODO: test if mode has SPSR - if (strcmp(name, "spsr") == 0) { - return cpu->spsr.packed; - } - if (name[0] == 'r' && name[1] >= '0' && name[1] <= '9') { - int reg = atoi(&name[1]); - if (reg < 16) { - return cpu->gprs[reg]; - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - void ARMCLIDebuggerCreate(struct CLIDebuggerSystem* debugger) { debugger->printStatus = _printStatus; debugger->disassemble = _disassemble; - debugger->lookupPlatformIdentifier = _lookupPlatformIdentifier; debugger->platformName = "ARM"; debugger->platformCommands = _armCommands; }
M src/arm/debugger/debugger.csrc/arm/debugger/debugger.c

@@ -10,6 +10,7 @@ #include <mgba/internal/arm/arm.h>

#include <mgba/internal/arm/decoder.h> #include <mgba/internal/arm/isa-inlines.h> #include <mgba/internal/arm/debugger/memory-debugger.h> +#include <mgba/internal/debugger/parser.h> DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); DEFINE_VECTOR(ARMDebugWatchpointList, struct ARMDebugWatchpoint);

@@ -24,6 +25,20 @@ }

return 0; } +static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) { + if (breakpoint->condition) { + parseFree(breakpoint->condition); + free(breakpoint->condition); + } +} + +static void _destroyWatchpoint(struct ARMDebugWatchpoint* watchpoint) { + if (watchpoint->condition) { + parseFree(watchpoint->condition); + free(watchpoint->condition); + } +} + static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; int instructionLength;

@@ -37,9 +52,16 @@ struct ARMDebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength);

if (!breakpoint) { return; } + if (breakpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(d->p, breakpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return; + } + } struct mDebuggerEntryInfo info = { .address = breakpoint->address, - .breakType = BREAKPOINT_HARDWARE + .type.bp.breakType = BREAKPOINT_HARDWARE }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); }

@@ -50,12 +72,16 @@

static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); +static void ARMDebuggerSetConditionalWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition); static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); +static bool ARMDebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value); +static bool ARMDebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value); struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct ARMDebugger));

@@ -63,12 +89,16 @@ platform->entered = ARMDebuggerEnter;

platform->init = ARMDebuggerInit; platform->deinit = ARMDebuggerDeinit; platform->setBreakpoint = ARMDebuggerSetBreakpoint; + platform->setConditionalBreakpoint = ARMDebuggerSetConditionalBreakpoint; platform->clearBreakpoint = ARMDebuggerClearBreakpoint; platform->setWatchpoint = ARMDebuggerSetWatchpoint; + platform->setConditionalWatchpoint = ARMDebuggerSetConditionalWatchpoint; platform->clearWatchpoint = ARMDebuggerClearWatchpoint; platform->checkBreakpoints = ARMDebuggerCheckBreakpoints; platform->hasBreakpoints = ARMDebuggerHasBreakpoints; platform->trace = ARMDebuggerTrace; + platform->getRegister = ARMDebuggerGetRegister; + platform->setRegister = ARMDebuggerSetRegister; return platform; }

@@ -93,7 +123,15 @@ }

} ARMDebuggerRemoveMemoryShim(debugger); + size_t i; + for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints); ++i) { + _destroyBreakpoint(ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); + } ARMDebugBreakpointListDeinit(&debugger->breakpoints); + + for (i = 0; i < ARMDebugWatchpointListSize(&debugger->watchpoints); ++i) { + _destroyWatchpoint(ARMDebugWatchpointListGetPointer(&debugger->watchpoints, i)); + } ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); ARMDebugWatchpointListDeinit(&debugger->watchpoints); }

@@ -161,9 +199,14 @@ }

} static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { + ARMDebuggerSetConditionalBreakpoint(d, address, segment, NULL); +} + +static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->condition = condition; breakpoint->address = address; breakpoint->isSw = false; }

@@ -175,6 +218,7 @@ struct ARMDebugBreakpointList* breakpoints = &debugger->breakpoints;

size_t i; for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) { if (ARMDebugBreakpointListGetPointer(breakpoints, i)->address == address) { + _destroyBreakpoint(ARMDebugBreakpointListGetPointer(breakpoints, i)); ARMDebugBreakpointListShift(breakpoints, i, 1); } }

@@ -186,6 +230,10 @@ return ARMDebugBreakpointListSize(&debugger->breakpoints) || ARMDebugWatchpointListSize(&debugger->watchpoints);

} static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type) { + ARMDebuggerSetConditionalWatchpoint(d, address, segment, type, NULL); +} + +static void ARMDebuggerSetConditionalWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition) { UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; if (!ARMDebugWatchpointListSize(&debugger->watchpoints)) {

@@ -194,6 +242,7 @@ }

struct ARMDebugWatchpoint* watchpoint = ARMDebugWatchpointListAppend(&debugger->watchpoints); watchpoint->address = address; watchpoint->type = type; + watchpoint->condition = condition; } static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) {

@@ -203,6 +252,7 @@ struct ARMDebugWatchpointList* watchpoints = &debugger->watchpoints;

size_t i; for (i = 0; i < ARMDebugWatchpointListSize(watchpoints); ++i) { if (ARMDebugWatchpointListGetPointer(watchpoints, i)->address == address) { + _destroyWatchpoint(ARMDebugWatchpointListGetPointer(watchpoints, i)); ARMDebugWatchpointListShift(watchpoints, i, 1); } }

@@ -246,3 +296,81 @@ cpu->gprs[8], cpu->gprs[9], cpu->gprs[10], cpu->gprs[11],

cpu->gprs[12], cpu->gprs[13], cpu->gprs[14], cpu->gprs[15], cpu->cpsr.packed, disassembly); } + +bool ARMDebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + + if (strcmp(name, "sp") == 0) { + *value = cpu->gprs[ARM_SP]; + return true; + } + if (strcmp(name, "lr") == 0) { + *value = cpu->gprs[ARM_LR]; + return true; + } + if (strcmp(name, "pc") == 0) { + *value = cpu->gprs[ARM_PC]; + return true; + } + if (strcmp(name, "cpsr") == 0) { + *value = cpu->cpsr.packed; + return true; + } + // TODO: test if mode has SPSR + if (strcmp(name, "spsr") == 0) { + *value = cpu->spsr.packed; + return true; + } + if (name[0] == 'r') { + char* end; + uint32_t reg = strtoul(&name[1], &end, 10); + if (reg <= ARM_PC) { + *value = cpu->gprs[reg]; + return true; + } + } + return false; +} + +bool ARMDebuggerSetRegister(struct mDebuggerPlatform* d, const char* name, int32_t value) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + + if (strcmp(name, "sp") == 0) { + cpu->gprs[ARM_SP] = value; + return true; + } + if (strcmp(name, "lr") == 0) { + cpu->gprs[ARM_LR] = value; + return true; + } + if (strcmp(name, "pc") == 0) { + cpu->gprs[ARM_PC] = value; + int32_t currentCycles = 0; + if (cpu->executionMode == MODE_ARM) { + ARM_WRITE_PC; + } else { + THUMB_WRITE_PC; + } + return true; + } + if (name[0] == 'r') { + char* end; + uint32_t reg = strtoul(&name[1], &end, 10); + if (reg > ARM_PC) { + return false; + } + cpu->gprs[reg] = value; + if (reg == ARM_PC) { + int32_t currentCycles = 0; + if (cpu->executionMode == MODE_ARM) { + ARM_WRITE_PC; + } else { + THUMB_WRITE_PC; + } + } + return true; + } + return false; +}
M src/arm/debugger/memory-debugger.csrc/arm/debugger/memory-debugger.c

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

#include <mgba/internal/arm/debugger/memory-debugger.h> #include <mgba/internal/arm/debugger/debugger.h> +#include <mgba/internal/debugger/parser.h> #include <mgba-util/math.h>

@@ -97,21 +98,29 @@ size_t i;

for (i = 0; i < ARMDebugWatchpointListSize(&debugger->watchpoints); ++i) { watchpoint = ARMDebugWatchpointListGetPointer(&debugger->watchpoints, i); if (!((watchpoint->address ^ address) & ~width) && watchpoint->type & type) { + if (watchpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(debugger->d.p, watchpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return false; + } + } + switch (width + 1) { case 1: - info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); + info->type.wp.oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); break; case 2: - info->oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0); + info->type.wp.oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0); break; case 4: - info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0); + info->type.wp.oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0); break; } - info->newValue = newValue; + info->type.wp.newValue = newValue; info->address = address; - info->watchType = watchpoint->type; - info->accessType = type; + info->type.wp.watchType = watchpoint->type; + info->type.wp.accessType = type; return true; } }
M src/core/cheats.csrc/core/cheats.c

@@ -51,6 +51,8 @@ void mCheatDeviceCreate(struct mCheatDevice* device) {

device->d.id = M_CHEAT_DEVICE_ID; device->d.init = mCheatDeviceInit; device->d.deinit = mCheatDeviceDeinit; + device->autosave = false; + device->buttonDown = false; mCheatSetsInit(&device->cheats, 4); }

@@ -252,11 +254,25 @@ StringListDeinit(&directives);

return true; } +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 +void mCheatAutosave(struct mCheatDevice* device) { + if (!device->autosave) { + return; + } + struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + mCheatSaveFile(device, vf); + vf->close(vf); +} +#endif + void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { + cheats->refresh(cheats, device); if (!cheats->enabled) { return; } - cheats->refresh(cheats, device); size_t elseLoc = 0; size_t endLoc = 0;

@@ -350,6 +366,12 @@ conditionRemaining = cheat->repeat;

negativeConditionRemaining = cheat->negativeRepeat; operationsRemaining = 1; break; + case CHEAT_IF_BUTTON: + condition = device->buttonDown; + conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; + operationsRemaining = 1; + break; } if (performAssignment) {

@@ -372,6 +394,10 @@ elseLoc = i + conditionRemaining;

endLoc = elseLoc + negativeConditionRemaining; } } +} + +void mCheatPressButton(struct mCheatDevice* device, bool down) { + device->buttonDown = down; } void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
M src/core/config.csrc/core/config.c

@@ -379,6 +379,7 @@ _lookupCharValue(config, "savegamePath", &opts->savegamePath);

_lookupCharValue(config, "savestatePath", &opts->savestatePath); _lookupCharValue(config, "screenshotPath", &opts->screenshotPath); _lookupCharValue(config, "patchPath", &opts->patchPath); + _lookupCharValue(config, "cheatsPath", &opts->cheatsPath); } void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) {

@@ -443,10 +444,12 @@ free(opts->savegamePath);

free(opts->savestatePath); free(opts->screenshotPath); free(opts->patchPath); + free(opts->cheatsPath); opts->bios = 0; opts->shader = 0; opts->savegamePath = 0; opts->savestatePath = 0; opts->screenshotPath = 0; opts->patchPath = 0; + opts->cheatsPath = 0; }
M src/core/core.csrc/core/core.c

@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/core/core.h> +#include <mgba/core/cheats.h> #include <mgba/core/log.h> #include <mgba/core/serialize.h> #include <mgba-util/vfs.h>

@@ -172,6 +173,30 @@ core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) ||

core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY)); } +bool mCoreAutoloadCheats(struct mCore* core) { + bool success = true; + int cheatAuto; + if (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto) { + struct VFile* vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.cheats, ".cheats", O_RDONLY); + if (vf) { + struct mCheatDevice* device = core->cheatDevice(core); + if (!device) { + success = false; + } else { + success = mCheatParseFile(device, vf); + } + vf->close(vf); + } + } + if (!mCoreConfigGetIntValue(&core->config, "cheatAutosave", &cheatAuto) || cheatAuto) { + struct mCheatDevice* device = core->cheatDevice(core); + if (device) { + device->autosave = true; + } + } + return success; +} + bool mCoreSaveState(struct mCore* core, int slot, int flags) { struct VFile* vf = mCoreGetState(core, slot, true); if (!vf) {

@@ -264,7 +289,7 @@ mCoreConfigInit(&core->config, port);

} void mCoreLoadConfig(struct mCore* core) { -#ifndef MINIMAL_CORE +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 mCoreConfigLoad(&core->config); #endif mCoreLoadForeignConfig(core, &core->config);

@@ -272,12 +297,16 @@ }

void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config) { mCoreConfigMap(config, &core->opts); -#ifndef MINIMAL_CORE +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 mDirectorySetMapOptions(&core->dirs, &core->opts); #endif if (core->opts.audioBuffers) { core->setAudioBufferSize(core, core->opts.audioBuffers); } + + mCoreConfigCopyValue(&core->config, config, "cheatAutosave"); + mCoreConfigCopyValue(&core->config, config, "cheatAutoload"); + core->loadConfig(core, config); }
M src/core/directories.csrc/core/directories.c

@@ -16,6 +16,7 @@ dirs->save = 0;

dirs->patch = 0; dirs->state = 0; dirs->screenshot = 0; + dirs->cheats = 0; } void mDirectorySetDeinit(struct mDirectorySet* dirs) {

@@ -34,6 +35,9 @@ }

if (dirs->archive == dirs->screenshot) { dirs->screenshot = NULL; } + if (dirs->archive == dirs->cheats) { + dirs->cheats = NULL; + } dirs->archive->close(dirs->archive); dirs->archive = NULL; }

@@ -48,6 +52,9 @@ }

if (dirs->save == dirs->screenshot) { dirs->screenshot = NULL; } + if (dirs->save == dirs->cheats) { + dirs->cheats = NULL; + } dirs->save->close(dirs->save); dirs->save = NULL; }

@@ -58,6 +65,9 @@ dirs->state = NULL;

} if (dirs->patch == dirs->screenshot) { dirs->screenshot = NULL; + } + if (dirs->patch == dirs->cheats) { + dirs->cheats = NULL; } dirs->patch->close(dirs->patch); dirs->patch = NULL;

@@ -67,14 +77,25 @@ if (dirs->state) {

if (dirs->state == dirs->screenshot) { dirs->state = NULL; } + if (dirs->state == dirs->cheats) { + dirs->cheats = NULL; + } dirs->state->close(dirs->state); dirs->state = NULL; } if (dirs->screenshot) { + if (dirs->screenshot == dirs->cheats) { + dirs->cheats = NULL; + } dirs->screenshot->close(dirs->screenshot); dirs->screenshot = NULL; } + + if (dirs->cheats) { + dirs->cheats->close(dirs->cheats); + dirs->cheats = NULL; + } } void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {

@@ -91,6 +112,9 @@ }

if (!dirs->screenshot) { dirs->screenshot = dirs->base; } + if (!dirs->cheats) { + dirs->cheats = dirs->base; + } } void mDirectorySetDetachBase(struct mDirectorySet* dirs) {

@@ -105,6 +129,9 @@ dirs->state = NULL;

} if (dirs->screenshot == dirs->base) { dirs->screenshot = NULL; + } + if (dirs->cheats == dirs->base) { + dirs->cheats = NULL; } if (dirs->base) {

@@ -181,6 +208,16 @@ if (dirs->patch && dirs->patch != dirs->base) {

dirs->patch->close(dirs->patch); } dirs->patch = dir; + } + } + + if (opts->cheatsPath) { + struct VDir* dir = VDirOpen(opts->cheatsPath); + if (dir) { + if (dirs->cheats && dirs->cheats != dirs->base) { + dirs->cheats->close(dirs->cheats); + } + dirs->cheats = dir; } } }
M src/core/input.csrc/core/input.c

@@ -11,7 +11,7 @@ #include <mgba-util/vector.h>

#include <inttypes.h> -#define SECTION_NAME_MAX 50 +#define SECTION_NAME_MAX 128 #define KEY_NAME_MAX 32 #define KEY_VALUE_MAX 16 #define AXIS_INFO_MAX 12
M src/core/mem-search.csrc/core/mem-search.c

@@ -10,248 +10,112 @@ #include <mgba/core/interface.h>

DEFINE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); -static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, struct mCoreMemorySearchResults* out, size_t limit) { +static bool _op(int32_t value, int32_t match, enum mCoreMemorySearchOp op) { + switch (op) { + case mCORE_MEMORY_SEARCH_GREATER: + return value > match; + case mCORE_MEMORY_SEARCH_LESS: + return value < match; + case mCORE_MEMORY_SEARCH_EQUAL: + case mCORE_MEMORY_SEARCH_DELTA: + return value == match; + } +} + +static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) { const uint32_t* mem32 = mem; size_t found = 0; uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; // TODO: Big endian - for (i = 0; (!limit || found < limit) && i < end; i += 16) { - int mask = 0; - mask |= (mem32[(i >> 2) + 0] == value32) << 0; - mask |= (mem32[(i >> 2) + 1] == value32) << 1; - mask |= (mem32[(i >> 2) + 2] == value32) << 2; - mask |= (mem32[(i >> 2) + 3] == value32) << 3; - if (!mask) { - continue; - } - if ((mask & 1) && (!limit || found < limit)) { + for (i = 0; (!limit || found < limit) && i < end; i += 4) { + if (_op(mem32[i >> 2], value32, op)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; - res->type = mCORE_MEMORY_SEARCH_32; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 2) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_32; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 4) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 8; - res->type = mCORE_MEMORY_SEARCH_32; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 8) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 12; - res->type = mCORE_MEMORY_SEARCH_32; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 4; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; + res->oldValue = value32; ++found; } } - // TODO: last 12 bytes return found; } -static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) { const uint16_t* mem16 = mem; size_t found = 0; uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; // TODO: Big endian - for (i = 0; (!limit || found < limit) && i < end; i += 16) { - int mask = 0; - mask |= (mem16[(i >> 1) + 0] == value16) << 0; - mask |= (mem16[(i >> 1) + 1] == value16) << 1; - mask |= (mem16[(i >> 1) + 2] == value16) << 2; - mask |= (mem16[(i >> 1) + 3] == value16) << 3; - mask |= (mem16[(i >> 1) + 4] == value16) << 4; - mask |= (mem16[(i >> 1) + 5] == value16) << 5; - mask |= (mem16[(i >> 1) + 6] == value16) << 6; - mask |= (mem16[(i >> 1) + 7] == value16) << 7; - if (!mask) { - continue; - } - if ((mask & 1) && (!limit || found < limit)) { + for (i = 0; (!limit || found < limit) && i < end; i += 2) { + if (_op(mem16[i >> 1], value16, op)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 2) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 2; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 4) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; - ++found; - } - if ((mask & 8) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 6; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 16) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 8; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 32) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 10; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 64) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 12; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 128) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 14; - res->type = mCORE_MEMORY_SEARCH_16; - res->segment = -1; // TODO - res->guessDivisor = 1; + res->guessMultiplier = 1; + res->oldValue = value16; ++found; } } - // TODO: last 14 bytes return found; } -static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) { const uint8_t* mem8 = mem; size_t found = 0; uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; - for (i = 0; (!limit || found < limit) && i < end; i += 8) { - int mask = 0; - mask |= (mem8[i + 0] == value8) << 0; - mask |= (mem8[i + 1] == value8) << 1; - mask |= (mem8[i + 2] == value8) << 2; - mask |= (mem8[i + 3] == value8) << 3; - mask |= (mem8[i + 4] == value8) << 4; - mask |= (mem8[i + 5] == value8) << 5; - mask |= (mem8[i + 6] == value8) << 6; - mask |= (mem8[i + 7] == value8) << 7; - if (!mask) { - continue; - } - if ((mask & 1) && (!limit || found < limit)) { + for (i = 0; (!limit || found < limit) && i < end; ++i) { + if (_op(mem8[i], value8, op)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; + res->oldValue = value8; ++found; } - if ((mask & 2) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 1; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 4) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 2; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 8) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 3; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 16) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 32) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 5; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 64) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 6; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; - } - if ((mask & 128) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 7; - res->type = mCORE_MEMORY_SEARCH_8; - res->segment = -1; // TODO - res->guessDivisor = 1; - ++found; + } + return found; +} + +static size_t _searchInt(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { + if (params->align == params->width || params->align == -1) { + switch (params->width) { + case 4: + return _search32(mem, size, block, params->valueInt, params->op, out, limit); + case 2: + return _search16(mem, size, block, params->valueInt, params->op, out, limit); + case 1: + return _search8(mem, size, block, params->valueInt, params->op, out, limit); } } - // TODO: last 7 bytes - return found; + return 0; } -static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, int len, struct mCoreMemorySearchResults* out, size_t limit) { const char* memStr = mem; size_t found = 0; - size_t len = strlen(valueStr); uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; for (i = 0; (!limit || found < limit) && i < end - len; ++i) { - if (!strncmp(valueStr, &memStr[i], len)) { + if (!memcmp(valueStr, &memStr[i], len)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; res->type = mCORE_MEMORY_SEARCH_STRING; + res->width = len; res->segment = -1; // TODO ++found; }

@@ -259,11 +123,11 @@ }

return found; } -static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { // TODO: As str char* end; - uint64_t value; + int64_t value; size_t found = 0;

@@ -271,14 +135,14 @@ struct mCoreMemorySearchResults tmp;

mCoreMemorySearchResultsInit(&tmp, 0); // Decimal: - value = strtoull(valueStr, &end, 10); + value = strtoll(params->valueStr, &end, 10); if (end && !end[0]) { - if (value > 0x10000) { - found += _search32(mem, size, block, value, out, limit ? limit - found : 0); - } else if (value > 0x100) { - found += _search16(mem, size, block, value, out, limit ? limit - found : 0); + if ((params->width == -1 && value > 0x10000) || params->width == 4) { + found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0); + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { + found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0); } uint32_t divisor = 1;

@@ -287,12 +151,12 @@ mCoreMemorySearchResultsClear(&tmp);

value /= 10; divisor *= 10; - if (value > 0x10000) { - found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0); - } else if (value > 0x100) { - found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0); + if ((params->width == -1 && value > 0x10000) || params->width == 4) { + found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { + found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } size_t i; for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) {

@@ -304,14 +168,14 @@ }

} // Hex: - value = strtoull(valueStr, &end, 16); + value = strtoll(params->valueStr, &end, 16); if (end && !end[0]) { - if (value > 0x10000) { - found += _search32(mem, size, block, value, out, limit ? limit - found : 0); - } else if (value > 0x100) { - found += _search16(mem, size, block, value, out, limit ? limit - found : 0); + if ((params->width == -1 && value > 0x10000) || params->width == 4) { + found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0); + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { + found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0); } uint32_t divisor = 1;

@@ -320,12 +184,12 @@ mCoreMemorySearchResultsClear(&tmp);

value >>= 4; divisor <<= 4; - if (value > 0x10000) { - found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0); - } else if (value > 0x100) { - found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0); + if ((params->width == -1 && value > 0x10000) || params->width == 4) { + found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { + found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } size_t i; for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) {

@@ -342,16 +206,12 @@ }

static size_t _search(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { switch (params->type) { - case mCORE_MEMORY_SEARCH_32: - return _search32(mem, size, block, params->value32, out, limit); - case mCORE_MEMORY_SEARCH_16: - return _search16(mem, size, block, params->value16, out, limit); - case mCORE_MEMORY_SEARCH_8: - return _search8(mem, size, block, params->value8, out, limit); + case mCORE_MEMORY_SEARCH_INT: + return _searchInt(mem, size, block, params, out, limit); case mCORE_MEMORY_SEARCH_STRING: - return _searchStr(mem, size, block, params->valueStr, out, limit); + return _searchStr(mem, size, block, params->valueStr, params->width, out, limit); case mCORE_MEMORY_SEARCH_GUESS: - return _searchGuess(mem, size, block, params->valueStr, out, limit); + return _searchGuess(mem, size, block, params, out, limit); } }

@@ -378,34 +238,42 @@ found += _search(mem, size, block, params, out, limit ? limit - found : 0);

} } -bool _testGuess(struct mCore* core, const struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) { - uint64_t value; +bool _testGuess(struct mCore* core, struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) { + int64_t value; + int32_t offset = 0; char* end; + if (params->op == mCORE_MEMORY_SEARCH_DELTA) { + offset = res->oldValue; + } - value = strtoull(params->valueStr, &end, 10); + value = strtoll(params->valueStr, &end, 10); if (end) { - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + res->oldValue += value; + if (_op(core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 1) && (res->width >= 2 || res->width == -1) && _op(core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 3) && (res->width >= 4 || res->width == -1) && _op(core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } + res->oldValue -= value; } - value = strtoull(params->valueStr, &end, 16); + value = strtoll(params->valueStr, &end, 16); if (end) { - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + res->oldValue += value; + if (_op(core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 1) && (res->width >= 2 || res->width == -1) && _op(core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 3) && (res->width >= 4 || res->width == -1) && _op(core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } + res->oldValue -= value; } return false; }

@@ -415,76 +283,39 @@ size_t i;

for (i = 0; i < mCoreMemorySearchResultsSize(inout); ++i) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i); switch (res->type) { - case mCORE_MEMORY_SEARCH_8: - switch (params->type) { - case mCORE_MEMORY_SEARCH_8: - if (core->rawRead8(core, res->address, res->segment) != params->value8) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_16: - if (core->rawRead8(core, res->address, res->segment) != params->value16) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_32: - if (core->rawRead32(core, res->address, res->segment) != params->value32) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_GUESS: + case mCORE_MEMORY_SEARCH_INT: + if (params->type == mCORE_MEMORY_SEARCH_GUESS) { if (!_testGuess(core, res, params)) { - mCoreMemorySearchResultsShift(inout, i, 1); + *res = *mCoreMemorySearchResultsGetPointer(inout, mCoreMemorySearchResultsSize(inout) - 1); + mCoreMemorySearchResultsResize(inout, -1); --i; } - break; - default: - break; - } - break; - case mCORE_MEMORY_SEARCH_16: - switch (params->type) { - case mCORE_MEMORY_SEARCH_16: - if (core->rawRead16(core, res->address, res->segment) != params->value16) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; + } else if (params->type == mCORE_MEMORY_SEARCH_INT) { + int32_t oldValue = params->valueInt; + if (params->op == mCORE_MEMORY_SEARCH_DELTA) { + oldValue += res->oldValue; } - break; - case mCORE_MEMORY_SEARCH_32: - if (core->rawRead32(core, res->address, res->segment) != params->value32) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; + int32_t value = 0; + switch (params->width) { + case 1: + value = core->rawRead8(core, res->address, res->segment); + break; + case 2: + value = core->rawRead16(core, res->address, res->segment); + break; + case 4: + value = core->rawRead32(core, res->address, res->segment); + break; + default: + break; } - break; - case mCORE_MEMORY_SEARCH_GUESS: - if (!_testGuess(core, res, params)) { - mCoreMemorySearchResultsShift(inout, i, 1); + if (!_op(value, oldValue, params->op)) { + *res = *mCoreMemorySearchResultsGetPointer(inout, mCoreMemorySearchResultsSize(inout) - 1); + mCoreMemorySearchResultsResize(inout, -1); --i; + } else { + res->oldValue = value; } - break; - default: - break; - } - break; - case mCORE_MEMORY_SEARCH_32: - switch (params->type) { - case mCORE_MEMORY_SEARCH_32: - if (core->rawRead32(core, res->address, res->segment) != params->value32) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_GUESS: - if (!_testGuess(core, res, params)) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - default: - break; } break; case mCORE_MEMORY_SEARCH_STRING:
M src/core/thread.csrc/core/thread.c

@@ -623,6 +623,12 @@ UNUSED(level);

printf("%s: ", mLogCategoryName(category)); vprintf(format, args); printf("\n"); + struct mCoreThread* thread = mCoreThreadGet(); + if (thread && level == mLOG_FATAL) { +#ifndef DISABLE_THREADING + mCoreThreadMarkCrashed(thread); +#endif + } } struct mLogger* mCoreThreadLogger(void) {
M src/debugger/cli-debugger.csrc/debugger/cli-debugger.c

@@ -26,6 +26,7 @@ #endif

const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share const char* ERROR_OVERFLOW = "Arguments overflow"; +const char* ERROR_INVALID_ARGS = "Invalid arguments"; #if !defined(NDEBUG) && !defined(_WIN32) static void _breakInto(struct CLIDebugger*, struct CLIDebugVector*);

@@ -51,6 +52,7 @@ static void _setWriteWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);

static void _trace(struct CLIDebugger*, struct CLIDebugVector*); static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*); static void _writeHalfword(struct CLIDebugger*, struct CLIDebugVector*); +static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*); static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*); static void _dumpByte(struct CLIDebugger*, struct CLIDebugVector*); static void _dumpHalfword(struct CLIDebugger*, struct CLIDebugVector*);

@@ -60,50 +62,51 @@ static void _source(struct CLIDebugger*, struct CLIDebugVector*);

#endif static struct CLIDebuggerCommandSummary _debuggerCommands[] = { - { "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, - { "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, - { "c", _continue, 0, "Continue execution" }, - { "continue", _continue, 0, "Continue execution" }, - { "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" }, - { "delete", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" }, - { "dis", _disassemble, CLIDVParse, "Disassemble instructions" }, - { "disasm", _disassemble, CLIDVParse, "Disassemble instructions" }, - { "disassemble", _disassemble, CLIDVParse, "Disassemble instructions" }, - { "h", _printHelp, CLIDVStringParse, "Print help" }, - { "help", _printHelp, CLIDVStringParse, "Print help" }, - { "i", _printStatus, 0, "Print the current status" }, - { "info", _printStatus, 0, "Print the current status" }, - { "n", _next, 0, "Execute next instruction" }, - { "next", _next, 0, "Execute next instruction" }, - { "p", _print, CLIDVParse, "Print a value" }, - { "p/t", _printBin, CLIDVParse, "Print a value as binary" }, - { "p/x", _printHex, CLIDVParse, "Print a value as hexadecimal" }, - { "print", _print, CLIDVParse, "Print a value" }, - { "print/t", _printBin, CLIDVParse, "Print a value as binary" }, - { "print/x", _printHex, CLIDVParse, "Print a value as hexadecimal" }, - { "q", _quit, 0, "Quit the emulator" }, - { "quit", _quit, 0, "Quit the emulator" }, - { "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" }, - { "r/4", _readWord, CLIDVParse, "Read a word from a specified offset" }, - { "status", _printStatus, 0, "Print the current status" }, - { "trace", _trace, CLIDVParse, "Trace a fixed number of instructions" }, - { "w", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, - { "w/1", _writeByte, CLIDVParse, "Write a byte at a specified offset" }, - { "w/2", _writeHalfword, CLIDVParse, "Write a halfword at a specified offset" }, - { "w/4", _writeWord, CLIDVParse, "Write a word at a specified offset" }, - { "watch", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, - { "watch/r", _setReadWatchpoint, CLIDVParse, "Set a read watchpoint" }, - { "watch/w", _setWriteWatchpoint, CLIDVParse, "Set a write watchpoint" }, - { "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" }, + { "b", _setBreakpoint, "Is", "Set a breakpoint" }, + { "break", _setBreakpoint, "Is", "Set a breakpoint" }, + { "c", _continue, "", "Continue execution" }, + { "continue", _continue, "", "Continue execution" }, + { "d", _clearBreakpoint, "I", "Delete a breakpoint" }, + { "delete", _clearBreakpoint, "I", "Delete a breakpoint" }, + { "dis", _disassemble, "Ii", "Disassemble instructions" }, + { "disasm", _disassemble, "Ii", "Disassemble instructions" }, + { "disassemble", _disassemble, "Ii", "Disassemble instructions" }, + { "h", _printHelp, "S", "Print help" }, + { "help", _printHelp, "S", "Print help" }, + { "i", _printStatus, "", "Print the current status" }, + { "info", _printStatus, "", "Print the current status" }, + { "n", _next, "", "Execute next instruction" }, + { "next", _next, "", "Execute next instruction" }, + { "p", _print, "I", "Print a value" }, + { "p/t", _printBin, "I", "Print a value as binary" }, + { "p/x", _printHex, "I", "Print a value as hexadecimal" }, + { "print", _print, "I", "Print a value" }, + { "print/t", _printBin, "I", "Print a value as binary" }, + { "print/x", _printHex, "I", "Print a value as hexadecimal" }, + { "q", _quit, "", "Quit the emulator" }, + { "quit", _quit, "", "Quit the emulator" }, + { "reset", _reset, "", "Reset the emulation" }, + { "r/1", _readByte, "I", "Read a byte from a specified offset" }, + { "r/2", _readHalfword, "I", "Read a halfword from a specified offset" }, + { "r/4", _readWord, "I", "Read a word from a specified offset" }, + { "status", _printStatus, "", "Print the current status" }, + { "trace", _trace, "I", "Trace a fixed number of instructions" }, + { "w", _setWatchpoint, "Is", "Set a watchpoint" }, + { "w/1", _writeByte, "II", "Write a byte at a specified offset" }, + { "w/2", _writeHalfword, "II", "Write a halfword at a specified offset" }, + { "w/r", _writeRegister, "SI", "Write a register" }, + { "w/4", _writeWord, "II", "Write a word at a specified offset" }, + { "watch", _setWatchpoint, "Is", "Set a watchpoint" }, + { "watch/r", _setReadWatchpoint, "Is", "Set a read watchpoint" }, + { "watch/w", _setWriteWatchpoint, "Is", "Set a write watchpoint" }, + { "x/1", _dumpByte, "Ii", "Examine bytes at a specified offset" }, + { "x/2", _dumpHalfword, "Ii", "Examine halfwords at a specified offset" }, + { "x/4", _dumpWord, "Ii", "Examine words at a specified offset" }, #ifdef ENABLE_SCRIPTING - { "source", _source, CLIDVStringParse, "Load a script" }, + { "source", _source, "S", "Load a script" }, #endif #if !defined(NDEBUG) && !defined(_WIN32) - { "!", _breakInto, 0, "Break into attached debugger (for developers)" }, + { "!", _breakInto, "", "Break into attached debugger (for developers)" }, #endif { 0, 0, 0, 0 } };

@@ -273,12 +276,12 @@ debugger->backend->printf(debugger->backend, " 0x%08X\n", value);

} static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + if (dv->type != CLIDV_INT_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } uint32_t address = dv->intValue;

@@ -295,12 +298,12 @@ }

} static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + if (dv->type != CLIDV_INT_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } uint32_t address = dv->intValue;

@@ -316,13 +319,27 @@ debugger->d.core->busWrite16(debugger->d.core, address, value);

} } +static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || !dv->next) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + return; + } + if (dv->type != CLIDV_CHAR_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + return; + } + if (!debugger->d.platform->setRegister(debugger->d.platform, dv->charValue, dv->next->intValue)) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } +} + static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + if (dv->type != CLIDV_INT_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } uint32_t address = dv->intValue;

@@ -435,13 +452,53 @@ }

} #endif +static struct ParseTree* _parseTree(const char* string) { + struct LexVector lv; + bool error = false; + LexVectorInit(&lv, 0); + size_t length = strlen(string); + size_t adjusted = lexExpression(&lv, string, length, NULL); + struct ParseTree* tree = malloc(sizeof(*tree)); + if (!adjusted) { + error = true; + } else { + parseLexedExpression(tree, &lv); + + if (adjusted > length) { + error = true; + } else { + length -= adjusted; + string += adjusted; + } + } + lexFree(&lv); + LexVectorClear(&lv); + LexVectorDeinit(&lv); + if (error) { + parseFree(tree); + free(tree); + return NULL; + } else { + return tree; + } +} + static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!dv || dv->type != CLIDV_INT_TYPE) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } uint32_t address = dv->intValue; - debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalBreakpoint(debugger->d.platform, address, dv->segmentValue, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); + } } static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -454,7 +511,16 @@ debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");

return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW); + } } static void _setReadWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -467,7 +533,16 @@ debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");

return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ); + } } static void _setWriteWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -480,8 +555,16 @@ debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");

return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE); -} + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE); + }} static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!dv || dv->type != CLIDV_INT_TYPE) {

@@ -521,86 +604,6 @@ UNUSED(dv);

debugger->system->printStatus(debugger->system); } -static uint32_t _performOperation(enum Operation operation, uint32_t current, uint32_t next, struct CLIDebugVector* dv) { - switch (operation) { - case OP_ASSIGN: - current = next; - break; - case OP_ADD: - current += next; - break; - case OP_SUBTRACT: - current -= next; - break; - case OP_MULTIPLY: - current *= next; - break; - case OP_DIVIDE: - if (next != 0) { - current /= next; - } else { - dv->type = CLIDV_ERROR_TYPE; - return 0; - } - break; - } - return current; -} - -static void _lookupIdentifier(struct mDebugger* debugger, const char* name, struct CLIDebugVector* dv) { - struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; - if (cliDebugger->system) { - uint32_t value; -#ifdef ENABLE_SCRIPTING - if (debugger->bridge && mScriptBridgeLookupSymbol(debugger->bridge, name, &dv->intValue)) { - return; - } -#endif - if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, &dv->intValue, &dv->segmentValue)) { - return; - } - value = cliDebugger->system->lookupPlatformIdentifier(cliDebugger->system, name, dv); - if (dv->type != CLIDV_ERROR_TYPE) { - dv->intValue = value; - return; - } - dv->type = CLIDV_INT_TYPE; - value = cliDebugger->system->lookupIdentifier(cliDebugger->system, name, dv); - if (dv->type != CLIDV_ERROR_TYPE) { - dv->intValue = value; - return; - } - } - dv->type = CLIDV_ERROR_TYPE; -} - -static void _evaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, struct CLIDebugVector* dv) { - int32_t lhs, rhs; - switch (tree->token.type) { - case TOKEN_UINT_TYPE: - dv->intValue = tree->token.uintValue; - break; - case TOKEN_SEGMENT_TYPE: - _evaluateParseTree(debugger, tree->lhs, dv); - dv->segmentValue = dv->intValue; - _evaluateParseTree(debugger, tree->rhs, dv); - break; - case TOKEN_OPERATOR_TYPE: - _evaluateParseTree(debugger, tree->lhs, dv); - lhs = dv->intValue; - _evaluateParseTree(debugger, tree->rhs, dv); - rhs = dv->intValue; - dv->intValue = _performOperation(tree->token.operatorValue, lhs, rhs, dv); - break; - case TOKEN_IDENTIFIER_TYPE: - _lookupIdentifier(debugger, tree->token.identifierValue, dv); - break; - case TOKEN_ERROR_TYPE: - default: - dv->type = CLIDV_ERROR_TYPE; - } -} - struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* string, size_t length) { if (!string || length < 1) { return 0;

@@ -608,11 +611,11 @@ }

struct CLIDebugVector dvTemp = { .type = CLIDV_INT_TYPE, .segmentValue = -1 }; - struct LexVector lv = { .next = 0 }; - size_t adjusted = lexExpression(&lv, string, length); + struct LexVector lv; + LexVectorInit(&lv, 0); + size_t adjusted = lexExpression(&lv, string, length, " "); if (adjusted > length) { dvTemp.type = CLIDV_ERROR_TYPE; - lexFree(lv.next); } struct ParseTree tree;

@@ -620,14 +623,15 @@ parseLexedExpression(&tree, &lv);

if (tree.token.type == TOKEN_ERROR_TYPE) { dvTemp.type = CLIDV_ERROR_TYPE; } else { - _evaluateParseTree(&debugger->d, &tree, &dvTemp); + if (!mDebuggerEvaluateParseTree(&debugger->d, &tree, &dvTemp.intValue, &dvTemp.segmentValue)) { + dvTemp.type = CLIDV_ERROR_TYPE; + } } - parseFree(tree.lhs); - parseFree(tree.rhs); + parseFree(&tree); - length -= adjusted; - string += adjusted; + lexFree(&lv); + LexVectorDeinit(&lv); struct CLIDebugVector* dv = malloc(sizeof(struct CLIDebugVector)); if (dvTemp.type == CLIDV_ERROR_TYPE) {

@@ -635,43 +639,22 @@ dv->type = CLIDV_ERROR_TYPE;

dv->next = 0; } else { *dv = dvTemp; - if (string[0] == ' ') { - dv->next = CLIDVParse(debugger, string + 1, length - 1); - if (dv->next && dv->next->type == CLIDV_ERROR_TYPE) { - dv->type = CLIDV_ERROR_TYPE; - } - } } return dv; } struct CLIDebugVector* CLIDVStringParse(struct CLIDebugger* debugger, const char* string, size_t length) { + UNUSED(debugger); if (!string || length < 1) { return 0; } struct CLIDebugVector dvTemp = { .type = CLIDV_CHAR_TYPE }; - size_t adjusted; - const char* next = strchr(string, ' '); - if (next) { - adjusted = next - string; - } else { - adjusted = length; - } - dvTemp.charValue = strndup(string, adjusted); - - length -= adjusted; - string += adjusted; + dvTemp.charValue = strndup(string, length); struct CLIDebugVector* dv = malloc(sizeof(struct CLIDebugVector)); *dv = dvTemp; - if (string[0] == ' ') { - dv->next = CLIDVStringParse(debugger, string + 1, length - 1); - if (dv->next && dv->next->type == CLIDV_ERROR_TYPE) { - dv->type = CLIDV_ERROR_TYPE; - } - } return dv; }

@@ -687,8 +670,28 @@ dv = next;

} } +static struct CLIDebugVector* _parseArg(struct CLIDebugger* debugger, const char* args, size_t argsLen, char type) { + struct CLIDebugVector* dv = NULL; + switch (type) { + case 'I': + case 'i': + return CLIDVParse(debugger, args, argsLen); + case 'S': + case 's': + return CLIDVStringParse(debugger, args, argsLen); + case '*': + dv = _parseArg(debugger, args, argsLen, 'I'); + if (!dv) { + dv = _parseArg(debugger, args, argsLen, 'S'); + } + break; + } + return dv; +} + static int _tryCommands(struct CLIDebugger* debugger, struct CLIDebuggerCommandSummary* commands, const char* command, size_t commandLen, const char* args, size_t argsLen) { - struct CLIDebugVector* dv = 0; + struct CLIDebugVector* dv = NULL; + struct CLIDebugVector* dvLast = NULL; int i; const char* name; for (i = 0; (name = commands[i].name); ++i) {

@@ -696,22 +699,78 @@ if (strlen(name) != commandLen) {

continue; } if (strncasecmp(name, command, commandLen) == 0) { - if (commands[i].parser) { - if (args) { - dv = commands[i].parser(debugger, args, argsLen); - if (dv && dv->type == CLIDV_ERROR_TYPE) { + if (commands[i].format && args) { + char lastArg = '\0'; + int arg; + for (arg = 0; commands[i].format[arg] && argsLen; ++arg) { + while (isspace(args[0]) && argsLen) { + ++args; + --argsLen; + } + if (!args[0] || !argsLen) { + debugger->backend->printf(debugger->backend, "Wrong number of arguments\n"); + _DVFree(dv); + return 0; + } + + size_t adjusted; + const char* next = strchr(args, ' '); + if (next) { + adjusted = next - args; + } else { + adjusted = argsLen; + } + + struct CLIDebugVector* dvNext = NULL; + bool nextArgMandatory = false; + + if (commands[i].format[arg] == '+') { + dvNext = _parseArg(debugger, args, adjusted, lastArg); + --args; + } else { + nextArgMandatory = isupper(commands[i].format[arg]) || (commands[i].format[arg] == '*'); + dvNext = _parseArg(debugger, args, adjusted, commands[i].format[arg]); + } + + args += adjusted; + argsLen -= adjusted; + + if (!dvNext) { + if (!nextArgMandatory) { + args = NULL; + } + break; + } + if (dvNext->type == CLIDV_ERROR_TYPE) { debugger->backend->printf(debugger->backend, "Parse error\n"); _DVFree(dv); - return false; + return 0; + } + + if (dvLast) { + dvLast->next = dvNext; + dvLast = dvNext; + } else { + dv = dvNext; + dvLast = dv; } } - } else if (args) { + } + + if (args) { + while (isspace(args[0]) && argsLen) { + ++args; + --argsLen; + } + } + if (args && argsLen) { debugger->backend->printf(debugger->backend, "Wrong number of arguments\n"); - return false; + _DVFree(dv); + return 0; } commands[i].command(debugger, dv); _DVFree(dv); - return true; + return 1; } } return -1;

@@ -781,10 +840,10 @@ }

break; case DEBUGGER_ENTER_WATCHPOINT: if (info) { - if (info->accessType & WATCHPOINT_WRITE) { - cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->newValue, info->oldValue); + if (info->type.wp.accessType & WATCHPOINT_WRITE) { + cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->type.wp.newValue, info->type.wp.oldValue); } else { - cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->oldValue); + cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->type.wp.oldValue); } } else { cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint\n");

@@ -792,7 +851,7 @@ }

break; case DEBUGGER_ENTER_ILLEGAL_OP: if (info) { - cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->opcode); + cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->type.bp.opcode); } else { cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode\n"); }
M src/debugger/debugger.csrc/debugger/debugger.c

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

#include <mgba/core/core.h> #include <mgba/internal/debugger/cli-debugger.h> +#include <mgba/internal/debugger/symbols.h> #ifdef USE_GDB_STUB #include <mgba/internal/debugger/gdb-stub.h>

@@ -137,3 +138,22 @@ debugger->deinit(debugger);

} debugger->platform->deinit(debugger->platform); } + +bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment) { + *segment = -1; +#ifdef ENABLE_SCRIPTING + if (debugger->bridge && mScriptBridgeLookupSymbol(debugger->bridge, name, value)) { + return true; + } +#endif + if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, value, segment)) { + return true; + } + if (debugger->core->lookupIdentifier(debugger->core, name, value, segment)) { + return true; + } + if (debugger->platform && debugger->platform->getRegister(debugger->platform, name, value)) { + return true; + } + return false; +}
M src/debugger/gdb-stub.csrc/debugger/gdb-stub.c

@@ -46,7 +46,7 @@ snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);

break; case DEBUGGER_ENTER_BREAKPOINT: if (stub->supportsHwbreak && stub->supportsSwbreak && info) { - snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%cwbreak:;", SIGTRAP, info->breakType == BREAKPOINT_SOFTWARE ? 's' : 'h'); + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%cwbreak:;", SIGTRAP, info->type.bp.breakType == BREAKPOINT_SOFTWARE ? 's' : 'h'); } else { snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02xk", SIGTRAP); }

@@ -54,9 +54,9 @@ break;

case DEBUGGER_ENTER_WATCHPOINT: if (info) { const char* type = 0; - switch (info->watchType) { + switch (info->type.wp.watchType) { case WATCHPOINT_WRITE: - if (info->newValue == info->oldValue) { + if (info->type.wp.newValue == info->type.wp.oldValue) { if (stub->d.state == DEBUGGER_PAUSED) { stub->d.state = DEBUGGER_RUNNING; }

@@ -363,7 +363,7 @@

#ifdef _MSC_VER value = _byteswap_ulong(value); #else - value = __builtin_bswap32(value); + LOAD_32BE(value, 0, &value); #endif if (reg <= ARM_PC) {
M src/debugger/parser.csrc/debugger/parser.c

@@ -5,39 +5,181 @@ * 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 <mgba/internal/debugger/parser.h> +#include <mgba/debugger/debugger.h> #include <mgba-util/string.h> -static struct LexVector* _lexOperator(struct LexVector* lv, char operator) { - struct LexVector* lvNext = malloc(sizeof(struct LexVector)); - lvNext->token.type = TOKEN_OPERATOR_TYPE; +DEFINE_VECTOR(LexVector, struct Token); + +enum LexState { + LEX_ERROR = -1, + LEX_ROOT = 0, + LEX_EXPECT_IDENTIFIER, + LEX_EXPECT_BINARY_FIRST, + LEX_EXPECT_BINARY, + LEX_EXPECT_DECIMAL, + LEX_EXPECT_HEX_FIRST, + LEX_EXPECT_HEX, + LEX_EXPECT_PREFIX, + LEX_EXPECT_OPERATOR, + LEX_EXPECT_OPERATOR2, +}; + +static void _lexOperator(struct LexVector* lv, char operator, enum LexState* state) { + if (*state == LEX_EXPECT_OPERATOR2) { + struct Token* lvNext = LexVectorGetPointer(lv, LexVectorSize(lv) - 1); + if (lvNext->type != TOKEN_OPERATOR_TYPE) { + lvNext->type = TOKEN_ERROR_TYPE; + *state = LEX_ERROR; + return; + } + switch (lvNext->operatorValue) { + case OP_AND: + if (operator == '&') { + lvNext->operatorValue = OP_LOGICAL_AND; + *state = LEX_ROOT; + return; + } + break; + case OP_OR: + if (operator == '|') { + lvNext->operatorValue = OP_LOGICAL_OR; + *state = LEX_ROOT; + return; + } + break; + case OP_LESS: + if (operator == '=') { + lvNext->operatorValue = OP_LE; + *state = LEX_ROOT; + return; + } + if (operator == '<') { + lvNext->operatorValue = OP_SHIFT_L; + *state = LEX_ROOT; + return; + } + break; + case OP_GREATER: + if (operator == '=') { + lvNext->operatorValue = OP_GE; + *state = LEX_ROOT; + return; + } + if (operator == '>') { + lvNext->operatorValue = OP_SHIFT_R; + *state = LEX_ROOT; + return; + } + break; + case OP_ASSIGN: + if (operator == '=') { + lvNext->operatorValue = OP_EQUAL; + *state = LEX_ROOT; + return; + } + break; + case OP_NOT: + if (operator == '=') { + lvNext->operatorValue = OP_NOT_EQUAL; + *state = LEX_ROOT; + return; + } + break; + default: + break; + } + *state = LEX_ERROR; + return; + } + struct Token* lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_OPERATOR_TYPE; + *state = LEX_EXPECT_OPERATOR2; switch (operator) { + case '=': + lvNext->operatorValue = OP_ASSIGN; + break; case '+': - lvNext->token.operatorValue = OP_ADD; + lvNext->operatorValue = OP_ADD; break; case '-': - lvNext->token.operatorValue = OP_SUBTRACT; + lvNext->operatorValue = OP_SUBTRACT; break; case '*': - lvNext->token.operatorValue = OP_MULTIPLY; + lvNext->operatorValue = OP_MULTIPLY; + break; + case '/': + lvNext->operatorValue = OP_DIVIDE; + break; + case '%': + lvNext->operatorValue = OP_MODULO; + break; + case '&': + lvNext->operatorValue = OP_AND; + break; + case '|': + lvNext->operatorValue = OP_OR; + break; + case '^': + lvNext->operatorValue = OP_XOR; + break; + case '<': + lvNext->operatorValue = OP_LESS; break; + case '>': + lvNext->operatorValue = OP_GREATER; + break; + case '!': + lvNext->operatorValue = OP_NOT; + break; + default: + lvNext->type = TOKEN_ERROR_TYPE; + break; + } +} + +static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexState* state) { + struct Token* lvNext; + + switch (token) { + case '=': + case '+': + case '-': + case '*': case '/': - lvNext->token.operatorValue = OP_DIVIDE; + case '%': + case '&': + case '|': + case '^': + case '<': + case '>': + case '!': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + _lexOperator(lv, token, state); + break; + case ')': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_CLOSE_PAREN_TYPE; + *state = LEX_EXPECT_OPERATOR; + break; + case ' ': + case '\t': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + *state = LEX_EXPECT_OPERATOR; break; default: - lvNext->token.type = TOKEN_ERROR_TYPE; + *state = LEX_ERROR; break; } - lvNext->next = lv->next; - lv->next = lvNext; - lv = lvNext; - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_ERROR_TYPE; - lv->next = lvNext; - return lvNext; } -size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { +size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol) { if (!string || length < 1) { return 0; }

@@ -47,14 +189,32 @@ size_t adjusted = 0;

enum LexState state = LEX_ROOT; const char* tokenStart = 0; - struct LexVector* lvNext; + struct Token* lvNext; + + if (!eol) { + eol = " \r\n"; + } - while (length > 0 && string[0] && string[0] != ' ' && state != LEX_ERROR) { + while (length > 0 && string[0] && !strchr(eol, string[0]) && state != LEX_ERROR) { char token = string[0]; ++string; ++adjusted; --length; switch (state) { + case LEX_EXPECT_OPERATOR2: + switch (token) { + case '&': + case '|': + case '=': + case '<': + case '>': + _lexOperator(lv, token, &state); + break; + } + if (state != LEX_EXPECT_OPERATOR2) { + break; + } + // Fall through case LEX_ROOT: tokenStart = string - 1; switch (token) {

@@ -75,17 +235,20 @@ state = LEX_EXPECT_PREFIX;

next = 0; break; case '$': - state = LEX_EXPECT_HEX; + state = LEX_EXPECT_HEX_FIRST; + next = 0; + break; + case '%': + state = LEX_EXPECT_BINARY_FIRST; next = 0; break; case '(': state = LEX_ROOT; - lv->token.type = TOKEN_OPEN_PAREN_TYPE; - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_ERROR_TYPE; - lv->next = lvNext; - lv = lvNext; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_OPEN_PAREN_TYPE; + break; + case ' ': + case '\t': break; default: if (tolower(token) >= 'a' && tolower(token <= 'z')) {

@@ -98,24 +261,45 @@ };

break; case LEX_EXPECT_IDENTIFIER: switch (token) { + case '=': case '+': case '-': case '*': case '/': - lv->token.type = TOKEN_IDENTIFIER_TYPE; - lv->token.identifierValue = strndup(tokenStart, string - tokenStart - 1); - lv = _lexOperator(lv, token); - state = LEX_ROOT; + case '%': + case '&': + case '|': + case '^': + case '<': + case '>': + case '!': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); + _lexOperator(lv, token, &state); break; case ')': - lv->token.type = TOKEN_IDENTIFIER_TYPE; - lv->token.identifierValue = strndup(tokenStart, string - tokenStart - 1); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_CLOSE_PAREN_TYPE; + state = LEX_EXPECT_OPERATOR; + break; + case ' ': + case '\t': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); state = LEX_EXPECT_OPERATOR; break; default: break; } break; + case LEX_EXPECT_BINARY_FIRST: + state = LEX_EXPECT_BINARY; + // Fall through case LEX_EXPECT_BINARY: switch (token) { case '0':

@@ -124,22 +308,8 @@ // TODO: handle overflow

next <<= 1; next += token - '0'; break; - case '+': - case '-': - case '*': - case '/': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; - break; default: - state = LEX_ERROR; + _lexValue(lv, token, next, &state); break; } break;

@@ -159,24 +329,14 @@ // TODO: handle overflow

next *= 10; next += token - '0'; break; - case '+': - case '-': - case '*': - case '/': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; + default: + _lexValue(lv, token, next, &state); break; - default: - state = LEX_ERROR; } break; + case LEX_EXPECT_HEX_FIRST: + state = LEX_EXPECT_HEX; + // Fall through case LEX_EXPECT_HEX: switch (token) { case '0':

@@ -213,33 +373,14 @@ // TODO: handle overflow

next *= 16; next += token - 'a' + 10; break; - case '+': - case '-': - case '*': - case '/': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; case ':': - lv->token.type = TOKEN_SEGMENT_TYPE; - lv->token.uintValue = next; - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_UINT_TYPE; - lv->next = lvNext; - lv = lvNext; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_SEGMENT_TYPE; + lvNext->uintValue = next; next = 0; - state = LEX_EXPECT_HEX; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; break; default: - state = LEX_ERROR; + _lexValue(lv, token, next, &state); break; } break;

@@ -248,26 +389,12 @@ switch (token) {

case 'X': case 'x': next = 0; - state = LEX_EXPECT_HEX; + state = LEX_EXPECT_HEX_FIRST; break; case 'B': case 'b': next = 0; - state = LEX_EXPECT_BINARY; - break; - case '+': - case '-': - case '*': - case '/': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; + state = LEX_EXPECT_BINARY_FIRST; break; case '0': case '1':

@@ -283,21 +410,32 @@ next = token - '0';

state = LEX_EXPECT_DECIMAL; break; default: - state = LEX_ERROR; + _lexValue(lv, token, next, &state); + break; } break; case LEX_EXPECT_OPERATOR: switch (token) { + case '=': case '+': case '-': case '*': case '/': - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; - lv->next = lvNext; - lv = _lexOperator(lv->next, token); - state = LEX_ROOT; + case '%': + case '&': + case '|': + case '^': + case '<': + case '>': + case '!': + _lexOperator(lv, token, &state); + break; + case ')': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_CLOSE_PAREN_TYPE; + break; + case ' ': + case '\t': break; default: state = LEX_ERROR;

@@ -314,33 +452,53 @@ case LEX_EXPECT_BINARY:

case LEX_EXPECT_DECIMAL: case LEX_EXPECT_HEX: case LEX_EXPECT_PREFIX: - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; break; case LEX_EXPECT_IDENTIFIER: - lv->token.type = TOKEN_IDENTIFIER_TYPE; - lv->token.identifierValue = strndup(tokenStart, string - tokenStart); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart); break; + case LEX_ROOT: case LEX_EXPECT_OPERATOR: - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; - lv->next = lvNext; + case LEX_EXPECT_OPERATOR2: break; + case LEX_EXPECT_BINARY_FIRST: + case LEX_EXPECT_HEX_FIRST: case LEX_ERROR: default: - lv->token.type = TOKEN_ERROR_TYPE; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_ERROR_TYPE; break; } return adjusted; } static const int _operatorPrecedence[] = { - 2, - 1, - 1, - 0, - 0 + [OP_ASSIGN] = 14, + [OP_ADD] = 4, + [OP_SUBTRACT] = 4, + [OP_MULTIPLY] = 3, + [OP_DIVIDE] = 3, + [OP_MODULO] = 3, + [OP_AND] = 8, + [OP_OR] = 10, + [OP_XOR] = 9, + [OP_LESS] = 6, + [OP_GREATER] = 6, + [OP_EQUAL] = 7, + [OP_NOT_EQUAL] = 7, + [OP_LE] = 6, + [OP_GE] = 6, + [OP_LOGICAL_AND] = 11, + [OP_LOGICAL_OR] = 12, + [OP_NEGATE] = 2, + [OP_FLIP] = 2, + [OP_NOT] = 2, + [OP_SHIFT_L] = 5, + [OP_SHIFT_R] = 5, }; static struct ParseTree* _parseTreeCreate() {

@@ -351,65 +509,69 @@ tree->lhs = 0;

return tree; } -static struct LexVector* _parseExpression(struct ParseTree* tree, struct LexVector* lv, int precedence, int openParens) { +static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, size_t i, int precedence, int* openParens) { struct ParseTree* newTree = 0; - while (lv) { + while (i < LexVectorSize(lv)) { + struct Token* token = LexVectorGetPointer(lv, i); int newPrecedence; - switch (lv->token.type) { + switch (token->type) { case TOKEN_IDENTIFIER_TYPE: case TOKEN_UINT_TYPE: if (tree->token.type == TOKEN_ERROR_TYPE) { - tree->token = lv->token; - lv = lv->next; + tree->token = *token; + if (token->type == TOKEN_IDENTIFIER_TYPE) { + tree->token.identifierValue = strdup(token->identifierValue); + } + ++i; } else { tree->token.type = TOKEN_ERROR_TYPE; - return 0; + return i + 1; } break; case TOKEN_SEGMENT_TYPE: tree->lhs = _parseTreeCreate(); tree->lhs->token.type = TOKEN_UINT_TYPE; - tree->lhs->token.uintValue = lv->token.uintValue; + tree->lhs->token.uintValue = token->uintValue; tree->rhs = _parseTreeCreate(); tree->token.type = TOKEN_SEGMENT_TYPE; - lv = _parseExpression(tree->rhs, lv->next, precedence, openParens); + i = _parseExpression(tree->rhs, lv, i + 1, precedence, openParens); if (tree->token.type == TOKEN_ERROR_TYPE) { tree->token.type = TOKEN_ERROR_TYPE; } break; case TOKEN_OPEN_PAREN_TYPE: - lv = _parseExpression(tree, lv->next, INT_MAX, openParens + 1); + ++*openParens; + i = _parseExpression(tree, lv, i + 1, INT_MAX, openParens); break; case TOKEN_CLOSE_PAREN_TYPE: - if (openParens <= 0) { + if (*openParens <= 0) { tree->token.type = TOKEN_ERROR_TYPE; - return 0; } - return lv->next; - break; + --*openParens; + return i + 1; case TOKEN_OPERATOR_TYPE: - newPrecedence = _operatorPrecedence[lv->token.operatorValue]; + newPrecedence = _operatorPrecedence[token->operatorValue]; if (newPrecedence < precedence) { newTree = _parseTreeCreate(); *newTree = *tree; tree->lhs = newTree; tree->rhs = _parseTreeCreate(); - tree->token = lv->token; - lv = _parseExpression(tree->rhs, lv->next, newPrecedence, openParens); + tree->token = *token; + i = _parseExpression(tree->rhs, lv, i + 1, newPrecedence, openParens); if (tree->token.type == TOKEN_ERROR_TYPE) { tree->token.type = TOKEN_ERROR_TYPE; } } else { - return lv; + return i; } break; case TOKEN_ERROR_TYPE: tree->token.type = TOKEN_ERROR_TYPE; - return 0; + return i + 1; } } - return 0; + return i; } void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv) {

@@ -421,14 +583,23 @@ tree->token.type = TOKEN_ERROR_TYPE;

tree->lhs = 0; tree->rhs = 0; - _parseExpression(tree, lv, _operatorPrecedence[OP_ASSIGN], 0); + int openParens = 0; + _parseExpression(tree, lv, 0, INT_MAX, &openParens); + if (openParens) { + if (tree->token.type == TOKEN_IDENTIFIER_TYPE) { + free(tree->token.identifierValue); + } + tree->token.type = TOKEN_ERROR_TYPE; + } } void lexFree(struct LexVector* lv) { - while (lv) { - struct LexVector* lvNext = lv->next; - free(lv); - lv = lvNext; + size_t i; + for (i = 0; i < LexVectorSize(lv); ++i) { + struct Token* token = LexVectorGetPointer(lv, i); + if (token->type == TOKEN_IDENTIFIER_TYPE) { + free(token->identifierValue); + } } }

@@ -437,11 +608,124 @@ if (!tree) {

return; } - parseFree(tree->lhs); - parseFree(tree->rhs); + if (tree->lhs) { + parseFree(tree->lhs); + free(tree->lhs); + } + if (tree->rhs) { + parseFree(tree->rhs); + free(tree->rhs); + } if (tree->token.type == TOKEN_IDENTIFIER_TYPE) { free(tree->token.identifierValue); } - free(tree); +} + +static bool _performOperation(enum Operation operation, int32_t current, int32_t next, int32_t* value) { + switch (operation) { + case OP_ASSIGN: + current = next; + break; + case OP_ADD: + current += next; + break; + case OP_SUBTRACT: + current -= next; + break; + case OP_MULTIPLY: + current *= next; + break; + case OP_DIVIDE: + if (next != 0) { + current /= next; + } else { + return false; + } + break; + case OP_MODULO: + if (next != 0) { + current %= next; + } else { + return false; + } + break; + case OP_AND: + current &= next; + break; + case OP_OR: + current |= next; + break; + case OP_XOR: + current ^= next; + break; + case OP_LESS: + current = current < next; + break; + case OP_GREATER: + current = current > next; + break; + case OP_EQUAL: + current = current == next; + break; + case OP_NOT_EQUAL: + current = current != next; + break; + case OP_LOGICAL_AND: + current = current && next; + break; + case OP_LOGICAL_OR: + current = current || next; + break; + case OP_LE: + current = current <= next; + break; + case OP_GE: + current = current >= next; + break; + case OP_SHIFT_L: + current <<= next; + break; + case OP_SHIFT_R: + current >>= next; + break; + default: + return false; + } + *value = current; + return true; +} + +bool mDebuggerEvaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, int32_t* value, int* segment) { + if (!value) { + return false; + } + int32_t lhs, rhs; + switch (tree->token.type) { + case TOKEN_UINT_TYPE: + if (segment) { + *segment = -1; + } + *value = tree->token.uintValue; + return true; + case TOKEN_SEGMENT_TYPE: + if (!mDebuggerEvaluateParseTree(debugger, tree->rhs, value, segment)) { + return false; + } + return mDebuggerEvaluateParseTree(debugger, tree->lhs, segment, NULL); + case TOKEN_OPERATOR_TYPE: + if (!mDebuggerEvaluateParseTree(debugger, tree->lhs, &lhs, segment)) { + return false; + } + if (!mDebuggerEvaluateParseTree(debugger, tree->rhs, &rhs, segment)) { + return false; + } + return _performOperation(tree->token.operatorValue, lhs, rhs, value); + case TOKEN_IDENTIFIER_TYPE: + return mDebuggerLookupIdentifier(debugger, tree->token.identifierValue, value, segment); + case TOKEN_ERROR_TYPE: + default: + break; + } + return false; }
A src/debugger/test/lexer.c

@@ -0,0 +1,855 @@

+/* Copyright (c) 2013-2017 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 "util/test/suite.h" + +#include <mgba/internal/debugger/parser.h> + +#define LEX(STR) \ + struct LexVector* lv = *state; \ + lexFree(lv); \ + LexVectorClear(lv); \ + size_t adjusted = lexExpression(lv, STR, strlen(STR), ""); \ + assert_false(adjusted > strlen(STR)) + +M_TEST_SUITE_SETUP(Lexer) { + struct LexVector* lv = malloc(sizeof(struct LexVector)); + LexVectorInit(lv, 0); + *state = lv; + return 0; +} + +M_TEST_SUITE_TEARDOWN(Lexer) { + struct LexVector* lv = *state; + lexFree(lv); + LexVectorDeinit(lv); + free(lv); + return 0; +} + +M_TEST_DEFINE(lexEmpty) { + LEX(""); + + assert_int_equal(LexVectorSize(lv), 0); +} + +M_TEST_DEFINE(lexInt) { + LEX("0"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0); +} + +M_TEST_DEFINE(lexDecimal) { + LEX("10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 10); +} + +M_TEST_DEFINE(lexBinary) { + LEX("0b10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 2); +} + +M_TEST_DEFINE(lexSigilBinary) { + LEX("%10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 2); +} + +M_TEST_DEFINE(lexHex) { + LEX("0x10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0x10); +} + +M_TEST_DEFINE(lexSigilHex) { + LEX("$10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0x10); +} + +M_TEST_DEFINE(lexInvalidDecimal) { + LEX("1a"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexInvalidBinary) { + LEX("0b12"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexInvalidHex) { + LEX("0x1g"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedBinary) { + LEX("0b"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedSigilBinary) { + LEX("%"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedSigilHex) { + LEX("$"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedHex) { + LEX("0x"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexSigilSegmentHex) { + LEX("$01:0010"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_SEGMENT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 0x10); +} + +M_TEST_DEFINE(lexIdentifier) { + LEX("x"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); +} + +M_TEST_DEFINE(lexAddOperator) { + LEX("1+"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); +} + +M_TEST_DEFINE(lexIdentifierAddOperator) { + LEX("x+"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); +} + +M_TEST_DEFINE(lexSubOperator) { + LEX("1-"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SUBTRACT); +} + +M_TEST_DEFINE(lexIdentifierSubOperator) { + LEX("x-"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SUBTRACT); +} + +M_TEST_DEFINE(lexMulOperator) { + LEX("1*"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MULTIPLY); +} + +M_TEST_DEFINE(lexIdentifierMulOperator) { + LEX("x*"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MULTIPLY); +} + +M_TEST_DEFINE(lexDivOperator) { + LEX("1/"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_DIVIDE); +} + +M_TEST_DEFINE(lexIdentifierDivOperator) { + LEX("x/"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_DIVIDE); +} + +M_TEST_DEFINE(lexModOperator) { + LEX("1%"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MODULO); +} + +M_TEST_DEFINE(lexIdentifierModOperator) { + LEX("x%"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MODULO); +} + +M_TEST_DEFINE(lexAndOperator) { + LEX("1&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); +} + +M_TEST_DEFINE(lexIdentifierAndOperator) { + LEX("x&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); +} + +M_TEST_DEFINE(lexOrOperator) { + LEX("1|"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); +} + +M_TEST_DEFINE(lexIdentifierOrOperator) { + LEX("x|"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); +} + +M_TEST_DEFINE(lexXorOperator) { + LEX("1^"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_XOR); +} + +M_TEST_DEFINE(lexIdentifierXorOperator) { + LEX("x^"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_XOR); +} + +M_TEST_DEFINE(lexLessOperator) { + LEX("1<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); +} + +M_TEST_DEFINE(lexIdentifierLessOperator) { + LEX("x<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); +} + +M_TEST_DEFINE(lexGreaterOperator) { + LEX("1>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); +} + +M_TEST_DEFINE(lexIdentifierGreaterOperator) { + LEX("x>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); +} + +M_TEST_DEFINE(lexEqualsOperator) { + LEX("1=="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_EQUAL); +} + +M_TEST_DEFINE(lexIdentifierEqualsOperator) { + LEX("x=="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_EQUAL); +} + +M_TEST_DEFINE(lexNotEqualsOperator) { + LEX("1!="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT_EQUAL); +} + +M_TEST_DEFINE(lexIdentifierNotEqualsOperator) { + LEX("x!="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT_EQUAL); +} + +M_TEST_DEFINE(lexLEOperator) { + LEX("1<="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LE); +} + +M_TEST_DEFINE(lexIdentifierLEOperator) { + LEX("x<="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LE); +} + +M_TEST_DEFINE(lexGEOperator) { + LEX("1>="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GE); +} + +M_TEST_DEFINE(lexIdentifierGEOperator) { + LEX("x>="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GE); +} + +M_TEST_DEFINE(lexLAndOperator) { + LEX("1&&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_AND); +} + +M_TEST_DEFINE(lexIdentifierLAndOperator) { + LEX("x&&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_AND); +} + +M_TEST_DEFINE(lexLOrOperator) { + LEX("1||"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_OR); +} + +M_TEST_DEFINE(lexIdentifierLOrOperator) { + LEX("x||"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_OR); +} + +M_TEST_DEFINE(lexShiftLOperator) { + LEX("1<<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_L); +} + +M_TEST_DEFINE(lexIdentifierShiftLOperator) { + LEX("x<<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_L); +} + +M_TEST_DEFINE(lexShiftROperator) { + LEX("1>>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_R); +} + +M_TEST_DEFINE(lexIdentifierShiftROperator) { + LEX("x>>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_R); +} + +M_TEST_DEFINE(lexEqualsInvalidOperator) { + LEX("1=|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ASSIGN); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierEqualsInvalidOperator) { + LEX("x=|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ASSIGN); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexNotInvalidOperator) { + LEX("1!|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierNotInvalidOperator) { + LEX("x!|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexLessInvalidOperator) { + LEX("1<|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierLessInvalidOperator) { + LEX("x<|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexGreaterInvalidOperator) { + LEX("1>|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierGreaterInvalidOperator) { + LEX("x>|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexAndInvalidOperator) { + LEX("1&|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierAndInvalidOperator) { + LEX("x&|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexOrInvalidOperator) { + LEX("1|>"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierOrInvalidOperator) { + LEX("x|>"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexSimpleExpression) { + LEX("1+1"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->uintValue, 1); +} + +M_TEST_DEFINE(lexOpenParen) { + LEX("("); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); +} + +M_TEST_DEFINE(lexCloseParen) { + LEX("(0)"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 0); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexIdentifierCloseParen) { + LEX("(x)"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 1)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexParentheticalExpression) { + LEX("(1+1)"); + + assert_int_equal(LexVectorSize(lv), 5); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 3)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexNestedParentheticalExpression) { + LEX("(1+(2+3))"); + + assert_int_equal(LexVectorSize(lv), 9); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 5)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 5)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 6)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 6)->uintValue, 3); + assert_int_equal(LexVectorGetPointer(lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexSpaceSimple) { + LEX(" 1 "); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); +} + +M_TEST_DEFINE(lexSpaceIdentifier) { + LEX(" x "); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); +} + +M_TEST_DEFINE(lexSpaceOperator) { + LEX("1 + 2"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->uintValue, 2); +} + +M_TEST_DEFINE(lexSpaceParen) { + LEX(" ( 1 + 2 ) "); + + assert_int_equal(LexVectorSize(lv), 5); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 3)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexSpaceParens) { + LEX(" ( 1 + ( 2 + 3 ) ) "); + + assert_int_equal(LexVectorSize(lv), 9); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 5)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 5)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 6)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 6)->uintValue, 3); + assert_int_equal(LexVectorGetPointer(lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, + cmocka_unit_test(lexEmpty), + cmocka_unit_test(lexInt), + cmocka_unit_test(lexDecimal), + cmocka_unit_test(lexBinary), + cmocka_unit_test(lexSigilBinary), + cmocka_unit_test(lexHex), + cmocka_unit_test(lexSigilHex), + cmocka_unit_test(lexSigilSegmentHex), + cmocka_unit_test(lexInvalidDecimal), + cmocka_unit_test(lexInvalidHex), + cmocka_unit_test(lexInvalidBinary), + cmocka_unit_test(lexTruncatedHex), + cmocka_unit_test(lexTruncatedSigilHex), + cmocka_unit_test(lexTruncatedBinary), + cmocka_unit_test(lexTruncatedSigilBinary), + cmocka_unit_test(lexIdentifier), + cmocka_unit_test(lexAddOperator), + cmocka_unit_test(lexIdentifierAddOperator), + cmocka_unit_test(lexSubOperator), + cmocka_unit_test(lexIdentifierSubOperator), + cmocka_unit_test(lexMulOperator), + cmocka_unit_test(lexIdentifierMulOperator), + cmocka_unit_test(lexDivOperator), + cmocka_unit_test(lexIdentifierDivOperator), + cmocka_unit_test(lexModOperator), + cmocka_unit_test(lexIdentifierModOperator), + cmocka_unit_test(lexAndOperator), + cmocka_unit_test(lexIdentifierAndOperator), + cmocka_unit_test(lexOrOperator), + cmocka_unit_test(lexIdentifierOrOperator), + cmocka_unit_test(lexXorOperator), + cmocka_unit_test(lexIdentifierXorOperator), + cmocka_unit_test(lexLessOperator), + cmocka_unit_test(lexIdentifierLessOperator), + cmocka_unit_test(lexGreaterOperator), + cmocka_unit_test(lexIdentifierGreaterOperator), + cmocka_unit_test(lexEqualsOperator), + cmocka_unit_test(lexIdentifierEqualsOperator), + cmocka_unit_test(lexNotEqualsOperator), + cmocka_unit_test(lexIdentifierNotEqualsOperator), + cmocka_unit_test(lexLEOperator), + cmocka_unit_test(lexIdentifierLEOperator), + cmocka_unit_test(lexGEOperator), + cmocka_unit_test(lexIdentifierGEOperator), + cmocka_unit_test(lexLAndOperator), + cmocka_unit_test(lexIdentifierLAndOperator), + cmocka_unit_test(lexLOrOperator), + cmocka_unit_test(lexIdentifierLOrOperator), + cmocka_unit_test(lexShiftLOperator), + cmocka_unit_test(lexIdentifierShiftLOperator), + cmocka_unit_test(lexShiftROperator), + cmocka_unit_test(lexIdentifierShiftROperator), + cmocka_unit_test(lexEqualsInvalidOperator), + cmocka_unit_test(lexIdentifierEqualsInvalidOperator), + cmocka_unit_test(lexNotInvalidOperator), + cmocka_unit_test(lexIdentifierNotInvalidOperator), + cmocka_unit_test(lexLessInvalidOperator), + cmocka_unit_test(lexIdentifierLessInvalidOperator), + cmocka_unit_test(lexGreaterInvalidOperator), + cmocka_unit_test(lexIdentifierGreaterInvalidOperator), + cmocka_unit_test(lexAndInvalidOperator), + cmocka_unit_test(lexIdentifierAndInvalidOperator), + cmocka_unit_test(lexOrInvalidOperator), + cmocka_unit_test(lexIdentifierOrInvalidOperator), + cmocka_unit_test(lexSimpleExpression), + cmocka_unit_test(lexOpenParen), + cmocka_unit_test(lexCloseParen), + cmocka_unit_test(lexIdentifierCloseParen), + cmocka_unit_test(lexParentheticalExpression), + cmocka_unit_test(lexNestedParentheticalExpression), + cmocka_unit_test(lexSpaceSimple), + cmocka_unit_test(lexSpaceIdentifier), + cmocka_unit_test(lexSpaceOperator), + cmocka_unit_test(lexSpaceParen), + cmocka_unit_test(lexSpaceParens))
A src/debugger/test/parser.c

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

+/* Copyright (c) 2013-2017 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 "util/test/suite.h" + +#include <mgba/internal/debugger/parser.h> + +struct LPTest { + struct LexVector lv; + struct ParseTree tree; +}; + +#define PARSE(STR) \ + struct LPTest* lp = *state; \ + lexFree(&lp->lv); \ + LexVectorClear(&lp->lv); \ + size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR), ""); \ + assert_false(adjusted > strlen(STR)); \ + struct ParseTree* tree = &lp->tree; \ + parseLexedExpression(tree, &lp->lv) + +M_TEST_SUITE_SETUP(Parser) { + struct LPTest* lp = malloc(sizeof(struct LPTest)); + LexVectorInit(&lp->lv, 0); + *state = lp; + return 0; +} + +M_TEST_SUITE_TEARDOWN(Parser) { + struct LPTest* lp = *state; + parseFree(&lp->tree); \ + lexFree(&lp->lv); + LexVectorDeinit(&lp->lv); + free(lp); + return 0; +} + +M_TEST_DEFINE(parseEmpty) { + PARSE(""); + + assert_int_equal(tree->token.type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(parseInt) { + PARSE("0"); + + assert_int_equal(tree->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->token.uintValue, 0); +} + +M_TEST_DEFINE(parseLexError) { + PARSE("@"); + + assert_int_equal(tree->token.type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(parseSimpleExpression) { + PARSE("1+2"); + + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_ADD); + assert_int_equal(tree->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->token.uintValue, 1); + assert_int_equal(tree->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->token.uintValue, 2); +} + +M_TEST_DEFINE(parseAddMultplyExpression) { + PARSE("1+2*3"); + + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_ADD); + assert_int_equal(tree->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->token.uintValue, 1); + assert_int_equal(tree->rhs->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->rhs->token.uintValue, OP_MULTIPLY); + assert_int_equal(tree->rhs->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->lhs->token.uintValue, 2); + assert_int_equal(tree->rhs->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->rhs->token.uintValue, 3); +} + +M_TEST_DEFINE(parseParentheticalExpression) { + PARSE("(1+2)"); + + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_ADD); + assert_int_equal(tree->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->token.uintValue, 1); + assert_int_equal(tree->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->token.uintValue, 2); +} + +M_TEST_DEFINE(parseParentheticalAddMultplyExpression) { + PARSE("(1+2)*3"); + + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_MULTIPLY); + assert_int_equal(tree->lhs->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->lhs->token.uintValue, OP_ADD); + assert_int_equal(tree->lhs->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->lhs->token.uintValue, 1); + assert_int_equal(tree->lhs->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->rhs->token.uintValue, 2); + assert_int_equal(tree->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->token.uintValue, 3); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Parser, + cmocka_unit_test(parseEmpty), + cmocka_unit_test(parseInt), + cmocka_unit_test(parseLexError), + cmocka_unit_test(parseSimpleExpression), + cmocka_unit_test(parseAddMultplyExpression), + cmocka_unit_test(parseParentheticalExpression), + cmocka_unit_test(parseParentheticalAddMultplyExpression))
M src/ds/dma.csrc/ds/dma.c

@@ -133,13 +133,13 @@ DSDMAService(dscore, dma);

} } else { dma->nextCount = 0; - if (!GBADMARegisterIsRepeat(dma->reg) || GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_NOW) { + if (!GBADMARegisterIsRepeat(dma->reg) || GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_NOW) { dma->reg = GBADMARegisterClearEnable(dma->reg); // Clear the enable bit in memory memory->io[(DS_REG_DMA0CNT_HI + memory->activeDMA * (DS_REG_DMA1CNT_HI - DS_REG_DMA0CNT_HI)) >> 1] &= 0x7FFF; } - if (GBADMARegisterGetDestControl(dma->reg) == DMA_INCREMENT_RELOAD) { + if (GBADMARegisterGetDestControl(dma->reg) == GBA_DMA_INCREMENT_RELOAD) { dma->nextDest = dma->dest; } if (GBADMARegisterIsDoIRQ(dma->reg)) {
M src/ds/ds.csrc/ds/ds.c

@@ -617,7 +617,7 @@ #ifdef USE_DEBUGGERS

if (ds->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .opcode = opcode + .type.bp.opcode = opcode }; mDebuggerEnter(ds->debugger->d.p, DEBUGGER_ENTER_ILLEGAL_OP, &info); }

@@ -641,7 +641,7 @@ #ifdef USE_DEBUGGERS

} else if (ds->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .opcode = opcode + .type.bp.opcode = opcode }; mDebuggerEnter(ds->debugger->d.p, DEBUGGER_ENTER_ILLEGAL_OP, &info); #endif
M src/ds/extra/cli.csrc/ds/extra/cli.c

@@ -12,7 +12,6 @@ #include <mgba/internal/ds/ds.h>

static void _DSCLIDebuggerInit(struct CLIDebuggerSystem*); static bool _DSCLIDebuggerCustom(struct CLIDebuggerSystem*); -static uint32_t _DSCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); static void _switchCpu(struct CLIDebugger*, struct CLIDebugVector*);

@@ -29,7 +28,6 @@ ARMCLIDebuggerCreate(&debugger->d);

debugger->d.init = _DSCLIDebuggerInit; debugger->d.deinit = NULL; debugger->d.custom = _DSCLIDebuggerCustom; - debugger->d.lookupIdentifier = _DSCLIDebuggerLookupIdentifier; debugger->d.name = "DS"; debugger->d.commands = _DSCLIDebuggerCommands;

@@ -47,12 +45,6 @@ }

static bool _DSCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { return false; -} - -static uint32_t _DSCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - UNUSED(debugger); - dv->type = CLIDV_ERROR_TYPE; - return 0; } static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
M src/feature/ffmpeg/ffmpeg-encoder.csrc/feature/ffmpeg/ffmpeg-encoder.c

@@ -235,7 +235,11 @@ encoder->audio->sample_fmt = encoder->sampleFormat;

AVDictionary* opts = 0; av_dict_set(&opts, "strict", "-2", 0); if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { +#ifdef AV_CODEC_FLAG_GLOBAL_HEADER + encoder->audio->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } avcodec_open2(encoder->audio, acodec, &opts); av_dict_free(&opts);

@@ -298,7 +302,11 @@ encoder->video->pix_fmt = encoder->pixFormat;

encoder->video->gop_size = 60; encoder->video->max_b_frames = 3; if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { +#ifdef AV_CODEC_FLAG_GLOBAL_HEADER + encoder->video->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } if (strcmp(vcodec->name, "libx264") == 0) { // Try to adaptively figure out when you can use a slower encoder
M src/feature/gui/gui-config.csrc/feature/gui/gui-config.c

@@ -10,6 +10,9 @@ #include <mgba/core/core.h>

#include "feature/gui/gui-runner.h" #include "feature/gui/remap.h" #include <mgba/internal/gba/gba.h> +#ifdef M_CORE_GB +#include <mgba/internal/gb/gb.h> +#endif #include <mgba-util/gui/file-select.h> #include <mgba-util/gui/menu.h>

@@ -39,6 +42,26 @@ .title = "Show framerate",

.data = "fpsCounter", .submenu = 0, .state = false, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Autosave state", + .data = "autosave", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Autoload state", + .data = "autoload", + .submenu = 0, + .state = true, .validStates = (const char*[]) { "Off", "On" },

@@ -55,9 +78,23 @@ },

.nStates = 2 }; *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { - .title = "Select BIOS path", - .data = "bios", + .title = "Select GBA BIOS path", + .data = "gba.bios", + }; +#ifdef M_CORE_GB + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select GB BIOS path", + .data = "gb.bios", }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select GBC BIOS path", + .data = "gbc.bios", + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select SGB BIOS path", + .data = "sgb.bios", + }; +#endif size_t i; const char* mapNames[GUI_MAX_INPUTS + 1]; if (runner->keySources) {

@@ -88,7 +125,12 @@ .title = "Cancel",

.data = 0, }; enum GUIMenuExitReason reason; - char biosPath[256] = ""; + char gbaBiosPath[256] = ""; +#ifdef M_CORE_GB + char gbBiosPath[256] = ""; + char gbcBiosPath[256] = ""; + char sgbBiosPath[256] = ""; +#endif struct GUIMenuItem* item; for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) {

@@ -105,8 +147,17 @@ if (reason != GUI_MENU_EXIT_ACCEPT || !item->data) {

break; } if (!strcmp(item->data, "*SAVE")) { - if (biosPath[0]) { - mCoreConfigSetValue(&runner->config, "bios", biosPath); + if (gbaBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "gba.bios", gbaBiosPath); + } + if (gbBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "gb.bios", gbBiosPath); + } + if (gbcBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "gbc.bios", gbcBiosPath); + } + if (sgbBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "sgb.bios", sgbBiosPath); } for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { item = GUIMenuItemListGetPointer(&menu.items, i);

@@ -130,13 +181,36 @@ if (!strcmp(item->data, "*REMAP")) {

mGUIRemapKeys(&runner->params, &runner->core->inputMap, &runner->keySources[item->state]); continue; } - if (!strcmp(item->data, "bios")) { + if (!strcmp(item->data, "gba.bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), GBAIsBIOS)) { + gbaBiosPath[0] = '\0'; + } + continue; + } +#ifdef M_CORE_GB + if (!strcmp(item->data, "gb.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, biosPath, sizeof(biosPath), GBAIsBIOS)) { - biosPath[0] = '\0'; + if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), GBIsBIOS)) { + gbBiosPath[0] = '\0'; } continue; } + if (!strcmp(item->data, "gbc.bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), GBIsBIOS)) { + gbcBiosPath[0] = '\0'; + } + continue; + } + if (!strcmp(item->data, "sgb.bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), GBIsBIOS)) { + sgbBiosPath[0] = '\0'; + } + continue; + } +#endif if (item->validStates) { ++item->state; if (item->state >= item->nStates) {
M src/feature/gui/gui-runner.csrc/feature/gui/gui-runner.c

@@ -18,15 +18,12 @@ #include <mgba-util/memory.h>

#include <mgba-util/png-io.h> #include <mgba-util/vfs.h> -#ifdef _3DS -#include <3ds.h> -#endif - #include <sys/time.h> mLOG_DECLARE_CATEGORY(GUI_RUNNER); mLOG_DEFINE_CATEGORY(GUI_RUNNER, "GUI Runner", "gui.runner"); +#define AUTOSAVE_GRANULARITY 600 #define FPS_GRANULARITY 120 #define FPS_BUFFER_SIZE 3

@@ -38,18 +35,10 @@ RUNNER_LOAD_STATE,

RUNNER_SCREENSHOT, RUNNER_CONFIG, RUNNER_RESET, - RUNNER_COMMAND_MASK = 0xFFFF, + RUNNER_COMMAND_MASK = 0xFFFF +}; - RUNNER_STATE_1 = 0x10000, - RUNNER_STATE_2 = 0x20000, - RUNNER_STATE_3 = 0x30000, - RUNNER_STATE_4 = 0x40000, - RUNNER_STATE_5 = 0x50000, - RUNNER_STATE_6 = 0x60000, - RUNNER_STATE_7 = 0x70000, - RUNNER_STATE_8 = 0x80000, - RUNNER_STATE_9 = 0x90000, -}; +#define RUNNER_STATE(X) ((X) << 16) static const struct mInputPlatformInfo _mGUIKeyInfo = { .platformName = "gui",

@@ -145,6 +134,27 @@ }

return 0xFF - value; } +static void _tryAutosave(struct mGUIRunner* runner) { + int autosave = false; + mCoreConfigGetIntValue(&runner->config, "autosave", &autosave); + if (!autosave) { + return; + } + +#ifdef DISABLE_THREADING + mCoreSaveState(runner->core, 0, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); +#else + if (!runner->autosave.buffer) { + runner->autosave.buffer = VFileMemChunk(NULL, 0); + } + MutexLock(&runner->autosave.mutex); + runner->autosave.core = runner->core; + mCoreSaveStateNamed(runner->core, runner->autosave.buffer, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); + ConditionWake(&runner->autosave.cond); + MutexUnlock(&runner->autosave.mutex); +#endif +} + void mGUIInit(struct mGUIRunner* runner, const char* port) { GUIInit(&runner->params); runner->port = port;

@@ -164,7 +174,14 @@ mCoreConfigInit(&runner->config, runner->port);

// TODO: Do we need to load more defaults? mCoreConfigSetDefaultIntValue(&runner->config, "volume", 0x100); mCoreConfigSetDefaultValue(&runner->config, "idleOptimization", "detect"); + mCoreConfigSetDefaultIntValue(&runner->config, "autoload", true); +#ifdef DISABLE_THREADING + mCoreConfigSetDefaultIntValue(&runner->config, "autosave", false); +#else + mCoreConfigSetDefaultIntValue(&runner->config, "autosave", true); +#endif mCoreConfigLoad(&runner->config); + mCoreConfigGetIntValue(&runner->config, "logLevel", &logger.logLevel); char path[PATH_MAX]; mCoreConfigDirectory(path, PATH_MAX);

@@ -177,9 +194,34 @@ if (lastPath) {

strncpy(runner->params.currentPath, lastPath, PATH_MAX - 1); runner->params.currentPath[PATH_MAX - 1] = '\0'; } + +#ifndef DISABLE_THREADING + if (!runner->autosave.running) { + runner->autosave.running = true; + MutexInit(&runner->autosave.mutex); + ConditionInit(&runner->autosave.cond); + ThreadCreate(&runner->autosave.thread, mGUIAutosaveThread, &runner->autosave); + } +#endif } void mGUIDeinit(struct mGUIRunner* runner) { +#ifndef DISABLE_THREADING + MutexLock(&runner->autosave.mutex); + runner->autosave.running = false; + ConditionWake(&runner->autosave.cond); + MutexUnlock(&runner->autosave.mutex); + + ThreadJoin(runner->autosave.thread); + + ConditionDeinit(&runner->autosave.cond); + MutexDeinit(&runner->autosave.mutex); + + if (runner->autosave.buffer) { + runner->autosave.buffer->close(runner->autosave.buffer); + } +#endif + if (runner->teardown) { runner->teardown(runner); }

@@ -242,25 +284,26 @@ *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Unpause", .data = (void*) RUNNER_CONTINUE };

*GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_1) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_2) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_3) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_4) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_5) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_6) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_7) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_8) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_9) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(1)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(2)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(3)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(4)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(5)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(6)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(7)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(8)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(9)) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_1) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_2) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_3) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_4) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_5) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_6) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "Autosave", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(0)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(1)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(2)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(3)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(4)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(5)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(6)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(7)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(8)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(9)) }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG };

@@ -283,7 +326,7 @@ runner->core = mCoreFind(path);

if (runner->core) { mLOG(GUI_RUNNER, INFO, "Found core"); runner->core->init(runner->core); - mCoreConfigInit(&runner->core->config, runner->port); + mCoreInitConfig(runner->core, runner->port); mInputMapInit(&runner->core->inputMap, &GBAInputInfo); found = mCoreLoadFile(runner->core, path); if (!found) {

@@ -302,10 +345,10 @@ runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d);

} mLOG(GUI_RUNNER, DEBUG, "Loading config..."); mCoreLoadForeignConfig(runner->core, &runner->config); - logger.logLevel = runner->core->opts.logLevel; mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mCoreAutoloadSave(runner->core); + mCoreAutoloadCheats(runner->core); if (runner->setup) { mLOG(GUI_RUNNER, DEBUG, "Setting up runner..."); runner->setup(runner);

@@ -320,7 +363,22 @@ }

mLOG(GUI_RUNNER, DEBUG, "Reseting..."); runner->core->reset(runner->core); mLOG(GUI_RUNNER, DEBUG, "Reset!"); + + + int autoload = false; + mCoreConfigGetIntValue(&runner->config, "autoload", &autoload); + if (autoload) { + mCoreLoadState(runner->core, 0, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); + } + bool running = true; + +#ifndef DISABLE_THREADING + MutexLock(&runner->autosave.mutex); + runner->autosave.core = runner->core; + MutexUnlock(&runner->autosave.mutex); +#endif + if (runner->gameLoaded) { runner->gameLoaded(runner); }

@@ -333,13 +391,14 @@ struct timeval tv;

gettimeofday(&tv, 0); runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; - while (true) { -#ifdef _3DS - running = aptMainLoop(); - if (!running) { - break; + int frame = 0; + while (running) { + if (runner->running) { + running = runner->running(runner); + if (!running) { + break; + } } -#endif uint32_t guiKeys; uint32_t heldKeys; GUIPollInput(&runner->params, &guiKeys, &heldKeys);

@@ -411,6 +470,11 @@ runner->totalDelta += delta;

runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t)); } } + if (frame == AUTOSAVE_GRANULARITY) { + frame = 0; + _tryAutosave(runner); + } + ++frame; } }

@@ -463,6 +527,18 @@ mLOG(GUI_RUNNER, DEBUG, "Shutting down...");

if (runner->gameUnloaded) { runner->gameUnloaded(runner); } +#ifndef DISABLE_THREADING + MutexLock(&runner->autosave.mutex); + runner->autosave.core = NULL; + MutexUnlock(&runner->autosave.mutex); +#endif + + int autosave = false; + mCoreConfigGetIntValue(&runner->config, "autosave", &autosave); + if (autosave) { + mCoreSaveState(runner->core, 0, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); + } + mLOG(GUI_RUNNER, DEBUG, "Unloading game..."); runner->core->unloadROM(runner->core); drawState.screenshotId = 0;

@@ -486,6 +562,7 @@ }

mInputMapDeinit(&runner->core->inputMap); mLOG(GUI_RUNNER, DEBUG, "Deinitializing core..."); runner->core->deinit(runner->core); + runner->core = NULL; GUIMenuItemListDeinit(&pauseMenu.items); GUIMenuItemListDeinit(&stateSaveMenu.items);

@@ -511,3 +588,21 @@ mCoreConfigSave(&runner->config);

mGUIRun(runner, path); } } + +#ifndef DISABLE_THREADING +THREAD_ENTRY mGUIAutosaveThread(void* context) { + struct mGUIAutosaveContext* autosave = context; + MutexLock(&autosave->mutex); + while (autosave->running) { + ConditionWait(&autosave->cond, &autosave->mutex); + if (autosave->running && autosave->core) { + struct VFile* vf = mCoreGetState(autosave->core, 0, true); + void* mem = autosave->buffer->map(autosave->buffer, autosave->buffer->size(autosave->buffer), MAP_READ); + vf->write(vf, mem, autosave->buffer->size(autosave->buffer)); + autosave->buffer->unmap(autosave->buffer, mem, autosave->buffer->size(autosave->buffer)); + vf->close(vf); + } + } + MutexUnlock(&autosave->mutex); +} +#endif
M src/feature/gui/gui-runner.hsrc/feature/gui/gui-runner.h

@@ -15,6 +15,7 @@ #include "feature/gui/remap.h"

#include <mgba/internal/gba/hardware.h> #include <mgba-util/circle-buffer.h> #include <mgba-util/gui.h> +#include <mgba-util/threading.h> enum mGUIInput { mGUI_INPUT_INCREASE_BRIGHTNESS = GUI_INPUT_USER_START,

@@ -38,12 +39,27 @@ struct GBALuminanceSource d;

int luxLevel; }; +#ifndef DISABLE_THREADING +struct VFile; +struct mGUIAutosaveContext { + struct VFile* buffer; + struct mCore* core; + Thread thread; + Mutex mutex; + Condition cond; + bool running; +}; +#endif + struct mGUIRunner { struct mCore* core; struct GUIParams params; struct mGUIBackground background; struct mGUIRunnerLux luminanceSource; +#ifndef DISABLE_THREADING + struct mGUIAutosaveContext autosave; +#endif struct mInputMap guiKeys; struct mCoreConfig config;

@@ -70,12 +86,17 @@ void (*unpaused)(struct mGUIRunner*);

void (*incrementScreenMode)(struct mGUIRunner*); void (*setFrameLimiter)(struct mGUIRunner*, bool limit); uint16_t (*pollGameInput)(struct mGUIRunner*); + bool (*running)(struct mGUIRunner*); }; void mGUIInit(struct mGUIRunner*, const char* port); void mGUIDeinit(struct mGUIRunner*); void mGUIRun(struct mGUIRunner*, const char* path); void mGUIRunloop(struct mGUIRunner*); + +#ifndef DISABLE_THREADING +THREAD_ENTRY mGUIAutosaveThread(void* context); +#endif CXX_GUARD_END
M src/gb/cheats.csrc/gb/cheats.c

@@ -244,7 +244,11 @@ }

static void GBCheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBCheatSet* gbset = (struct GBCheatSet*) cheats; - _patchROM(device, gbset); + if (cheats->enabled) { + _patchROM(device, gbset); + } else { + _unpatchROM(device, gbset); + } } static void GBCheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) {
M src/gb/core.csrc/gb/core.c

@@ -463,6 +463,10 @@ }

#endif LR35902Reset(core->cpu); + + if (core->opts.skipBios) { + GBSkipBIOS(core->board); + } } static void _GBCoreRunFrame(struct mCore* core) {

@@ -746,6 +750,20 @@ return;

} GBLoadSymbols(core->symbolTable, vf); } + +static bool _GBCoreLookupIdentifier(struct mCore* core, const char* name, int32_t* value, int* segment) { + UNUSED(core); + *segment = -1; + int i; + for (i = 0; i < REG_MAX; ++i) { + const char* reg = GBIORegisterNames[i]; + if (reg && strcasecmp(reg, name) == 0) { + *value = GB_BASE_IO | i; + return true; + } + } + return false; +} #endif static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) {

@@ -835,6 +853,7 @@ break;

} } +#ifndef MINIMAL_CORE static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { struct GBCore* gbcore = (struct GBCore*) core; struct GB* gb = core->board;

@@ -857,6 +876,7 @@ GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);

free(gbcore->proxyRenderer.logger); gbcore->proxyRenderer.logger = NULL; } +#endif struct mCore* GBCoreCreate(void) { struct GBCore* gbcore = malloc(sizeof(*gbcore));

@@ -928,6 +948,7 @@ core->cliDebuggerSystem = _GBCoreCliDebuggerSystem;

core->attachDebugger = _GBCoreAttachDebugger; core->detachDebugger = _GBCoreDetachDebugger; core->loadSymbols = _GBCoreLoadSymbols; + core->lookupIdentifier = _GBCoreLookupIdentifier; #endif core->cheatDevice = _GBCoreCheatDevice; core->savedataClone = _GBCoreSavedataClone;
M src/gb/debugger/cli.csrc/gb/debugger/cli.c

@@ -14,16 +14,15 @@ #include <mgba/internal/lr35902/debugger/cli-debugger.h>

static void _GBCLIDebuggerInit(struct CLIDebuggerSystem*); static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*); -static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); static void _load(struct CLIDebugger*, struct CLIDebugVector*); static void _save(struct CLIDebugger*, struct CLIDebugVector*); struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = { - { "frame", _frame, 0, "Frame advance" }, - { "load", _load, CLIDVParse, "Load a savestate" }, - { "save", _save, CLIDVParse, "Save a savestate" }, + { "frame", _frame, "", "Frame advance" }, + { "load", _load, "*", "Load a savestate" }, + { "save", _save, "*", "Save a savestate" }, { 0, 0, 0, 0 } };

@@ -34,7 +33,6 @@ LR35902CLIDebuggerCreate(&debugger->d);

debugger->d.init = _GBCLIDebuggerInit; debugger->d.deinit = NULL; debugger->d.custom = _GBCLIDebuggerCustom; - debugger->d.lookupIdentifier = _GBCLIDebuggerLookupIdentifier; debugger->d.name = "Game Boy"; debugger->d.commands = _GBCLIDebuggerCommands;

@@ -63,19 +61,6 @@ gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1;

return true; } return false; -} - -static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - UNUSED(debugger); - int i; - for (i = 0; i < REG_MAX; ++i) { - const char* reg = GBIORegisterNames[i]; - if (reg && strcasecmp(reg, name) == 0) { - return GB_BASE_IO | i; - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; } static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
M src/gb/gb.csrc/gb/gb.c

@@ -435,83 +435,10 @@ cpu->b = 0;

cpu->d = 0; gb->timer.internalDiv = 0; - int nextDiv = 0; - if (!gb->biosVf) { - switch (gb->model) { - case GB_MODEL_AUTODETECT: // Silence warnings - gb->model = GB_MODEL_DMG; - case GB_MODEL_DMG: - cpu->a = 1; - cpu->f.packed = 0xB0; - cpu->c = 0x13; - cpu->e = 0xD8; - cpu->h = 1; - cpu->l = 0x4D; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_SGB: - cpu->a = 1; - cpu->f.packed = 0x00; - cpu->c = 0x14; - cpu->e = 0x00; - cpu->h = 0xC0; - cpu->l = 0x60; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_MGB: - cpu->a = 0xFF; - cpu->f.packed = 0xB0; - cpu->c = 0x13; - cpu->e = 0xD8; - cpu->h = 1; - cpu->l = 0x4D; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_SGB2: - cpu->a = 0xFF; - cpu->f.packed = 0x00; - cpu->c = 0x14; - cpu->e = 0x00; - cpu->h = 0xC0; - cpu->l = 0x60; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_AGB: - cpu->a = 0x11; - cpu->b = 1; - cpu->f.packed = 0x00; - cpu->c = 0; - cpu->e = 0x08; - cpu->h = 0; - cpu->l = 0x7C; - gb->timer.internalDiv = 0x1EA; - nextDiv = 0xC; - break; - case GB_MODEL_CGB: - cpu->a = 0x11; - cpu->f.packed = 0x80; - cpu->c = 0; - cpu->e = 0x08; - cpu->h = 0; - cpu->l = 0x7C; - gb->timer.internalDiv = 0x1EA; - nextDiv = 0xC; - break; - } - - cpu->sp = 0xFFFE; - cpu->pc = 0x100; - } gb->cpuBlocked = false; gb->earlyExit = false; gb->doubleSpeed = 0; - - cpu->memory.setActiveRegion(cpu, cpu->pc); if (gb->yankedRomSize) { gb->memory.romSize = gb->yankedRomSize;

@@ -527,15 +454,109 @@

GBMemoryReset(gb); GBVideoReset(&gb->video); GBTimerReset(&gb->timer); - mTimingSchedule(&gb->timing, &gb->timer.event, nextDiv); + if (!gb->biosVf) { + GBSkipBIOS(gb); + } else { + mTimingSchedule(&gb->timing, &gb->timer.event, 0); + } GBIOReset(gb); GBAudioReset(&gb->audio); GBSIOReset(&gb->sio); + + cpu->memory.setActiveRegion(cpu, cpu->pc); GBSavedataUnmask(gb); } +void GBSkipBIOS(struct GB* gb) { + struct LR35902Core* cpu = gb->cpu; + int nextDiv = 0; + + switch (gb->model) { + case GB_MODEL_AUTODETECT: // Silence warnings + gb->model = GB_MODEL_DMG; + case GB_MODEL_DMG: + cpu->a = 1; + cpu->f.packed = 0xB0; + cpu->c = 0x13; + cpu->e = 0xD8; + cpu->h = 1; + cpu->l = 0x4D; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_SGB: + cpu->a = 1; + cpu->f.packed = 0x00; + cpu->c = 0x14; + cpu->e = 0x00; + cpu->h = 0xC0; + cpu->l = 0x60; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_MGB: + cpu->a = 0xFF; + cpu->f.packed = 0xB0; + cpu->c = 0x13; + cpu->e = 0xD8; + cpu->h = 1; + cpu->l = 0x4D; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_SGB2: + cpu->a = 0xFF; + cpu->f.packed = 0x00; + cpu->c = 0x14; + cpu->e = 0x00; + cpu->h = 0xC0; + cpu->l = 0x60; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_AGB: + cpu->a = 0x11; + cpu->b = 1; + cpu->f.packed = 0x00; + cpu->c = 0; + cpu->e = 0x08; + cpu->h = 0; + cpu->l = 0x7C; + gb->timer.internalDiv = 0x1EA; + nextDiv = 0xC; + break; + case GB_MODEL_CGB: + cpu->a = 0x11; + cpu->f.packed = 0x80; + cpu->c = 0; + cpu->e = 0x08; + cpu->h = 0; + cpu->l = 0x7C; + gb->timer.internalDiv = 0x1EA; + nextDiv = 0xC; + break; + } + + cpu->sp = 0xFFFE; + cpu->pc = 0x100; + + mTimingDeschedule(&gb->timing, &gb->timer.event); + mTimingSchedule(&gb->timing, &gb->timer.event, 0); + + if (gb->biosVf) { + GBUnmapBIOS(gb); + } +} + +void GBUnmapBIOS(struct GB* gb) { + if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { + free(gb->memory.romBase); + gb->memory.romBase = gb->memory.rom; + } +} + void GBDetectModel(struct GB* gb) { if (gb->model != GB_MODEL_AUTODETECT) { return;

@@ -677,9 +698,12 @@ GBUpdateIRQs(gb);

} void GBHalt(struct LR35902Core* cpu) { - if (!cpu->irqPending) { + struct GB* gb = (struct GB*) cpu->master; + if (!(gb->memory.ie & gb->memory.io[REG_IF])) { cpu->cycles = cpu->nextEvent; cpu->halted = true; + } else if (gb->model < GB_MODEL_CGB) { + mLOG(GB, STUB, "Unimplemented HALT bug"); } }

@@ -698,7 +722,7 @@ #ifdef USE_DEBUGGERS

if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) { struct mDebuggerEntryInfo info = { .address = cpu->pc - 1, - .opcode = 0x1000 | cpu->bus + .type.bp.opcode = 0x1000 | cpu->bus }; mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info); }

@@ -717,7 +741,7 @@ #ifdef USE_DEBUGGERS

if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) { struct mDebuggerEntryInfo info = { .address = cpu->pc, - .opcode = cpu->bus + .type.bp.opcode = cpu->bus }; mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info); }
M src/gb/io.csrc/gb/io.c

@@ -101,7 +101,6 @@ [REG_OCPS] = 0x40,

[REG_BCPS] = 0x40, [REG_UNK6C] = 0xFE, [REG_SVBK] = 0xF8, - [REG_UNK75] = 0x8F, [REG_IE] = 0xE0, };

@@ -174,6 +173,7 @@ }

GBIOWrite(gb, REG_WY, 0x00); GBIOWrite(gb, REG_WX, 0x00); if (gb->model >= GB_MODEL_CGB) { + GBIOWrite(gb, REG_UNK4C, 0); GBIOWrite(gb, REG_JOYP, 0xFF); GBIOWrite(gb, REG_VBK, 0); GBIOWrite(gb, REG_BCPS, 0);

@@ -392,7 +392,7 @@ GBUpdateIRQs(gb);

return; case REG_LCDC: // TODO: handle GBC differences - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value); GBVideoWriteLCDC(&gb->video, value); break;

@@ -406,13 +406,13 @@ case REG_SCY:

case REG_SCX: case REG_WY: case REG_WX: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value); break; case REG_BGP: case REG_OBP0: case REG_OBP1: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); GBVideoWritePalette(&gb->video, address, value); break; case REG_STAT:

@@ -420,11 +420,8 @@ GBVideoWriteSTAT(&gb->video, value);

value = gb->video.stat; break; case 0x50: - if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { - free(gb->memory.romBase); - gb->memory.romBase = gb->memory.rom; - } - if (gb->model >= GB_MODEL_CGB && gb->memory.io[0x6C]) { + GBUnmapBIOS(gb); + if (gb->model >= GB_MODEL_CGB && gb->memory.io[REG_UNK4C] < 0x80) { gb->model = GB_MODEL_DMG; GBVideoDisableCGB(&gb->video); }

@@ -436,6 +433,8 @@ return;

default: if (gb->model >= GB_MODEL_CGB) { switch (address) { + case REG_UNK4C: + break; case REG_KEY1: value &= 0x1; value |= gb->memory.io[address] & 0x80;

@@ -450,8 +449,7 @@ case REG_HDMA4:

// Handled transparently by the registers break; case REG_HDMA5: - GBMemoryWriteHDMA5(gb, value); - value &= 0x7F; + value = GBMemoryWriteHDMA5(gb, value); break; case REG_BCPS: gb->video.bcpIndex = value & 0x3F;

@@ -459,7 +457,7 @@ gb->video.bcpIncrement = value & 0x80;

gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1)); break; case REG_BCPD: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); GBVideoWritePalette(&gb->video, address, value); return; case REG_OCPS:

@@ -468,7 +466,7 @@ gb->video.ocpIncrement = value & 0x80;

gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1)); break; case REG_OCPD: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); GBVideoWritePalette(&gb->video, address, value); return; case REG_SVBK:
M src/gb/mbc.csrc/gb/mbc.c

@@ -31,6 +31,7 @@ static void _GBHuC3(struct GB*, uint16_t address, uint8_t value);

static void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value); static void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value); +static uint8_t _GBMBC2Read(struct GBMemory*, uint16_t address); static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value);

@@ -45,9 +46,6 @@ if (bankStart + GB_SIZE_CART_BANK0 > gb->memory.romSize) {

mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank); bankStart &= (gb->memory.romSize - 1); bank = bankStart / GB_SIZE_CART_BANK0; - if (!bank) { - ++bank; - } } gb->memory.romBank = &gb->memory.rom[bankStart]; gb->memory.currentBank = bank;

@@ -217,7 +215,8 @@ gb->memory.mbcWrite = _GBMBC1;

break; case GB_MBC2: gb->memory.mbcWrite = _GBMBC2; - gb->sramSize = 0x200; + gb->memory.mbcRead = _GBMBC2Read; + gb->sramSize = 0x100; break; case GB_MBC3: gb->memory.mbcWrite = _GBMBC3;

@@ -399,6 +398,7 @@ }

void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; + int shift = (address & 1) * 4; int bank = value & 0xF; switch (address >> 13) { case 0x0:

@@ -408,7 +408,6 @@ memory->sramAccess = false;

break; case 0xA: memory->sramAccess = true; - GBMBCSwitchSramBank(gb, memory->sramCurrentBank); break; default: // TODO

@@ -422,11 +421,24 @@ ++bank;

} GBMBCSwitchBank(gb, bank); break; + case 0x5: + if (!memory->sramAccess) { + return; + } + address &= 0x1FF; + memory->sramBank[(address >> 1)] &= 0xF0 >> shift; + memory->sramBank[(address >> 1)] |= (value & 0xF) << shift; default: // TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value); break; } +} + +static uint8_t _GBMBC2Read(struct GBMemory* memory, uint16_t address) { + address &= 0x1FF; + int shift = (address & 1) * 4; + return (memory->sramBank[(address >> 1)] >> shift) | 0xF0; } void _GBMBC3(struct GB* gb, uint16_t address, uint8_t value) {
M src/gb/memory.csrc/gb/memory.c

@@ -294,7 +294,7 @@ case GB_REGION_EXTERNAL_RAM:

case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { memory->rtcRegs[memory->activeRtcReg] = value; - } else if (memory->sramAccess && memory->sram) { + } else if (memory->sramAccess && memory->sram && memory->mbcType != GB_MBC2) { memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; } else { memory->mbcWrite(gb, address, value);

@@ -454,7 +454,7 @@ gb->memory.dmaDest = 0;

gb->memory.dmaRemaining = 0xA0; } -void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { +uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { gb->memory.hdmaSource = gb->memory.io[REG_HDMA1] << 8; gb->memory.hdmaSource |= gb->memory.io[REG_HDMA2]; gb->memory.hdmaDest = gb->memory.io[REG_HDMA3] << 8;

@@ -462,7 +462,7 @@ gb->memory.hdmaDest |= gb->memory.io[REG_HDMA4];

gb->memory.hdmaSource &= 0xFFF0; if (gb->memory.hdmaSource >= 0x8000 && gb->memory.hdmaSource < 0xA000) { mLOG(GB_MEM, GAME_ERROR, "Invalid HDMA source: %04X", gb->memory.hdmaSource); - return; + return value | 0x80; } gb->memory.hdmaDest &= 0x1FF0; gb->memory.hdmaDest |= 0x8000;

@@ -476,7 +476,10 @@ gb->memory.hdmaRemaining = ((value & 0x7F) + 1) * 0x10;

} gb->cpuBlocked = true; mTimingSchedule(&gb->timing, &gb->memory.hdmaEvent, 0); + } else if (gb->memory.isHdma && !GBRegisterLCDCIsEnable(gb->memory.io[REG_LCDC])) { + return 0x80 | ((value + 1) & 0x7F); } + return value & 0x7F; } void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesLate) {
M src/gb/renderers/software.csrc/gb/renderers/software.c

@@ -146,6 +146,10 @@ }

} } +static bool _inWindow(struct GBVideoSoftwareRenderer* renderer) { + return GBRegisterLCDCIsWindow(renderer->lcdc) && GB_VIDEO_HORIZONTAL_PIXELS + 7 > renderer->wx; +} + void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) { renderer->d.init = GBVideoSoftwareRendererInit; renderer->d.deinit = GBVideoSoftwareRendererDeinit;

@@ -174,6 +178,8 @@ softwareRenderer->scy = 0;

softwareRenderer->scx = 0; softwareRenderer->wy = 0; softwareRenderer->currentWy = 0; + softwareRenderer->lastY = 0; + softwareRenderer->hasWindow = false; softwareRenderer->wx = 0; softwareRenderer->model = model; softwareRenderer->sgbTransfer = 0;

@@ -193,14 +199,34 @@ struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;

UNUSED(softwareRenderer); } +static void GBVideoSoftwareRendererUpdateWindow(struct GBVideoSoftwareRenderer* renderer, bool before, bool after) { + if (renderer->lastY >= GB_VIDEO_VERTICAL_PIXELS || after == before) { + return; + } + if (renderer->lastY >= renderer->wy) { + if (!after) { + renderer->currentWy -= renderer->lastY; + renderer->hasWindow = true; + } else { + if (!renderer->hasWindow) { + renderer->currentWy = renderer->lastY + 1 - renderer->wy; + } else { + renderer->currentWy += renderer->lastY; + } + } + } +} + static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; if (renderer->cache) { GBVideoCacheWriteVideoRegister(renderer->cache, address, value); } + bool wasWindow = _inWindow(softwareRenderer); switch (address) { case REG_LCDC: softwareRenderer->lcdc = value; + GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer)); break; case REG_SCY: softwareRenderer->scy = value;

@@ -210,9 +236,11 @@ softwareRenderer->scx = value;

break; case REG_WY: softwareRenderer->wy = value; + GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer)); break; case REG_WX: softwareRenderer->wx = value; + GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer)); break; case REG_BGP: softwareRenderer->lookup[0] = value & 3;

@@ -275,6 +303,51 @@ if (i < 16 && softwareRenderer->sgbDataSets) {

memcpy(softwareRenderer->sgbPartialDataSet, &softwareRenderer->sgbPacket[i], 16 - i); } break; + case SGB_ATTR_CHR: + if (softwareRenderer->sgbPacketId == 1) { + softwareRenderer->sgbAttrX = softwareRenderer->sgbPacket[1]; + softwareRenderer->sgbAttrY = softwareRenderer->sgbPacket[2]; + if (softwareRenderer->sgbAttrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) { + softwareRenderer->sgbAttrX = 0; + } + if (softwareRenderer->sgbAttrY >= GB_VIDEO_VERTICAL_PIXELS / 8) { + softwareRenderer->sgbAttrY = 0; + } + softwareRenderer->sgbDataSets = softwareRenderer->sgbPacket[3]; + softwareRenderer->sgbDataSets |= softwareRenderer->sgbPacket[4] << 8; + softwareRenderer->sgbAttrDirection = softwareRenderer->sgbPacket[5]; + i = 6; + } else { + i = 0; + } + for (; i < 16 && softwareRenderer->sgbDataSets; ++i) { + int j; + for (j = 0; j < 4 && softwareRenderer->sgbDataSets; ++j, --softwareRenderer->sgbDataSets) { + uint8_t p = softwareRenderer->sgbPacket[i] >> (6 - j * 2); + _setAttribute(renderer->sgbAttributes, softwareRenderer->sgbAttrX, softwareRenderer->sgbAttrY, p & 3); + if (softwareRenderer->sgbAttrDirection) { + ++softwareRenderer->sgbAttrY; + if (softwareRenderer->sgbAttrY >= GB_VIDEO_VERTICAL_PIXELS / 8) { + softwareRenderer->sgbAttrY = 0; + ++softwareRenderer->sgbAttrX; + } + if (softwareRenderer->sgbAttrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) { + softwareRenderer->sgbAttrX = 0; + } + } else { + ++softwareRenderer->sgbAttrX; + if (softwareRenderer->sgbAttrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) { + softwareRenderer->sgbAttrX = 0; + ++softwareRenderer->sgbAttrY; + } + if (softwareRenderer->sgbAttrY >= GB_VIDEO_VERTICAL_PIXELS / 8) { + softwareRenderer->sgbAttrY = 0; + } + } + } + } + + break; case SGB_ATRC_EN: if (softwareRenderer->sgbBorders) { _regenerateSGBBorder(softwareRenderer);

@@ -306,6 +379,7 @@ }

static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; + softwareRenderer->lastY = y; uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP;

@@ -314,7 +388,8 @@ if (softwareRenderer->d.disableBG) {

memset(&softwareRenderer->row[startX], 0, endX - startX); } if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) { - if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) { + int wy = softwareRenderer->wy + softwareRenderer->currentWy; + if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && wy <= y && endX >= softwareRenderer->wx - 7) { if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y); }

@@ -324,7 +399,7 @@ if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) {

maps += GB_SIZE_MAP; } if (!softwareRenderer->d.disableWIN) { - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy); + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, y - wy); } } else if (!softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y);

@@ -428,9 +503,6 @@

static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; - if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && softwareRenderer->wx - 7 < GB_VIDEO_HORIZONTAL_PIXELS) { - ++softwareRenderer->currentWy; - } if (softwareRenderer->sgbTransfer == 1) { size_t offset = 2 * ((y & 7) + (y >> 3) * GB_VIDEO_HORIZONTAL_PIXELS); if (offset >= 0x1000) {

@@ -520,7 +592,9 @@ default:

break; } } + softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS; softwareRenderer->currentWy = 0; + softwareRenderer->hasWindow = false; } static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) {
M src/gb/serialize.csrc/gb/serialize.c

@@ -9,6 +9,8 @@ #include <mgba/internal/gb/io.h>

#include <mgba/internal/gb/timer.h> #include <mgba/internal/lr35902/lr35902.h> +#include <mgba-util/memory.h> + mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate", "gb.serialize"); const uint32_t GB_SAVESTATE_MAGIC = 0x00400000;

@@ -171,12 +173,17 @@ if (GBSerializedCpuFlagsIsEiPending(flags)) {

mTimingSchedule(&gb->timing, &gb->eiPending, when); } + enum GBModel oldModel = gb->model; gb->model = state->model; if (gb->model < GB_MODEL_CGB) { gb->audio.style = GB_AUDIO_DMG; } else { gb->audio.style = GB_AUDIO_CGB; + } + + if (gb->model != GB_MODEL_SGB || oldModel != GB_MODEL_SGB) { + gb->video.sgbBorders = false; } GBMemoryDeserialize(gb, state);

@@ -237,21 +244,27 @@ gb->video.renderer->sgbRenderMode = GBSerializedSGBFlagsGetRenderMode(flags);

memcpy(gb->sgbPacket, state->sgb.packet, sizeof(state->sgb.packet)); - if (gb->video.renderer->sgbCharRam) { - memcpy(gb->video.renderer->sgbCharRam, state->sgb.charRam, sizeof(state->sgb.charRam)); + if (!gb->video.renderer->sgbCharRam) { + gb->video.renderer->sgbCharRam = anonymousMemoryMap(SGB_SIZE_CHAR_RAM); } - if (gb->video.renderer->sgbMapRam) { - memcpy(gb->video.renderer->sgbMapRam, state->sgb.mapRam, sizeof(state->sgb.mapRam)); + if (!gb->video.renderer->sgbMapRam) { + gb->video.renderer->sgbMapRam = anonymousMemoryMap(SGB_SIZE_MAP_RAM); } - if (gb->video.renderer->sgbPalRam) { - memcpy(gb->video.renderer->sgbPalRam, state->sgb.palRam, sizeof(state->sgb.palRam)); + if (!gb->video.renderer->sgbPalRam) { + gb->video.renderer->sgbPalRam = anonymousMemoryMap(SGB_SIZE_PAL_RAM); } - if (gb->video.renderer->sgbAttributeFiles) { - memcpy(gb->video.renderer->sgbAttributeFiles, state->sgb.atfRam, sizeof(state->sgb.atfRam)); + if (!gb->video.renderer->sgbAttributeFiles) { + gb->video.renderer->sgbAttributeFiles = anonymousMemoryMap(SGB_SIZE_ATF_RAM); } - if (gb->video.renderer->sgbAttributes) { - memcpy(gb->video.renderer->sgbAttributes, state->sgb.attributes, sizeof(state->sgb.attributes)); + if (!gb->video.renderer->sgbAttributes) { + gb->video.renderer->sgbAttributes = malloc(90 * 45); } + + memcpy(gb->video.renderer->sgbCharRam, state->sgb.charRam, sizeof(state->sgb.charRam)); + memcpy(gb->video.renderer->sgbMapRam, state->sgb.mapRam, sizeof(state->sgb.mapRam)); + memcpy(gb->video.renderer->sgbPalRam, state->sgb.palRam, sizeof(state->sgb.palRam)); + memcpy(gb->video.renderer->sgbAttributeFiles, state->sgb.atfRam, sizeof(state->sgb.atfRam)); + memcpy(gb->video.renderer->sgbAttributes, state->sgb.attributes, sizeof(state->sgb.attributes)); GBVideoWriteSGBPacket(&gb->video, (uint8_t[16]) { (SGB_ATRC_EN << 3) | 1, 0 }); GBVideoWriteSGBPacket(&gb->video, gb->sgbPacket);
M src/gb/sio/printer.csrc/gb/sio/printer.c

@@ -69,8 +69,8 @@

static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value) { struct GBPrinter* printer = (struct GBPrinter*) driver; if ((value & 0x81) == 0x81) { - switch (printer->next) { driver->p->pendingSB = 0; + switch (printer->next) { case GB_PRINTER_BYTE_MAGIC_0: if (printer->byte == 0x88) { printer->next = GB_PRINTER_BYTE_MAGIC_1;

@@ -183,51 +183,54 @@ printer->printWait = 0;

} break; case GB_PRINTER_COMMAND_STATUS: - if (!printer->printWait) { - printer->status &= ~GB_PRINTER_STATUS_READY; - printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ; - if (printer->print) { - size_t y; - for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) { - uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2]; - uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y]; - size_t i; - for (i = 0; i < sizeof(lineBuffer); i += 2) { - uint8_t ilo = buffer[i + 0x0]; - uint8_t ihi = buffer[i + 0x1]; - uint8_t olo = 0; - uint8_t ohi = 0; - olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1); - olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2); - olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3); - olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4); - ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3); - ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2); - ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1); - ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0); - lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo; - lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi; - } - memcpy(buffer, lineBuffer, sizeof(lineBuffer)); - } - printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer); - } - } - if (printer->printWait >= 0) { - --printer->printWait; - } default: break; } + driver->p->pendingSB = printer->status; printer->next = GB_PRINTER_BYTE_MAGIC_0; break; } + + if (!printer->printWait) { + printer->status &= ~GB_PRINTER_STATUS_READY; + printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ; + if (printer->print) { + size_t y; + for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) { + uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2]; + uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y]; + size_t i; + for (i = 0; i < sizeof(lineBuffer); i += 2) { + uint8_t ilo = buffer[i + 0x0]; + uint8_t ihi = buffer[i + 0x1]; + uint8_t olo = 0; + uint8_t ohi = 0; + olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1); + olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2); + olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3); + olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4); + ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3); + ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2); + ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1); + ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0); + lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo; + lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi; + } + memcpy(buffer, lineBuffer, sizeof(lineBuffer)); + } + printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer); + } + printer->printWait = -1; + } else if (printer->printWait > 0) { + --printer->printWait; + } + printer->byte = 0; } return value; } void GBPrinterDonePrinting(struct GBPrinter* printer) { - printer->status &= ~GB_PRINTER_STATUS_PRINTING; + printer->status &= ~(GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ); }
M src/gb/video.csrc/gb/video.c

@@ -56,7 +56,7 @@ void GBVideoInit(struct GBVideo* video) {

video->renderer = &dummyRenderer; video->renderer->cache = NULL; video->renderer->sgbRenderMode = 0; - video->vram = 0; + video->vram = anonymousMemoryMap(GB_SIZE_VRAM); video->frameskip = 0; video->modeEvent.context = video;

@@ -99,10 +99,6 @@

video->frameCounter = 0; video->frameskipCounter = 0; - if (video->vram) { - mappedMemoryFree(video->vram, GB_SIZE_VRAM); - } - video->vram = anonymousMemoryMap(GB_SIZE_VRAM); GBVideoSwitchBank(video, 0); video->renderer->vram = video->vram; memset(&video->oam, 0, sizeof(video->oam));

@@ -310,7 +306,7 @@ }

void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; - GBVideoProcessDots(video); + GBVideoProcessDots(video, cyclesLate); if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { video->p->memory.hdmaRemaining = 0x10; video->p->cpuBlocked = true;

@@ -400,12 +396,12 @@ }

video->objMax = o; } -void GBVideoProcessDots(struct GBVideo* video) { +void GBVideoProcessDots(struct GBVideo* video, uint32_t cyclesLate) { if (video->mode != 3) { return; } int oldX = video->x; - video->x = (video->p->timing.masterCycles - video->dotClock + video->p->cpu->cycles) >> video->p->doubleSpeed; + video->x = (mTimingCurrentTime(&video->p->timing) - video->dotClock - cyclesLate) >> video->p->doubleSpeed; if (video->x > GB_VIDEO_HORIZONTAL_PIXELS) { video->x = GB_VIDEO_HORIZONTAL_PIXELS; } else if (video->x < 0) {

@@ -455,7 +451,10 @@

void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) { GBRegisterSTAT oldStat = video->stat; video->stat = (video->stat & 0x7) | (value & 0x78); - if (video->p->model < GB_MODEL_CGB && !_statIRQAsserted(video, oldStat) && video->mode < 3) { + if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) || video->p->model >= GB_MODEL_CGB) { + return; + } + if (!_statIRQAsserted(video, oldStat) && video->mode < 3) { // TODO: variable for the IRQ line value? video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p);

@@ -595,39 +594,39 @@ video->palette[1] = data[3] | (data[4] << 8);

video->palette[2] = data[5] | (data[6] << 8); video->palette[3] = data[7] | (data[8] << 8); - video->palette[16] = data[1] | (data[2] << 8); - video->palette[17] = data[9] | (data[10] << 8); - video->palette[18] = data[11] | (data[12] << 8); - video->palette[19] = data[13] | (data[14] << 8); + video->palette[4] = data[1] | (data[2] << 8); + video->palette[5] = data[9] | (data[10] << 8); + video->palette[6] = data[11] | (data[12] << 8); + video->palette[7] = data[13] | (data[14] << 8); - video->palette[32] = data[1] | (data[2] << 8); - video->palette[48] = data[1] | (data[2] << 8); + video->palette[8] = data[1] | (data[2] << 8); + video->palette[12] = data[1] | (data[2] << 8); video->renderer->writePalette(video->renderer, 0, video->palette[0]); video->renderer->writePalette(video->renderer, 1, video->palette[1]); video->renderer->writePalette(video->renderer, 2, video->palette[2]); video->renderer->writePalette(video->renderer, 3, video->palette[3]); - video->renderer->writePalette(video->renderer, 16, video->palette[0]); - video->renderer->writePalette(video->renderer, 17, video->palette[17]); - video->renderer->writePalette(video->renderer, 18, video->palette[18]); - video->renderer->writePalette(video->renderer, 19, video->palette[19]); - video->renderer->writePalette(video->renderer, 32, video->palette[0]); - video->renderer->writePalette(video->renderer, 48, video->palette[0]); + video->renderer->writePalette(video->renderer, 4, video->palette[0]); + video->renderer->writePalette(video->renderer, 5, video->palette[5]); + video->renderer->writePalette(video->renderer, 6, video->palette[6]); + video->renderer->writePalette(video->renderer, 7, video->palette[7]); + video->renderer->writePalette(video->renderer, 8, video->palette[0]); + video->renderer->writePalette(video->renderer, 12, video->palette[0]); break; case SGB_PAL23: - video->palette[33] = data[3] | (data[4] << 8); - video->palette[34] = data[5] | (data[6] << 8); - video->palette[35] = data[7] | (data[8] << 8); + video->palette[9] = data[3] | (data[4] << 8); + video->palette[10] = data[5] | (data[6] << 8); + video->palette[11] = data[7] | (data[8] << 8); - video->palette[49] = data[9] | (data[10] << 8); - video->palette[50] = data[11] | (data[12] << 8); - video->palette[51] = data[13] | (data[14] << 8); - video->renderer->writePalette(video->renderer, 33, video->palette[33]); - video->renderer->writePalette(video->renderer, 34, video->palette[34]); - video->renderer->writePalette(video->renderer, 35, video->palette[35]); - video->renderer->writePalette(video->renderer, 49, video->palette[49]); - video->renderer->writePalette(video->renderer, 50, video->palette[50]); - video->renderer->writePalette(video->renderer, 51, video->palette[51]); + video->palette[13] = data[9] | (data[10] << 8); + video->palette[14] = data[11] | (data[12] << 8); + video->palette[15] = data[13] | (data[14] << 8); + video->renderer->writePalette(video->renderer, 9, video->palette[9]); + video->renderer->writePalette(video->renderer, 10, video->palette[10]); + video->renderer->writePalette(video->renderer, 11, video->palette[11]); + video->renderer->writePalette(video->renderer, 13, video->palette[13]); + video->renderer->writePalette(video->renderer, 14, video->palette[14]); + video->renderer->writePalette(video->renderer, 15, video->palette[15]); break; case SGB_PAL03: video->palette[0] = data[1] | (data[2] << 8);

@@ -635,38 +634,38 @@ video->palette[1] = data[3] | (data[4] << 8);

video->palette[2] = data[5] | (data[6] << 8); video->palette[3] = data[7] | (data[8] << 8); - video->palette[16] = data[1] | (data[2] << 8); - video->palette[32] = data[1] | (data[2] << 8); + video->palette[4] = data[1] | (data[2] << 8); + video->palette[8] = data[1] | (data[2] << 8); - video->palette[48] = data[1] | (data[2] << 8); - video->palette[49] = data[9] | (data[10] << 8); - video->palette[50] = data[11] | (data[12] << 8); - video->palette[51] = data[13] | (data[14] << 8); + video->palette[12] = data[1] | (data[2] << 8); + video->palette[13] = data[9] | (data[10] << 8); + video->palette[14] = data[11] | (data[12] << 8); + video->palette[15] = data[13] | (data[14] << 8); video->renderer->writePalette(video->renderer, 0, video->palette[0]); video->renderer->writePalette(video->renderer, 1, video->palette[1]); video->renderer->writePalette(video->renderer, 2, video->palette[2]); video->renderer->writePalette(video->renderer, 3, video->palette[3]); - video->renderer->writePalette(video->renderer, 16, video->palette[0]); - video->renderer->writePalette(video->renderer, 32, video->palette[0]); - video->renderer->writePalette(video->renderer, 48, video->palette[0]); - video->renderer->writePalette(video->renderer, 49, video->palette[49]); - video->renderer->writePalette(video->renderer, 50, video->palette[50]); - video->renderer->writePalette(video->renderer, 51, video->palette[51]); + video->renderer->writePalette(video->renderer, 4, video->palette[0]); + video->renderer->writePalette(video->renderer, 8, video->palette[0]); + video->renderer->writePalette(video->renderer, 12, video->palette[0]); + video->renderer->writePalette(video->renderer, 13, video->palette[13]); + video->renderer->writePalette(video->renderer, 14, video->palette[14]); + video->renderer->writePalette(video->renderer, 15, video->palette[15]); break; case SGB_PAL12: - video->palette[17] = data[3] | (data[4] << 8); - video->palette[18] = data[5] | (data[6] << 8); - video->palette[19] = data[7] | (data[8] << 8); + video->palette[5] = data[3] | (data[4] << 8); + video->palette[6] = data[5] | (data[6] << 8); + video->palette[7] = data[7] | (data[8] << 8); - video->palette[33] = data[9] | (data[10] << 8); - video->palette[34] = data[11] | (data[12] << 8); - video->palette[35] = data[13] | (data[14] << 8); - video->renderer->writePalette(video->renderer, 17, video->palette[17]); - video->renderer->writePalette(video->renderer, 18, video->palette[18]); - video->renderer->writePalette(video->renderer, 19, video->palette[19]); - video->renderer->writePalette(video->renderer, 33, video->palette[33]); - video->renderer->writePalette(video->renderer, 34, video->palette[34]); - video->renderer->writePalette(video->renderer, 35, video->palette[35]); + video->palette[9] = data[9] | (data[10] << 8); + video->palette[10] = data[11] | (data[12] << 8); + video->palette[11] = data[13] | (data[14] << 8); + video->renderer->writePalette(video->renderer, 5, video->palette[5]); + video->renderer->writePalette(video->renderer, 6, video->palette[6]); + video->renderer->writePalette(video->renderer, 7, video->palette[7]); + video->renderer->writePalette(video->renderer, 9, video->palette[9]); + video->renderer->writePalette(video->renderer, 10, video->palette[10]); + video->renderer->writePalette(video->renderer, 11, video->palette[11]); break; case SGB_PAL_SET: for (i = 0; i < 4; ++i) {

@@ -686,6 +685,7 @@ video->renderer->writePalette(video->renderer, i * 4 + 3, video->palette[i * 4 + 3]);

} break; case SGB_ATTR_BLK: + case SGB_ATTR_CHR: case SGB_PAL_TRN: case SGB_ATRC_EN: case SGB_CHR_TRN:

@@ -786,6 +786,7 @@ void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state) {

STORE_16LE(video->x, 0, &state->video.x); STORE_16LE(video->ly, 0, &state->video.ly); STORE_32LE(video->frameCounter, 0, &state->video.frameCounter); + STORE_32LE(video->dotClock, 0, &state->video.dotCounter); state->video.vramCurrentBank = video->vramCurrentBank; GBSerializedVideoFlags flags = 0;

@@ -814,6 +815,7 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state) {

LOAD_16LE(video->x, 0, &state->video.x); LOAD_16LE(video->ly, 0, &state->video.ly); LOAD_32LE(video->frameCounter, 0, &state->video.frameCounter); + LOAD_32LE(video->dotClock, 0, &state->video.dotCounter); video->vramCurrentBank = state->video.vramCurrentBank; GBSerializedVideoFlags flags = state->video.flags;
M src/gba/audio.csrc/gba/audio.c

@@ -111,7 +111,7 @@ default:

mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } - info->reg = GBADMARegisterSetDestControl(info->reg, DMA_FIXED); + info->reg = GBADMARegisterSetDestControl(info->reg, GBA_DMA_FIXED); info->reg = GBADMARegisterSetWidth(info->reg, 1); }

@@ -235,7 +235,7 @@ return;

} if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) { struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource]; - if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) { + if (GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM) { dma->when = mTimingCurrentTime(&audio->p->timing) - cycles; dma->nextCount = 4; GBADMASchedule(audio->p, channel->dmaSource, dma);

@@ -260,7 +260,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {

struct GBAAudio* audio = user; int16_t sampleLeft = 0; int16_t sampleRight = 0; - int psgShift = 5 - audio->volume; + int psgShift = 4 - audio->volume; GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight); sampleLeft >>= psgShift; sampleRight >>= psgShift;
M src/gba/bios.csrc/gba/bios.c

@@ -333,6 +333,12 @@ struct GBA* gba = (struct GBA*) cpu->master;

mLOG(GBA_BIOS, DEBUG, "SWI: %02X r0: %08X r1: %08X r2: %08X r3: %08X", immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); + switch (immediate) { + case 0xFA: + GBAPrintFlush(gba); + return; + } + if (gba->memory.fullBios) { ARMRaiseSWI(cpu); return;

@@ -529,8 +535,10 @@ int block = cpu->memory.load8(cpu, source + 1, 0) | (cpu->memory.load8(cpu, source, 0) << 8);

source += 2; disp = dest - (block & 0x0FFF) - 1; bytes = (block >> 12) + 3; - while (bytes-- && remaining) { - --remaining; + while (bytes--) { + if (remaining) { + --remaining; + } if (width == 2) { byte = (int16_t) cpu->memory.load16(cpu, disp & ~1, 0); if (dest & 1) {
M src/gba/cheats.csrc/gba/cheats.c

@@ -274,7 +274,11 @@ }

static void GBACheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats; - _patchROM(device, gbaset); + if (cheats->enabled) { + _patchROM(device, gbaset); + } else { + _unpatchROM(device, gbaset); + } } static void GBACheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) {
M src/gba/cheats/gameshark.csrc/gba/cheats/gameshark.c

@@ -154,9 +154,32 @@ cheats->romPatches[0].applied = false;

cheats->romPatches[0].exists = true; return true; case GSA_BUTTON: - // TODO: Implement button - mLOG(CHEATS, STUB, "GameShark button unimplemented"); - return false; + switch (op1 & 0x00F00000) { + case 0x00100000: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0F0FFFFF; + break; + case 0x00200000: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0F0FFFFF; + break; + default: + mLOG(CHEATS, STUB, "GameShark button type unimplemented"); + return false; + } + break; case GSA_IF_EQ: if (op1 == 0xDEADFACE) { GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2);

@@ -174,6 +197,7 @@ cheat->width = 2;

cheat->address = op2 & 0x0FFFFFFF; cheat->operand = op1 & 0xFFFF; cheat->repeat = (op1 >> 16) & 0xFF; + cheat->negativeRepeat = 0; return true; case GSA_HOOK: if (cheats->hook) {

@@ -190,6 +214,7 @@ return false;

} cheat->operand = op2; cheat->repeat = 1; + cheat->negativeRepeat = 0; return true; }
M src/gba/cheats/parv3.csrc/gba/cheats/parv3.c

@@ -132,49 +132,65 @@ break;

case PAR3_COND_UGT: cheat->type = CHEAT_IF_UGT; break; - case PAR3_COND_LAND: - cheat->type = CHEAT_IF_LAND; + case PAR3_COND_AND: + cheat->type = CHEAT_IF_AND; break; } return true; } static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { + int romPatch = -1; struct mCheat* cheat; switch (op2 & 0xFF000000) { case PAR3_OTHER_SLOWDOWN: // TODO: Slowdown + mLOG(CHEATS, STUB, "Unimplemented PARv3 slowdown"); return false; case PAR3_OTHER_BUTTON_1: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_BUTTON_2: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_BUTTON_4: - // TODO: Button - mLOG(CHEATS, STUB, "GameShark button unimplemented"); - return false; - // TODO: Fix overriding existing patches + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_PATCH_1: - cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[0].applied = false; - cheats->romPatches[0].exists = true; - cheats->incompletePatch = &cheats->romPatches[0]; + romPatch = 0; break; case PAR3_OTHER_PATCH_2: - cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[1].applied = false; - cheats->romPatches[1].exists = true; - cheats->incompletePatch = &cheats->romPatches[1]; + romPatch = 1; break; case PAR3_OTHER_PATCH_3: - cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[2].applied = false; - cheats->romPatches[2].exists = true; - cheats->incompletePatch = &cheats->romPatches[2]; + romPatch = 2; break; case PAR3_OTHER_PATCH_4: - cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[3].applied = false; - cheats->romPatches[3].exists = true; - cheats->incompletePatch = &cheats->romPatches[3]; + romPatch = 3; break; case PAR3_OTHER_ENDIF: if (cheats->currentBlock != COMPLETE) {

@@ -190,23 +206,38 @@ }

return false; case PAR3_OTHER_FILL_1: cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; cheat->address = _parAddr(op2); cheat->width = 1; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; case PAR3_OTHER_FILL_2: cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; cheat->address = _parAddr(op2); cheat->width = 2; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; case PAR3_OTHER_FILL_4: cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; cheat->address = _parAddr(op2); - cheat->width = 3; + cheat->width = 4; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; } + if (romPatch >= 0) { + while (cheats->romPatches[romPatch].exists) { + ++romPatch; + if (romPatch >= MAX_ROM_PATCHES) { + break; + } + } + cheats->romPatches[romPatch].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[romPatch].applied = false; + cheats->romPatches[romPatch].exists = true; + cheats->incompletePatch = &cheats->romPatches[romPatch]; + } return true; }

@@ -219,7 +250,14 @@ }

if (cheats->incompleteCheat != COMPLETE) { struct mCheat* incompleteCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat); incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - incompleteCheat->width) * 8)); - incompleteCheat->addressOffset = op2 >> 24; + if (cheats->incompleteCheat > 0) { + struct mCheat* lastCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat - 1); + if (lastCheat->type == CHEAT_IF_BUTTON) { + cheats->incompleteCheat = COMPLETE; + return true; + } + } + incompleteCheat->operandOffset = op2 >> 24; incompleteCheat->repeat = (op2 >> 16) & 0xFF; incompleteCheat->addressOffset = (op2 & 0xFFFF) * incompleteCheat->width; cheats->incompleteCheat = COMPLETE;

@@ -339,7 +377,7 @@ if (op1 == 0xDEADFACE && !(op2 & 0xFFFF0000)) {

return 0x100; } if (!op1) { - probability += 0x20; + probability += 0x40; uint32_t address = _parAddr(op2); switch (op2 & 0xFE000000) { case PAR3_OTHER_FILL_1:

@@ -360,8 +398,8 @@ case PAR3_OTHER_BUTTON_2:

case PAR3_OTHER_BUTTON_4: case PAR3_OTHER_ENDIF: case PAR3_OTHER_ELSE: - if (op2 & 0x01FFFFFF) { - probability -= 0x20; + if (op2 & 0x01000000) { + probability -= 0x40; } break; default:
M src/gba/core.csrc/gba/core.c

@@ -746,6 +746,20 @@ vf->close(vf);

} #endif } + +static bool _GBACoreLookupIdentifier(struct mCore* core, const char* name, int32_t* value, int* segment) { + UNUSED(core); + *segment = -1; + int i; + for (i = 0; i < REG_MAX; i += 2) { + const char* reg = GBAIORegisterNames[i >> 1]; + if (reg && strcasecmp(reg, name) == 0) { + *value = BASE_IO | i; + return true; + } + } + return false; +} #endif static struct mCheatDevice* _GBACoreCheatDevice(struct mCore* core) {

@@ -847,6 +861,7 @@ break;

} } +#ifndef MINIMAL_CORE static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = core->board;

@@ -873,6 +888,7 @@ GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer);

free(gbacore->proxyRenderer.logger); gbacore->proxyRenderer.logger = NULL; } +#endif struct mCore* GBACoreCreate(void) { struct GBACore* gbacore = malloc(sizeof(*gbacore));

@@ -943,6 +959,7 @@ core->cliDebuggerSystem = _GBACoreCliDebuggerSystem;

core->attachDebugger = _GBACoreAttachDebugger; core->detachDebugger = _GBACoreDetachDebugger; core->loadSymbols = _GBACoreLoadSymbols; + core->lookupIdentifier = _GBACoreLookupIdentifier; #endif core->cheatDevice = _GBACoreCheatDevice; core->savedataClone = _GBACoreSavedataClone;
M src/gba/debugger/cli.csrc/gba/debugger/cli.c

@@ -14,16 +14,15 @@ #include <mgba/internal/arm/debugger/cli-debugger.h>

static void _GBACLIDebuggerInit(struct CLIDebuggerSystem*); static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem*); -static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); static void _load(struct CLIDebugger*, struct CLIDebugVector*); static void _save(struct CLIDebugger*, struct CLIDebugVector*); struct CLIDebuggerCommandSummary _GBACLIDebuggerCommands[] = { - { "frame", _frame, 0, "Frame advance" }, - { "load", _load, CLIDVParse, "Load a savestate" }, - { "save", _save, CLIDVParse, "Save a savestate" }, + { "frame", _frame, "", "Frame advance" }, + { "load", _load, "*", "Load a savestate" }, + { "save", _save, "*", "Save a savestate" }, { 0, 0, 0, 0 } };

@@ -33,7 +32,6 @@ ARMCLIDebuggerCreate(&debugger->d);

debugger->d.init = _GBACLIDebuggerInit; debugger->d.deinit = NULL; debugger->d.custom = _GBACLIDebuggerCustom; - debugger->d.lookupIdentifier = _GBACLIDebuggerLookupIdentifier; debugger->d.name = "Game Boy Advance"; debugger->d.commands = _GBACLIDebuggerCommands;

@@ -62,19 +60,6 @@ gbaDebugger->inVblank = GBARegisterDISPSTATGetInVblank(((struct GBA*) gbaDebugger->core->board)->memory.io[REG_DISPSTAT >> 1]);

return true; } return false; -} - -static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - UNUSED(debugger); - int i; - for (i = 0; i < REG_MAX; i += 2) { - const char* reg = GBAIORegisterNames[i >> 1]; - if (reg && strcasecmp(reg, name) == 0) { - return BASE_IO | i; - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; } static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
M src/gba/dma.csrc/gba/dma.c

@@ -46,6 +46,8 @@ struct GBAMemory* memory = &gba->memory;

address &= 0x0FFFFFFE; if (_isValidDMASAD(dma, address)) { memory->dma[dma].source = address; + } else { + memory->dma[dma].source = 0; } return memory->dma[dma].source; }

@@ -81,7 +83,19 @@ }

if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) { currentDma->nextSource = currentDma->source; + if (currentDma->nextSource >= BASE_CART0 && currentDma->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(currentDma->reg) < 3) { + currentDma->reg = GBADMARegisterClearSrcControl(currentDma->reg); + } currentDma->nextDest = currentDma->dest; + + uint32_t width = 2 << GBADMARegisterGetWidth(currentDma->reg); + if (currentDma->nextSource & (width - 1)) { + mLOG(GBA_MEM, GAME_ERROR, "Misaligned DMA source address: 0x%08X", currentDma->nextSource); + } + if (currentDma->nextDest & (width - 1)) { + mLOG(GBA_MEM, GAME_ERROR, "Misaligned DMA destination address: 0x%08X", currentDma->nextDest); + } + GBADMASchedule(gba, dma, currentDma); } // If the DMA has already occurred, this value might have changed since the function started

@@ -90,15 +104,15 @@ };

void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) { switch (GBADMARegisterGetTiming(info->reg)) { - case DMA_TIMING_NOW: + case GBA_DMA_TIMING_NOW: info->when = mTimingCurrentTime(&gba->timing) + 3; // DMAs take 3 cycles to start info->nextCount = info->count; break; - case DMA_TIMING_HBLANK: - case DMA_TIMING_VBLANK: + case GBA_DMA_TIMING_HBLANK: + case GBA_DMA_TIMING_VBLANK: // Handled implicitly return; - case DMA_TIMING_CUSTOM: + case GBA_DMA_TIMING_CUSTOM: switch (number) { case 0: mLOG(GBA_MEM, WARN, "Discarding invalid DMA0 scheduling");

@@ -108,7 +122,7 @@ case 2:

GBAAudioScheduleFifoDma(&gba->audio, number, info); break; case 3: - // GBAVideoScheduleVCaptureDma(dma, info); + // Handled implicitly break; } }

@@ -121,7 +135,7 @@ struct GBADMA* dma;

int i; for (i = 0; i < 4; ++i) { dma = &memory->dma[i]; - if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_HBLANK && !dma->nextCount) { + if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_HBLANK && !dma->nextCount) { dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles; dma->nextCount = dma->count; }

@@ -135,7 +149,7 @@ struct GBADMA* dma;

int i; for (i = 0; i < 4; ++i) { dma = &memory->dma[i]; - if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_VBLANK && !dma->nextCount) { + if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_VBLANK && !dma->nextCount) { dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles; dma->nextCount = dma->count; }

@@ -143,6 +157,16 @@ }

GBADMAUpdate(gba); } +void GBADMARunDisplayStart(struct GBA* gba, int32_t cycles) { + struct GBAMemory* memory = &gba->memory; + struct GBADMA* dma = &memory->dma[3]; + if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM && !dma->nextCount) { + dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles; + dma->nextCount = dma->count; + GBADMAUpdate(gba); + } +} + void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); UNUSED(cyclesLate);

@@ -156,13 +180,16 @@ if (dma->nextCount & 0xFFFFF) {

GBADMAService(gba, memory->activeDMA, dma); } else { dma->nextCount = 0; - if (!GBADMARegisterIsRepeat(dma->reg) || GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_NOW) { + bool noRepeat = !GBADMARegisterIsRepeat(dma->reg); + noRepeat |= GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_NOW; + noRepeat |= memory->activeDMA == 3 && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM; + if (noRepeat) { dma->reg = GBADMARegisterClearEnable(dma->reg); // Clear the enable bit in memory memory->io[(REG_DMA0CNT_HI + memory->activeDMA * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0; } - if (GBADMARegisterGetDestControl(dma->reg) == DMA_INCREMENT_RELOAD) { + if (GBADMARegisterGetDestControl(dma->reg) == GBA_DMA_INCREMENT_RELOAD) { dma->nextDest = dma->dest; } if (GBADMARegisterIsDoIRQ(dma->reg)) {

@@ -226,31 +253,44 @@ }

info->when += cycles; gba->performingDMA = 1 | (number << 1); - uint32_t word; if (width == 4) { - word = cpu->memory.load32(cpu, source, 0); - gba->bus = word; - cpu->memory.store32(cpu, dest, word, 0); + if (source) { + memory->dmaTransferRegister = cpu->memory.load32(cpu, source, 0); + } + gba->bus = memory->dmaTransferRegister; + cpu->memory.store32(cpu, dest, memory->dmaTransferRegister, 0); + memory->dmaTransferRegister &= 0xFFFF0000; + memory->dmaTransferRegister |= memory->dmaTransferRegister >> 16; } else { if (sourceRegion == REGION_CART2_EX && memory->savedata.type == SAVEDATA_EEPROM) { - word = GBASavedataReadEEPROM(&memory->savedata); - cpu->memory.store16(cpu, dest, word, 0); - } else if (destRegion == REGION_CART2_EX) { + if (memory->savedata.type == SAVEDATA_AUTODETECT) { + mLOG(GBA_MEM, INFO, "Detected EEPROM savegame"); + GBASavedataInitEEPROM(&memory->savedata, gba->realisticTiming); + } + memory->dmaTransferRegister = GBASavedataReadEEPROM(&memory->savedata); + } else { + if (source) { + memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0); + } + } + if (destRegion == REGION_CART2_EX) { if (memory->savedata.type == SAVEDATA_AUTODETECT) { mLOG(GBA_MEM, INFO, "Detected EEPROM savegame"); GBASavedataInitEEPROM(&memory->savedata, gba->realisticTiming); } - word = cpu->memory.load16(cpu, source, 0); - GBASavedataWriteEEPROM(&memory->savedata, word, wordsRemaining); + GBASavedataWriteEEPROM(&memory->savedata, memory->dmaTransferRegister, wordsRemaining); } else { - word = cpu->memory.load16(cpu, source, 0); - cpu->memory.store16(cpu, dest, word, 0); + cpu->memory.store16(cpu, dest, memory->dmaTransferRegister, 0); + } - gba->bus = word | (word << 16); + memory->dmaTransferRegister |= memory->dmaTransferRegister << 16; + gba->bus = memory->dmaTransferRegister; } int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width; int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width; - source += sourceOffset; + if (source) { + source += sourceOffset; + } dest += destOffset; --wordsRemaining; gba->performingDMA = 0;
M src/gba/extra/proxy.csrc/gba/extra/proxy.c

@@ -189,11 +189,16 @@

uint16_t GBAVideoProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; switch (address) { + case REG_DISPCNT: + value &= 0xFFF7; + break; case REG_BG0CNT: case REG_BG1CNT: + value &= 0xDFFF; + break; case REG_BG2CNT: case REG_BG3CNT: - value &= 0xFFCF; + value &= 0xFFFF; break; case REG_BG0HOFS: case REG_BG0VOFS:
M src/gba/gba.csrc/gba/gba.c

@@ -125,7 +125,9 @@ }

if (gba->romVf) { #ifndef FIXED_ROM_BUFFER - gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize); + if (gba->isPristine) { + gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize); + } #endif gba->romVf->close(gba->romVf); gba->romVf = NULL;

@@ -207,6 +209,9 @@ gba->idleDetectionFailures = 0;

gba->debug = false; memset(gba->debugString, 0, sizeof(gba->debugString)); + if (gba->pristineRomSize > SIZE_CART0) { + GBAMatrixReset(gba); + } if (!gba->romVf && gba->memory.rom) { GBASkipBIOS(gba);

@@ -243,19 +248,17 @@ }

int32_t nextEvent = cpu->nextEvent; while (cpu->cycles >= nextEvent) { - int32_t cycles = cpu->cycles; - - cpu->cycles = 0; cpu->nextEvent = INT_MAX; - + nextEvent = 0; + do { + int32_t cycles = cpu->cycles; + cpu->cycles = 0; #ifndef NDEBUG - if (cycles < 0) { - mLOG(GBA, FATAL, "Negative cycles passed: %i", cycles); - } + if (cycles < 0) { + mLOG(GBA, FATAL, "Negative cycles passed: %i", cycles); + } #endif - nextEvent = cycles; - do { - nextEvent = mTimingTick(&gba->timing, nextEvent); + nextEvent = mTimingTick(&gba->timing, nextEvent + cycles); } while (gba->cpuBlocked); cpu->nextEvent = nextEvent;

@@ -276,6 +279,11 @@ mLOG(GBA, FATAL, "Negative cycles will pass: %i", nextEvent);

} #endif } +#ifndef NDEBUG + if (gba->cpuBlocked) { + mLOG(GBA, FATAL, "CPU is blocked!"); + } +#endif } #ifdef USE_DEBUGGERS

@@ -300,7 +308,6 @@ bool GBALoadNull(struct GBA* gba) {

GBAUnloadROM(gba); gba->romVf = NULL; gba->pristineRomSize = 0; - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); #ifndef FIXED_ROM_BUFFER gba->memory.rom = anonymousMemoryMap(SIZE_CART0); #else

@@ -328,7 +335,6 @@ if (gba->pristineRomSize > SIZE_WORKING_RAM) {

gba->pristineRomSize = SIZE_WORKING_RAM; } gba->isPristine = true; - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); memset(gba->memory.wram, 0, SIZE_WORKING_RAM); vf->read(vf, gba->memory.wram, gba->pristineRomSize); if (!gba->memory.wram) {

@@ -354,23 +360,30 @@ gba->romVf = vf;

gba->pristineRomSize = vf->size(vf); vf->seek(vf, 0, SEEK_SET); if (gba->pristineRomSize > SIZE_CART0) { - gba->pristineRomSize = SIZE_CART0; - } - gba->isPristine = true; + gba->isPristine = false; + gba->memory.romSize = 0x01000000; #ifdef FIXED_ROM_BUFFER - if (gba->pristineRomSize <= romBufferSize) { gba->memory.rom = romBuffer; - vf->read(vf, romBuffer, gba->pristineRomSize); - } +#else + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); +#endif + } else { + gba->isPristine = true; +#ifdef FIXED_ROM_BUFFER + if (gba->pristineRomSize <= romBufferSize) { + gba->memory.rom = romBuffer; + vf->read(vf, romBuffer, gba->pristineRomSize); + } #else - gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ); + gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ); #endif + gba->memory.romSize = gba->pristineRomSize; + } if (!gba->memory.rom) { mLOG(GBA, WARN, "Couldn't map ROM"); return false; } gba->yankedRomSize = 0; - gba->memory.romSize = gba->pristineRomSize; gba->memory.romMask = toPow2(gba->memory.romSize) - 1; gba->memory.mirroring = false; gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);

@@ -568,16 +581,49 @@ uint32_t opcode;

LOAD_32(opcode, 0, &signature); struct ARMInstructionInfo info; ARMDecodeARM(opcode, &info); - if (info.branchType != ARM_BRANCH) { - return false; + if (info.branchType == ARM_BRANCH) { + if (info.op1.immediate <= 0) { + return false; + } else if (info.op1.immediate == 28) { + // Ancient toolchain that is known to throw MB detection for a loop + return false; + } else if (info.op1.immediate != 24) { + return true; + } } - if (info.op1.immediate <= 0) { - return false; - } else if (info.op1.immediate == 28) { - // Ancient toolchain that is known to throw MB detection for a loop - return false; - } else if (info.op1.immediate != 24) { - return true; + + uint32_t pc = GBA_MB_MAGIC_OFFSET; + int i; + for (i = 0; i < 80; ++i) { + if (vf->read(vf, &signature, sizeof(signature)) != sizeof(signature)) { + break; + } + pc += 4; + LOAD_32(opcode, 0, &signature); + ARMDecodeARM(opcode, &info); + if (info.mnemonic != ARM_MN_LDR) { + continue; + } + if ((info.operandFormat & ARM_OPERAND_MEMORY) && info.memory.baseReg == ARM_PC && info.memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) { + uint32_t immediate = info.memory.offset.immediate; + if (info.memory.format & ARM_MEMORY_OFFSET_SUBTRACT) { + immediate = -immediate; + } + immediate += pc + 8; + if (vf->seek(vf, immediate, SEEK_SET) < 0) { + break; + } + if (vf->read(vf, &signature, sizeof(signature)) != sizeof(signature)) { + break; + } + LOAD_32(immediate, 0, &signature); + if (vf->seek(vf, pc, SEEK_SET) < 0) { + break; + } + if ((immediate & ~0x7FF) == BASE_WORKING_RAM) { + return true; + } + } } // Found a libgba-linked cart...these are a bit harder to detect. return false;

@@ -628,7 +674,7 @@ #ifdef USE_DEBUGGERS

if (gba->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .opcode = opcode + .type.bp.opcode = opcode }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_ILLEGAL_OP, &info); }

@@ -651,7 +697,7 @@ #ifdef USE_DEBUGGERS

if (gba->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .opcode = opcode + .type.bp.opcode = opcode }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_ILLEGAL_OP, &info); } else

@@ -672,7 +718,7 @@ case CPU_COMPONENT_DEBUGGER:

if (gba->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .breakType = BREAKPOINT_SOFTWARE + .type.bp.breakType = BREAKPOINT_SOFTWARE }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_BREAKPOINT, &info); }
M src/gba/io.csrc/gba/io.c

@@ -939,6 +939,8 @@ STORE_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount);

STORE_32(gba->memory.dma[i].when, 0, &state->dma[i].when); } + state->dmaTransferRegister = gba->memory.dmaTransferRegister; + GBAHardwareSerialize(&gba->memory.hw, state); }

@@ -979,11 +981,12 @@ LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);

LOAD_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest); LOAD_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount); LOAD_32(gba->memory.dma[i].when, 0, &state->dma[i].when); - if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != DMA_TIMING_NOW) { + if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != GBA_DMA_TIMING_NOW) { GBADMASchedule(gba, i, &gba->memory.dma[i]); } } GBAAudioWriteSOUNDCNT_X(&gba->audio, gba->memory.io[REG_SOUNDCNT_X >> 1]); + gba->memory.dmaTransferRegister = state->dmaTransferRegister; GBADMAUpdate(gba); GBAHardwareDeserialize(&gba->memory.hw, state); }
A src/gba/matrix.c

@@ -0,0 +1,75 @@

+/* Copyright (c) 2013-2018 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 <mgba/internal/gba/matrix.h> + +#include <mgba/internal/arm/macros.h> +#include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/memory.h> +#include <mgba-util/vfs.h> + +static void _remapMatrix(struct GBA* gba) { + gba->romVf->seek(gba->romVf, gba->memory.matrix.paddr, SEEK_SET); + gba->romVf->read(gba->romVf, &gba->memory.rom[gba->memory.matrix.vaddr >> 2], gba->memory.matrix.size); +} + +void GBAMatrixReset(struct GBA* gba) { + gba->memory.matrix.paddr = 0x200; + gba->memory.matrix.size = 0x1000; + + gba->memory.matrix.vaddr = 0; + _remapMatrix(gba); + gba->memory.matrix.vaddr = 0x1000; + _remapMatrix(gba); + + gba->memory.matrix.paddr = 0; + gba->memory.matrix.vaddr = 0; + gba->memory.matrix.size = 0x100; + _remapMatrix(gba); +} + +void GBAMatrixWrite(struct GBA* gba, uint32_t address, uint32_t value) { + switch (address) { + case 0x0: + gba->memory.matrix.cmd = value; + switch (value) { + case 0x01: + case 0x11: + _remapMatrix(gba); + break; + default: + mLOG(GBA_MEM, STUB, "Unknown Matrix command: %08X", value); + break; + } + return; + case 0x4: + gba->memory.matrix.paddr = value & 0x03FFFFFF; + return; + case 0x8: + gba->memory.matrix.vaddr = value & 0x007FFFFF; + return; + case 0xC: + gba->memory.matrix.size = value << 9; + return; + } + mLOG(GBA_MEM, STUB, "Unknown Matrix write: %08X:%04X", address, value); +} + +void GBAMatrixWrite16(struct GBA* gba, uint32_t address, uint16_t value) { + switch (address) { + case 0x0: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.cmd & 0xFFFF0000)); + break; + case 0x4: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.paddr & 0xFFFF0000)); + break; + case 0x8: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.vaddr & 0xFFFF0000)); + break; + case 0xC: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.size & 0xFFFF0000)); + break; + } +}
M src/gba/memory.csrc/gba/memory.c

@@ -22,7 +22,10 @@

mLOG_DEFINE_CATEGORY(GBA_MEM, "GBA Memory", "gba.memory"); static void _pristineCow(struct GBA* gba); -static uint32_t _deadbeef[1] = { 0xE710B710 }; // Illegal instruction on both ARM and Thumb +static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value); +static int16_t _agbPrintLoad(struct GBA* gba, uint32_t address); +static uint8_t _deadbeef[4] = { 0x10, 0xB7, 0x10, 0xE7 }; // Illegal instruction on both ARM and Thumb +static uint8_t _agbPrintFunc[4] = { 0xFA, 0xDF /* swi 0xFF */, 0x70, 0x47 /* bx lr */ }; static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait);

@@ -80,15 +83,19 @@ cpu->memory.activeNonseqCycles16 = 0;

gba->memory.biosPrefetch = 0; gba->memory.mirroring = false; - gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); + gba->memory.agbPrint = 0; + memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); + gba->memory.agbPrintBuffer = NULL; + + gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM + SIZE_WORKING_IRAM); + gba->memory.iwram = &gba->memory.wram[SIZE_WORKING_RAM >> 2]; GBADMAInit(gba); GBAVFameInit(&gba->memory.vfame); } void GBAMemoryDeinit(struct GBA* gba) { - mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM); - mappedMemoryFree(gba->memory.iwram, SIZE_WORKING_IRAM); + mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM + SIZE_WORKING_IRAM); if (gba->memory.rom) { mappedMemoryFree(gba->memory.rom, gba->memory.romSize); }

@@ -98,15 +105,15 @@ GBASavedataDeinit(&gba->memory.savedata);

if (gba->memory.savedata.realVf) { gba->memory.savedata.realVf->close(gba->memory.savedata.realVf); } + + if (gba->memory.agbPrintBuffer) { + mappedMemoryFree(gba->memory.agbPrintBuffer, SIZE_AGB_PRINT); + } } void GBAMemoryReset(struct GBA* gba) { - if (gba->memory.rom || gba->memory.fullBios || !gba->memory.wram) { - // Not multiboot - if (gba->memory.wram) { - mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM); - } - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); + if (gba->memory.wram && gba->memory.rom) { + memset(gba->memory.wram, 0, SIZE_WORKING_RAM); } if (gba->memory.iwram) {

@@ -115,6 +122,12 @@ }

memset(gba->memory.io, 0, sizeof(gba->memory.io)); + gba->memory.agbPrint = 0; + memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); + if (gba->memory.agbPrintBuffer) { + gba->memory.agbPrintBuffer = NULL; + } + gba->memory.prefetch = false; gba->memory.lastPrefetchedPc = 0;

@@ -124,6 +137,7 @@ mLOG(GBA_MEM, FATAL, "Could not map memory");

} GBADMAReset(gba); + memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix)); } static void _analyzeForIdleLoop(struct GBA* gba, struct ARMCore* cpu, uint32_t address) {

@@ -292,12 +306,17 @@ case REGION_CART2_EX:

cpu->memory.activeRegion = memory->rom; cpu->memory.activeMask = memory->romMask; if ((address & (SIZE_CART0 - 1)) < memory->romSize) { + break; + } + if ((address & (SIZE_CART0 - 1)) == AGB_PRINT_FLUSH_ADDR && memory->agbPrint == 0x20) { + cpu->memory.activeRegion = (uint32_t*) _agbPrintFunc; + cpu->memory.activeMask = sizeof(_agbPrintFunc) - 1; break; } // Fall through default: memory->activeRegion = -1; - cpu->memory.activeRegion = _deadbeef; + cpu->memory.activeRegion = (uint32_t*) _deadbeef; cpu->memory.activeMask = 0; if (!gba->yankedRomSize && mCoreCallbacksListSize(&gba->coreCallbacks)) {

@@ -527,6 +546,16 @@ } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {

LOAD_16(value, address & memory->romMask, memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); + } else if ((address & (SIZE_CART0 - 1)) >= AGB_PRINT_BASE) { + uint32_t agbPrintAddr = address & 0x00FFFFFF; + if (agbPrintAddr == AGB_PRINT_PROTECT) { + value = memory->agbPrint; + } else if (agbPrintAddr < AGB_PRINT_TOP || (agbPrintAddr & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + value = _agbPrintLoad(gba, address); + } else { + mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); + value = (address >> 1) & 0xFFFF; + } } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF;

@@ -720,6 +749,10 @@ }

#define STORE_CART \ wait += waitstatesRegion[address >> BASE_OFFSET]; \ + if (memory->matrix.size && (address & 0x01FFFF00) == 0x00800100) { \ + GBAMatrixWrite(gba, address & 0x3C, value); \ + break; \ + } \ mLOG(GBA_MEM, STUB, "Unimplemented memory Store32: 0x%08X", address); #define STORE_SRAM \

@@ -837,9 +870,27 @@ case REGION_CART0:

if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFE)) { uint32_t reg = address & 0xFFFFFE; GBAHardwareGPIOWrite(&memory->hw, reg, value); - } else { - mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); + break; } + if (memory->matrix.size && (address & 0x01FFFF00) == 0x00800100) { + GBAMatrixWrite16(gba, address & 0x3C, value); + break; + } + // Fall through + case REGION_CART0_EX: + if ((address & 0x00FFFFFF) >= AGB_PRINT_BASE) { + uint32_t agbPrintAddr = address & 0x00FFFFFF; + if (agbPrintAddr == AGB_PRINT_PROTECT) { + memory->agbPrint = value; + _agbPrintStore(gba, address, value); + break; + } + if (memory->agbPrint == 0x20 && (agbPrintAddr < AGB_PRINT_TOP || (agbPrintAddr & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8))) { + _agbPrintStore(gba, address, value); + break; + } + } + mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); break; case REGION_CART2_EX: if (memory->savedata.type == SAVEDATA_AUTODETECT) {

@@ -1531,24 +1582,23 @@

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 < 8) { - previousLoads = dist; + uint32_t dist = (memory->lastPrefetchedPc - cpu->gprs[ARM_PC]); + int32_t maxLoads = 8; + if (dist < 16) { + previousLoads = dist >> 1; + maxLoads -= previousLoads; } - int32_t s = cpu->memory.activeSeqCycles16; - int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; + 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; - if (stall < wait) { - int32_t maxLoads = 8 - previousLoads; - while (stall < wait && loads < maxLoads) { - stall += s; - ++loads; - } + while (stall < wait && loads < maxLoads) { + stall += s; + ++loads; } if (stall > wait) { // The wait cannot take less time than the prefetch stalls

@@ -1561,7 +1611,7 @@

memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1); // The next |loads|S waitstates disappear entirely, so long as they're all in a row - cpu->cycles -= stall; + cpu->cycles -= (s - 1) * loads; return wait; }

@@ -1579,6 +1629,7 @@ void _pristineCow(struct GBA* gba) {

if (!gba->isPristine) { return; } +#ifndef FIXED_ROM_BUFFER void* newRom = anonymousMemoryMap(SIZE_CART0); memcpy(newRom, gba->memory.rom, gba->memory.romSize); memset(((uint8_t*) newRom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize);

@@ -1586,12 +1637,63 @@ if (gba->cpu->memory.activeRegion == gba->memory.rom) {

gba->cpu->memory.activeRegion = newRom; } if (gba->romVf) { -#ifndef FIXED_ROM_BUFFER gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize); -#endif gba->romVf->close(gba->romVf); gba->romVf = NULL; } gba->memory.rom = newRom; gba->memory.hw.gpioBase = &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]; +#endif + gba->isPristine = false; +} + +void GBAPrintFlush(struct GBA* gba) { + char oolBuf[0x101]; + size_t i; + for (i = 0; gba->memory.agbPrintCtx.get != gba->memory.agbPrintCtx.put && i < 0x100; ++i) { + int16_t value; + LOAD_16(value, gba->memory.agbPrintCtx.get & -2, gba->memory.agbPrintBuffer); + if (gba->memory.agbPrintCtx.get & 1) { + value >>= 8; + } else { + value &= 0xFF; + } + oolBuf[i] = value; + oolBuf[i + 1] = 0; + ++gba->memory.agbPrintCtx.get; + } + _agbPrintStore(gba, AGB_PRINT_STRUCT + 4, gba->memory.agbPrintCtx.get); + + mLOG(GBA_DEBUG, INFO, "%s", oolBuf); +} + +static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value) { + struct GBAMemory* memory = &gba->memory; + if ((address & 0x00FFFFFF) < AGB_PRINT_TOP) { + if (!memory->agbPrintBuffer) { + memory->agbPrintBuffer = anonymousMemoryMap(SIZE_AGB_PRINT); + } + STORE_16(value, address & (SIZE_AGB_PRINT - 2), memory->agbPrintBuffer); + } else if ((address & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + (&memory->agbPrintCtx.request)[(address & 7) >> 1] = value; + } + if (memory->romSize == SIZE_CART0) { + _pristineCow(gba); + memcpy(&memory->rom[AGB_PRINT_FLUSH_ADDR >> 2], _agbPrintFunc, sizeof(_agbPrintFunc)); + STORE_16(value, address & (SIZE_CART0 - 2), memory->rom); + } else if (memory->agbPrintCtx.bank == 0xFD && memory->romSize >= SIZE_CART0 / 2) { + _pristineCow(gba); + STORE_16(value, address & (SIZE_CART0 / 2 - 2), memory->rom); + } +} + +static int16_t _agbPrintLoad(struct GBA* gba, uint32_t address) { + struct GBAMemory* memory = &gba->memory; + int16_t value = 0xFFFF; + if (address < AGB_PRINT_TOP) { + LOAD_16(value, address & (SIZE_AGB_PRINT - 1), memory->agbPrintBuffer); + } else if ((address & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + value = (&memory->agbPrintCtx.request)[(address & 7) >> 1]; + } + return value; }
M src/gba/overrides.csrc/gba/overrides.c

@@ -180,6 +180,9 @@ { "KYGJ", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE, false },

{ "KYGE", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE, false }, { "KYGP", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE, false }, + // Aging cartridge + { "TCHK", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + { { 0, 0, 0, 0 }, 0, 0, IDLE_LOOP_NONE, false } };
M src/gba/renderers/cache-set.csrc/gba/renderers/cache-set.c

@@ -57,12 +57,15 @@ GBAVideoCacheWriteVideoRegister(cache, REG_BG3CNT, video->p->memory.io[REG_BG3CNT >> 1]);

} static void mapParser0(struct mMapCache* cache, struct mMapCacheEntry* entry, void* vram) { - UNUSED(cache); uint16_t map = *(uint16_t*) vram; entry->tileId = GBA_TEXT_MAP_TILE(map); entry->flags = mMapCacheEntryFlagsSetHMirror(entry->flags, !!GBA_TEXT_MAP_HFLIP(map)); entry->flags = mMapCacheEntryFlagsSetVMirror(entry->flags, !!GBA_TEXT_MAP_VFLIP(map)); - entry->flags = mMapCacheEntryFlagsSetPaletteId(entry->flags, GBA_TEXT_MAP_PALETTE(map)); + if (mMapCacheSystemInfoGetPaletteBPP(cache->sysConfig) == 3) { + entry->flags = mMapCacheEntryFlagsClearPaletteId(entry->flags); + } else { + entry->flags = mMapCacheEntryFlagsSetPaletteId(entry->flags, GBA_TEXT_MAP_PALETTE(map)); + } } static void mapParser2(struct mMapCache* cache, struct mMapCacheEntry* entry, void* vram) {

@@ -82,10 +85,14 @@ mMapCacheSetGetPointer(&cache->maps, 1)->mapParser = mapParser0;

mMapCacheSetGetPointer(&cache->maps, 2)->mapParser = mapParser0; mMapCacheSetGetPointer(&cache->maps, 3)->mapParser = mapParser0; - mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 2)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 3)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); + mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 0)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 1)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 2)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 2)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 3)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 3)->sysConfig) == 3); break; case 1: case 2:

@@ -94,8 +101,11 @@ mMapCacheSetGetPointer(&cache->maps, 1)->mapParser = mapParser0;

mMapCacheSetGetPointer(&cache->maps, 2)->mapParser = mapParser2; mMapCacheSetGetPointer(&cache->maps, 3)->mapParser = mapParser2; - mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); + mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 0)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 1)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 2)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 1); mMapCacheSetGetPointer(&cache->maps, 3)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 1); break;

@@ -113,6 +123,7 @@ int tilesWide = 0;

int tilesHigh = 0; mMapCacheSystemInfo sysconfig = 0; if (map->mapParser == mapParser0) { + map->tileCache = mTileCacheSetGetPointer(&cache->tiles, p); sysconfig = mMapCacheSystemInfoSetPaletteBPP(sysconfig, 2 + p); sysconfig = mMapCacheSystemInfoSetPaletteCount(sysconfig, 4 * !p); sysconfig = mMapCacheSystemInfoSetMacroTileSize(sysconfig, 5);

@@ -125,8 +136,9 @@ }

if (size & 2) { ++tilesHigh; } - map->tileStart = tileStart * 2; + map->tileStart = tileStart * (2 - p); } else if (map->mapParser == mapParser2) { + map->tileCache = mTileCacheSetGetPointer(&cache->tiles, 1); sysconfig = mMapCacheSystemInfoSetPaletteBPP(sysconfig, 3); sysconfig = mMapCacheSystemInfoSetPaletteCount(sysconfig, 0); sysconfig = mMapCacheSystemInfoSetMacroTileSize(sysconfig, 4 + size);
M src/gba/renderers/software-mode0.csrc/gba/renderers/software-mode0.c

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

#include <mgba/internal/gba/gba.h> #define BACKGROUND_TEXT_SELECT_CHARACTER \ - localX = tileX * 8 + inX; \ xBase = localX & 0xF8; \ if (background->size & 1) { \ xBase += (localX & 0x100) << 5; \

@@ -16,10 +15,6 @@ } \

screenBase = background->screenBase + yBase + (xBase >> 2); \ uint16_t* screenBlock = renderer->d.vramBG[screenBase >> VRAM_BLOCK_OFFSET]; \ LOAD_16(mapData, screenBase & VRAM_BLOCK_MASK, screenBlock); \ - 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; \

@@ -75,11 +70,21 @@ int baseX = x - (mosaicH - mosaicWait); \

if (baseX < 0) { \ int disturbX = (16 + baseX) >> 3; \ inX -= disturbX << 3; \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ baseX -= disturbX << 3; \ inX += disturbX << 3; \ } else { \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \

@@ -97,8 +102,14 @@ tileData |= tileData << 8; \

tileData |= tileData << 16; \ carryData = tileData; \ } \ + localX = tileX * 8 + inX; \ for (; length; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ tileData = carryData; \

@@ -128,7 +139,12 @@ }

#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ palette = &mainPalette[paletteData]; \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \

@@ -238,7 +254,12 @@ }

#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ if (!GBA_TEXT_MAP_HFLIP(mapData)) { \

@@ -279,8 +300,14 @@ } \

} #define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \ + localX = tileX * 8 + inX; \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ tileData = carryData; \

@@ -398,9 +425,14 @@ }

#define DRAW_BACKGROUND_MODE_0_TILES_256EXT(BLEND, OBJWIN) \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \ palette = &mainPalette[paletteData]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ if (!GBA_TEXT_MAP_HFLIP(mapData)) { \

@@ -441,8 +473,14 @@ } \

} #define DRAW_BACKGROUND_MODE_0_MOSAIC_256EXT(BLEND, OBJWIN) \ + localX = tileX * 8 + inX; \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ tileData = carryData; \

@@ -490,8 +528,12 @@ return; \

} \ \ if (inX & 0x7) { \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ int mod8 = inX & 0x7; \ int end = outX + 0x8 - mod8; \ if (end > renderer->end) { \

@@ -514,10 +556,15 @@ return; \

} \ length -= end - renderer->start; \ } \ + localX = (tileX * 8 + inX) & 0x1FF; \ DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \ if (length & 0x7) { \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ int mod8 = length & 0x7; \ if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \ mLOG(GBA_VIDEO, FATAL, "Invariant doesn't hold in background draw!"); \

@@ -531,7 +578,7 @@ return; \

} void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) { - int inX = renderer->start + background->x; + int inX = (renderer->start + background->x) & 0x1FF; int length = renderer->end - renderer->start; if (background->mosaic) { int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1;

@@ -587,10 +634,20 @@ uint32_t tileData;

uint32_t current; int pixelData; int paletteData; - int tileX = 0; + int tileX; int tileEnd = ((length + inX) >> 3) - (inX >> 3); uint16_t* vram = NULL; + if (background->yCache != inY >> 3) { + localX = 0; + for (tileX = 0; tileX < 64; ++tileX, localX += 8) { + BACKGROUND_TEXT_SELECT_CHARACTER; + background->mapCache[tileX] = mapData; + } + background->yCache = inY >> 3; + } + + tileX = 0; if (!objwinSlowPath) { if (!(flags & FLAG_TARGET_2)) { if (!background->multipalette) {
M src/gba/renderers/software-obj.csrc/gba/renderers/software-obj.c

@@ -245,26 +245,26 @@ if (renderer->spriteCyclesRemaining <= 0) {

return 0; } + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); 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 ((1 << GBAObjAttributesCGetPriority(sprite->c)) <= target2) { + if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || objwinSlowPath) { + int target2 = renderer->target2Bd; + target2 |= renderer->bg[0].target2; + target2 |= renderer->bg[1].target2; + target2 |= renderer->bg[2].target2; + target2 |= renderer->bg[3].target2; + if (target2) { flags |= FLAG_REBLEND; variant = 0; - } else if (!target2) { + } else { flags &= ~FLAG_TARGET_1; } } color_t* palette = &renderer->normalPalette[0x100]; color_t* objwinPalette = palette; - int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); if (GBAObjAttributesAIs256Color(sprite->a) && renderer->objExtPalette) { if (!variant) {
M src/gba/renderers/software-private.hsrc/gba/renderers/software-private.h

@@ -47,7 +47,7 @@ if (color >= current) {

if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->alphaA[x], current, renderer->alphaB[x], color); } else { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } } else { color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN);

@@ -63,7 +63,7 @@ if (color >= current) {

if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->alphaA[x], current, renderer->alphaB[x], color); } else { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } } else { color = color & ~FLAG_TARGET_2;

@@ -77,7 +77,7 @@ UNUSED(renderer);

if (color < current) { color |= (current & FLAG_OBJWIN); } else { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } renderer->row[x] = color; }

@@ -86,7 +86,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, int x, uint32_t color,

uint32_t current) { UNUSED(renderer); if (color >= current) { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } renderer->row[x] = color; }
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -149,6 +149,7 @@ bg->dy = 0;

bg->dmy = 256; bg->sx = 0; bg->sy = 0; + bg->yCache = -1; bg->extPalette = NULL; bg->variantPalette = NULL; }

@@ -167,6 +168,7 @@ }

switch (address) { case REG_DISPCNT: + value &= 0xFFF7; softwareRenderer->dispcnt = value; GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer); break;

@@ -407,6 +409,10 @@ if (renderer->cache) {

mCacheSetWriteVRAM(renderer->cache, address); } memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty)); + softwareRenderer->bg[0].yCache = -1; + softwareRenderer->bg[1].yCache = -1; + softwareRenderer->bg[2].yCache = -1; + softwareRenderer->bg[3].yCache = -1; } static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {

@@ -586,6 +592,10 @@

GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y); int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y); softwareRenderer->d.vramOBJ[0] = objVramBase; + if (softwareRenderer->blendDirty) { + _updatePalettes(softwareRenderer); + softwareRenderer->blendDirty = false; + } int w; unsigned priority;

@@ -636,6 +646,38 @@ }

} } } + if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) { + int x = 0; + uint32_t mask = 0xFF000000 & ~FLAG_OBJWIN; + uint32_t match = FLAG_REBLEND; + if (GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) { + mask |= FLAG_OBJWIN; + if (GBAWindowControlIsBlendEnable(softwareRenderer->objwin.packed)) { + match |= FLAG_OBJWIN; + } + } + for (w = 0; w < softwareRenderer->nWindows; ++w) { + if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { + continue; + } + int end = softwareRenderer->windows[w].endX; + if (softwareRenderer->blendEffect == BLEND_DARKEN) { + for (; x < end; ++x) { + uint32_t color = softwareRenderer->row[x]; + if ((color & mask) == match) { + softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy); + } + } + } else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) { + for (; x < end; ++x) { + uint32_t color = softwareRenderer->row[x]; + if ((color & mask) == match) { + softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy); + } + } + } + } + } if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) { softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx; softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy;

@@ -643,6 +685,19 @@ softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx;

softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy; } + if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) { + ++softwareRenderer->bg[0].enabled; + } + if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) { + ++softwareRenderer->bg[1].enabled; + } + if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) { + ++softwareRenderer->bg[2].enabled; + } + if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) { + ++softwareRenderer->bg[3].enabled; + } + GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer); #ifdef COLOR_16_BIT

@@ -671,6 +726,19 @@ softwareRenderer->bg[2].sx = softwareRenderer->bg[2].refx;

softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy; softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx; softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy; + + if (softwareRenderer->bg[0].enabled > 0) { + softwareRenderer->bg[0].enabled = 4; + } + if (softwareRenderer->bg[1].enabled > 0) { + softwareRenderer->bg[1].enabled = 4; + } + if (softwareRenderer->bg[2].enabled > 0) { + softwareRenderer->bg[2].enabled = 4; + } + if (softwareRenderer->bg[3].enabled > 0) { + softwareRenderer->bg[3].enabled = 4; + } } static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {

@@ -689,11 +757,23 @@ memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], softwareRenderer->masterEnd * BYTES_PER_PIXEL);

} } +static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) { + if (renderer->d.disableBG[bg] || !active) { + renderer->bg[bg].enabled = 0; + } else if (!renderer->bg[bg].enabled && active) { + if (renderer->nextY == 0) { + renderer->bg[bg].enabled = 4; + } else { + renderer->bg[bg].enabled = 1; + } + } +} + static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) { - renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0]; - renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1]; - renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2]; - renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3]; + _enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt)); + _enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt)); + _enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt)); + _enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt)); } static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
M src/gba/savedata.csrc/gba/savedata.c

@@ -576,9 +576,15 @@ savedata->currentBank = &savedata->data[bank << 16];

if (bank > 0 && savedata->type == SAVEDATA_FLASH512) { mLOG(GBA_SAVE, INFO, "Updating flash chip from 512kb to 1Mb"); savedata->type = SAVEDATA_FLASH1M; - if (savedata->vf && savedata->vf->size(savedata->vf) == SIZE_CART_FLASH512) { - savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); - memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512); + if (savedata->vf) { + savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_FLASH512); + if (savedata->vf->size(savedata->vf) == SIZE_CART_FLASH512) { + savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); + savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, MAP_WRITE); + memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512); + } else { + savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, MAP_WRITE); + } } } }
A src/gba/sio/joybus.c

@@ -0,0 +1,76 @@

+/* Copyright (c) 2013-2017 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 <mgba/internal/gba/sio.h> + +#include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/io.h> + +static uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value); + +void GBASIOJOYCreate(struct GBASIODriver* sio) { + sio->init = NULL; + sio->deinit = NULL; + sio->load = NULL; + sio->unload = NULL; + sio->writeRegister = GBASIOJOYWriteRegister; +} + +uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value) { + switch (address) { + case REG_JOYCNT: + return (value & 0x0040) | (sio->p->p->memory.io[REG_JOYCNT >> 1] & ~(value & 0x7) & ~0x0040); + case REG_JOYSTAT: + return (value & 0x0030) | (sio->p->p->memory.io[REG_JOYSTAT >> 1] & ~0x30); + case REG_JOY_TRANS_LO: + case REG_JOY_TRANS_HI: + sio->p->p->memory.io[REG_JOYSTAT >> 1] |= 8; + break; + } + return value; +} + +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) { + switch (command) { + case JOY_RESET: + sio->p->p->memory.io[REG_JOYCNT >> 1] |= 1; + if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { + GBARaiseIRQ(sio->p->p, IRQ_SIO); + } + // Fall through + case JOY_POLL: + data[0] = 0x00; + data[1] = 0x04; + data[2] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; + return 3; + case JOY_RECV: + sio->p->p->memory.io[REG_JOYCNT >> 1] |= 2; + sio->p->p->memory.io[REG_JOYSTAT >> 1] |= 2; + + sio->p->p->memory.io[REG_JOY_RECV_LO >> 1] = data[0] | (data[1] << 8); + sio->p->p->memory.io[REG_JOY_RECV_HI >> 1] = data[2] | (data[3] << 8); + + data[0] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; + + if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { + GBARaiseIRQ(sio->p->p, IRQ_SIO); + } + return 1; + case JOY_TRANS: + sio->p->p->memory.io[REG_JOYCNT >> 1] |= 4; + sio->p->p->memory.io[REG_JOYSTAT >> 1] &= ~8; + data[0] = sio->p->p->memory.io[REG_JOY_TRANS_LO >> 1]; + data[1] = sio->p->p->memory.io[REG_JOY_TRANS_LO >> 1] >> 8; + data[2] = sio->p->p->memory.io[REG_JOY_TRANS_HI >> 1]; + data[3] = sio->p->p->memory.io[REG_JOY_TRANS_HI >> 1] >> 8; + data[4] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; + + if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { + GBARaiseIRQ(sio->p->p, IRQ_SIO); + } + return 5; + } + return 0; +}
M src/gba/test/cheats.csrc/gba/test/cheats.c

@@ -75,6 +75,93 @@

set->deinit(set); } +M_TEST_DEFINE(doPARv3Slide1) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 80300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 01020002", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 2); + assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0); + + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3Slide2) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 82300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 01020002", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead16(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000002, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000006, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000008, -1), 0); + assert_int_equal(core->rawRead16(core, 0x0300000A, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead16(core, 0x03000000, -1), 1); + assert_int_equal(core->rawRead16(core, 0x03000002, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000004, -1), 2); + assert_int_equal(core->rawRead16(core, 0x03000006, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000008, -1), 0); + assert_int_equal(core->rawRead16(core, 0x0300000A, -1), 0); + + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3Slide4) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 84300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 01020002", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead32(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000008, -1), 0); + assert_int_equal(core->rawRead32(core, 0x0300000C, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000010, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000014, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead16(core, 0x03000000, -1), 1); + assert_int_equal(core->rawRead16(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000008, -1), 2); + assert_int_equal(core->rawRead16(core, 0x0300000C, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000010, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000014, -1), 0); + + set->deinit(set); +} + M_TEST_DEFINE(doPARv3If1) { struct mCore* core = *state; struct mCheatDevice* device = core->cheatDevice(core);

@@ -334,6 +421,7 @@ core->rawWrite8(core, 0x03000000, -1, 0x1);

mCheatRefresh(device, set); assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXxX) {

@@ -396,6 +484,7 @@ assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x1);

assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21); assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElse) {

@@ -430,6 +519,7 @@ mCheatRefresh(device, set);

assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x22); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElsexX) {

@@ -500,6 +590,7 @@ assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21);

assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x42); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x51); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElsexXElse) {

@@ -577,6 +668,7 @@ assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32);

assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x42); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x51); assert_int_equal(core->rawRead8(core, 0x03000006, -1), 0x62); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXContain1) {

@@ -638,6 +730,7 @@ assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x1);

assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21); assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x31); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXContain1Else) {

@@ -707,6 +800,7 @@ assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21);

assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x31); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x52); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElseContain1) {

@@ -776,6 +870,7 @@ assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21);

assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x52); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXContain1ElseContain1) {

@@ -921,12 +1016,46 @@ assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x51);

assert_int_equal(core->rawRead8(core, 0x03000006, -1), 0x62); assert_int_equal(core->rawRead8(core, 0x03000007, -1), 0x71); assert_int_equal(core->rawRead8(core, 0x03000008, -1), 0x82); + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3IfButton) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 10300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatPressButton(device, true); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + + mCheatPressButton(device, false); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + + core->rawWrite8(core, 0x03000000, -1, 0); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + set->deinit(set); } M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBACheats, cmocka_unit_test(createSet), cmocka_unit_test(addRawPARv3), cmocka_unit_test(doPARv3Assign), + cmocka_unit_test(doPARv3Slide1), + cmocka_unit_test(doPARv3Slide2), + cmocka_unit_test(doPARv3Slide4), cmocka_unit_test(doPARv3If1), cmocka_unit_test(doPARv3If1x1), cmocka_unit_test(doPARv3If2),

@@ -940,4 +1069,5 @@ cmocka_unit_test(doPARv3IfXElsexXElse),

cmocka_unit_test(doPARv3IfXContain1), cmocka_unit_test(doPARv3IfXContain1Else), cmocka_unit_test(doPARv3IfXElseContain1), - cmocka_unit_test(doPARv3IfXContain1ElseContain1)) + cmocka_unit_test(doPARv3IfXContain1ElseContain1), + cmocka_unit_test(doPARv3IfButton))
M src/gba/video.csrc/gba/video.c

@@ -71,7 +71,7 @@

void GBAVideoInit(struct GBAVideo* video) { video->renderer = &dummyRenderer; video->renderer->cache = NULL; - video->vram = 0; + video->vram = anonymousMemoryMap(SIZE_VRAM); video->frameskip = 0; video->event.name = "GBA Video"; video->event.callback = NULL;

@@ -94,10 +94,6 @@

video->frameCounter = 0; video->frameskipCounter = 0; - if (video->vram) { - mappedMemoryFree(video->vram, SIZE_VRAM); - } - video->vram = anonymousMemoryMap(SIZE_VRAM); memset(video->renderer->vramBG, 0, sizeof(video->renderer->vramBG)); video->renderer->vramBG[0] = &video->vram[0x0000]; video->renderer->vramBG[1] = &video->vram[0x2000];

@@ -207,6 +203,9 @@

if (video->vcount < VIDEO_VERTICAL_PIXELS) { GBADMARunHblank(video->p, -cyclesLate); } + if (video->vcount >= 2 && video->vcount < VIDEO_VERTICAL_PIXELS + 2) { + GBADMARunDisplayStart(video->p, -cyclesLate); + } if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { GBARaiseIRQ(video->p, IRQ_HBLANK); }

@@ -239,6 +238,9 @@ if (renderer->cache) {

GBAVideoCacheWriteVideoRegister(renderer->cache, address, value); } switch (address) { + case REG_DISPCNT: + value &= 0xFFF7; + break; case REG_BG0CNT: case REG_BG1CNT: value &= 0xDFFF;
M src/lr35902/debugger/cli-debugger.csrc/lr35902/debugger/cli-debugger.c

@@ -100,58 +100,9 @@ _printFlags(be, cpu->f);

_printLine(debugger->p, cpu->pc, cpu->memory.currentSegment(cpu, cpu->pc)); } -static uint32_t _lookupPlatformIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - struct LR35902Core* cpu = debugger->p->d.core->cpu; - if (strcmp(name, "a") == 0) { - return cpu->a; - } - if (strcmp(name, "b") == 0) { - return cpu->b; - } - if (strcmp(name, "c") == 0) { - return cpu->c; - } - if (strcmp(name, "d") == 0) { - return cpu->d; - } - if (strcmp(name, "e") == 0) { - return cpu->e; - } - if (strcmp(name, "h") == 0) { - return cpu->h; - } - if (strcmp(name, "l") == 0) { - return cpu->l; - } - if (strcmp(name, "bc") == 0) { - return cpu->bc; - } - if (strcmp(name, "de") == 0) { - return cpu->de; - } - if (strcmp(name, "hl") == 0) { - return cpu->hl; - } - if (strcmp(name, "af") == 0) { - return cpu->af; - } - if (strcmp(name, "pc") == 0) { - return cpu->pc; - } - if (strcmp(name, "sp") == 0) { - return cpu->sp; - } - if (strcmp(name, "f") == 0) { - return cpu->f.packed; - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - void LR35902CLIDebuggerCreate(struct CLIDebuggerSystem* debugger) { debugger->printStatus = _printStatus; debugger->disassemble = _disassemble; - debugger->lookupPlatformIdentifier = _lookupPlatformIdentifier; debugger->platformName = "GB-Z80"; debugger->platformCommands = _lr35902Commands; }
M src/lr35902/debugger/debugger.csrc/lr35902/debugger/debugger.c

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

#include <mgba/internal/lr35902/debugger/debugger.h> #include <mgba/core/core.h> +#include <mgba/internal/debugger/parser.h> #include <mgba/internal/lr35902/decoder.h> #include <mgba/internal/lr35902/lr35902.h> #include <mgba/internal/lr35902/debugger/memory-debugger.h>

@@ -23,6 +24,20 @@ }

return 0; } +static void _destroyBreakpoint(struct LR35902DebugBreakpoint* breakpoint) { + if (breakpoint->condition) { + parseFree(breakpoint->condition); + free(breakpoint->condition); + } +} + +static void _destroyWatchpoint(struct LR35902DebugWatchpoint* watchpoint) { + if (watchpoint->condition) { + parseFree(watchpoint->condition); + free(watchpoint->condition); + } +} + static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; struct LR35902DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->pc);

@@ -32,6 +47,13 @@ }

if (breakpoint->segment >= 0 && debugger->cpu->memory.currentSegment(debugger->cpu, breakpoint->address) != breakpoint->segment) { return; } + if (breakpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(d->p, breakpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return; + } + } struct mDebuggerEntryInfo info = { .address = breakpoint->address };

@@ -44,12 +66,16 @@

static void LR35902DebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); +static void LR35902DebuggerSetConditionalWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition); static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform*); static void LR35902DebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); +static bool LR35902DebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value); +static bool LR35902DebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value); struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct LR35902Debugger));

@@ -57,12 +83,16 @@ platform->entered = LR35902DebuggerEnter;

platform->init = LR35902DebuggerInit; platform->deinit = LR35902DebuggerDeinit; platform->setBreakpoint = LR35902DebuggerSetBreakpoint; + platform->setConditionalBreakpoint = LR35902DebuggerSetConditionalBreakpoint; platform->clearBreakpoint = LR35902DebuggerClearBreakpoint; platform->setWatchpoint = LR35902DebuggerSetWatchpoint; + platform->setConditionalWatchpoint = LR35902DebuggerSetConditionalWatchpoint; platform->clearWatchpoint = LR35902DebuggerClearWatchpoint; platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints; platform->hasBreakpoints = LR35902DebuggerHasBreakpoints; platform->trace = LR35902DebuggerTrace; + platform->getRegister = LR35902DebuggerGetRegister; + platform->setRegister = LR35902DebuggerSetRegister; return platform; }

@@ -75,7 +105,15 @@ }

void LR35902DebuggerDeinit(struct mDebuggerPlatform* platform) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform; + size_t i; + for (i = 0; i < LR35902DebugBreakpointListSize(&debugger->breakpoints); ++i) { + _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(&debugger->breakpoints, i)); + } LR35902DebugBreakpointListDeinit(&debugger->breakpoints); + + for (i = 0; i < LR35902DebugWatchpointListSize(&debugger->watchpoints); ++i) { + _destroyWatchpoint(LR35902DebugWatchpointListGetPointer(&debugger->watchpoints, i)); + } LR35902DebugWatchpointListDeinit(&debugger->watchpoints); }

@@ -92,10 +130,15 @@ }

} static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { + LR35902DebuggerSetConditionalBreakpoint(d, address, segment, NULL); +} + +static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); breakpoint->address = address; breakpoint->segment = segment; + breakpoint->condition = condition; } static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) {

@@ -105,6 +148,7 @@ size_t i;

for (i = 0; i < LR35902DebugBreakpointListSize(breakpoints); ++i) { struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListGetPointer(breakpoints, i); if (breakpoint->address == address && breakpoint->segment == segment) { + _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(breakpoints, i)); LR35902DebugBreakpointListShift(breakpoints, i, 1); } }

@@ -116,6 +160,10 @@ return LR35902DebugBreakpointListSize(&debugger->breakpoints) || LR35902DebugWatchpointListSize(&debugger->watchpoints);

} static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type) { + LR35902DebuggerSetConditionalWatchpoint(d, address, segment, type, NULL); +} + +static void LR35902DebuggerSetConditionalWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; if (!LR35902DebugWatchpointListSize(&debugger->watchpoints)) { LR35902DebuggerInstallMemoryShim(debugger);

@@ -124,6 +172,7 @@ struct LR35902DebugWatchpoint* watchpoint = LR35902DebugWatchpointListAppend(&debugger->watchpoints);

watchpoint->address = address; watchpoint->type = type; watchpoint->segment = segment; + watchpoint->condition = condition; } static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) {

@@ -168,3 +217,131 @@ cpu->a, cpu->f.packed, cpu->b, cpu->c,

cpu->d, cpu->e, cpu->h, cpu->l, cpu->sp, cpu->pc, disassembly); } + +bool LR35902DebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902Core* cpu = debugger->cpu; + + if (strcmp(name, "a") == 0) { + *value = cpu->a; + return true; + } + if (strcmp(name, "b") == 0) { + *value = cpu->b; + return true; + } + if (strcmp(name, "c") == 0) { + *value = cpu->c; + return true; + } + if (strcmp(name, "d") == 0) { + *value = cpu->d; + return true; + } + if (strcmp(name, "e") == 0) { + *value = cpu->e; + return true; + } + if (strcmp(name, "h") == 0) { + *value = cpu->h; + return true; + } + if (strcmp(name, "l") == 0) { + *value = cpu->l; + return true; + } + if (strcmp(name, "bc") == 0) { + *value = cpu->bc; + return true; + } + if (strcmp(name, "de") == 0) { + *value = cpu->de; + return true; + } + if (strcmp(name, "hl") == 0) { + *value = cpu->hl; + return true; + } + if (strcmp(name, "af") == 0) { + *value = cpu->af; + return true; + } + if (strcmp(name, "pc") == 0) { + *value = cpu->pc; + return true; + } + if (strcmp(name, "sp") == 0) { + *value = cpu->sp; + return true; + } + if (strcmp(name, "f") == 0) { + *value = cpu->f.packed; + return true; + } + return false; +} + +bool LR35902DebuggerSetRegister(struct mDebuggerPlatform* d, const char* name, int32_t value) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902Core* cpu = debugger->cpu; + + if (strcmp(name, "a") == 0) { + cpu->a = value; + return true; + } + if (strcmp(name, "b") == 0) { + cpu->b = value; + return true; + } + if (strcmp(name, "c") == 0) { + cpu->c = value; + return true; + } + if (strcmp(name, "d") == 0) { + cpu->d = value; + return true; + } + if (strcmp(name, "e") == 0) { + cpu->e = value; + return true; + } + if (strcmp(name, "h") == 0) { + cpu->h = value; + return true; + } + if (strcmp(name, "l") == 0) { + cpu->l = value; + return true; + } + if (strcmp(name, "bc") == 0) { + cpu->bc = value; + return true; + } + if (strcmp(name, "de") == 0) { + cpu->de = value; + return true; + } + if (strcmp(name, "hl") == 0) { + cpu->hl = value; + return true; + } + if (strcmp(name, "af") == 0) { + cpu->af = value; + cpu->f.packed &= 0xF0; + return true; + } + if (strcmp(name, "pc") == 0) { + cpu->pc = value; + cpu->memory.setActiveRegion(cpu, cpu->pc); + return true; + } + if (strcmp(name, "sp") == 0) { + cpu->sp = value; + return true; + } + if (strcmp(name, "f") == 0) { + cpu->f.packed = value & 0xF0; + return true; + } + return false; +}
M src/lr35902/debugger/memory-debugger.csrc/lr35902/debugger/memory-debugger.c

@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/internal/lr35902/debugger/memory-debugger.h> +#include <mgba/internal/debugger/parser.h> #include <mgba/internal/lr35902/debugger/debugger.h> #include <mgba-util/math.h>

@@ -27,19 +28,19 @@ abort(); \

debuggerFound: break; \ } while(0) -#define CREATE_WATCHPOINT_SHIM(NAME, RW, RETURN, TYPES, ...) \ +#define CREATE_WATCHPOINT_SHIM(NAME, RW, VALUE, RETURN, TYPES, ...) \ static RETURN DebuggerShim_ ## NAME TYPES { \ struct LR35902Debugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, 0)) { \ + if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, VALUE)) { \ mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ } \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } -CREATE_WATCHPOINT_SHIM(load8, READ, uint8_t, (struct LR35902Core* cpu, uint16_t address), address) -CREATE_WATCHPOINT_SHIM(store8, WRITE, void, (struct LR35902Core* cpu, uint16_t address, int8_t value), address, value) +CREATE_WATCHPOINT_SHIM(load8, READ, 0, uint8_t, (struct LR35902Core* cpu, uint16_t address), address) +CREATE_WATCHPOINT_SHIM(store8, WRITE, value, void, (struct LR35902Core* cpu, uint16_t address, int8_t value), address, value) static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue) { struct LR35902DebugWatchpoint* watchpoint;

@@ -47,11 +48,18 @@ size_t i;

for (i = 0; i < LR35902DebugWatchpointListSize(&debugger->watchpoints); ++i) { watchpoint = LR35902DebugWatchpointListGetPointer(&debugger->watchpoints, i); if (watchpoint->address == address && (watchpoint->segment < 0 || watchpoint->segment == debugger->originalMemory.currentSegment(debugger->cpu, address)) && watchpoint->type & type) { - info->oldValue = debugger->originalMemory.load8(debugger->cpu, address); - info->newValue = newValue; + if (watchpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(debugger->d.p, watchpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return false; + } + } + info->type.wp.oldValue = debugger->originalMemory.load8(debugger->cpu, address); + info->type.wp.newValue = newValue; info->address = address; - info->watchType = watchpoint->type; - info->accessType = type; + info->type.wp.watchType = watchpoint->type; + info->type.wp.accessType = type; return true; } }
M src/lr35902/lr35902.csrc/lr35902/lr35902.c

@@ -137,22 +137,22 @@ }

} void LR35902Tick(struct LR35902Core* cpu) { + if (cpu->cycles >= cpu->nextEvent) { + cpu->irqh.processEvents(cpu); + } _LR35902Step(cpu); if (cpu->cycles + 2 >= cpu->nextEvent) { int32_t diff = cpu->nextEvent - cpu->cycles; cpu->cycles = cpu->nextEvent; cpu->executionState += diff; cpu->irqh.processEvents(cpu); - cpu->cycles += 2 - diff; + cpu->cycles += LR35902_CORE_EXECUTE - cpu->executionState; } else { cpu->cycles += 2; } cpu->executionState = LR35902_CORE_FETCH; cpu->instruction(cpu); ++cpu->cycles; - if (cpu->cycles >= cpu->nextEvent) { - cpu->irqh.processEvents(cpu); - } } void LR35902Run(struct LR35902Core* cpu) {

@@ -168,7 +168,7 @@ int32_t diff = cpu->nextEvent - cpu->cycles;

cpu->cycles = cpu->nextEvent; cpu->executionState += diff; cpu->irqh.processEvents(cpu); - cpu->cycles += 2 - diff; + cpu->cycles += LR35902_CORE_EXECUTE - cpu->executionState; running = false; } else { cpu->cycles += 2;
M src/platform/3ds/3ds-vfs.csrc/platform/3ds/3ds-vfs.c

@@ -241,7 +241,11 @@ return 0;

} const char* dir = vd3d->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s/%s", dir, path); + if (dir[strlen(dir) - 1] == '/') { + sprintf(combined, "%s%s", dir, path); + } else { + sprintf(combined, "%s/%s", dir, path); + } struct VFile* file = VFileOpen(combined, mode); free(combined);

@@ -255,7 +259,11 @@ return 0;

} const char* dir = vd3d->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s/%s", dir, path); + if (dir[strlen(dir) - 1] == '/') { + sprintf(combined, "%s%s", dir, path); + } else { + sprintf(combined, "%s/%s", dir, path); + } struct VDir* vd2 = VDirOpen(combined); if (!vd2) {

@@ -272,7 +280,11 @@ return 0;

} const char* dir = vd3d->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s/%s", dir, path); + if (dir[strlen(dir) - 1] == '/') { + sprintf(combined, "%s%s", dir, path); + } else { + sprintf(combined, "%s/%s", dir, path); + } uint16_t utf16Path[PATH_MAX + 1]; ssize_t units = utf8_to_utf16(utf16Path, (const uint8_t*) combined, PATH_MAX);
M src/platform/3ds/CMakeLists.txtsrc/platform/3ds/CMakeLists.txt

@@ -57,6 +57,10 @@ add_custom_command(OUTPUT ${BINARY_NAME}.smdh

COMMAND ${BANNERTOOL} makesmdh -s "${PROJECT_NAME}" -l "${SUMMARY}" -p "endrift" -i ${CMAKE_SOURCE_DIR}/res/mgba-48.png -o ${BINARY_NAME}.smdh DEPENDS ${CMAKE_SOURCE_DIR}/res/${BINARY_NAME}-48.png) +add_custom_command(OUTPUT ${BINARY_NAME}.xml + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/hbl.xml ${BINARY_NAME}.xml + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hbl.xml) + add_custom_command(OUTPUT ${BINARY_NAME}.bnr COMMAND ${BANNERTOOL} makebanner -ci ${CMAKE_CURRENT_SOURCE_DIR}/banner.cgfx -a ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav -o ${BINARY_NAME}.bnr DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/banner.cgfx ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav)

@@ -84,7 +88,7 @@ COMMENT "raw2c uishader.shbin")

add_custom_command(OUTPUT ${BINARY_NAME}.3dsx COMMAND ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh - DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) + DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh ${BINARY_NAME}.xml) add_custom_target(${BINARY_NAME}.3dsx ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx) add_custom_command(OUTPUT ${BINARY_NAME}.cia

@@ -112,5 +116,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cia.rsf.in ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.xml + DESTINATION 3dsx COMPONENT ${BINARY_NAME}-3ds) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.cia - DESTINATION . COMPONENT ${BINARY_NAME}-3ds) + DESTINATION cia COMPONENT ${BINARY_NAME}-3ds)
M src/platform/3ds/CMakeToolchain.txtsrc/platform/3ds/CMakeToolchain.txt

@@ -23,9 +23,9 @@ endif()

set(CMAKE_PROGRAM_PATH ${DEVKITARM}/bin) set(cross_prefix arm-none-eabi-) -set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard") +set(arch_flags "-march=armv6k -mtune=mpcore -mfloat-abi=hard -ffunction-sections") set(inc_flags "-I${CTRULIB}/include ${arch_flags} -mword-relocations") -set(link_flags "-L${CTRULIB}/lib -lctru -specs=3dsx.specs ${arch_flags}") +set(link_flags "-L${CTRULIB}/lib -lctru -specs=3dsx.specs ${arch_flags} -Wl,--gc-sections") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor")
M src/platform/3ds/cia.rsf.insrc/platform/3ds/cia.rsf.in

@@ -174,7 +174,6 @@ - y2r:u

- ldr:ro - ir:USER - ir:u - - csnd:SND SystemControlInfo:

@@ -195,7 +194,6 @@ camera: 0x0004013000001602L

cecd: 0x0004013000002602L cfg: 0x0004013000001702L codec: 0x0004013000001802L - csnd: 0x0004013000002702L dlp: 0x0004013000002802L dsp: 0x0004013000001a02L friends: 0x0004013000003202L
M src/platform/3ds/gui-font.csrc/platform/3ds/gui-font.c

@@ -11,13 +11,12 @@ #include "icons.h"

#include "ctr-gpu.h" -#define CELL_HEIGHT 16 -#define CELL_WIDTH 16 -#define FONT_SIZE 0.52f +#define FONT_SIZE 15.6f struct GUIFont { C3D_Tex* sheets; C3D_Tex icons; + float size; }; struct GUIFont* GUIFontCreate(void) {

@@ -29,6 +28,7 @@ }

C3D_Tex* tex; TGLP_s* glyphInfo = fontGetGlyphInfo(); + guiFont->size = FONT_SIZE / glyphInfo->cellHeight; guiFont->sheets = malloc(sizeof(*guiFont->sheets) * glyphInfo->nSheets); int i;

@@ -59,16 +59,14 @@ free(font);

} unsigned GUIFontHeight(const struct GUIFont* font) { - UNUSED(font); - return fontGetInfo()->lineFeed * FONT_SIZE; + return fontGetInfo()->lineFeed * font->size; } unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { - UNUSED(font); int index = fontGlyphIndexFromCodePoint(glyph); charWidthInfo_s* info = fontGetCharWidthInfo(index); if (info) { - return info->charWidth * FONT_SIZE; + return info->charWidth * font->size; } return 0; }

@@ -108,7 +106,7 @@ u16 u = tex->width * data.texcoord.left;

u16 v = tex->height * data.texcoord.bottom; ctrAddRectEx(color, x, y, - tex->width * width * FONT_SIZE, tex->height * height * -FONT_SIZE, + tex->width * width * font->size, tex->height * height * -font->size, u, v, tex->width * width, tex->height * height, 0); }
A src/platform/3ds/hbl.xml

@@ -0,0 +1,8 @@

+<targets selectable="false"> + <title mediatype="0">0004001000020e00</title> + <title mediatype="0">0004001000021e00</title> + <title mediatype="0">0004001000022e00</title> + <title mediatype="0">0004001000026e00</title> + <title mediatype="0">0004001000027e00</title> + <title mediatype="0">0004001000028e00</title> +</targets>
M src/platform/3ds/main.csrc/platform/3ds/main.c

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

#include <mgba/core/blip_buf.h> #include <mgba/core/core.h> +#include <mgba/core/serialize.h> #ifdef M_CORE_GBA #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/input.h>

@@ -22,6 +23,7 @@ #include <mgba-util/gui/menu.h>

#include <mgba-util/memory.h> #include <mgba-util/platform/3ds/3ds-vfs.h> +#include <mgba-util/threading.h> #include "ctr-gpu.h" #include <3ds.h>

@@ -78,8 +80,7 @@ } camera;

static enum { NO_SOUND, - DSP_SUPPORTED, - CSND_SUPPORTED + DSP_SUPPORTED } hasSound; // TODO: Move into context

@@ -154,37 +155,19 @@ if (hasSound != NO_SOUND) {

linearFree(audioLeft); } - if (hasSound == CSND_SUPPORTED) { - linearFree(audioRight); - csndExit(); - } - if (hasSound == DSP_SUPPORTED) { ndspExit(); } camExit(); - csndExit(); + ndspExit(); ptmuExit(); } static void _aptHook(APT_HookType hook, void* user) { UNUSED(user); switch (hook) { - case APTHOOK_ONSUSPEND: - case APTHOOK_ONSLEEP: - if (hasSound == CSND_SUPPORTED) { - CSND_SetPlayState(8, 0); - CSND_SetPlayState(9, 0); - csndExecCmds(false); - } - break; case APTHOOK_ONEXIT: - if (hasSound == CSND_SUPPORTED) { - CSND_SetPlayState(8, 0); - CSND_SetPlayState(9, 0); - csndExecCmds(false); - } _cleanup(); exit(0); break;

@@ -197,33 +180,6 @@ static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {

mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key); } -static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) { - u32 pleft = 0, pright = 0; - - int loopMode = (flags >> 10) & 3; - if (!loopMode) { - flags |= SOUND_ONE_SHOT; - } - - pleft = osConvertVirtToPhys(left); - pright = osConvertVirtToPhys(right); - - u32 timer = CSND_TIMER(sampleRate); - if (timer < 0x0042) { - timer = 0x0042; - } - else if (timer > 0xFFFF) { - timer = 0xFFFF; - } - flags &= ~0xFFFF001F; - flags |= SOUND_ENABLE | (timer << 16); - - u32 volumes = CSND_VOL(vol, -1.0); - CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes); - volumes = CSND_VOL(vol, 1.0); - CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes); -} - static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right); static void _drawStart(void) {

@@ -381,12 +337,7 @@ blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);

if (hasSound != NO_SOUND) { audioPos = 0; } - if (hasSound == CSND_SUPPORTED) { - memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - csndExecCmds(false); - } else if (hasSound == DSP_SUPPORTED) { + if (hasSound == DSP_SUPPORTED) { memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t)); } unsigned mode;

@@ -428,11 +379,6 @@ }

} static void _gameUnloaded(struct mGUIRunner* runner) { - if (hasSound == CSND_SUPPORTED) { - CSND_SetPlayState(8, 0); - CSND_SetPlayState(9, 0); - csndExecCmds(false); - } osSetSpeedupEnable(false); frameLimiter = true;

@@ -678,6 +624,11 @@ frameLimiter = limit;

tickCounter = svcGetSystemTick(); } +static bool _running(struct mGUIRunner* runner) { + UNUSED(runner); + return aptMainLoop(); +} + static uint32_t _pollInput(const struct mInputMap* map) { hidScanInput(); int activeKeys = hidKeysHeld();

@@ -789,22 +740,7 @@ }

static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { UNUSED(stream); - if (hasSound == CSND_SUPPORTED) { - blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false); - blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false); - GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); - GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); - audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER; - if (audioPos == AUDIO_SAMPLES * 3) { - u8 playing = 0; - csndIsPlaying(0x8, &playing); - if (!playing) { - CSND_SetPlayState(0x8, 1); - CSND_SetPlayState(0x9, 1); - csndExecCmds(false); - } - } - } else if (hasSound == DSP_SUPPORTED) { + if (hasSound == DSP_SUPPORTED) { int startId = bufferId; while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) { bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);

@@ -870,12 +806,6 @@ for (i = 0; i < DSP_BUFFERS; ++i) {

dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2]; dspBuffer[i].nsamples = AUDIO_SAMPLES; } - } - - if (hasSound == NO_SOUND && !csndInit()) { - hasSound = CSND_SUPPORTED; - audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); - audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); } gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);

@@ -1021,8 +951,16 @@ .paused = _gameUnloaded,

.unpaused = _gameLoaded, .incrementScreenMode = _incrementScreenMode, .setFrameLimiter = _setFrameLimiter, - .pollGameInput = _pollGameInput + .pollGameInput = _pollGameInput, + .running = _running }; + + runner.autosave.running = true; + MutexInit(&runner.autosave.mutex); + ConditionInit(&runner.autosave.cond); + + APT_SetAppCpuTimeLimit(20); + runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true); mGUIInit(&runner, "3ds");
M src/platform/example/client-server/client.csrc/platform/example/client-server/client.c

@@ -29,19 +29,19 @@ SocketRecv(server, &height, sizeof(height));

SocketRecv(server, &bpp, sizeof(bpp)); width = ntohl(width); height = ntohl(height); - if (ntohl(bpp) != BYTES_PER_PIXEL) { + bpp = ntohl(bpp); + ssize_t bufferSize = width * height * bpp; + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + if (bpp == 2) { + SDL_SetVideoMode(width, height, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); + } else if (bpp == 4) { + SDL_SetVideoMode(width, height, 32, SDL_DOUBLEBUF | SDL_HWSURFACE); + } else { SocketClose(server); SocketSubsystemDeinit(); return 1; } - ssize_t bufferSize = width * height * BYTES_PER_PIXEL; - -#if !SDL_VERSION_ATLEAST(2, 0, 0) -#ifdef COLOR_16_BIT - SDL_SetVideoMode(width, height, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); -#else - SDL_SetVideoMode(width, height, 32, SDL_DOUBLEBUF | SDL_HWSURFACE); -#endif #endif #if SDL_VERSION_ATLEAST(2, 0, 0)

@@ -49,15 +49,16 @@ SDL_Window* window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_OPENGL);

SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); Uint32 pixfmt; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - pixfmt = SDL_PIXELFORMAT_RGB565; -#else - pixfmt = SDL_PIXELFORMAT_ABGR1555; -#endif -#else - pixfmt = SDL_PIXELFORMAT_ABGR8888; -#endif + if (bpp == 2) { + pixfmt = SDL_PIXELFORMAT_RGB565; + } else if (bpp == 4) { + pixfmt = SDL_PIXELFORMAT_ABGR8888; + } else { + SocketClose(server); + SocketSubsystemDeinit(); + return 1; + } + SDL_Texture* sdlTex = SDL_CreateTexture(renderer, pixfmt, SDL_TEXTUREACCESS_STREAMING, width, height); #endif
M src/platform/libretro/libretro.csrc/platform/libretro/libretro.c

@@ -88,6 +88,13 @@ mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");

} } + var.key = "mgba_frameskip"; + var.value = 0; + if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + opts.frameskip = strtol(var.value, NULL, 10); + + } + mCoreConfigLoadDefaults(&core->config, &opts); mCoreLoadConfig(core); }

@@ -105,6 +112,7 @@ { "mgba_allow_opposing_directions", "Allow opposing directional input; OFF|ON" },

{ "mgba_use_bios", "Use BIOS file if found (requires restart); ON|OFF" }, { "mgba_skip_bios", "Skip BIOS intro (requires restart); OFF|ON" }, { "mgba_idle_optimization", "Idle loop removal; Remove Known|Detect and Remove|Don't Remove" }, + { "mgba_frameskip", "Frameskip; 0|1|2|3|4|5|6|7|8|9|10" }, { 0, 0 } };

@@ -223,15 +231,21 @@ void retro_run(void) {

uint16_t keys; inputPollCallback(); - struct retro_variable var = { - .key = "mgba_allow_opposing_directions", - .value = 0 - }; - bool updated = false; if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) { + struct retro_variable var = { + .key = "mgba_allow_opposing_directions", + .value = 0 + }; if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { ((struct GBA*) core->board)->allowOpposingDirections = strcmp(var.value, "yes") == 0; + } + + var.key = "mgba_frameskip"; + var.value = 0; + if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + mCoreConfigSetUIntValue(&core->config, "frameskip", strtol(var.value, NULL, 10)); + mCoreLoadConfig(core); } }
M src/platform/opengl/gl.csrc/platform/opengl/gl.c

@@ -38,6 +38,8 @@ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);

#else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif

@@ -116,6 +118,8 @@ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);

#else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif +#elif defined(__BIG_ENDIAN__) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif
M src/platform/opengl/gles2.csrc/platform/opengl/gles2.c

@@ -149,6 +149,8 @@ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);

#else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif

@@ -324,6 +326,8 @@ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);

#else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif
M src/platform/psp2/CMakeLists.txtsrc/platform/psp2/CMakeLists.txt

@@ -13,7 +13,21 @@

list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceAppMgr_stub -lSceCtrl_stub -lSceAudio_stub -lSceCamera_stub -lSceCommonDialog_stub -lSceDisplay_stub -lSceGxm_stub -lSceMotion_stub -lScePgf_stub -lScePhotoExport_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) +set(OS_LIB -lvita2d -l${M_LIBRARY} + -lSceAppMgr_stub + -lSceAppUtil_stub + -lSceAudio_stub + -lSceCamera_stub + -lSceCommonDialog_stub + -lSceCtrl_stub + -lSceDisplay_stub + -lSceGxm_stub + -lSceMotion_stub + -lScePgf_stub + -lScePhotoExport_stub + -lScePower_stub + -lSceSysmodule_stub + -lSceTouch_stub) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c)
M src/platform/psp2/main.csrc/platform/psp2/main.c

@@ -12,12 +12,14 @@ #include <mgba-util/gui/font.h>

#include <mgba-util/gui/file-select.h> #include <mgba-util/gui/menu.h> +#include <psp2/apputil.h> #include <psp2/ctrl.h> #include <psp2/display.h> #include <psp2/kernel/processmgr.h> #include <psp2/kernel/threadmgr.h> #include <psp2/power.h> #include <psp2/sysmodule.h> +#include <psp2/system_param.h> #include <psp2/touch.h> #include <vita2d.h>

@@ -87,7 +89,8 @@ struct GUIFont* font = GUIFontCreate();

struct mGUIRunner runner = { .params = { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, - font, "ux0:data", _drawStart, _drawEnd, + font, "", + _drawStart, _drawEnd, _pollInput, _pollCursor, _batteryState, 0, 0,

@@ -150,7 +153,7 @@ .setup = mPSP2Setup,

.teardown = mPSP2Teardown, .gameLoaded = mPSP2LoadROM, .gameUnloaded = mPSP2UnloadROM, - .prepareForFrame = mPSP2PrepareForFrame, + .prepareForFrame = NULL, .drawFrame = mPSP2Draw, .drawScreenshot = mPSP2DrawScreenshot, .paused = mPSP2Paused,

@@ -163,11 +166,26 @@

sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START); sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG_WIDE); sceSysmoduleLoadModule(SCE_SYSMODULE_PHOTO_EXPORT); + sceSysmoduleLoadModule(SCE_SYSMODULE_APPUTIL); mGUIInit(&runner, "psvita"); - mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CROSS, GUI_INPUT_SELECT); - mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CIRCLE, GUI_INPUT_BACK); + int enterButton; + SceAppUtilInitParam initParam; + SceAppUtilBootParam bootParam; + memset(&initParam, 0, sizeof(SceAppUtilInitParam)); + memset(&bootParam, 0, sizeof(SceAppUtilBootParam)); + sceAppUtilInit(&initParam, &bootParam); + sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_ENTER_BUTTON, &enterButton); + sceAppUtilShutdown(); + + if (enterButton == SCE_SYSTEM_PARAM_ENTER_BUTTON_CIRCLE) { + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CROSS, GUI_INPUT_BACK); + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CIRCLE, GUI_INPUT_SELECT); + } else { + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CROSS, GUI_INPUT_SELECT); + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CIRCLE, GUI_INPUT_BACK); + } mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_TRIANGLE, GUI_INPUT_CANCEL); mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_UP, GUI_INPUT_UP); mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_DOWN, GUI_INPUT_DOWN);
M src/platform/psp2/psp2-context.csrc/platform/psp2/psp2-context.c

@@ -21,7 +21,6 @@

#include <mgba-util/memory.h> #include <mgba-util/circle-buffer.h> #include <mgba-util/math.h> -#include <mgba-util/ring-fifo.h> #include <mgba-util/threading.h> #include <mgba-util/vfs.h> #include <mgba-util/platform/psp2/sce-vfs.h>

@@ -71,16 +70,20 @@ unsigned cam;

size_t bufferOffset; } camera; +static struct mAVStream stream; + bool frameLimiter = true; extern const uint8_t _binary_backdrop_png_start[]; static vita2d_texture* backdrop = 0; -#define PSP2_SAMPLES 256 -#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 20) +#define PSP2_SAMPLES 512 +#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 16) static struct mPSP2AudioContext { - struct RingFIFO buffer; + struct GBAStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE]; + size_t writeOffset; + size_t readOffset; size_t samples; Mutex mutex; Condition cond;

@@ -93,26 +96,25 @@ }

static THREAD_ENTRY _audioThread(void* context) { struct mPSP2AudioContext* audio = (struct mPSP2AudioContext*) context; + uint32_t zeroBuffer[PSP2_SAMPLES] = {0}; int audioPort = sceAudioOutOpenPort(SCE_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, SCE_AUDIO_OUT_MODE_STEREO); while (audio->running) { MutexLock(&audio->mutex); - int len = audio->samples; - if (len > PSP2_SAMPLES) { - len = PSP2_SAMPLES; + void* buffer; + if (audio->samples >= PSP2_SAMPLES) { + buffer = &audio->buffer[audio->readOffset]; + audio->samples -= PSP2_SAMPLES; + audio->readOffset += PSP2_SAMPLES; + if (audio->readOffset >= PSP2_AUDIO_BUFFER_SIZE) { + audio->readOffset = 0; + } + ConditionWake(&audio->cond); + } else { + buffer = zeroBuffer; } - struct GBAStereoSample* buffer = audio->buffer.readPtr; - RingFIFORead(&audio->buffer, NULL, len * 4); - audio->samples -= len; - ConditionWake(&audio->cond); + MutexUnlock(&audio->mutex); - MutexUnlock(&audio->mutex); sceAudioOutOutput(audioPort, buffer); - MutexLock(&audio->mutex); - - if (audio->samples < PSP2_SAMPLES) { - ConditionWait(&audio->cond, &audio->mutex); - } - MutexUnlock(&audio->mutex); } sceAudioOutReleasePort(audioPort); return 0;

@@ -229,6 +231,29 @@ };

sceCameraRead(imageSource->cam - 1, &read); } +static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { + UNUSED(stream); + MutexLock(&audioContext.mutex); + struct GBAStereoSample* samples = &audioContext.buffer[audioContext.writeOffset]; + while (audioContext.samples == PSP2_AUDIO_BUFFER_SIZE) { + if (!frameLimiter) { + blip_clear(left); + blip_clear(right); + MutexUnlock(&audioContext.mutex); + return; + } + ConditionWait(&audioContext.cond, &audioContext.mutex); + } + blip_read_samples(left, &samples[0].left, PSP2_SAMPLES, true); + blip_read_samples(right, &samples[0].right, PSP2_SAMPLES, true); + audioContext.samples += PSP2_SAMPLES; + audioContext.writeOffset += PSP2_SAMPLES; + if (audioContext.writeOffset >= PSP2_AUDIO_BUFFER_SIZE) { + audioContext.writeOffset = 0; + } + MutexUnlock(&audioContext.mutex); +} + uint16_t mPSP2PollInput(struct mGUIRunner* runner) { SceCtrlData pad; sceCtrlPeekBufferPositive(0, &pad, 1);

@@ -285,6 +310,7 @@ screenshot = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);

outputBuffer = vita2d_texture_get_datap(tex); runner->core->setVideoBuffer(runner->core, outputBuffer, 256); + runner->core->setAudioBufferSize(runner->core, PSP2_SAMPLES); rotation.d.sample = _sampleRotation; rotation.d.readTiltX = _readTiltX;

@@ -302,6 +328,13 @@ camera.d.requestImage = _requestImage;

camera.buffer = NULL; camera.cam = 1; runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d); + + + stream.videoDimensionsChanged = NULL; + stream.postAudioFrame = NULL; + stream.postAudioBuffer = _postAudioBuffer; + stream.postVideoFrame = NULL; + runner->core->setAVStream(runner->core, &stream); frameLimiter = true; backdrop = vita2d_load_PNG_buffer(_binary_backdrop_png_start);

@@ -341,37 +374,15 @@ default:

break; } - RingFIFOInit(&audioContext.buffer, PSP2_AUDIO_BUFFER_SIZE * sizeof(struct GBAStereoSample)); MutexInit(&audioContext.mutex); ConditionInit(&audioContext.cond); + memset(audioContext.buffer, 0, sizeof(audioContext.buffer)); + audioContext.readOffset = 0; + audioContext.writeOffset = 0; audioContext.running = true; ThreadCreate(&audioThread, _audioThread, &audioContext); } -void mPSP2PrepareForFrame(struct mGUIRunner* runner) { - int nSamples = 0; - while (blip_samples_avail(runner->core->getAudioChannel(runner->core, 0)) >= PSP2_SAMPLES) { - struct GBAStereoSample* samples = audioContext.buffer.writePtr; - if (nSamples > (PSP2_AUDIO_BUFFER_SIZE >> 2) + (PSP2_AUDIO_BUFFER_SIZE >> 1)) { // * 0.75 - if (!frameLimiter) { - blip_clear(runner->core->getAudioChannel(runner->core, 0)); - blip_clear(runner->core->getAudioChannel(runner->core, 1)); - break; - } - } - blip_read_samples(runner->core->getAudioChannel(runner->core, 0), &samples[0].left, PSP2_SAMPLES, true); - blip_read_samples(runner->core->getAudioChannel(runner->core, 1), &samples[0].right, PSP2_SAMPLES, true); - while (!RingFIFOWrite(&audioContext.buffer, NULL, PSP2_SAMPLES * 4)) { - ConditionWake(&audioContext.cond); - // Spinloooooooop! - } - MutexLock(&audioContext.mutex); - audioContext.samples += PSP2_SAMPLES; - nSamples = audioContext.samples; - ConditionWake(&audioContext.cond); - MutexUnlock(&audioContext.mutex); - } -} void mPSP2UnloadROM(struct mGUIRunner* runner) { switch (runner->core->platform(runner->core)) {
M src/platform/psp2/sce-vfs.csrc/platform/psp2/sce-vfs.c

@@ -10,6 +10,10 @@

#include <mgba-util/vfs.h> #include <mgba-util/memory.h> +#ifndef SCE_CST_SIZE +#define SCE_CST_SIZE 0x0004 +#endif + struct VFileSce { struct VFile d;

@@ -47,6 +51,16 @@ static bool _vdsceDeleteFile(struct VDir* vd, const char* path);

static const char* _vdesceName(struct VDirEntry* vde); static enum VFSType _vdesceType(struct VDirEntry* vde); + +static bool _vdlsceClose(struct VDir* vd); +static void _vdlsceRewind(struct VDir* vd); +static struct VDirEntry* _vdlsceListNext(struct VDir* vd); +static struct VFile* _vdlsceOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdlsceOpenDir(struct VDir* vd, const char* path); +static bool _vdlsceDeleteFile(struct VDir* vd, const char* path); + +static const char* _vdlesceName(struct VDirEntry* vde); +static enum VFSType _vdlesceType(struct VDirEntry* vde); struct VFile* VFileOpenSce(const char* path, int flags, SceMode mode) { struct VFileSce* vfsce = malloc(sizeof(struct VFileSce));

@@ -120,20 +134,8 @@ }

static void _vfsceTruncate(struct VFile* vf, size_t size) { struct VFileSce* vfsce = (struct VFileSce*) vf; - SceOff cur = sceIoLseek(vfsce->fd, 0, SEEK_CUR); - SceOff end = sceIoLseek(vfsce->fd, 0, SEEK_END); - if (end < size) { - uint8_t buffer[2048] = {}; - size_t write = size - end; - while (write >= sizeof(buffer)) { - sceIoWrite(vfsce->fd, buffer, sizeof(buffer)); - write -= sizeof(buffer); - } - if (write) { - sceIoWrite(vfsce->fd, buffer, write); - } - } // TODO: Else - sceIoLseek(vfsce->fd, cur, SEEK_SET); + SceIoStat stat = { .st_size = size }; + sceIoChstatByFd(vfsce->fd, &stat, SCE_CST_SIZE); } ssize_t _vfsceSize(struct VFile* vf) {

@@ -156,6 +158,10 @@ return sceIoSyncByFd(vfsce->fd) >= 0;

} struct VDir* VDirOpen(const char* path) { + if (!path || !path[0]) { + return VDeviceList(); + } + SceUID dir = sceIoDopen(path); if (dir < 0) { return 0;

@@ -258,3 +264,96 @@ return VFS_DIRECTORY;

} return VFS_FILE; } + +struct VDirEntrySceDevList { + struct VDirEntry d; + ssize_t index; + const char* name; +}; + +struct VDirSceDevList { + struct VDir d; + struct VDirEntrySceDevList vde; +}; + +static const char* _devs[] = { + "ux0:", + "ur0:", + "uma0:" +}; + +struct VDir* VDeviceList() { + struct VDirSceDevList* vd = malloc(sizeof(struct VDirSceDevList)); + if (!vd) { + return 0; + } + + vd->d.close = _vdlsceClose; + vd->d.rewind = _vdlsceRewind; + vd->d.listNext = _vdlsceListNext; + vd->d.openFile = _vdlsceOpenFile; + vd->d.openDir = _vdlsceOpenDir; + vd->d.deleteFile = _vdlsceDeleteFile; + + vd->vde.d.name = _vdlesceName; + vd->vde.d.type = _vdlesceType; + vd->vde.index = -1; + vd->vde.name = 0; + + return &vd->d; +} + +static bool _vdlsceClose(struct VDir* vd) { + struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd; + free(vdl); + return true; +} + +static void _vdlsceRewind(struct VDir* vd) { + struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd; + vdl->vde.name = NULL; + vdl->vde.index = -1; +} + +static struct VDirEntry* _vdlsceListNext(struct VDir* vd) { + struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd; + while (vdl->vde.index < 3) { + ++vdl->vde.index; + vdl->vde.name = _devs[vdl->vde.index]; + SceUID dir = sceIoDopen(vdl->vde.name); + if (dir < 0) { + continue; + } + sceIoDclose(dir); + return &vdl->vde.d; + } + return 0; +} + +static struct VFile* _vdlsceOpenFile(struct VDir* vd, const char* path, int mode) { + UNUSED(vd); + UNUSED(path); + UNUSED(mode); + return NULL; +} + +static struct VDir* _vdlsceOpenDir(struct VDir* vd, const char* path) { + UNUSED(vd); + return VDirOpen(path); +} + +static bool _vdlsceDeleteFile(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return false; +} + +static const char* _vdlesceName(struct VDirEntry* vde) { + struct VDirEntrySceDevList* vdle = (struct VDirEntrySceDevList*) vde; + return vdle->name; +} + +static enum VFSType _vdlesceType(struct VDirEntry* vde) { + UNUSED(vde); + return VFS_DIRECTORY; +}
M src/platform/python/CMakeLists.txtsrc/platform/python/CMakeLists.txt

@@ -13,9 +13,9 @@ file(GLOB PYTHON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h)

if(NOT GIT_TAG) if(GIT_BRANCH STREQUAL "master" OR NOT GIT_BRANCH) - set(PYLIB_VERSION -b -${GIT_REV}-${GIT_COMMIT_SHORT}) + set(PYLIB_VERSION -b .dev${GIT_REV}+g${GIT_COMMIT_SHORT}) else() - set(PYLIB_VERSION -b -${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT}) + set(PYLIB_VERSION -b .dev${GIT_REV}+${GIT_BRANCH}.g${GIT_COMMIT_SHORT}) endif() endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py)
M src/platform/python/_builder.hsrc/platform/python/_builder.h

@@ -54,6 +54,7 @@ #ifdef USE_PNG

#include <mgba-util/png-io.h> #endif #ifdef M_CORE_GBA +#include <mgba/gba/interface.h> #include <mgba/internal/arm/arm.h> #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/input.h>
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -30,6 +30,7 @@ #include <mgba/core/mem-search.h>

#include <mgba/core/thread.h> #include <mgba/core/version.h> #include <mgba/debugger/debugger.h> +#include <mgba/gba/interface.h> #include <mgba/internal/arm/arm.h> #include <mgba/internal/debugger/cli-debugger.h> #include <mgba/internal/ds/ds.h>
M src/platform/python/mgba/core.pysrc/platform/python/mgba/core.py

@@ -142,9 +142,6 @@ if not success:

raise RuntimeError("Failed to initialize core") return cls._detect(core) - def _deinit(self): - self._core.deinit(self._core) - @classmethod def _detect(cls, core): if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA:

@@ -158,6 +155,9 @@ from .ds import DS

return DS(core) return Core(core) + def _load(self): + self._wasReset = True + def loadFile(self, path): return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))

@@ -166,6 +166,9 @@ return bool(self._core.isROM(vf.handle))

def loadROM(self, vf): return bool(self._core.loadROM(self._core, vf.handle)) + + def loadBIOS(self, vf, id=0): + return bool(self._core.loadBIOS(self._core, vf.handle, id)) def loadSave(self, vf): return bool(self._core.loadSave(self._core, vf.handle))

@@ -185,6 +188,9 @@

def autoloadPatch(self): return bool(lib.mCoreAutoloadPatch(self._core)) + def autoloadCheats(self): + return bool(lib.mCoreAutoloadCheats(self._core)) + def platform(self): return self._core.platform(self._core)

@@ -199,7 +205,7 @@ self._core.setVideoBuffer(self._core, image.buffer, image.stride)

def reset(self): self._core.reset(self._core) - self._wasReset = True + self._load() @needsReset @protected

@@ -260,6 +266,10 @@ return ffi.string(code, 12).decode("ascii")

def addFrameCallback(self, cb): self._callbacks.videoFrameEnded.append(cb) + + @property + def crc32(self): + return self._native.romCrc32 class ICoreOwner(object): def claim(self):
M src/platform/python/mgba/debugger.pysrc/platform/python/mgba/debugger.py

@@ -41,7 +41,7 @@ def __init__(self, native):

self._native = native self._cbs = [] self._core = Core._detect(native.core) - self._core._wasReset = True + self._core._load() def pause(self): lib.mDebuggerEnter(self._native, lib.DEBUGGER_ENTER_MANUAL, ffi.NULL)
A src/platform/python/mgba/gamedata.py

@@ -0,0 +1,22 @@

+# Copyright (c) 2013-2017 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/. +try: + import mgba_gamedata +except ImportError: + pass + +def search(core): + crc32 = None + if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA: + platform = 'GBA' + crc32 = core.crc32 + if hasattr(core, 'PLATFORM_GB') and core.platform() == core.PLATFORM_GB: + platform = 'GB' + crc32 = core.crc32 + cls = mgba_gamedata.registry.search(platform, {'crc32': crc32}) + if not cls: + return None + return cls(core.memory.u8)
M src/platform/python/mgba/gb.pysrc/platform/python/mgba/gb.py

@@ -36,12 +36,15 @@ lib.mCacheSetDeinit(cache)

if self._wasReset: self._native.video.renderer.cache = ffi.NULL - def reset(self): - super(GB, self).reset() + def _load(self): + super(GB, self)._load() self.memory = GBMemory(self._core) def attachSIO(self, link): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) + + def __del__(self): + lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL) createCallback("GBSIOPythonDriver", "init") createCallback("GBSIOPythonDriver", "deinit")
M src/platform/python/mgba/gba.pysrc/platform/python/mgba/gba.py

@@ -26,6 +26,7 @@ SIO_NORMAL_8 = lib.SIO_NORMAL_8

SIO_NORMAL_32 = lib.SIO_NORMAL_32 SIO_MULTI = lib.SIO_MULTI SIO_UART = lib.SIO_UART + SIO_JOYBUS = lib.SIO_JOYBUS SIO_GPIO = lib.SIO_GPIO def __init__(self, native):

@@ -33,6 +34,7 @@ super(GBA, self).__init__(native)

self._native = ffi.cast("struct GBA*", native.board) self.sprites = GBAObjs(self) self.cpu = ARMCore(self._core.cpu) + self._sio = set() @needsReset def _initCache(self, cache):

@@ -44,13 +46,18 @@ lib.mCacheSetDeinit(cache)

if self._wasReset: self._native.video.renderer.cache = ffi.NULL - def reset(self): - super(GBA, self).reset() + def _load(self): + super(GBA, self)._load() self.memory = GBAMemory(self._core, self._native.memory.romSize) def attachSIO(self, link, mode=lib.SIO_MULTI): + self._sio.add(mode) lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode) + def __del__(self): + for mode in self._sio: + lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode) + createCallback("GBASIOPythonDriver", "init") createCallback("GBASIOPythonDriver", "deinit") createCallback("GBASIOPythonDriver", "load")

@@ -76,6 +83,32 @@ return True

def writeRegister(self, address, value): return value + +class GBASIOJOYDriver(GBASIODriver): + RESET = lib.JOY_RESET + POLL = lib.JOY_POLL + TRANS = lib.JOY_TRANS + RECV = lib.JOY_RECV + + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free) + + def sendCommand(self, cmd, data): + buffer = ffi.new('uint8_t[5]') + try: + buffer[0] = data[0] + buffer[1] = data[1] + buffer[2] = data[2] + buffer[3] = data[3] + buffer[4] = data[4] + except IndexError: + pass + + outlen = lib.GBASIOJOYSendCommand(self._native, cmd, buffer) + if outlen > 0 and outlen <= 5: + return bytes(buffer[0:outlen]) + return None class GBAMemory(Memory): def __init__(self, core, romSize=lib.SIZE_CART0):
M src/platform/python/mgba/memory.pysrc/platform/python/mgba/memory.py

@@ -100,11 +100,11 @@ self._memory[self.address] = v // self.guessDivisor

class Memory(object): - SEARCH_32 = lib.mCORE_MEMORY_SEARCH_32 - SEARCH_16 = lib.mCORE_MEMORY_SEARCH_16 - SEARCH_8 = lib.mCORE_MEMORY_SEARCH_8 + SEARCH_INT = lib.mCORE_MEMORY_SEARCH_INT SEARCH_STRING = lib.mCORE_MEMORY_SEARCH_STRING SEARCH_GUESS = lib.mCORE_MEMORY_SEARCH_GUESS + + SEARCH_EQUAL = lib.mCORE_MEMORY_SEARCH_EQUAL READ = lib.mCORE_MEMORY_READ WRITE = lib.mCORE_MEMORY_READ

@@ -131,12 +131,9 @@ lib.mCoreMemorySearchResultsInit(results, len(old_results))

params = ffi.new("struct mCoreMemorySearchParams*") params.memoryFlags = flags params.type = type - if type == self.SEARCH_8: - params.value8 = int(value) - elif type == self.SEARCH_16: - params.value16 = int(value) - elif type == self.SEARCH_32: - params.value32 = int(value) + params.op = self.SEARCH_EQUAL + if type == self.SEARCH_INT: + params.valueInt = int(value) else: params.valueStr = ffi.new("char[]", str(value).encode("ascii"))

@@ -153,3 +150,9 @@ lib.mCoreMemorySearch(self._core, params, results, limit)

new_results = [MemorySearchResult(self, lib.mCoreMemorySearchResultsGetPointer(results, i)) for i in range(lib.mCoreMemorySearchResultsSize(results))] lib.mCoreMemorySearchResultsDeinit(results) return new_results + + def __getitem__(self, address): + if isinstance(address, slice): + return bytearray(self.u8[address]) + else: + return self.u8[address]
M src/platform/python/setup.py.insrc/platform/python/setup.py.in

@@ -5,7 +5,6 @@ import sys

os.environ["BINDIR"] = "${CMAKE_BINARY_DIR}" os.environ["CPPFLAGS"] = " ".join([d for d in "${INCLUDE_FLAGS}".split(";") if d]) -os.chdir("${CMAKE_CURRENT_SOURCE_DIR}") classifiers = [ "Programming Language :: C",

@@ -22,11 +21,14 @@ author="Jeffrey Pfau",

author_email="jeffrey@endrift.com", url="http://github.com/mgba-emu/mgba/", packages=["mgba"], + package_dir={ + "mgba": "${CMAKE_CURRENT_SOURCE_DIR}" + }, setup_requires=['cffi>=1.6', 'pytest-runner'], install_requires=['cffi>=1.6', 'cached-property'], extras_require={'pil': ['Pillow>=2.3'], 'cinema': ['pyyaml', 'pytest']}, tests_require=['pytest'], - cffi_modules=["_builder.py:ffi"], + cffi_modules=["${CMAKE_CURRENT_SOURCE_DIR}/_builder.py:ffi"], license="MPL 2.0", classifiers=classifiers )
M src/platform/python/sio.csrc/platform/python/sio.c

@@ -46,6 +46,18 @@ driver->pyobj = pyobj;

return &driver->d; } +struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj) { + struct GBASIOPythonDriver* driver = malloc(sizeof(*driver)); + GBASIOJOYCreate(&driver->d); + driver->d.init = _pyGBASIOPythonDriverInitShim; + driver->d.deinit = _pyGBASIOPythonDriverDeinitShim; + driver->d.load = _pyGBASIOPythonDriverLoadShim; + driver->d.unload = _pyGBASIOPythonDriverUnloadShim; + + driver->pyobj = pyobj; + return &driver->d; +} + #endif #ifdef M_CORE_GB
M src/platform/python/sio.hsrc/platform/python/sio.h

@@ -13,6 +13,7 @@ void* pyobj;

}; struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj); +struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj); PYEXPORT bool _pyGBASIOPythonDriverInit(void* driver); PYEXPORT void _pyGBASIOPythonDriverDeinit(void* driver);
M src/platform/python/test_cinema.pysrc/platform/python/test_cinema.py

@@ -18,7 +18,7 @@ return l

def pytest_generate_tests(metafunc): if 'vtest' in metafunc.fixturenames: - tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), 'tests/cinema')) + tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema')) testList = flatten(tests) params = [] for test in testList:
M src/platform/python/tests/mgba/test_vfs.pysrc/platform/python/tests/mgba/test_vfs.py

@@ -19,7 +19,7 @@ def test_vfs_read():

vf = vfs.openPath(__file__) buffer = ffi.new('char[13]') assert vf.read(buffer, 13) == 13 - assert ffi.string(buffer) == 'import pytest' + assert ffi.string(buffer) == b'import pytest' vf.close() def test_vfs_readline():

@@ -28,9 +28,9 @@ buffer = ffi.new('char[16]')

linelen = vf.readline(buffer, 16) assert linelen in (14, 15) if linelen == 14: - assert ffi.string(buffer) == 'import pytest\n' + assert ffi.string(buffer) == b'import pytest\n' elif linelen == 15: - assert ffi.string(buffer) == 'import pytest\r\n' + assert ffi.string(buffer) == b'import pytest\r\n' vf.close() def test_vfs_readAllSize():
M src/platform/qt/AssetTile.uisrc/platform/qt/AssetTile.ui

@@ -50,6 +50,9 @@ </property>

<property name="alignment"> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> + </property> </widget> </item> </layout>

@@ -91,6 +94,9 @@ <string>0x06000000</string>

</property> <property name="alignment"> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> </property> </widget> </item>
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -2,13 +2,6 @@ set(CMAKE_CXX_STANDARD 14)

set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - endif() -endif() - set(PLATFORM_SRC) set(QT_STATIC OFF)

@@ -42,6 +35,17 @@ set(BUILD_QT OFF PARENT_SCOPE)

return() endif() +if(APPLE) + if(Qt5Widgets_VERSION MATCHES "^5.1[0-9]") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") + endif() + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() +endif() + if(BUILD_GL) list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) if(NOT WIN32 OR USE_EPOXY)

@@ -93,6 +97,7 @@ ObjView.cpp

OverrideView.cpp PaletteView.cpp PrinterView.cpp + RegisterView.cpp ROMInfo.cpp SavestateButton.cpp SensorView.cpp

@@ -219,6 +224,8 @@ endif()

if(NOT DEFINED DATADIR) if(APPLE) set(DATADIR Applications/${PROJECT_NAME}.app/Contents/Resources) + elseif(WIN32 AND NOT WIN32_UNIX_PATHS) + set(DATADIR ".") else() set(DATADIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME}) endif()

@@ -316,8 +323,13 @@ set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R "${CROSS_ROOT}")

endif() install(CODE "execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/tools/deploy-mac.py\" -v ${DEPLOY_OPTIONS} \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Applications/${PROJECT_NAME}.app\")") endif() -endif() -if(WIN32 AND CMAKE_MAJOR_VERSION GREATER 2 AND CMAKE_MINOR_VERSION GREATER 7) - # Work around CMake issue #16907 - set_target_properties(${BINARY_NAME}-qt PROPERTIES AUTORCC ON SKIP_AUTORCC ON) +elseif(WIN32) + if(CMAKE_MAJOR_VERSION EQUAL 3 AND CMAKE_MINOR_VERSION EQUAL 8) + # Work around CMake issue #16907 + set_target_properties(${BINARY_NAME}-qt PROPERTIES AUTORCC ON SKIP_AUTORCC ON) + endif() + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + find_program(BASH bash) + install(CODE "execute_process(COMMAND \"${BASH}\" \"${CMAKE_SOURCE_DIR}/tools/deploy-win.sh\" \"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe\" \"\${CMAKE_INSTALL_PREFIX}\" \"\$ENV{PWD}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")" COMPONENT ${BINARY_NAME}-qt) + endif() endif()
M src/platform/qt/CheatsModel.cppsrc/platform/qt/CheatsModel.cpp

@@ -70,10 +70,12 @@ switch (role) {

case Qt::DisplayRole: case Qt::EditRole: mCheatSetRename(cheats, value.toString().toUtf8().constData()); + mCheatAutosave(m_device); emit dataChanged(index, index); return true; case Qt::CheckStateRole: cheats->enabled = value == Qt::Checked; + mCheatAutosave(m_device); emit dataChanged(index, index); return true; default:

@@ -154,7 +156,8 @@ mCheatSet* set = *mCheatSetsGetPointer(&m_device->cheats, index.row());

beginRemoveRows(QModelIndex(), row, row); mCheatRemoveSet(m_device, set); mCheatSetDeinit(set); - endInsertRows(); + endRemoveRows(); + mCheatAutosave(m_device); } QString CheatsModel::toString(const QModelIndexList& indices) const {

@@ -201,6 +204,7 @@ }

void CheatsModel::endAppendRow() { endInsertRows(); + mCheatAutosave(m_device); } void CheatsModel::loadFile(const QString& path) {

@@ -232,6 +236,7 @@ set->copyProperties(set, *mCheatSetsGetPointer(&m_device->cheats, size - 1));

} mCheatAddSet(m_device, set); endInsertRows(); + mCheatAutosave(m_device); } void CheatsModel::invalidated() {
M src/platform/qt/CheatsView.cppsrc/platform/qt/CheatsView.cpp

@@ -109,14 +109,14 @@ return false;

} void CheatsView::load() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file")); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file"), tr(("Cheats file (*.cheats *.cht *.clt)"))); if (!filename.isEmpty()) { m_model.loadFile(filename); } } void CheatsView::save() { - QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file")); + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file"), tr(("Cheats file (*.cheats *.cht *.clt)"))); if (!filename.isEmpty()) { m_model.saveFile(filename); }
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -28,8 +28,9 @@ #endif

#include <mgba-util/math.h> #include <mgba-util/vfs.h> +#define AUTOSAVE_GRANULARITY 600 + using namespace QGBA; - CoreController::CoreController(mCore* core, QObject* parent) : QObject(parent)

@@ -48,6 +49,12 @@ m_activeBuffer = &m_buffers[0];

m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width()); + m_resetActions.append([this]() { + if (m_autoload) { + mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags); + } + }); + m_threadContext.startCallback = [](mCoreThread* context) { CoreController* controller = static_cast<CoreController*>(context->userData);

@@ -60,6 +67,8 @@ #endif

default: break; } + + controller->updateFastForward(); if (controller->m_multiplayer) { controller->m_multiplayer->attachGame(controller);

@@ -79,10 +88,6 @@ controller->m_override->identify(context->core);

controller->m_override->apply(context->core); } - if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { - mCoreDeleteState(context->core, 0); - } - controller->m_resetActions.clear(); QSize size = controller->screenDimensions();

@@ -99,6 +104,14 @@ };

m_threadContext.frameCallback = [](mCoreThread* context) { CoreController* controller = static_cast<CoreController*>(context->userData); + + if (controller->m_autosaveCounter == AUTOSAVE_GRANULARITY) { + if (controller->m_autosave) { + mCoreSaveState(context->core, 0, controller->m_saveStateFlags); + } + controller->m_autosaveCounter = 0; + } + ++controller->m_autosaveCounter; controller->finishFrame(); };

@@ -106,6 +119,10 @@

m_threadContext.cleanCallback = [](mCoreThread* context) { CoreController* controller = static_cast<CoreController*>(context->userData); + if (controller->m_autosave) { + mCoreSaveState(context->core, 0, controller->m_saveStateFlags); + } + controller->clearMultiplayerController(); QMetaObject::invokeMethod(controller, "stopping"); };

@@ -126,7 +143,8 @@ m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {

mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger); mCoreThread* context = logContext->p; - static const char* savestateMessage = "State %i loaded"; + static const char* savestateMessage = "State %i saved"; + static const char* loadstateMessage = "State %i loaded"; static const char* savestateFailedMessage = "State %i failed to load"; static int biosCat = -1; static int statusCat = -1;

@@ -152,7 +170,7 @@ } else

#endif if (category == statusCat) { // Slot 0 is reserved for suspend points - if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { + if (strncmp(loadstateMessage, format, strlen(loadstateMessage)) == 0) { va_list argc; va_copy(argc, args); int slot = va_arg(argc, int);

@@ -160,7 +178,7 @@ va_end(argc);

if (slot == 0) { format = "Loaded suspend state"; } - } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { + } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0 || strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { va_list argc; va_copy(argc, args); int slot = va_arg(argc, int);

@@ -209,6 +227,10 @@ bool CoreController::isPaused() {

return mCoreThreadIsPaused(&m_threadContext); } +bool CoreController::hasStarted() { + return mCoreThreadHasStarted(&m_threadContext); +} + mPlatform CoreController::platform() const { return m_threadContext.core->platform(m_threadContext.core); }

@@ -232,10 +254,14 @@ m_fastForwardRatio = config->getOption("fastForwardRatio", m_fastForwardRatio).toFloat();

m_videoSync = config->getOption("videoSync", m_videoSync).toInt(); m_audioSync = config->getOption("audioSync", m_audioSync).toInt(); m_fpsTarget = config->getOption("fpsTarget").toFloat(); + m_autosave = config->getOption("autosave", false).toInt(); + m_autoload = config->getOption("autoload", true).toInt(); m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt(); - updateFastForward(); mCoreLoadForeignConfig(m_threadContext.core, config->config()); - mCoreThreadRewindParamsChanged(&m_threadContext); + if (hasStarted()) { + updateFastForward(); + mCoreThreadRewindParamsChanged(&m_threadContext); + } } #ifdef USE_DEBUGGERS

@@ -628,6 +654,7 @@ }

} QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image)); }; + Interrupter interrupter(this); GBSIOSetDriver(&gb->sio, &m_printer.d.d); #endif }

@@ -637,6 +664,7 @@ #ifdef M_CORE_GB

if (platform() != PLATFORM_GB) { return; } + Interrupter interrupter(this); GB* gb = static_cast<GB*>(m_threadContext.core->board); GBPrinterDonePrinting(&m_printer.d); GBSIOSetDriver(&gb->sio, nullptr);

@@ -648,6 +676,7 @@ #ifdef M_CORE_GB

if (platform() != PLATFORM_GB) { return; } + Interrupter interrupter(this); GBPrinterDonePrinting(&m_printer.d); #endif }
M src/platform/qt/CoreController.hsrc/platform/qt/CoreController.h

@@ -61,6 +61,7 @@

color_t* drawContext(); bool isPaused(); + bool hasStarted(); mPlatform platform() const; QSize screenDimensions() const;

@@ -189,6 +190,10 @@ int m_saveStateFlags;

bool m_audioSync = AUDIO_SYNC; bool m_videoSync = VIDEO_SYNC; + + bool m_autosave; + bool m_autoload; + int m_autosaveCounter; int m_fastForward = false; int m_fastForwardForced = false;
M src/platform/qt/CoreManager.cppsrc/platform/qt/CoreManager.cpp

@@ -109,6 +109,7 @@ }

bytes = info.dir().canonicalPath().toUtf8(); mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); mCoreAutoloadSave(core); + mCoreAutoloadCheats(core); CoreController* cc = new CoreController(core); if (m_multiplayer) {
M src/platform/qt/GBAApp.cppsrc/platform/qt/GBAApp.cpp

@@ -67,10 +67,7 @@ }

} GBAApp::~GBAApp() { -#ifdef USE_SQLITE3 - m_parseThread.quit(); - m_parseThread.wait(); -#endif + m_workerThreads.waitForDone(); } bool GBAApp::event(QEvent* event) {

@@ -180,14 +177,8 @@ if (db && m_db) {

NoIntroDBDestroy(m_db); } if (db) { - if (m_parseThread.isRunning()) { - m_parseThread.quit(); - m_parseThread.wait(); - } GameDBParser* parser = new GameDBParser(db); - m_parseThread.start(); - parser->moveToThread(&m_parseThread); - QMetaObject::invokeMethod(parser, "parseNoIntroDB"); + submitWorkerJob(std::bind(&GameDBParser::parseNoIntroDB, parser)); m_db = db; return true; }

@@ -198,6 +189,77 @@ bool GBAApp::reloadGameDB() {

return false; } #endif + +qint64 GBAApp::submitWorkerJob(std::function<void ()> job, std::function<void ()> callback) { + return submitWorkerJob(job, nullptr, callback); +} + +qint64 GBAApp::submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback) { + qint64 jobId = m_nextJob; + ++m_nextJob; + WorkerJob* jobRunnable = new WorkerJob(jobId, job, this); + m_workerJobs.insert(jobId, jobRunnable); + if (callback) { + waitOnJob(jobId, context, callback); + } + m_workerThreads.start(jobRunnable); + return jobId; +} + +bool GBAApp::removeWorkerJob(qint64 jobId) { + for (auto& job : m_workerJobCallbacks.values(jobId)) { + disconnect(job); + } + m_workerJobCallbacks.remove(jobId); + if (!m_workerJobs.contains(jobId)) { + return true; + } + bool success = false; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + success = m_workerThreads.tryTake(m_workerJobs[jobId]); +#endif + if (success) { + m_workerJobs.remove(jobId); + } + return success; +} + + +bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback) { + if (!m_workerJobs.contains(jobId)) { + return false; + } + if (!context) { + context = this; + } + QMetaObject::Connection connection = connect(this, &GBAApp::jobFinished, context, [jobId, callback](qint64 testedJobId) { + if (jobId != testedJobId) { + return; + } + callback(); + }); + m_workerJobCallbacks.insert(m_nextJob, connection); + return true; +} + +void GBAApp::finishJob(qint64 jobId) { + m_workerJobs.remove(jobId); + emit jobFinished(jobId); + m_workerJobCallbacks.remove(jobId); +} + +GBAApp::WorkerJob::WorkerJob(qint64 id, std::function<void ()> job, GBAApp* owner) + : m_id(id) + , m_job(job) + , m_owner(owner) +{ + setAutoDelete(true); +} + +void GBAApp::WorkerJob::run() { + m_job(); + QMetaObject::invokeMethod(m_owner, "finishJob", Q_ARG(qint64, m_id)); +} #ifdef USE_SQLITE3 GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent)
M src/platform/qt/GBAApp.hsrc/platform/qt/GBAApp.h

@@ -8,9 +8,14 @@

#include <QApplication> #include <QFileDialog> #include <QList> +#include <QMap> +#include <QMultiMap> #include <QObject> +#include <QRunnable> #include <QString> -#include <QThread> +#include <QThreadPool> + +#include <functional> #include "CoreManager.h" #include "MultiplayerController.h"

@@ -61,10 +66,34 @@

const NoIntroDB* gameDB() const { return m_db; } bool reloadGameDB(); + qint64 submitWorkerJob(std::function<void ()> job, std::function<void ()> callback = {}); + qint64 submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback); + bool removeWorkerJob(qint64 jobId); + bool waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback); + +signals: + void jobFinished(qint64 jobId); + protected: bool event(QEvent*); +private slots: + void finishJob(qint64 jobId); + private: + class WorkerJob : public QRunnable { + public: + WorkerJob(qint64 id, std::function<void ()> job, GBAApp* owner); + + public: + void run() override; + + private: + qint64 m_id; + std::function<void ()> m_job; + GBAApp* m_owner; + }; + Window* newWindowInternal(); void pauseAll(QList<Window*>* paused);

@@ -75,10 +104,12 @@ QList<Window*> m_windows;

MultiplayerController m_multiplayer; CoreManager m_manager; + QMap<qint64, WorkerJob*> m_workerJobs; + QMultiMap<qint64, QMetaObject::Connection> m_workerJobCallbacks; + QThreadPool m_workerThreads; + qint64 m_nextJob = 1; + NoIntroDB* m_db = nullptr; -#ifdef USE_SQLITE3 - QThread m_parseThread; -#endif }; }
M src/platform/qt/LogController.cppsrc/platform/qt/LogController.cpp

@@ -13,7 +13,7 @@ LogController::LogController(int levels, QObject* parent)

: QObject(parent) { mLogFilterInit(&m_filter); - mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB); + mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB | mLOG_FATAL); mLogFilterSet(&m_filter, "core.status", mLOG_ALL & ~mLOG_DEBUG); m_filter.defaultLevels = levels;
M src/platform/qt/MemoryModel.cppsrc/platform/qt/MemoryModel.cpp

@@ -99,7 +99,7 @@ void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name, int segment) {

m_top = 0; m_base = base; m_size = size; - m_regionName = name; + m_regionName = QStaticText(name); m_regionName.prepare(QTransform(), m_font); m_currentBank = segment; verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
M src/platform/qt/MemorySearch.cppsrc/platform/qt/MemorySearch.cpp

@@ -41,49 +41,60 @@

QByteArray string; bool ok = false; if (m_ui.typeNum->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_INT; + if (m_ui.opDelta->isChecked()) { + params->op = mCORE_MEMORY_SEARCH_DELTA; + } else if (m_ui.opGreater->isChecked()) { + params->op = mCORE_MEMORY_SEARCH_GREATER; + } else if (m_ui.opLess->isChecked()) { + params->op = mCORE_MEMORY_SEARCH_LESS; + } else { + params->op = mCORE_MEMORY_SEARCH_EQUAL; + } + params->align = -1; if (m_ui.bits8->isChecked()) { - params->type = mCORE_MEMORY_SEARCH_8; + params->width = 1; } if (m_ui.bits16->isChecked()) { - params->type = mCORE_MEMORY_SEARCH_16; + params->width = 2; } if (m_ui.bits32->isChecked()) { - params->type = mCORE_MEMORY_SEARCH_32; + params->width = 4; + } + if (m_ui.bitsGuess->isChecked()) { + params->width = -1; } if (m_ui.numHex->isChecked()) { uint32_t v = m_ui.value->text().toUInt(&ok, 16); if (ok) { - switch (params->type) { - case mCORE_MEMORY_SEARCH_8: + params->valueInt = v; + switch (params->width) { + case 1: ok = v < 0x100; - params->value8 = v; break; - case mCORE_MEMORY_SEARCH_16: + case 2: ok = v < 0x10000; - params->value16 = v; break; - case mCORE_MEMORY_SEARCH_32: - params->value32 = v; + case 4: break; default: ok = false; + break; } } } if (m_ui.numDec->isChecked()) { uint32_t v = m_ui.value->text().toUInt(&ok, 10); if (ok) { - switch (params->type) { - case mCORE_MEMORY_SEARCH_8: + params->valueInt = v; + switch (params->width) { + case 1: ok = v < 0x100; - params->value8 = v; break; - case mCORE_MEMORY_SEARCH_16: + case 2: ok = v < 0x10000; - params->value16 = v; break; - case mCORE_MEMORY_SEARCH_32: - params->value32 = v; + case 4: break; default: ok = false;

@@ -101,6 +112,7 @@ if (m_ui.typeStr->isChecked()) {

params->type = mCORE_MEMORY_SEARCH_STRING; m_string = m_ui.value->text().toLocal8Bit(); params->valueStr = m_string.constData(); + params->width = m_ui.value->text().size(); ok = true; } return ok;

@@ -140,66 +152,73 @@ mCore* core = m_controller->thread()->core;

m_ui.results->clearContents(); m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results)); + m_ui.opDelta->setEnabled(false); for (size_t i = 0; i < mCoreMemorySearchResultsSize(&m_results); ++i) { mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i); QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); m_ui.results->setItem(i, 0, item); QTableWidgetItem* type; - if (m_ui.numHex->isChecked()) { - switch (result->type) { - case mCORE_MEMORY_SEARCH_8: + QByteArray string; + if (result->type == mCORE_MEMORY_SEARCH_INT && m_ui.numHex->isChecked()) { + switch (result->width) { + case 1: item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, result->segment), 2, 16, QChar('0'))); break; - case mCORE_MEMORY_SEARCH_16: + case 2: item = new QTableWidgetItem(QString("%1").arg(core->rawRead16(core, result->address, result->segment), 4, 16, QChar('0'))); break; - case mCORE_MEMORY_SEARCH_GUESS: - case mCORE_MEMORY_SEARCH_32: + case 4: item = new QTableWidgetItem(QString("%1").arg(core->rawRead32(core, result->address, result->segment), 8, 16, QChar('0'))); break; - case mCORE_MEMORY_SEARCH_STRING: - item = new QTableWidgetItem("?"); // TODO } } else { switch (result->type) { - case mCORE_MEMORY_SEARCH_8: - item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment))); - break; - case mCORE_MEMORY_SEARCH_16: - item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment))); - break; - case mCORE_MEMORY_SEARCH_GUESS: - case mCORE_MEMORY_SEARCH_32: - item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment))); + case mCORE_MEMORY_SEARCH_INT: + switch (result->width) { + case 1: + item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment))); + break; + case 2: + item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment))); + break; + case 4: + item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment))); + break; + } break; case mCORE_MEMORY_SEARCH_STRING: - item = new QTableWidgetItem("?"); // TODO + string.reserve(result->width); + for (int i = 0; i < result->width; ++i) { + string.append(core->rawRead8(core, result->address + i, result->segment)); + } + item = new QTableWidgetItem(QLatin1String(string)); // TODO } } QString divisor; if (result->guessDivisor > 1) { - divisor = tr(" (⅟%0×)").arg(result->guessDivisor); + if (result->guessMultiplier > 1) { + divisor = tr(" (%0/%1×)").arg(result->guessMultiplier).arg(result->guessMultiplier); + } else { + divisor = tr(" (⅟%0×)").arg(result->guessDivisor); + } + } else if (result->guessMultiplier > 1) { + divisor = tr(" (%0×)").arg(result->guessMultiplier); } switch (result->type) { - case mCORE_MEMORY_SEARCH_8: - type = new QTableWidgetItem(tr("1 byte%0").arg(divisor)); - break; - case mCORE_MEMORY_SEARCH_16: - type = new QTableWidgetItem(tr("2 bytes%0").arg(divisor)); - break; - case mCORE_MEMORY_SEARCH_GUESS: - case mCORE_MEMORY_SEARCH_32: - type = new QTableWidgetItem(tr("4 bytes%0").arg(divisor)); + case mCORE_MEMORY_SEARCH_INT: + type = new QTableWidgetItem(tr("%1 byte%2").arg(result->width).arg(divisor)); break; case mCORE_MEMORY_SEARCH_STRING: - item = new QTableWidgetItem("?"); // TODO + type = new QTableWidgetItem("string"); } m_ui.results->setItem(i, 1, item); m_ui.results->setItem(i, 2, type); + m_ui.opDelta->setEnabled(true); + } + if (m_ui.opDelta->isChecked() && !m_ui.opDelta->isEnabled()) { + m_ui.opEqual->setChecked(true); } m_ui.results->sortItems(0); - m_ui.results->resizeColumnsToContents(); - m_ui.results->resizeRowsToContents(); } void MemorySearch::openMemory() {
M src/platform/qt/MemorySearch.uisrc/platform/qt/MemorySearch.ui

@@ -6,8 +6,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>631</width> - <height>378</height> + <width>540</width> + <height>491</height> </rect> </property> <property name="minimumSize">

@@ -99,14 +99,34 @@ <string notr="true">type</string>

</attribute> </widget> </item> - <item row="3" column="0"> + <item row="3" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="4" column="0"> <widget class="QLabel" name="label_3"> <property name="text"> <string>Width</string> </property> </widget> </item> - <item row="3" column="1"> + <item row="4" column="1"> + <widget class="QRadioButton" name="bitsGuess"> + <property name="text"> + <string>Guess</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">width</string> + </attribute> + </widget> + </item> + <item row="5" column="1"> <widget class="QRadioButton" name="bits8"> <property name="text"> <string>1 Byte (8-bit)</string>

@@ -116,7 +136,7 @@ <string notr="true">width</string>

</attribute> </widget> </item> - <item row="4" column="1"> + <item row="6" column="1"> <widget class="QRadioButton" name="bits16"> <property name="text"> <string>2 Bytes (16-bit)</string>

@@ -126,50 +146,117 @@ <string notr="true">width</string>

</attribute> </widget> </item> - <item row="5" column="1"> + <item row="7" column="1"> <widget class="QRadioButton" name="bits32"> <property name="text"> <string>4 Bytes (32-bit)</string> </property> <property name="checked"> - <bool>true</bool> + <bool>false</bool> </property> <attribute name="buttonGroup"> <string notr="true">width</string> </attribute> </widget> </item> - <item row="6" column="0"> + <item row="8" column="0" colspan="2"> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="9" column="0"> <widget class="QLabel" name="label_4"> <property name="text"> <string>Number type</string> </property> </widget> </item> - <item row="6" column="1"> - <widget class="QRadioButton" name="numHex"> + <item row="9" column="1"> + <widget class="QRadioButton" name="numGuess"> <property name="text"> - <string>Hexadecimal</string> + <string>Guess</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="7" column="1"> + <item row="10" column="1"> <widget class="QRadioButton" name="numDec"> <property name="text"> <string>Decimal</string> </property> </widget> </item> - <item row="8" column="1"> - <widget class="QRadioButton" name="numGuess"> + <item row="11" column="1"> + <widget class="QRadioButton" name="numHex"> <property name="text"> - <string>Guess</string> + <string>Hexadecimal</string> </property> </widget> </item> + <item row="12" column="0" colspan="2"> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Compare</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QRadioButton" name="opEqual"> + <property name="text"> + <string>Equal</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">op</string> + </attribute> + </widget> + </item> + <item row="14" column="1"> + <widget class="QRadioButton" name="opGreater"> + <property name="text"> + <string>Greater</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">op</string> + </attribute> + </widget> + </item> + <item row="15" column="1"> + <widget class="QRadioButton" name="opLess"> + <property name="text"> + <string>Less</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">op</string> + </attribute> + </widget> + </item> + <item row="16" column="1"> + <widget class="QRadioButton" name="opDelta"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Delta</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">op</string> + </attribute> + </widget> + </item> </layout> </item> <item row="2" column="0" colspan="2">

@@ -235,5 +322,6 @@ </connections>

<buttongroups> <buttongroup name="width"/> <buttongroup name="type"/> + <buttongroup name="op"/> </buttongroups> </ui>
M src/platform/qt/ObjView.uisrc/platform/qt/ObjView.ui

@@ -561,6 +561,9 @@ </property>

<property name="alignment"> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> + </property> </widget> </item> </layout>
M src/platform/qt/OverrideView.cppsrc/platform/qt/OverrideView.cpp

@@ -93,13 +93,16 @@ #endif

connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &OverrideView::saveOverride); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); + + m_recheck.setInterval(200); + connect(&m_recheck, &QTimer::timeout, this, &OverrideView::recheck); } void OverrideView::setController(std::shared_ptr<CoreController> controller) { m_controller = controller; connect(controller.get(), &CoreController::started, this, &OverrideView::gameStarted); connect(controller.get(), &CoreController::stopping, this, &OverrideView::gameStopped); - updateOverrides(); + recheck(); } void OverrideView::saveOverride() {

@@ -115,6 +118,17 @@ }

m_config->saveOverride(*override); } +void OverrideView::recheck() { + if (!m_controller) { + return; + } + if (m_controller->hasStarted()) { + gameStarted(); + } else { + updateOverrides(); + } +} + void OverrideView::updateOverrides() { if (!m_controller) { return;

@@ -190,6 +204,7 @@ CoreController::Interrupter interrupter(m_controller);

mCoreThread* thread = m_controller->thread(); m_ui.tabWidget->setEnabled(false); + m_recheck.start(); switch (thread->core->platform(thread->core)) { #ifdef M_CORE_GBA

@@ -242,6 +257,7 @@ }

} void OverrideView::gameStopped() { + m_recheck.stop(); m_controller.reset(); m_ui.tabWidget->setEnabled(true); m_ui.savetype->setCurrentIndex(0);
M src/platform/qt/OverrideView.hsrc/platform/qt/OverrideView.h

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

#pragma once #include <QDialog> +#include <QTimer> #include <memory>

@@ -36,6 +37,7 @@ void setController(std::shared_ptr<CoreController> controller);

public slots: void saveOverride(); + void recheck(); private slots: void updateOverrides();

@@ -48,6 +50,7 @@

std::shared_ptr<CoreController> m_controller; ConfigController* m_config; bool m_savePending = false; + QTimer m_recheck; #ifdef M_CORE_GB uint32_t m_gbColors[12]{};
A src/platform/qt/RegisterView.cpp

@@ -0,0 +1,144 @@

+/* Copyright (c) 2013-2017 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 "RegisterView.h" + +#include "CoreController.h" + +#ifdef M_CORE_GBA +#include <mgba/internal/arm/arm.h> +#endif +#ifdef M_CORE_GB +#include <mgba/internal/lr35902/lr35902.h> +#endif + +#include <QFontDatabase> +#include <QFormLayout> +#include <QLabel> + +using namespace QGBA; + +RegisterView::RegisterView(std::shared_ptr<CoreController> controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + QFormLayout* layout = new QFormLayout; + setLayout(layout); + + switch (controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + addRegisters({ + "r0", + "r1", + "r2", + "r3", + "r4", + "r5", + "r6", + "r7", + "r8", + "r9", + "r10", + "r11", + "r12", + "sp", + "lr", + "pc", + "cpsr", + }); + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + addRegisters({ + "a", + "f", + "b", + "c", + "d", + "e", + "h", + "l", + "sp", + "pc" + }); + break; +#endif + default: + break; + } +} + +void RegisterView::addRegisters(const QStringList& names) { + QFormLayout* form = static_cast<QFormLayout*>(layout()); + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + for (const auto& reg : names) { + QLabel* value = new QLabel; + value->setTextInteractionFlags(Qt::TextSelectableByMouse); + value->setFont(font); + form->addWidget(value); + m_registers[reg] = value; + form->addRow(reg, value); + } +} + +void RegisterView::updateRegisters() { + switch (m_controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + updateRegistersARM(); + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + updateRegistersLR35902(); + break; +#endif + default: + break; + } +} + +#ifdef M_CORE_GBA +void RegisterView::updateRegistersARM() { + CoreController::Interrupter interrupter(m_controller); + struct ARMCore* core = static_cast<ARMCore*>(m_controller->thread()->core->cpu); + m_registers["r0"]->setText(QString("%1").arg((uint32_t) core->gprs[0], 8, 16, QChar('0')).toUpper()); + m_registers["r1"]->setText(QString("%1").arg((uint32_t) core->gprs[1], 8, 16, QChar('0')).toUpper()); + m_registers["r2"]->setText(QString("%1").arg((uint32_t) core->gprs[2], 8, 16, QChar('0')).toUpper()); + m_registers["r3"]->setText(QString("%1").arg((uint32_t) core->gprs[3], 8, 16, QChar('0')).toUpper()); + m_registers["r4"]->setText(QString("%1").arg((uint32_t) core->gprs[4], 8, 16, QChar('0')).toUpper()); + m_registers["r5"]->setText(QString("%1").arg((uint32_t) core->gprs[5], 8, 16, QChar('0')).toUpper()); + m_registers["r6"]->setText(QString("%1").arg((uint32_t) core->gprs[6], 8, 16, QChar('0')).toUpper()); + m_registers["r7"]->setText(QString("%1").arg((uint32_t) core->gprs[7], 8, 16, QChar('0')).toUpper()); + m_registers["r8"]->setText(QString("%1").arg((uint32_t) core->gprs[8], 8, 16, QChar('0')).toUpper()); + m_registers["r9"]->setText(QString("%1").arg((uint32_t) core->gprs[9], 8, 16, QChar('0')).toUpper()); + m_registers["r10"]->setText(QString("%1").arg((uint32_t) core->gprs[10], 8, 16, QChar('0')).toUpper()); + m_registers["r11"]->setText(QString("%1").arg((uint32_t) core->gprs[11], 8, 16, QChar('0')).toUpper()); + m_registers["r12"]->setText(QString("%1").arg((uint32_t) core->gprs[12], 8, 16, QChar('0')).toUpper()); + m_registers["sp"]->setText(QString("%1").arg((uint32_t) core->gprs[ARM_SP], 8, 16, QChar('0')).toUpper()); + m_registers["lr"]->setText(QString("%1").arg((uint32_t) core->gprs[ARM_LR], 8, 16, QChar('0')).toUpper()); + m_registers["pc"]->setText(QString("%1").arg((uint32_t) core->gprs[ARM_PC], 8, 16, QChar('0')).toUpper()); + m_registers["cpsr"]->setText(QString("%1").arg((uint32_t) core->cpsr.packed, 8, 16, QChar('0')).toUpper()); +} +#endif + +#ifdef M_CORE_GB +void RegisterView::updateRegistersLR35902() { + CoreController::Interrupter interrupter(m_controller); + struct LR35902Core* core = static_cast<LR35902Core*>(m_controller->thread()->core->cpu); + m_registers["a"]->setText(QString("%1").arg((uint8_t) core->a, 2, 16, QChar('0')).toUpper()); + m_registers["f"]->setText(QString("%1").arg((uint8_t) core->f.packed, 2, 16, QChar('0')).toUpper()); + m_registers["b"]->setText(QString("%1").arg((uint8_t) core->b, 2, 16, QChar('0')).toUpper()); + m_registers["c"]->setText(QString("%1").arg((uint8_t) core->c, 2, 16, QChar('0')).toUpper()); + m_registers["d"]->setText(QString("%1").arg((uint8_t) core->d, 2, 16, QChar('0')).toUpper()); + m_registers["e"]->setText(QString("%1").arg((uint8_t) core->e, 2, 16, QChar('0')).toUpper()); + m_registers["h"]->setText(QString("%1").arg((uint8_t) core->h, 2, 16, QChar('0')).toUpper()); + m_registers["l"]->setText(QString("%1").arg((uint8_t) core->l, 2, 16, QChar('0')).toUpper()); + m_registers["sp"]->setText(QString("%1").arg((uint8_t) core->sp, 4, 16, QChar('0')).toUpper()); + m_registers["pc"]->setText(QString("%1").arg((uint8_t) core->pc, 4, 16, QChar('0')).toUpper()); +} +#endif
A src/platform/qt/RegisterView.h

@@ -0,0 +1,42 @@

+/* Copyright (c) 2013-2017 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/. */ +#pragma once + +#include <QMap> +#include <QWidget> + +#include <memory> + +class QLabel; + +namespace QGBA { + +class CoreController; + +class RegisterView : public QWidget { +Q_OBJECT + +public: + RegisterView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); + +public slots: + void updateRegisters(); + +private: + void addRegisters(const QStringList& names); +#ifdef M_CORE_GBA + void updateRegistersARM(); +#endif +#ifdef M_CORE_GB + void updateRegistersLR35902(); +#endif + + QMap<QString, QLabel*> m_registers; + + std::shared_ptr<CoreController> m_controller; +}; + +}
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -106,6 +106,22 @@ m_ui.patchSameDir->setChecked(false);

m_ui.patchPath->setText(path); } }); + + if (m_ui.cheatsPath->text().isEmpty()) { + m_ui.cheatsSameDir->setChecked(true); + } + connect(m_ui.cheatsSameDir, &QAbstractButton::toggled, [this] (bool e) { + if (e) { + m_ui.cheatsPath->clear(); + } + }); + connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () { + QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory"); + if (!path.isNull()) { + m_ui.cheatsSameDir->setChecked(false); + m_ui.cheatsPath->setText(path); + } + }); connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig()

@@ -257,7 +273,7 @@ continue;

} QLocale locale(name.remove(QString("%0-").arg(binaryName)).remove(".qm")); m_ui.languages->addItem(locale.nativeLanguageName(), locale); - if (locale == QLocale()) { + if (locale.bcp47Name() == QLocale().bcp47Name()) { m_ui.languages->setCurrentIndex(m_ui.languages->count() - 1); } }

@@ -339,9 +355,15 @@ saveSetting("savegamePath", m_ui.savegamePath);

saveSetting("savestatePath", m_ui.savestatePath); saveSetting("screenshotPath", m_ui.screenshotPath); saveSetting("patchPath", m_ui.patchPath); + saveSetting("cheatsPath", m_ui.cheatsPath); saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex()); saveSetting("showLibrary", m_ui.showLibrary); saveSetting("preload", m_ui.preload); + saveSetting("showFps", m_ui.showFps); + saveSetting("cheatAutoload", m_ui.cheatAutoload); + saveSetting("cheatAutosave", m_ui.cheatAutosave); + saveSetting("autoload", m_ui.autoload); + saveSetting("autosave", m_ui.autosave); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1");

@@ -464,8 +486,14 @@ loadSetting("savegamePath", m_ui.savegamePath);

loadSetting("savestatePath", m_ui.savestatePath); loadSetting("screenshotPath", m_ui.screenshotPath); loadSetting("patchPath", m_ui.patchPath); + loadSetting("cheatsPath", m_ui.cheatsPath); loadSetting("showLibrary", m_ui.showLibrary); loadSetting("preload", m_ui.preload); + loadSetting("showFps", m_ui.showFps, true); + loadSetting("cheatAutoload", m_ui.cheatAutoload, true); + loadSetting("cheatAutosave", m_ui.cheatAutosave, true); + loadSetting("autoload", m_ui.autoload, true); + loadSetting("autosave", m_ui.autosave, false); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -419,6 +419,13 @@ </property>

</item> </widget> </item> + <item row="1" column="0" colspan="2"> + <widget class="Line" name="line_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> <item row="2" column="0"> <widget class="QLabel" name="label_6"> <property name="text">

@@ -488,10 +495,67 @@ <string>Pause when inactive</string>

</property> </widget> </item> - <item row="1" column="0" colspan="2"> - <widget class="Line" name="line_10"> + <item row="9" column="1"> + <widget class="QCheckBox" name="showFps"> + <property name="text"> + <string>Show FPS in title bar</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="10" column="0" colspan="2"> + <widget class="Line" name="line_13"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="13" column="0" colspan="2"> + <widget class="Line" name="line_16"> <property name="orientation"> <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="14" column="1"> + <widget class="QCheckBox" name="cheatAutosave"> + <property name="text"> + <string>Automatically save cheats</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="15" column="1"> + <widget class="QCheckBox" name="cheatAutoload"> + <property name="text"> + <string>Automatically load cheats</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QCheckBox" name="autosave"> + <property name="text"> + <string>Automatically save state</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QCheckBox" name="autoload"> + <property name="text"> + <string>Automatically load state</string> + </property> + <property name="checked"> + <bool>true</bool> </property> </widget> </item>

@@ -1138,6 +1202,54 @@ <string>Same directory as the ROM</string>

</property> </widget> </item> + <item row="12" column="0" colspan="2"> + <widget class="Line" name="line_14"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="label_48"> + <property name="text"> + <string>Cheats</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_27"> + <item> + <widget class="QLineEdit" name="cheatsPath"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>170</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cheatsBrowse"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="14" column="1"> + <widget class="QCheckBox" name="cheatsSameDir"> + <property name="text"> + <string>Same directory as the ROM</string> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="gb">

@@ -1666,6 +1778,38 @@ </hint>

<hint type="destinationlabel"> <x>340</x> <y>285</y> + </hint> + </hints> + </connection> + <connection> + <sender>cheatsSameDir</sender> + <signal>toggled(bool)</signal> + <receiver>cheatsPath</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>351</x> + <y>407</y> + </hint> + <hint type="destinationlabel"> + <x>343</x> + <y>372</y> + </hint> + </hints> + </connection> + <connection> + <sender>fastForwardUnbounded</sender> + <signal>toggled(bool)</signal> + <receiver>fastForwardRatio</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>445</x> + <y>38</y> + </hint> + <hint type="destinationlabel"> + <x>349</x> + <y>38</y> </hint> </hints> </connection>
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -52,6 +52,7 @@ #include "TileView.h"

#include "VideoView.h" #include <mgba/core/version.h> +#include <mgba/core/cheats.h> #ifdef M_CORE_GB #include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/input.h>

@@ -275,6 +276,9 @@ "*.zip",

#endif #ifdef USE_LZMA "*.7z", +#endif +#ifdef USE_ELF + "*.elf", #endif "*.agb", "*.mb",

@@ -564,11 +568,12 @@ }

m_wasOpened = true; resizeFrame(m_screenWidget->sizeHint()); QVariant windowPos = m_config->getQtOption("windowPos"); - if (!windowPos.isNull()) { + QRect geom = QApplication::desktop()->availableGeometry(this); + if (!windowPos.isNull() && geom.contains(windowPos.toPoint())) { move(windowPos.toPoint()); } else { QRect rect = frameGeometry(); - rect.moveCenter(QApplication::desktop()->availableGeometry().center()); + rect.moveCenter(geom.center()); move(rect.topLeft()); } if (m_fullscreenOnStart) {

@@ -734,7 +739,9 @@ }

#endif m_hitUnimplementedBiosCall = false; - m_fpsTimer.start(); + if (m_config->getOption("showFps", "1").toInt()) { + m_fpsTimer.start(); + } m_focusCheck.start(); if (m_display->underMouse()) { m_screenWidget->setCursor(Qt::BlankCursor);

@@ -1499,6 +1506,7 @@ }

connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close); } m_overrideView->show(); + m_overrideView->recheck(); }); addControlledAction(toolsMenu, overrides, "overrideWindow");

@@ -1644,10 +1652,30 @@ m_manager->setPreload(value.toBool());

}, this); m_config->updateOption("preload"); + ConfigOption* showFps = m_config->addOption("showFps"); + showFps->connect([this](const QVariant& value) { + if (!value.toInt()) { + m_fpsTimer.stop(); + updateTitle(); + } else if (m_controller) { + m_fpsTimer.start(); + } + }, this); + QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu); connect(exitFullScreen, &QAction::triggered, this, &Window::exitFullScreen); exitFullScreen->setShortcut(QKeySequence("Esc")); addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); + + m_inputController.inputIndex()->addItem(qMakePair([this]() { + if (m_controller) { + mCheatPressButton(m_controller->cheatDevice(), true); + } + }, [this]() { + if (m_controller) { + mCheatPressButton(m_controller->cheatDevice(), false); + } + }), tr("GameShark Button (held)"), "holdGSButton", toolsMenu)->setShortcut(QKeySequence(Qt::Key_Apostrophe)[0]); for (QAction* action : m_gameActions) { action->setDisabled(true);

@@ -1834,8 +1862,8 @@ m_controller->loadPatch(m_pendingPatch);

m_pendingPatch = QString(); } - m_controller->start(); m_controller->loadConfig(m_config); + m_controller->start(); } WindowBackground::WindowBackground(QWidget* parent)
M src/platform/qt/library/LibraryController.cppsrc/platform/qt/library/LibraryController.cpp

@@ -29,16 +29,6 @@ removeEntry(o);

} } -LibraryLoaderThread::LibraryLoaderThread(QObject* parent) - : QThread(parent) -{ -} - -void LibraryLoaderThread::run() { - mLibraryLoadDirectory(m_library, m_directory.toUtf8().constData()); - m_directory = QString(); -} - LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config) : QStackedWidget(parent) , m_config(config)

@@ -47,21 +37,19 @@ mLibraryListingInit(&m_listing, 0);

if (!path.isNull()) { // This can return NULL if the library is already open - m_library = mLibraryLoad(path.toUtf8().constData()); + m_library = std::shared_ptr<mLibrary>(mLibraryLoad(path.toUtf8().constData()), mLibraryDestroy); } if (!m_library) { - m_library = mLibraryCreateEmpty(); + m_library = std::shared_ptr<mLibrary>(mLibraryCreateEmpty(), mLibraryDestroy); } - mLibraryAttachGameDB(m_library, GBAApp::app()->gameDB()); + mLibraryAttachGameDB(m_library.get(), GBAApp::app()->gameDB()); m_libraryTree = new LibraryTree(this); addWidget(m_libraryTree->widget()); m_libraryGrid = new LibraryGrid(this); addWidget(m_libraryGrid->widget()); - - connect(&m_loaderThread, &QThread::finished, this, &LibraryController::refresh, Qt::QueuedConnection); setViewStyle(LibraryStyle::STYLE_LIST); refresh();

@@ -69,17 +57,6 @@ }

LibraryController::~LibraryController() { mLibraryListingDeinit(&m_listing); - - if (m_loaderThread.isRunning()) { - m_loaderThread.wait(); - } - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } - if (m_library) { - mLibraryDestroy(m_library); - } } void LibraryController::setViewStyle(LibraryStyle newStyle) {

@@ -117,7 +94,7 @@

VFile* LibraryController::selectedVFile() { LibraryEntryRef entry = selectedEntry(); if (entry) { - return mLibraryOpenVFile(m_library, entry->entry); + return mLibraryOpenVFile(m_library.get(), entry->entry); } else { return nullptr; }

@@ -129,35 +106,26 @@ return e ? qMakePair(e->base(), e->filename()) : qMakePair<QString, QString>("", "");

} void LibraryController::addDirectory(const QString& dir) { - m_loaderThread.m_directory = dir; - m_loaderThread.m_library = m_library; - // The m_loaderThread temporarily owns the library - m_library = nullptr; - m_loaderThread.start(); + // The worker thread temporarily owns the library + std::shared_ptr<mLibrary> library = m_library; + m_libraryJob = GBAApp::app()->submitWorkerJob(std::bind(&LibraryController::loadDirectory, this, dir), this, [this, library]() { + m_libraryJob = -1; + refresh(); + }); } void LibraryController::clear() { - if (!m_library) { - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } else { - return; - } + if (m_libraryJob > 0) { + return; } - mLibraryClear(m_library); + mLibraryClear(m_library.get()); refresh(); } void LibraryController::refresh() { - if (!m_library) { - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } else { - return; - } + if (m_libraryJob > 0) { + return; } setDisabled(true);

@@ -166,7 +134,7 @@ QStringList allEntries;

QList<LibraryEntryRef> newEntries; mLibraryListingClear(&m_listing); - mLibraryGetEntries(m_library, &m_listing, 0, 0, nullptr); + mLibraryGetEntries(m_library.get(), &m_listing, 0, 0, nullptr); for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) { mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i); QString fullpath = QString("%1/%2").arg(entry->base, entry->filename);

@@ -208,6 +176,12 @@ const QString lastfile = m_config->getMRU().first();

if (m_entries.contains(lastfile)) { selectEntry(m_entries.value(lastfile)); } +} + +void LibraryController::loadDirectory(const QString& dir) { + // This class can get delted during this function (sigh) so we need to hold onto this + std::shared_ptr<mLibrary> library = m_library; + mLibraryLoadDirectory(library.get(), dir.toUtf8().constData()); } }
M src/platform/qt/library/LibraryController.hsrc/platform/qt/library/LibraryController.h

@@ -9,7 +9,6 @@ #include <memory>

#include <QList> #include <QMap> -#include <QThread> #include <QStackedWidget> #include <mgba/core/library.h>

@@ -66,19 +65,6 @@

virtual QWidget* widget() = 0; }; -class LibraryLoaderThread final : public QThread { -Q_OBJECT - -public: - LibraryLoaderThread(QObject* parent = nullptr); - - mLibrary* m_library = nullptr; - QString m_directory; - -protected: - virtual void run() override; -}; - class LibraryController final : public QStackedWidget { Q_OBJECT

@@ -110,9 +96,11 @@ private slots:

void refresh(); private: + void loadDirectory(const QString&); // Called on separate thread + ConfigController* m_config = nullptr; - LibraryLoaderThread m_loaderThread; - mLibrary* m_library = nullptr; + std::shared_ptr<mLibrary> m_library; + qint64 m_libraryJob = -1; mLibraryListing m_listing; QMap<QString, LibraryEntryRef> m_entries;
M src/platform/qt/ts/medusa-emu-de.tssrc/platform/qt/ts/medusa-emu-de.ts

@@ -83,44 +83,44 @@ <translation>Tile #</translation>

</message> <message> <location filename="../AssetTile.ui" line="48"/> - <location filename="../AssetTile.ui" line="69"/> + <location filename="../AssetTile.ui" line="72"/> <source>0</source> <translation>0</translation> </message> <message> - <location filename="../AssetTile.ui" line="62"/> + <location filename="../AssetTile.ui" line="65"/> <source>Palette #</source> <translation>Palette #</translation> </message> <message> - <location filename="../AssetTile.ui" line="83"/> + <location filename="../AssetTile.ui" line="86"/> <source>Address</source> <translation>Adresse</translation> </message> <message> - <location filename="../AssetTile.ui" line="90"/> + <location filename="../AssetTile.ui" line="93"/> <source>0x06000000</source> <translation>0x06000000</translation> </message> <message> - <location filename="../AssetTile.ui" line="129"/> + <location filename="../AssetTile.ui" line="135"/> <source>Red</source> <translation>Rot</translation> </message> <message> - <location filename="../AssetTile.ui" line="136"/> + <location filename="../AssetTile.ui" line="142"/> <source>Green</source> <translation>Grün</translation> </message> <message> - <location filename="../AssetTile.ui" line="143"/> + <location filename="../AssetTile.ui" line="149"/> <source>Blue</source> <translation>Blau</translation> </message> <message> - <location filename="../AssetTile.ui" line="157"/> - <location filename="../AssetTile.ui" line="164"/> - <location filename="../AssetTile.ui" line="171"/> + <location filename="../AssetTile.ui" line="163"/> + <location filename="../AssetTile.ui" line="170"/> + <location filename="../AssetTile.ui" line="177"/> <source>0x00 (00)</source> <translation>0x00 (00)</translation> </message>

@@ -522,62 +522,88 @@ <source>Text</source>

<translation>Text</translation> </message> <message> - <location filename="../MemorySearch.ui" line="105"/> + <location filename="../MemorySearch.ui" line="112"/> <source>Width</source> <translation>Breite</translation> </message> <message> - <location filename="../MemorySearch.ui" line="112"/> + <location filename="../MemorySearch.ui" line="132"/> <source>1 Byte (8-bit)</source> <translation>1 Byte (8-bit)</translation> </message> <message> - <location filename="../MemorySearch.ui" line="122"/> + <location filename="../MemorySearch.ui" line="142"/> <source>2 Bytes (16-bit)</source> <translation>2 Bytes (16-bit)</translation> </message> <message> - <location filename="../MemorySearch.ui" line="132"/> + <location filename="../MemorySearch.ui" line="152"/> <source>4 Bytes (32-bit)</source> <translation>4 Bytes (32-bit)</translation> </message> <message> - <location filename="../MemorySearch.ui" line="145"/> + <location filename="../MemorySearch.ui" line="172"/> <source>Number type</source> <translation>Zahlensystem</translation> </message> <message> - <location filename="../MemorySearch.ui" line="152"/> + <location filename="../MemorySearch.ui" line="196"/> <source>Hexadecimal</source> <translation>Hexadezimal</translation> </message> <message> - <location filename="../MemorySearch.ui" line="162"/> + <location filename="../MemorySearch.ui" line="189"/> <source>Decimal</source> <translation>Dezimal</translation> </message> <message> - <location filename="../MemorySearch.ui" line="169"/> + <location filename="../MemorySearch.ui" line="119"/> + <location filename="../MemorySearch.ui" line="179"/> <source>Guess</source> <translation>automatisch</translation> </message> <message> - <location filename="../MemorySearch.ui" line="187"/> + <location filename="../MemorySearch.ui" line="210"/> + <source>Compare</source> + <translation>Vergleichen</translation> + </message> + <message> + <location filename="../MemorySearch.ui" line="217"/> + <source>Equal</source> + <translation>Gleichwertig</translation> + </message> + <message> + <location filename="../MemorySearch.ui" line="230"/> + <source>Greater</source> + <translation>Größer</translation> + </message> + <message> + <location filename="../MemorySearch.ui" line="240"/> + <source>Less</source> + <translation>Kleiner</translation> + </message> + <message> + <location filename="../MemorySearch.ui" line="253"/> + <source>Delta</source> + <translation>Differenz</translation> + </message> + <message> + <location filename="../MemorySearch.ui" line="274"/> <source>Search</source> <translation>Suchen</translation> </message> <message> - <location filename="../MemorySearch.ui" line="194"/> + <location filename="../MemorySearch.ui" line="281"/> <source>Search Within</source> <translation>Suchen innerhalb</translation> </message> <message> - <location filename="../MemorySearch.ui" line="201"/> + <location filename="../MemorySearch.ui" line="288"/> <source>Open in Memory Viewer</source> <translation>Im Speicher-Monitor öffnen</translation> </message> <message> - <location filename="../MemorySearch.ui" line="208"/> + <location filename="../MemorySearch.ui" line="295"/> <source>Refresh</source> <translation>Aktualisieren</translation> </message>

@@ -1119,7 +1145,7 @@ <source>(untitled)</source>

<translation>(unbenannt)</translation> </message> <message> - <location filename="../CheatsModel.cpp" line="209"/> + <location filename="../CheatsModel.cpp" line="213"/> <source>Failed to open cheats file: %1</source> <translation>Fehler beim Öffnen der Cheat-Datei: %1</translation> </message>

@@ -1157,22 +1183,22 @@ </context>

<context> <name>QGBA::CoreController</name> <message> - <location filename="../CoreController.cpp" line="495"/> + <location filename="../CoreController.cpp" line="521"/> <source>Failed to open save file: %1</source> <translation>Fehler beim Öffnen der Speicherdatei: %1</translation> </message> <message> - <location filename="../CoreController.cpp" line="524"/> + <location filename="../CoreController.cpp" line="550"/> <source>Failed to open game file: %1</source> <translation>Fehler beim Öffnen der Spieldatei: %1</translation> </message> <message> - <location filename="../CoreController.cpp" line="589"/> + <location filename="../CoreController.cpp" line="615"/> <source>Failed to open snapshot file for reading: %1</source> <translation>Konnte Snapshot-Datei %1 nicht zum Lesen öffnen</translation> </message> <message> - <location filename="../CoreController.cpp" line="605"/> + <location filename="../CoreController.cpp" line="631"/> <source>Failed to open snapshot file for writing: %1</source> <translation>Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen</translation> </message>

@@ -2858,24 +2884,24 @@ </context>

<context> <name>QGBA::MemorySearch</name> <message> - <location filename="../MemorySearch.cpp" line="181"/> - <source> (⅟%0×)</source> - <translation> (⅟%0×)</translation> + <location filename="../MemorySearch.cpp" line="200"/> + <source> (%0/%1×)</source> + <translation> (%0/%1×)</translation> </message> <message> - <location filename="../MemorySearch.cpp" line="185"/> - <source>1 byte%0</source> - <translation>1 Byte%0</translation> + <location filename="../MemorySearch.cpp" line="202"/> + <source> (⅟%0×)</source> + <translation> (⅟%0×)</translation> </message> <message> - <location filename="../MemorySearch.cpp" line="188"/> - <source>2 bytes%0</source> - <translation>2 Bytes%0</translation> + <location filename="../MemorySearch.cpp" line="205"/> + <source> (%0×)</source> + <translation> (%0×)</translation> </message> <message> - <location filename="../MemorySearch.cpp" line="192"/> - <source>4 bytes%0</source> - <translation>4 Bytes%0</translation> + <location filename="../MemorySearch.cpp" line="209"/> + <source>%1 byte%2</source> + <translation>%1 byte%2</translation> </message> </context> <context>

@@ -3012,59 +3038,59 @@ </context>

<context> <name>QGBA::SettingsView</name> <message> - <location filename="../SettingsView.cpp" line="114"/> - <location filename="../SettingsView.cpp" line="156"/> + <location filename="../SettingsView.cpp" line="130"/> + <location filename="../SettingsView.cpp" line="172"/> <source>Qt Multimedia</source> <translation>Qt Multimedia</translation> </message> <message> - <location filename="../SettingsView.cpp" line="121"/> + <location filename="../SettingsView.cpp" line="137"/> <source>SDL</source> <translation>SDL</translation> </message> <message> - <location filename="../SettingsView.cpp" line="129"/> + <location filename="../SettingsView.cpp" line="145"/> <source>Software (Qt)</source> <translation>Software (Qt)</translation> </message> <message> - <location filename="../SettingsView.cpp" line="135"/> + <location filename="../SettingsView.cpp" line="151"/> <source>OpenGL</source> <translation>OpenGL</translation> </message> <message> - <location filename="../SettingsView.cpp" line="142"/> + <location filename="../SettingsView.cpp" line="158"/> <source>OpenGL (force version 1.x)</source> <translation>OpenGL (erzwinge Version 1.x)</translation> </message> <message> - <location filename="../SettingsView.cpp" line="150"/> + <location filename="../SettingsView.cpp" line="166"/> <source>None (Still Image)</source> <translation>Keiner (Standbild)</translation> </message> <message> - <location filename="../SettingsView.cpp" line="231"/> + <location filename="../SettingsView.cpp" line="247"/> <source>Keyboard</source> <translation>Tastatur</translation> </message> <message> - <location filename="../SettingsView.cpp" line="240"/> + <location filename="../SettingsView.cpp" line="256"/> <source>Controllers</source> <translation>Gamepads</translation> </message> <message> - <location filename="../SettingsView.cpp" line="272"/> + <location filename="../SettingsView.cpp" line="288"/> <source>Shortcuts</source> <translation>Tastenkürzel</translation> </message> <message> - <location filename="../SettingsView.cpp" line="284"/> - <location filename="../SettingsView.cpp" line="294"/> + <location filename="../SettingsView.cpp" line="300"/> + <location filename="../SettingsView.cpp" line="310"/> <source>Shaders</source> <translation>Shader</translation> </message> <message> - <location filename="../SettingsView.cpp" line="301"/> + <location filename="../SettingsView.cpp" line="317"/> <source>Select BIOS</source> <translation>BIOS auswählen</translation> </message>

@@ -3123,7 +3149,7 @@ </context>

<context> <name>QGBA::Window</name> <message> - <location filename="../Window.cpp" line="254"/> + <location filename="../Window.cpp" line="255"/> <source>Game Boy Advance ROMs (%1)</source> <translation>Game Boy Advance-ROMs (%1)</translation> </message>

@@ -3138,62 +3164,62 @@ <source>Game Boy ROMs (%1)</source>

<translation>Game Boy-ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="274"/> + <location filename="../Window.cpp" line="275"/> <source>All ROMs (%1)</source> <translation>Alle ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="275"/> + <location filename="../Window.cpp" line="276"/> <source>%1 Video Logs (*.mvl)</source> <translation>%1 Video-Logs (*.mvl)</translation> </message> <message> - <location filename="../Window.cpp" line="290"/> + <location filename="../Window.cpp" line="291"/> <source>Archives (%1)</source> <translation>Archive (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="295"/> - <location filename="../Window.cpp" line="303"/> - <location filename="../Window.cpp" line="330"/> + <location filename="../Window.cpp" line="296"/> + <location filename="../Window.cpp" line="304"/> + <location filename="../Window.cpp" line="331"/> <source>Select ROM</source> <translation>ROM auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="338"/> + <location filename="../Window.cpp" line="339"/> <source>Game Boy Advance save files (%1)</source> <translation>Game Boy Advance-Speicherdateien (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="339"/> - <location filename="../Window.cpp" line="384"/> - <location filename="../Window.cpp" line="391"/> + <location filename="../Window.cpp" line="340"/> + <location filename="../Window.cpp" line="385"/> + <location filename="../Window.cpp" line="392"/> <source>Select save</source> <translation>Speicherdatei wählen</translation> </message> <message> - <location filename="../Window.cpp" line="360"/> + <location filename="../Window.cpp" line="361"/> <source>Select patch</source> <translation>Patch wählen</translation> </message> <message> - <location filename="../Window.cpp" line="360"/> + <location filename="../Window.cpp" line="361"/> <source>Patches (*.ips *.ups *.bps)</source> <translation>Patches (*.ips *.ups *.bps)</translation> </message> <message> - <location filename="../Window.cpp" line="377"/> + <location filename="../Window.cpp" line="378"/> <source>Select image</source> <translation>Bild auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="377"/> + <location filename="../Window.cpp" line="378"/> <source>Image file (*.png *.gif *.jpg *.jpeg);;All files (*)</source> <translation>Bild-Datei (*.png *.gif *.jpg *.jpeg);;Alle Dateien (*)</translation> </message> <message> - <location filename="../Window.cpp" line="384"/> - <location filename="../Window.cpp" line="391"/> + <location filename="../Window.cpp" line="385"/> + <location filename="../Window.cpp" line="392"/> <source>GameShark saves (*.sps *.xps)</source> <translation>GameShark-Speicherdaten (*.sps *.xps)</translation> </message>

@@ -3213,17 +3239,17 @@ <source>Select video log</source>

<translation>Video-Log auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="414"/> + <location filename="../Window.cpp" line="415"/> <source>Video logs (*.mvl)</source> <translation>Video-Logs (*.mvl)</translation> </message> <message> - <location filename="../Window.cpp" line="752"/> + <location filename="../Window.cpp" line="759"/> <source>Crash</source> <translation>Absturz</translation> </message> <message> - <location filename="../Window.cpp" line="753"/> + <location filename="../Window.cpp" line="760"/> <source>The game has crashed with the following error: %1</source>

@@ -3232,528 +3258,528 @@

%1</translation> </message> <message> - <location filename="../Window.cpp" line="761"/> + <location filename="../Window.cpp" line="768"/> <source>Couldn&apos;t Load</source> <translation>Konnte nicht geladen werden</translation> </message> <message> - <location filename="../Window.cpp" line="762"/> + <location filename="../Window.cpp" line="769"/> <source>Could not load game. Are you sure it&apos;s in the correct format?</source> <translation>Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt?</translation> </message> <message> - <location filename="../Window.cpp" line="775"/> + <location filename="../Window.cpp" line="782"/> <source>Unimplemented BIOS call</source> <translation>Nicht implementierter BIOS-Aufruf</translation> </message> <message> - <location filename="../Window.cpp" line="776"/> + <location filename="../Window.cpp" line="783"/> <source>This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience.</source> <translation>Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS.</translation> </message> <message> - <location filename="../Window.cpp" line="851"/> + <location filename="../Window.cpp" line="858"/> <source>Really make portable?</source> <translation>Portablen Modus wirklich aktivieren?</translation> </message> <message> - <location filename="../Window.cpp" line="852"/> + <location filename="../Window.cpp" line="859"/> <source>This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?</source> <translation>Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren?</translation> </message> <message> - <location filename="../Window.cpp" line="860"/> + <location filename="../Window.cpp" line="867"/> <source>Restart needed</source> <translation>Neustart benötigt</translation> </message> <message> - <location filename="../Window.cpp" line="861"/> + <location filename="../Window.cpp" line="868"/> <source>Some changes will not take effect until the emulator is restarted.</source> <translation>Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde.</translation> </message> <message> - <location filename="../Window.cpp" line="907"/> + <location filename="../Window.cpp" line="914"/> <source> - Player %1 of %2</source> <translation> - Spieler %1 von %2</translation> </message> <message> - <location filename="../Window.cpp" line="918"/> + <location filename="../Window.cpp" line="925"/> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> - <location filename="../Window.cpp" line="920"/> + <location filename="../Window.cpp" line="927"/> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> - <location filename="../Window.cpp" line="922"/> + <location filename="../Window.cpp" line="929"/> <source>%1 - %2 (%3 fps) - %4</source> <translation>%1 - %2 (%3 Bilder/Sekunde) - %4</translation> </message> <message> - <location filename="../Window.cpp" line="957"/> + <location filename="../Window.cpp" line="964"/> <source>&amp;File</source> <translation>&amp;Datei</translation> </message> <message> - <location filename="../Window.cpp" line="960"/> + <location filename="../Window.cpp" line="967"/> <source>Load &amp;ROM...</source> <translation>&amp;ROM laden...</translation> </message> <message> - <location filename="../Window.cpp" line="963"/> + <location filename="../Window.cpp" line="970"/> <source>Load ROM in archive...</source> <translation>ROM aus Archiv laden...</translation> </message> <message> - <location filename="../Window.cpp" line="969"/> + <location filename="../Window.cpp" line="976"/> <source>Load temporary save...</source> <translation>Temporäre Speicherdatei laden...</translation> </message> <message> - <location filename="../Window.cpp" line="974"/> + <location filename="../Window.cpp" line="981"/> <source>Load &amp;patch...</source> <translation>&amp;Patch laden...</translation> </message> <message> - <location filename="../Window.cpp" line="977"/> + <location filename="../Window.cpp" line="984"/> <source>Boot BIOS</source> <translation>BIOS booten</translation> </message> <message> - <location filename="../Window.cpp" line="984"/> + <location filename="../Window.cpp" line="991"/> <source>Replace ROM...</source> <translation>ROM ersetzen...</translation> </message> <message> - <location filename="../Window.cpp" line="986"/> + <location filename="../Window.cpp" line="993"/> <source>ROM &amp;info...</source> <translation>ROM-&amp;Informationen...</translation> </message> <message> - <location filename="../Window.cpp" line="991"/> + <location filename="../Window.cpp" line="998"/> <source>Recent</source> <translation>Zuletzt verwendet</translation> </message> <message> - <location filename="../Window.cpp" line="995"/> + <location filename="../Window.cpp" line="1002"/> <source>Make portable</source> <translation>Portablen Modus aktivieren</translation> </message> <message> - <location filename="../Window.cpp" line="999"/> + <location filename="../Window.cpp" line="1006"/> <source>&amp;Load state</source> - <translation>Savestate &amp;laden</translation> + <translation>Savestate (aktueller Zustand) &amp;laden</translation> </message> <message> - <location filename="../Window.cpp" line="1000"/> + <location filename="../Window.cpp" line="1007"/> <source>F10</source> <translation>F10</translation> </message> <message> - <location filename="../Window.cpp" line="1006"/> + <location filename="../Window.cpp" line="1013"/> <source>&amp;Save state</source> - <translation>Savestate &amp;speichern</translation> + <translation>Savestate (aktueller Zustand) &amp;speichern</translation> </message> <message> - <location filename="../Window.cpp" line="1007"/> + <location filename="../Window.cpp" line="1014"/> <source>Shift+F10</source> <translation>Umschalt+F10</translation> </message> <message> - <location filename="../Window.cpp" line="1013"/> + <location filename="../Window.cpp" line="1020"/> <source>Quick load</source> <translation>Schnell laden</translation> </message> <message> - <location filename="../Window.cpp" line="1014"/> + <location filename="../Window.cpp" line="1021"/> <source>Quick save</source> <translation>Schnell speichern</translation> </message> <message> - <location filename="../Window.cpp" line="1018"/> + <location filename="../Window.cpp" line="1025"/> <source>Load recent</source> <translation>Lade zuletzt gespeicherten Savestate</translation> </message> <message> - <location filename="../Window.cpp" line="1026"/> + <location filename="../Window.cpp" line="1033"/> <source>Save recent</source> - <translation>Speichere aktuellen Stand</translation> + <translation>Speichere aktuellen Zustand</translation> </message> <message> - <location filename="../Window.cpp" line="1037"/> + <location filename="../Window.cpp" line="1044"/> <source>Undo load state</source> <translation>Laden des Savestate rückgängig machen</translation> </message> <message> - <location filename="../Window.cpp" line="1038"/> + <location filename="../Window.cpp" line="1045"/> <source>F11</source> <translation>F11</translation> </message> <message> - <location filename="../Window.cpp" line="1046"/> + <location filename="../Window.cpp" line="1053"/> <source>Undo save state</source> <translation>Speichern des Savestate rückgängig machen</translation> </message> <message> - <location filename="../Window.cpp" line="1047"/> + <location filename="../Window.cpp" line="1054"/> <source>Shift+F11</source> <translation>Umschalt+F11</translation> </message> <message> - <location filename="../Window.cpp" line="1060"/> - <location filename="../Window.cpp" line="1069"/> + <location filename="../Window.cpp" line="1067"/> + <location filename="../Window.cpp" line="1076"/> <source>State &amp;%1</source> <translation>Savestate &amp;%1</translation> </message> <message> - <location filename="../Window.cpp" line="1061"/> + <location filename="../Window.cpp" line="1068"/> <source>F%1</source> <translation>F%1</translation> </message> <message> - <location filename="../Window.cpp" line="1070"/> + <location filename="../Window.cpp" line="1077"/> <source>Shift+F%1</source> <translation>Umschalt+F%1</translation> </message> <message> - <location filename="../Window.cpp" line="1080"/> + <location filename="../Window.cpp" line="1087"/> <source>Load camera image...</source> <translation>Lade Kamerabild...</translation> </message> <message> - <location filename="../Window.cpp" line="1086"/> + <location filename="../Window.cpp" line="1093"/> <source>Import GameShark Save</source> <translation>Importiere GameShark-Speicherstand</translation> </message> <message> - <location filename="../Window.cpp" line="1092"/> + <location filename="../Window.cpp" line="1099"/> <source>Export GameShark Save</source> <translation>Exportiere GameShark-Speicherstand</translation> </message> <message> - <location filename="../Window.cpp" line="1100"/> + <location filename="../Window.cpp" line="1107"/> <source>New multiplayer window</source> <translation>Neues Multiplayer-Fenster</translation> </message> <message> - <location filename="../Window.cpp" line="1110"/> + <location filename="../Window.cpp" line="1117"/> <source>About</source> <translation>Über</translation> </message> <message> - <location filename="../Window.cpp" line="1115"/> + <location filename="../Window.cpp" line="1122"/> <source>E&amp;xit</source> <translation>&amp;Beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1118"/> + <location filename="../Window.cpp" line="1125"/> <source>&amp;Emulation</source> <translation>&amp;Emulation</translation> </message> <message> - <location filename="../Window.cpp" line="1120"/> + <location filename="../Window.cpp" line="1127"/> <source>&amp;Reset</source> <translation>Zu&amp;rücksetzen</translation> </message> <message> - <location filename="../Window.cpp" line="1121"/> + <location filename="../Window.cpp" line="1128"/> <source>Ctrl+R</source> <translation>Strg+R</translation> </message> <message> - <location filename="../Window.cpp" line="1128"/> + <location filename="../Window.cpp" line="1135"/> <source>Sh&amp;utdown</source> <translation>Schli&amp;eßen</translation> </message> <message> - <location filename="../Window.cpp" line="1136"/> + <location filename="../Window.cpp" line="1143"/> <source>Yank game pak</source> <translation>Spielmodul herausziehen</translation> </message> <message> - <location filename="../Window.cpp" line="1146"/> + <location filename="../Window.cpp" line="1153"/> <source>&amp;Pause</source> <translation>&amp;Pause</translation> </message> <message> - <location filename="../Window.cpp" line="1149"/> + <location filename="../Window.cpp" line="1156"/> <source>Ctrl+P</source> <translation>Strg+P</translation> </message> <message> - <location filename="../Window.cpp" line="1159"/> + <location filename="../Window.cpp" line="1166"/> <source>&amp;Next frame</source> <translation>&amp;Nächstes Bild</translation> </message> <message> - <location filename="../Window.cpp" line="1160"/> + <location filename="../Window.cpp" line="1167"/> <source>Ctrl+N</source> <translation>Strg+N</translation> </message> <message> - <location filename="../Window.cpp" line="1177"/> + <location filename="../Window.cpp" line="1184"/> <source>Fast forward (held)</source> <translation>Schneller Vorlauf (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1179"/> + <location filename="../Window.cpp" line="1186"/> <source>&amp;Fast forward</source> <translation>Schneller &amp;Vorlauf</translation> </message> <message> - <location filename="../Window.cpp" line="1182"/> + <location filename="../Window.cpp" line="1189"/> <source>Shift+Tab</source> <translation>Umschalt+Tab</translation> </message> <message> - <location filename="../Window.cpp" line="1189"/> + <location filename="../Window.cpp" line="1196"/> <source>Fast forward speed</source> <translation>Vorlauf-Geschwindigkeit</translation> </message> <message> - <location filename="../Window.cpp" line="1194"/> + <location filename="../Window.cpp" line="1201"/> <source>Unbounded</source> <translation>Unbegrenzt</translation> </message> <message> - <location filename="../Window.cpp" line="1198"/> + <location filename="../Window.cpp" line="1205"/> <source>%0x</source> <translation>%0x</translation> </message> <message> - <location filename="../Window.cpp" line="1210"/> + <location filename="../Window.cpp" line="1217"/> <source>Rewind (held)</source> <translation>Zurückspulen (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1212"/> + <location filename="../Window.cpp" line="1219"/> <source>Re&amp;wind</source> <translation>Zur&amp;ückspulen</translation> </message> <message> - <location filename="../Window.cpp" line="1213"/> + <location filename="../Window.cpp" line="1220"/> <source>~</source> <translation>~</translation> </message> <message> - <location filename="../Window.cpp" line="1221"/> + <location filename="../Window.cpp" line="1228"/> <source>Step backwards</source> <translation>Schrittweiser Rücklauf</translation> </message> <message> - <location filename="../Window.cpp" line="1222"/> + <location filename="../Window.cpp" line="1229"/> <source>Ctrl+B</source> <translation>Strg+B</translation> </message> <message> - <location filename="../Window.cpp" line="1231"/> + <location filename="../Window.cpp" line="1238"/> <source>Sync to &amp;video</source> <translation>Mit &amp;Video synchronisieren</translation> </message> <message> - <location filename="../Window.cpp" line="1238"/> + <location filename="../Window.cpp" line="1245"/> <source>Sync to &amp;audio</source> <translation>Mit &amp;Audio synchronisieren</translation> </message> <message> - <location filename="../Window.cpp" line="1246"/> + <location filename="../Window.cpp" line="1253"/> <source>Solar sensor</source> <translation>Solar-Sensor</translation> </message> <message> - <location filename="../Window.cpp" line="1248"/> + <location filename="../Window.cpp" line="1255"/> <source>Increase solar level</source> <translation>Sonnen-Level erhöhen</translation> </message> <message> - <location filename="../Window.cpp" line="1252"/> + <location filename="../Window.cpp" line="1259"/> <source>Decrease solar level</source> <translation>Sonnen-Level verringern</translation> </message> <message> - <location filename="../Window.cpp" line="1256"/> + <location filename="../Window.cpp" line="1263"/> <source>Brightest solar level</source> <translation>Hellster Sonnen-Level</translation> </message> <message> - <location filename="../Window.cpp" line="1260"/> + <location filename="../Window.cpp" line="1267"/> <source>Darkest solar level</source> <translation>Dunkelster Sonnen-Level</translation> </message> <message> - <location filename="../Window.cpp" line="1266"/> + <location filename="../Window.cpp" line="1273"/> <source>Brightness %1</source> <translation>Helligkeit %1</translation> </message> <message> - <location filename="../Window.cpp" line="1273"/> + <location filename="../Window.cpp" line="1280"/> <source>Audio/&amp;Video</source> <translation>Audio/&amp;Video</translation> </message> <message> - <location filename="../Window.cpp" line="1275"/> + <location filename="../Window.cpp" line="1282"/> <source>Frame size</source> <translation>Bildgröße</translation> </message> <message> - <location filename="../Window.cpp" line="1278"/> + <location filename="../Window.cpp" line="1285"/> <source>%1x</source> <translation>%1x</translation> </message> <message> - <location filename="../Window.cpp" line="1306"/> + <location filename="../Window.cpp" line="1313"/> <source>Toggle fullscreen</source> <translation>Vollbildmodus umschalten</translation> </message> <message> - <location filename="../Window.cpp" line="1309"/> + <location filename="../Window.cpp" line="1316"/> <source>Lock aspect ratio</source> <translation>Seitenverhältnis korrigieren</translation> </message> <message> - <location filename="../Window.cpp" line="1319"/> + <location filename="../Window.cpp" line="1326"/> <source>Force integer scaling</source> <translation>Pixelgenaue Skalierung (Integer scaling)</translation> </message> <message> - <location filename="../Window.cpp" line="1335"/> + <location filename="../Window.cpp" line="1342"/> <source>Frame&amp;skip</source> <translation>Frame&amp;skip</translation> </message> <message> - <location filename="../Window.cpp" line="1348"/> + <location filename="../Window.cpp" line="1355"/> <source>Mute</source> <translation>Stummschalten</translation> </message> <message> - <location filename="../Window.cpp" line="1355"/> + <location filename="../Window.cpp" line="1362"/> <source>FPS target</source> <translation>Bildwiederholrate</translation> </message> <message> - <location filename="../Window.cpp" line="1360"/> + <location filename="../Window.cpp" line="1367"/> <source>15</source> <translation>15</translation> </message> <message> - <location filename="../Window.cpp" line="1361"/> + <location filename="../Window.cpp" line="1368"/> <source>30</source> <translation>30</translation> </message> <message> - <location filename="../Window.cpp" line="1362"/> + <location filename="../Window.cpp" line="1369"/> <source>45</source> <translation>45</translation> </message> <message> - <location filename="../Window.cpp" line="1363"/> + <location filename="../Window.cpp" line="1370"/> <source>Native (59.7)</source> <translation>Nativ (59.7)</translation> </message> <message> - <location filename="../Window.cpp" line="1364"/> + <location filename="../Window.cpp" line="1371"/> <source>60</source> <translation>60</translation> </message> <message> - <location filename="../Window.cpp" line="1365"/> + <location filename="../Window.cpp" line="1372"/> <source>90</source> <translation>90</translation> </message> <message> - <location filename="../Window.cpp" line="1366"/> + <location filename="../Window.cpp" line="1373"/> <source>120</source> <translation>120</translation> </message> <message> - <location filename="../Window.cpp" line="1367"/> + <location filename="../Window.cpp" line="1374"/> <source>240</source> <translation>240</translation> </message> <message> - <location filename="../Window.cpp" line="1373"/> + <location filename="../Window.cpp" line="1380"/> <source>Take &amp;screenshot</source> <translation>&amp;Screenshot erstellen</translation> </message> <message> - <location filename="../Window.cpp" line="1374"/> + <location filename="../Window.cpp" line="1381"/> <source>F12</source> <translation>F12</translation> </message> <message> - <location filename="../Window.cpp" line="1383"/> + <location filename="../Window.cpp" line="1390"/> <source>Record output...</source> <translation>Ausgabe aufzeichen...</translation> </message> <message> - <location filename="../Window.cpp" line="1390"/> + <location filename="../Window.cpp" line="1397"/> <source>Record GIF...</source> <translation>GIF aufzeichen...</translation> </message> <message> - <location filename="../Window.cpp" line="1395"/> + <location filename="../Window.cpp" line="1402"/> <source>Record video log...</source> <translation>Video-Log aufzeichnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1400"/> + <location filename="../Window.cpp" line="1407"/> <source>Stop video log</source> <translation>Video-Log beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1408"/> + <location filename="../Window.cpp" line="1415"/> <source>Game Boy Printer...</source> <translation>Game Boy Printer...</translation> </message> <message> - <location filename="../Window.cpp" line="1420"/> + <location filename="../Window.cpp" line="1427"/> <source>Video layers</source> <translation>Video-Ebenen</translation> </message> <message> - <location filename="../Window.cpp" line="1423"/> + <location filename="../Window.cpp" line="1430"/> <source>Audio channels</source> <translation>Audio-Kanäle</translation> </message> <message> - <location filename="../Window.cpp" line="1426"/> + <location filename="../Window.cpp" line="1433"/> <source>&amp;Tools</source> <translation>&amp;Werkzeuge</translation> </message> <message> - <location filename="../Window.cpp" line="1428"/> + <location filename="../Window.cpp" line="1435"/> <source>View &amp;logs...</source> <translation>&amp;Logs ansehen...</translation> </message> <message> - <location filename="../Window.cpp" line="1432"/> + <location filename="../Window.cpp" line="1439"/> <source>Game &amp;overrides...</source> <translation>Spiel-&amp;Überschreibungen...</translation> </message> <message> - <location filename="../Window.cpp" line="1445"/> + <location filename="../Window.cpp" line="1453"/> <source>Game &amp;Pak sensors...</source> <translation>Game &amp;Pak-Sensoren...</translation> </message> <message> - <location filename="../Window.cpp" line="1458"/> + <location filename="../Window.cpp" line="1466"/> <source>&amp;Cheats...</source> <translation>&amp;Cheats...</translation> </message> <message> - <location filename="../Window.cpp" line="1470"/> + <location filename="../Window.cpp" line="1478"/> <source>Open debugger console...</source> <translation>Debugger-Konsole äffnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1476"/> + <location filename="../Window.cpp" line="1484"/> <source>Start &amp;GDB server...</source> <translation>&amp;GDB-Server starten...</translation> </message> <message> - <location filename="../Window.cpp" line="1464"/> + <location filename="../Window.cpp" line="1472"/> <source>Settings...</source> <translation>Einstellungen...</translation> </message>

@@ -3778,37 +3804,37 @@ <source>Select folder</source>

<translation>Ordner auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="965"/> + <location filename="../Window.cpp" line="972"/> <source>Add folder to library...</source> <translation>Ordner zur Bibliothek hinzufügen...</translation> </message> <message> - <location filename="../Window.cpp" line="1329"/> + <location filename="../Window.cpp" line="1336"/> <source>Bilinear filtering</source> <translation>Bilineare Filterung</translation> </message> <message> - <location filename="../Window.cpp" line="1484"/> + <location filename="../Window.cpp" line="1492"/> <source>View &amp;palette...</source> <translation>&amp;Palette betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1489"/> + <location filename="../Window.cpp" line="1497"/> <source>View &amp;sprites...</source> <translation>&amp;Sprites betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1494"/> + <location filename="../Window.cpp" line="1502"/> <source>View &amp;tiles...</source> <translation>&amp;Tiles betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1499"/> + <location filename="../Window.cpp" line="1507"/> <source>View &amp;map...</source> <translation>&amp;Map betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1504"/> + <location filename="../Window.cpp" line="1512"/> <source>View memory...</source> <translation>Speicher betrachten...</translation> </message>

@@ -3823,67 +3849,72 @@ <source>Search memory...</source>

<translation>Speicher durchsuchen...</translation> </message> <message> - <location filename="../Window.cpp" line="1515"/> + <location filename="../Window.cpp" line="1523"/> <source>View &amp;I/O registers...</source> <translation>&amp;I/O-Register betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1583"/> + <location filename="../Window.cpp" line="1601"/> <source>Exit fullscreen</source> <translation>Vollbildmodus beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1588"/> + <location filename="../Window.cpp" line="1614"/> + <source>GameShark Button (held)</source> + <translation>GameShark-Taste (gehalten)</translation> + </message> + <message> + <location filename="../Window.cpp" line="1616"/> <source>Autofire</source> <translation>Autofeuer</translation> </message> <message> - <location filename="../Window.cpp" line="1595"/> + <location filename="../Window.cpp" line="1623"/> <source>Autofire A</source> <translation>Autofeuer A</translation> </message> <message> - <location filename="../Window.cpp" line="1601"/> + <location filename="../Window.cpp" line="1629"/> <source>Autofire B</source> <translation>Autofeuer B</translation> </message> <message> - <location filename="../Window.cpp" line="1607"/> + <location filename="../Window.cpp" line="1635"/> <source>Autofire L</source> <translation>Autofeuer L</translation> </message> <message> - <location filename="../Window.cpp" line="1613"/> + <location filename="../Window.cpp" line="1641"/> <source>Autofire R</source> <translation>Autofeuer R</translation> </message> <message> - <location filename="../Window.cpp" line="1619"/> + <location filename="../Window.cpp" line="1647"/> <source>Autofire Start</source> <translation>Autofeuer Start</translation> </message> <message> - <location filename="../Window.cpp" line="1625"/> + <location filename="../Window.cpp" line="1653"/> <source>Autofire Select</source> <translation>Autofeuer Select</translation> </message> <message> - <location filename="../Window.cpp" line="1631"/> + <location filename="../Window.cpp" line="1659"/> <source>Autofire Up</source> <translation>Autofeuer nach oben</translation> </message> <message> - <location filename="../Window.cpp" line="1637"/> + <location filename="../Window.cpp" line="1665"/> <source>Autofire Right</source> <translation>Autofeuer rechts</translation> </message> <message> - <location filename="../Window.cpp" line="1643"/> + <location filename="../Window.cpp" line="1671"/> <source>Autofire Down</source> <translation>Autofeuer nach unten</translation> </message> <message> - <location filename="../Window.cpp" line="1649"/> + <location filename="../Window.cpp" line="1677"/> <source>Autofire Left</source> <translation>Autofeuer links</translation> </message>

@@ -4176,13 +4207,13 @@ </message>

<message> <location filename="../SettingsView.ui" line="302"/> <source>Skip every</source> - <translation>Alle</translation> + <translation>Überspringe</translation> </message> <message> <location filename="../SettingsView.ui" line="312"/> - <location filename="../SettingsView.ui" line="581"/> + <location filename="../SettingsView.ui" line="645"/> <source>frames</source> - <translation>Bilder</translation> + <translation>Bild(er)</translation> </message> <message> <location filename="../SettingsView.ui" line="321"/>

@@ -4231,131 +4262,162 @@ <source>English</source>

<translation>Englisch</translation> </message> <message> - <location filename="../SettingsView.ui" line="433"/> + <location filename="../SettingsView.ui" line="440"/> <source>List view</source> <translation>Listenansicht</translation> </message> <message> - <location filename="../SettingsView.ui" line="438"/> + <location filename="../SettingsView.ui" line="445"/> <source>Tree view</source> <translation>Baumansicht</translation> </message> <message> - <location filename="../SettingsView.ui" line="1068"/> + <location filename="../SettingsView.ui" line="501"/> + <source>Show FPS in title bar</source> + <translation>Bildwiederholrate in der Titelleiste anzeigen</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="525"/> + <source>Automatically save cheats</source> + <translation>Cheats automatisch speichern</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="535"/> + <source>Automatically load cheats</source> + <translation>Cheats automatisch laden</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="545"/> + <source>Automatically save state</source> + <translation>Zustand (Savestate) automatisch speichern</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="555"/> + <source>Automatically load state</source> + <translation>Zustand (Savestate) automatisch laden</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="1135"/> + <source>Cheats</source> + <translation>Cheats</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="1180"/> <source>Game Boy model</source> <translation>Game Boy-Modell</translation> </message> <message> - <location filename="../SettingsView.ui" line="1076"/> - <location filename="../SettingsView.ui" line="1112"/> - <location filename="../SettingsView.ui" line="1148"/> + <location filename="../SettingsView.ui" line="1188"/> + <location filename="../SettingsView.ui" line="1224"/> + <location filename="../SettingsView.ui" line="1260"/> <source>Autodetect</source> <translation>Automatisch erkennen</translation> </message> <message> - <location filename="../SettingsView.ui" line="1081"/> - <location filename="../SettingsView.ui" line="1117"/> - <location filename="../SettingsView.ui" line="1153"/> + <location filename="../SettingsView.ui" line="1193"/> + <location filename="../SettingsView.ui" line="1229"/> + <location filename="../SettingsView.ui" line="1265"/> <source>Game Boy (DMG)</source> <translation>Game Boy (DMG)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1086"/> - <location filename="../SettingsView.ui" line="1122"/> - <location filename="../SettingsView.ui" line="1158"/> + <location filename="../SettingsView.ui" line="1198"/> + <location filename="../SettingsView.ui" line="1234"/> + <location filename="../SettingsView.ui" line="1270"/> <source>Super Game Boy (SGB)</source> <translation>Super Game Boy (SGB)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1091"/> - <location filename="../SettingsView.ui" line="1127"/> - <location filename="../SettingsView.ui" line="1163"/> + <location filename="../SettingsView.ui" line="1203"/> + <location filename="../SettingsView.ui" line="1239"/> + <location filename="../SettingsView.ui" line="1275"/> <source>Game Boy Color (CGB)</source> <translation>Game Boy Color (CGB)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1096"/> - <location filename="../SettingsView.ui" line="1132"/> - <location filename="../SettingsView.ui" line="1168"/> + <location filename="../SettingsView.ui" line="1208"/> + <location filename="../SettingsView.ui" line="1244"/> + <location filename="../SettingsView.ui" line="1280"/> <source>Game Boy Advance (AGB)</source> <translation>Game Boy Advance (AGB)</translation> </message> <message> - <location filename="../SettingsView.ui" line="1104"/> + <location filename="../SettingsView.ui" line="1216"/> <source>Super Game Boy model</source> <translation>Super Game Boy-Modell</translation> </message> <message> - <location filename="../SettingsView.ui" line="1140"/> + <location filename="../SettingsView.ui" line="1252"/> <source>Game Boy Color model</source> <translation>Game Boy Color-Modell</translation> </message> <message> - <location filename="../SettingsView.ui" line="1183"/> + <location filename="../SettingsView.ui" line="1295"/> <source>Default BG colors:</source> <translation>Standard-Hintergrundfarben:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1461"/> + <location filename="../SettingsView.ui" line="1573"/> <source>Default sprite colors 1:</source> <translation>Standard-Sprite-Farben 1:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1468"/> + <location filename="../SettingsView.ui" line="1580"/> <source>Default sprite colors 2:</source> <translation>Standard-Sprite-Farben 2:</translation> </message> <message> - <location filename="../SettingsView.ui" line="1350"/> + <location filename="../SettingsView.ui" line="1462"/> <source>Super Game Boy borders</source> <translation>Super Game Boy-Rahmen</translation> </message> <message> - <location filename="../SettingsView.ui" line="1364"/> + <location filename="../SettingsView.ui" line="1476"/> <source>Camera driver:</source> <translation>Kamera-Treiber:</translation> </message> <message> - <location filename="../SettingsView.ui" line="425"/> + <location filename="../SettingsView.ui" line="432"/> <source>Library:</source> <translation>Bibliothek:</translation> </message> <message> - <location filename="../SettingsView.ui" line="446"/> + <location filename="../SettingsView.ui" line="453"/> <source>Show when no game open</source> <translation>Anzeigen, wenn kein Spiel geöffnet ist</translation> </message> <message> - <location filename="../SettingsView.ui" line="456"/> + <location filename="../SettingsView.ui" line="463"/> <source>Clear cache</source> <translation>Cache leeren</translation> </message> <message> - <location filename="../SettingsView.ui" line="508"/> + <location filename="../SettingsView.ui" line="572"/> <source>Fast forward speed:</source> <translation>Vorlauf-Geschwindigkeit:</translation> </message> <message> - <location filename="../SettingsView.ui" line="705"/> + <location filename="../SettingsView.ui" line="769"/> <source>Rewind affects save data</source> <translation>Rücklauf beeinflusst Speicherdaten</translation> </message> <message> - <location filename="../SettingsView.ui" line="715"/> + <location filename="../SettingsView.ui" line="779"/> <source>Preload entire ROM into memory</source> <translation>ROM-Datei vollständig in Arbeitsspeicher vorladen</translation> </message> <message> - <location filename="../SettingsView.ui" line="762"/> - <location filename="../SettingsView.ui" line="800"/> - <location filename="../SettingsView.ui" line="835"/> - <location filename="../SettingsView.ui" line="863"/> - <location filename="../SettingsView.ui" line="904"/> - <location filename="../SettingsView.ui" line="952"/> - <location filename="../SettingsView.ui" line="1000"/> - <location filename="../SettingsView.ui" line="1048"/> + <location filename="../SettingsView.ui" line="826"/> + <location filename="../SettingsView.ui" line="864"/> + <location filename="../SettingsView.ui" line="899"/> + <location filename="../SettingsView.ui" line="927"/> + <location filename="../SettingsView.ui" line="968"/> + <location filename="../SettingsView.ui" line="1016"/> + <location filename="../SettingsView.ui" line="1064"/> + <location filename="../SettingsView.ui" line="1112"/> + <location filename="../SettingsView.ui" line="1160"/> <source>Browse</source> <translation>Durchsuchen</translation> </message>

@@ -4376,22 +4438,22 @@ <translation>BIOS-Datei verwenden,

wenn vorhanden</translation> </message> <message> - <location filename="../SettingsView.ui" line="781"/> + <location filename="../SettingsView.ui" line="845"/> <source>Skip BIOS intro</source> <translation>BIOS-Intro überspringen</translation> </message> <message> - <location filename="../SettingsView.ui" line="520"/> + <location filename="../SettingsView.ui" line="584"/> <source>×</source> <translation>×</translation> </message> <message> - <location filename="../SettingsView.ui" line="539"/> + <location filename="../SettingsView.ui" line="603"/> <source>Unbounded</source> <translation>unbegrenzt</translation> </message> <message> - <location filename="../SettingsView.ui" line="477"/> + <location filename="../SettingsView.ui" line="484"/> <source>Suspend screensaver</source> <translation>Bildschirmschoner deaktivieren</translation> </message>

@@ -4401,50 +4463,50 @@ <source>BIOS</source>

<translation>BIOS</translation> </message> <message> - <location filename="../SettingsView.ui" line="487"/> + <location filename="../SettingsView.ui" line="494"/> <source>Pause when inactive</source> <translation>Pause, wenn inaktiv</translation> </message> <message> - <location filename="../SettingsView.ui" line="605"/> + <location filename="../SettingsView.ui" line="669"/> <source>Run all</source> <translation>Alle ausführen</translation> </message> <message> - <location filename="../SettingsView.ui" line="610"/> + <location filename="../SettingsView.ui" line="674"/> <source>Remove known</source> <translation>Bekannte entfernen</translation> </message> <message> - <location filename="../SettingsView.ui" line="615"/> + <location filename="../SettingsView.ui" line="679"/> <source>Detect and remove</source> <translation>Erkennen und entfernen</translation> </message> <message> - <location filename="../SettingsView.ui" line="470"/> + <location filename="../SettingsView.ui" line="477"/> <source>Allow opposing input directions</source> <translation>Gegensätzliche Eingaberichtungen erlauben</translation> </message> <message> - <location filename="../SettingsView.ui" line="637"/> - <location filename="../SettingsView.ui" line="674"/> + <location filename="../SettingsView.ui" line="701"/> + <location filename="../SettingsView.ui" line="738"/> <source>Screenshot</source> <translation>Screenshot</translation> </message> <message> - <location filename="../SettingsView.ui" line="647"/> - <location filename="../SettingsView.ui" line="684"/> + <location filename="../SettingsView.ui" line="711"/> + <location filename="../SettingsView.ui" line="748"/> <source>Save data</source> <translation>Speicherdaten</translation> </message> <message> - <location filename="../SettingsView.ui" line="657"/> - <location filename="../SettingsView.ui" line="691"/> + <location filename="../SettingsView.ui" line="721"/> + <location filename="../SettingsView.ui" line="755"/> <source>Cheat codes</source> <translation>Cheat-Codes</translation> </message> <message> - <location filename="../SettingsView.ui" line="558"/> + <location filename="../SettingsView.ui" line="622"/> <source>Enable rewind</source> <translation>Rücklauf aktivieren</translation> </message>

@@ -4454,42 +4516,42 @@ <source>Bilinear filtering</source>

<translation>Bilineare Filterung</translation> </message> <message> - <location filename="../SettingsView.ui" line="565"/> + <location filename="../SettingsView.ui" line="629"/> <source>Rewind history:</source> <translation>Rücklauf-Verlauf:</translation> </message> <message> - <location filename="../SettingsView.ui" line="597"/> + <location filename="../SettingsView.ui" line="661"/> <source>Idle loops:</source> <translation>Leerlaufprozesse:</translation> </message> <message> - <location filename="../SettingsView.ui" line="630"/> + <location filename="../SettingsView.ui" line="694"/> <source>Savestate extra data:</source> <translation>Zusätzliche Savestate-Daten:</translation> </message> <message> - <location filename="../SettingsView.ui" line="667"/> + <location filename="../SettingsView.ui" line="731"/> <source>Load extra data:</source> <translation>Lade zusätzliche Daten:</translation> </message> <message> - <location filename="../SettingsView.ui" line="722"/> + <location filename="../SettingsView.ui" line="786"/> <source>Autofire interval:</source> <translation>Autofeuer-Intervall:</translation> </message> <message> - <location filename="../SettingsView.ui" line="743"/> + <location filename="../SettingsView.ui" line="807"/> <source>GB BIOS file:</source> <translation>Datei mit GB-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="809"/> + <location filename="../SettingsView.ui" line="873"/> <source>GBA BIOS file:</source> <translation>Datei mit GBA-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="816"/> + <location filename="../SettingsView.ui" line="880"/> <source>GBC BIOS file:</source> <translation>Datei mit GBC-BIOS:</translation> </message>

@@ -4504,30 +4566,31 @@ <source>SGB BIOS file:</source>

<translation>Datei mit SGB-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="879"/> + <location filename="../SettingsView.ui" line="943"/> <source>Save games</source> <translation>Spielstände</translation> </message> <message> - <location filename="../SettingsView.ui" line="913"/> - <location filename="../SettingsView.ui" line="961"/> - <location filename="../SettingsView.ui" line="1009"/> - <location filename="../SettingsView.ui" line="1057"/> + <location filename="../SettingsView.ui" line="977"/> + <location filename="../SettingsView.ui" line="1025"/> + <location filename="../SettingsView.ui" line="1073"/> + <location filename="../SettingsView.ui" line="1121"/> + <location filename="../SettingsView.ui" line="1169"/> <source>Same directory as the ROM</source> <translation>Verzeichnis der ROM-Datei</translation> </message> <message> - <location filename="../SettingsView.ui" line="927"/> + <location filename="../SettingsView.ui" line="991"/> <source>Save states</source> <translation>Savestates</translation> </message> <message> - <location filename="../SettingsView.ui" line="975"/> + <location filename="../SettingsView.ui" line="1039"/> <source>Screenshots</source> <translation>Screenshots</translation> </message> <message> - <location filename="../SettingsView.ui" line="1023"/> + <location filename="../SettingsView.ui" line="1087"/> <source>Patches</source> <translation>Patches</translation> </message>
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -95,7 +95,9 @@

add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) -set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) +if(NOT WIN32) + set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) +endif() install(TARGETS ${BINARY_NAME}-sdl DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl) if(UNIX) install(FILES ${CMAKE_SOURCE_DIR}/doc/${BINARY_NAME}.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl)
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -181,6 +181,7 @@ if (!mCoreLoadFile(renderer->core, args->fname)) {

return 1; } mCoreAutoloadSave(renderer->core); + mCoreAutoloadCheats(renderer->core); #ifdef ENABLE_SCRIPTING struct mScriptBridge* bridge = mScriptBridgeCreate(); #ifdef ENABLE_PYTHON
M src/platform/sdl/sdl-audio.hsrc/platform/sdl/sdl-audio.h

@@ -13,6 +13,14 @@

#include <mgba/core/log.h> #include <SDL.h> +// Altivec sometimes defines this +#ifdef vector +#undef vector +#endif +#ifdef bool +#undef bool +#define bool _Bool +#endif mLOG_DECLARE_CATEGORY(SDL_AUDIO);
M src/platform/sdl/sdl-events.csrc/platform/sdl/sdl-events.c

@@ -407,7 +407,7 @@ int key = -1;

if (!event->keysym.mod) { key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym); } - if (key != -1) { + if (key != -1 && !event->repeat) { mCoreThreadInterrupt(context); if (event->type == SDL_KEYDOWN) { context->core->addKeys(context->core, 1 << key);
M src/platform/sdl/sdl-events.hsrc/platform/sdl/sdl-events.h

@@ -16,6 +16,14 @@ #include <mgba-util/circle-buffer.h>

#include <mgba-util/vector.h> #include <SDL.h> +// Altivec sometimes defines this +#ifdef vector +#undef vector +#endif +#ifdef bool +#undef bool +#define bool _Bool +#endif mLOG_DECLARE_CATEGORY(SDL_EVENTS);
M src/third-party/inih/ini.csrc/third-party/inih/ini.c

@@ -21,8 +21,8 @@ #if !INI_USE_STACK

#include <stdlib.h> #endif -#define MAX_SECTION 50 -#define MAX_NAME 50 +#define MAX_SECTION 128 +#define MAX_NAME 128 /* Strip whitespace chars off end of given string, in place. Return s. */ static char* rstrip(char* s)
M src/util/png-io.csrc/util/png-io.c

@@ -47,16 +47,19 @@ if (setjmp(png_jmpbuf(png))) {

return 0; } png_set_IHDR(png, info, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - png_write_info(png, info); return info; } png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB); + png_infop info = _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB); + png_write_info(png, info); + return info; } png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB_ALPHA); + png_infop info = _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB_ALPHA); + png_write_info(png, info); + return info; } png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height) {
A tools/deploy-win.sh

@@ -0,0 +1,27 @@

+#!/bin/bash +BINARY=$1 +INSTALLPATH="$2" +WORKDIR="$3" + + +if [ -z "$DESTDIR" ]; then + OUTDIR="$INSTALLPATH" +else + if [ -n $(echo ${INSTALLPATH} | grep "^[A-Z]:") ]; then + INSTALLPATH="${INSTALLPATH:3}" + fi + OUTDIR="$WORKDIR/$DESTDIR/$INSTALLPATH" +fi + +IFS=$'\n' +if [ -n $(which ntldd 2>&1 | grep /ntldd) ]; then + DLLS=$(ntldd -R "$BINARY" | grep -i mingw | cut -d">" -f2 | sed -e 's/(0x[0-9a-f]\+)//' -e 's/^ \+//' -e 's/ \+$//' -e 's,\\,/,g') +elif [ -n $(which gdb 2>&1 | grep /gdb) ]; then + DLLS=$(gdb "$BINARY" --command=$(dirname $0)/dlls.gdb | grep -i mingw | cut -d" " -f7- | sed -e 's/^ \+//' -e 's/ \+$//' -e 's,\\,/,g') +else + echo "Please install gdb or ntldd for deploying DLLs" +fi +cp -vu $DLLS "$OUTDIR" +if [ -n $(which windeployqt 2>&1 | grep /windeployqt) ]; then + windeployqt --no-opengl-sw --no-svg --release --dir "$OUTDIR" "$BINARY" +fi
A tools/dlls.gdb

@@ -0,0 +1,3 @@

+start +info sharedlibrary +q