all repos — mgba @ ab07c280fe5ecad1b939a4f9f3ea10f80885206d

mGBA Game Boy Advance Emulator

Merge branch 'master' into feature/input-revamp
Vicki Pfau vi@endrift.com
Sun, 25 Jun 2017 17:40:23 -0700
commit

ab07c280fe5ecad1b939a4f9f3ea10f80885206d

parent

fa73d25acb0b20636bd0adbe3611b528cc1f954f

99 files changed, 2595 insertions(+), 469 deletions(-)

jump to
M CHANGESCHANGES

@@ -21,8 +21,10 @@ - Library view

- Debugger: Segment/bank support - GB: Symbol table support - GB MBC: Add MBC1 multicart support - - GBA: Implement keypad interrupts + - Implement keypad interrupts - LR35902: Watchpoints + - Memory search + - Debugger: Execution tracing Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior

@@ -64,6 +66,13 @@ - Qt: Disable "New multiplayer window" when MAX_GBAS is reached (fixes mgba.io/i/107)

- LR35902: Fix decoding LD r, $imm and 0-valued immediates (fixes mgba.io/i/735) - GB: Fix STAT blocking - GB MBC: Fix swapping carts not detect new MBC + - GB Timer: Fix DIV batching if TAC changes + - GB Video: Reset renderer when loading state + - GBA BIOS: Fix INT_MIN/-1 crash + - GBA Savedata: Update and fix Sharkport importing (fixes mgba.io/i/658) + - OpenGL: Fix some shaders causing offset graphics + - Qt: Fix game unpausing after frame advancing and refocusing + - GB Timer: Fix sub-M-cycle DIV reset timing and edge triggering Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers

@@ -124,6 +133,15 @@ - Core: Move rewind diffing to its own thread

- Util: Tune patch-fast extent sizes - Qt: Relax hard dependency on OpenGL - GB Video: Improved video timings + - Core: List memory segments in the core + - Core: Move savestate creation time to extdata + - Debugger: Add mDebuggerRunFrame convenience function + - GBA Memory: Remove unused prefetch cruft + - GB: Trust ROM header for number of SRAM banks (fixes mgba.io/i/726) + - Core: Config values can now be hexadecimal + - GB: Reset with initial state of DIV register + - GB MBC: New MBC7 implementation + - Qt: Better highlight active key in control binding 0.5.2: (2016-12-31) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -155,8 +155,6 @@ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

include(${CMAKE_CURRENT_SOURCE_DIR}/version.cmake) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME}) list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c) source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c)

@@ -200,6 +198,7 @@ add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() elseif(UNIX) + set(USE_PTHREADS ON) add_definitions(-DUSE_PTHREADS) if(CMAKE_SYSTEM_NAME STREQUAL "Linux")

@@ -843,6 +842,10 @@ COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}"

INCLUDE_DIRECTORIES "${SDL_INCLUDE_DIR};${CMAKE_CURRENT_SOURCE_DIR}/src;${CMAKE_CURRENT_SOURCE_DIR}/include") endif() endif() + +message(STATUS ${USE_PTHREADS}) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME}) # Packaging set(CPACK_PACKAGE_VERSION ${VERSION_STRING})
M README.mdREADME.md

@@ -29,7 +29,7 @@ - Video and GIF recording.

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

@@ -94,6 +94,16 @@ sudo make install

This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them. +If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are: + + brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit + mkdir build + cd build + cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` .. + make + +Note that you should not do a `make install` on macOS, as it will not work properly. + #### Windows developer building To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("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):

@@ -133,7 +143,7 @@ - libzip or zlib: for loading ROMs stored in zip files.

- ImageMagick: for GIF recording. - SQLite3: for game databases. -Both libpng and zlib are included with the emulator, so they do not need to be externally compiled first. +SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first. Footnotes ---------

@@ -145,7 +155,7 @@ - Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))

<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. -<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older. +<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.5, and may work on older. [downloads]: http://mgba.io/downloads.html [source]: https://github.com/mgba-emu/mgba/

@@ -153,7 +163,7 @@

Copyright --------- -mGBA is Copyright © 2013 – 2016 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. +mGBA is Copyright © 2013 – 2017 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. mGBA contains the following third-party libraries:
M include/mgba/core/core.hinclude/mgba/core/core.h

@@ -19,8 +19,7 @@ #include <mgba/core/input.h>

#endif #include <mgba/core/interface.h> #ifdef USE_DEBUGGERS -// TODO: Fix layering violation -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> #endif enum mPlatform {

@@ -131,6 +130,9 @@

void (*rawWrite8)(struct mCore*, uint32_t address, int segment, uint8_t); void (*rawWrite16)(struct mCore*, uint32_t address, int segment, uint16_t); void (*rawWrite32)(struct mCore*, uint32_t address, int segment, uint32_t); + + size_t (*listMemoryBlocks)(const struct mCore*, const struct mCoreMemoryBlock**); + void* (*getMemoryBlock)(struct mCore*, size_t id, size_t* sizeOut); #ifdef USE_DEBUGGERS bool (*supportsDebuggerType)(struct mCore*, enum mDebuggerType);
M include/mgba/core/interface.hinclude/mgba/core/interface.h

@@ -31,6 +31,10 @@ #define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 5)

#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5) #define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5) +#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) +#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9)) +#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19)) + struct blip_t; struct mCoreCallbacks {

@@ -110,6 +114,27 @@ size_t id;

const char* internalName; const char* visibleName; const char* visibleType; +}; + +enum mCoreMemoryBlockFlags { + mCORE_MEMORY_READ = 0x01, + mCORE_MEMORY_WRITE = 0x02, + mCORE_MEMORY_RW = 0x03, + mCORE_MEMORY_MAPPED = 0x10, + mCORE_MEMORY_VIRTUAL = 0x20, +}; + +struct mCoreMemoryBlock { + size_t id; + const char* internalName; + const char* shortName; + const char* longName; + uint32_t start; + uint32_t end; + uint32_t size; + uint32_t flags; + uint16_t maxSegment; + uint32_t segmentStart; }; CXX_GUARD_END
M include/mgba/core/library.hinclude/mgba/core/library.h

@@ -36,6 +36,7 @@

struct VDir; struct VFile; void mLibraryLoadDirectory(struct mLibrary* library, const char* base); +void mLibraryClear(struct mLibrary* library); size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints); size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints);
A include/mgba/core/mem-search.h

@@ -0,0 +1,49 @@

+/* 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/. */ +#ifndef CORE_MEM_SEARCH_H +#define CORE_MEM_SEARCH_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba-util/vector.h> + +enum mCoreMemorySearchType { + mCORE_MEMORY_SEARCH_32, + mCORE_MEMORY_SEARCH_16, + mCORE_MEMORY_SEARCH_8, + mCORE_MEMORY_SEARCH_STRING, + mCORE_MEMORY_SEARCH_GUESS, +}; + +struct mCoreMemorySearchParams { + int memoryFlags; + enum mCoreMemorySearchType type; + union { + const char* valueStr; + uint32_t value32; + uint32_t value16; + uint32_t value8; + }; +}; + +struct mCoreMemorySearchResult { + uint32_t address; + int segment; + uint64_t guessDivisor; + enum mCoreMemorySearchType type; +}; + +DECLARE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); + +struct mCore; +void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit); +void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout); + +CXX_GUARD_END + +#endif
M include/mgba/core/serialize.hinclude/mgba/core/serialize.h

@@ -16,6 +16,7 @@ EXTDATA_SCREENSHOT = 1,

EXTDATA_SAVEDATA = 2, EXTDATA_CHEATS = 3, EXTDATA_RTC = 4, + EXTDATA_META_TIME = 0x101, EXTDATA_MAX };

@@ -23,6 +24,7 @@ #define SAVESTATE_SCREENSHOT 1

#define SAVESTATE_SAVEDATA 2 #define SAVESTATE_CHEATS 4 #define SAVESTATE_RTC 8 +#define SAVESTATE_METADATA 16 struct mStateExtdataItem { int32_t size;
M include/mgba/core/timing.hinclude/mgba/core/timing.h

@@ -38,6 +38,7 @@ bool mTimingIsScheduled(const struct mTiming* timing, const struct mTimingEvent*);

int32_t mTimingTick(struct mTiming* timing, int32_t cycles); int32_t mTimingCurrentTime(const struct mTiming* timing); int32_t mTimingNextEvent(struct mTiming* timing); +int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent*); CXX_GUARD_END
M include/mgba/feature/commandline.hinclude/mgba/feature/commandline.h

@@ -12,7 +12,7 @@ CXX_GUARD_START

#include <mgba-util/table.h> -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> struct mArguments { char* fname;
M include/mgba/gb/interface.hinclude/mgba/gb/interface.h

@@ -30,6 +30,7 @@ GB_MBC7 = 7,

GB_MMM01 = 0x10, GB_HuC1 = 0x11, GB_HuC3 = 0x12, + GB_POCKETCAM = 0x13, GB_MBC3_RTC = 0x103, GB_MBC5_RUMBLE = 0x105 };
M include/mgba/internal/arm/debugger/debugger.hinclude/mgba/internal/arm/debugger/debugger.h

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

CXX_GUARD_START -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> #include <mgba/internal/arm/arm.h> +#include <mgba-util/vector.h> struct ARMDebugBreakpoint { uint32_t address;
M include/mgba/internal/debugger/cli-debugger.hinclude/mgba/internal/debugger/cli-debugger.h

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

CXX_GUARD_START -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> + +extern const char* ERROR_MISSING_ARGS; +extern const char* ERROR_OVERFLOW; struct CLIDebugger;
M include/mgba/internal/debugger/debugger.hinclude/mgba/debugger/debugger.h

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

-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* 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

@@ -12,7 +12,6 @@ CXX_GUARD_START

#include <mgba/core/cpu.h> #include <mgba/core/log.h> -#include <mgba-util/vector.h> mLOG_DECLARE_CATEGORY(DEBUGGER);

@@ -53,14 +52,6 @@ DEBUGGER_ENTER_WATCHPOINT,

DEBUGGER_ENTER_ILLEGAL_OP }; -struct mDebugWatchpoint { - uint32_t address; - enum mWatchpointType type; -}; - -extern const char* ERROR_MISSING_ARGS; -extern const char* ERROR_OVERFLOW; - struct mDebuggerEntryInfo { uint32_t address; union {

@@ -92,6 +83,7 @@ void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment);

void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*checkBreakpoints)(struct mDebuggerPlatform*); + void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); }; struct mDebuggerSymbols;

@@ -112,6 +104,7 @@

struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore*); void mDebuggerAttach(struct mDebugger*, struct mCore*); void mDebuggerRun(struct mDebugger*); +void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); CXX_GUARD_END
M include/mgba/internal/debugger/gdb-stub.hinclude/mgba/internal/debugger/gdb-stub.h

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

CXX_GUARD_START -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> #include <mgba-util/socket.h>
M include/mgba/internal/debugger/parser.hinclude/mgba/internal/debugger/parser.h

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

CXX_GUARD_START -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> enum LexState { LEX_ERROR = -1,
M include/mgba/internal/gb/gb.hinclude/mgba/internal/gb/gb.h

@@ -139,6 +139,8 @@ bool GBIsROM(struct VFile* vf);

void GBGetGameTitle(const struct GB* gba, char* out); void GBGetGameCode(const struct GB* gba, char* out); +void GBTestKeypadIRQ(struct GB* gb); + void GBFrameEnded(struct GB* gb); CXX_GUARD_END
M include/mgba/internal/gb/mbc.hinclude/mgba/internal/gb/mbc.h

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

void GBMBCRTCRead(struct GB* gb); void GBMBCRTCWrite(struct GB* gb); -uint8_t GBMBC7Read(struct GBMemory*, uint16_t address); void GBMBC7Write(struct GBMemory*, uint16_t address, uint8_t value); CXX_GUARD_END
M include/mgba/internal/gb/memory.hinclude/mgba/internal/gb/memory.h

@@ -63,24 +63,27 @@ GB_SRAM_DIRT_SEEN = 2

}; struct GBMemory; -typedef void (*GBMemoryBankController)(struct GB*, uint16_t address, uint8_t value); +typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value); +typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address); DECL_BITFIELD(GBMBC7Field, uint8_t); -DECL_BIT(GBMBC7Field, SK, 6); DECL_BIT(GBMBC7Field, CS, 7); -DECL_BIT(GBMBC7Field, IO, 1); +DECL_BIT(GBMBC7Field, CLK, 6); +DECL_BIT(GBMBC7Field, DI, 1); +DECL_BIT(GBMBC7Field, DO, 0); enum GBMBC7MachineState { - GBMBC7_STATE_NULL = -1, GBMBC7_STATE_IDLE = 0, GBMBC7_STATE_READ_COMMAND = 1, - GBMBC7_STATE_READ_ADDRESS = 2, - GBMBC7_STATE_COMMAND_0 = 3, - GBMBC7_STATE_COMMAND_SR_WRITE = 4, - GBMBC7_STATE_COMMAND_SR_READ = 5, - GBMBC7_STATE_COMMAND_SR_FILL = 6, - GBMBC7_STATE_READ = 7, - GBMBC7_STATE_WRITE = 8, + GBMBC7_STATE_DO = 2, + + GBMBC7_STATE_EEPROM_EWDS = 0x10, + GBMBC7_STATE_EEPROM_WRAL = 0x11, + GBMBC7_STATE_EEPROM_ERAL = 0x12, + GBMBC7_STATE_EEPROM_EWEN = 0x13, + GBMBC7_STATE_EEPROM_WRITE = 0x14, + GBMBC7_STATE_EEPROM_READ = 0x18, + GBMBC7_STATE_EEPROM_ERASE = 0x1C, }; struct GBMBC1State {

@@ -90,17 +93,23 @@ };

struct GBMBC7State { enum GBMBC7MachineState state; - uint32_t sr; + uint16_t sr; uint8_t address; bool writable; int srBits; - int command; - GBMBC7Field field; + uint8_t access; + uint8_t latch; + GBMBC7Field eeprom; +}; + +struct GBPocketCamState { + bool registersActive; }; union GBMBCState { struct GBMBC1State mbc1; struct GBMBC7State mbc7; + struct GBPocketCamState pocketCam; }; struct mRotationSource;

@@ -109,7 +118,8 @@ uint8_t* rom;

uint8_t* romBase; uint8_t* romBank; enum GBMemoryBankControllerType mbcType; - GBMemoryBankController mbc; + GBMemoryBankControllerWrite mbcWrite; + GBMemoryBankControllerRead mbcRead; union GBMBCState mbcState; int currentBank;
M include/mgba/internal/gb/overrides.hinclude/mgba/internal/gb/overrides.h

@@ -16,6 +16,8 @@ struct GBCartridgeOverride {

int headerCrc32; enum GBModel model; enum GBMemoryBankControllerType mbc; + + uint32_t gbColors[4]; }; struct Configuration;
M include/mgba/internal/gb/serialize.hinclude/mgba/internal/gb/serialize.h

@@ -154,8 +154,7 @@ * | bit 3: IME

* | bit 4: Is HDMA active? * | bits 5 - 7: Active RTC register * | 0x00196 - 0x00197: Reserved (leave zero) - * 0x00198 - 0x0019F: Savestate creation time (usec since 1970) - * 0x001A0 - 0x0025F: Reserved (leave zero) + * 0x00198 - 0x0025F: Reserved (leave zero) * 0x00260 - 0x002FF: OAM * 0x00300 - 0x0037F: I/O memory * 0x00380 - 0x003FE: HRAM

@@ -354,9 +353,7 @@ GBSerializedMemoryFlags flags;

uint16_t reserved; } memory; - uint64_t creationUsec; - - uint32_t reserved[48]; + uint32_t reserved[50]; uint8_t oam[GB_SIZE_OAM];
M include/mgba/internal/gb/video.hinclude/mgba/internal/gb/video.h

@@ -144,7 +144,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value);

void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value); void GBVideoSwitchBank(struct GBVideo* video, uint8_t value); -void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color); +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color); struct GBSerializedState; void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state);
M include/mgba/internal/gba/memory.hinclude/mgba/internal/gba/memory.h

@@ -132,10 +132,6 @@ char waitstatesSeq32[256];

char waitstatesSeq16[256]; char waitstatesNonseq32[256]; char waitstatesNonseq16[256]; - char waitstatesPrefetchSeq32[16]; - char waitstatesPrefetchSeq16[16]; - char waitstatesPrefetchNonseq32[16]; - char waitstatesPrefetchNonseq16[16]; int activeRegion; bool prefetch; uint32_t lastPrefetchedPc;
M include/mgba/internal/gba/serialize.hinclude/mgba/internal/gba/serialize.h

@@ -190,8 +190,7 @@ * | 0x002F4 - 0x002F7: GBA BIOS bus prefetch

* | 0x002F8 - 0x002FB: CPU prefecth (decode slot) * | 0x002FC - 0x002FF: CPU prefetch (fetch slot) * 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream) - * 0x00304 - 0x0030F: Reserved (leave zero) - * 0x00310 - 0x00317: Savestate creation time (usec since 1970) + * 0x00304 - 0x00317: Savestate creation time (usec since 1970) * 0x00318 - 0x0031B: Last prefetched program counter * 0x0031C - 0x0031F: Miscellaneous flags * | bit 0: Is CPU halted?

@@ -312,9 +311,7 @@ uint32_t biosPrefetch;

uint32_t cpuPrefetch[2]; uint32_t associatedStreamId; - uint32_t reservedRr[3]; - - uint64_t creationUsec; + uint32_t reservedRr[5]; uint32_t lastPrefetchedPc; GBASerializedMiscFlags miscFlags;
M include/mgba/internal/lr35902/debugger/debugger.hinclude/mgba/internal/lr35902/debugger/debugger.h

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

CXX_GUARD_START -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> #include <mgba/internal/lr35902/lr35902.h> +#include <mgba-util/vector.h> struct LR35902DebugBreakpoint {
M res/shaders/agb001.shader/manifest.inires/shaders/agb001.shader/manifest.ini

@@ -7,5 +7,5 @@

[pass.0] fragmentShader=agb001.fs blend=1 -width=960 -height=640 +width=-4 +height=-4
M res/shaders/ags001.shader/manifest.inires/shaders/ags001.shader/manifest.ini

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

[pass.0] fragmentShader=ags001.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 [pass.1] fragmentShader=ags001-light.fs -width=960 -height=640 +width=-4 +height=-4 [pass.1.uniform.lightBrightness] type=float
M res/shaders/fish.shader/fish.fsres/shaders/fish.shader/fish.fs

@@ -23,19 +23,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

THE SOFTWARE. */ -precision highp float; - varying vec2 texCoord; uniform sampler2D tex; uniform vec2 texSize; uniform float similarity_threshold; -#define screen_res 240,160 - vec4 texel_fetch(sampler2D t, ivec2 c) // because GLSL TexelFetch is not supported { - return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * vec2(screen_res)) ); + return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * texSize) ); } float pixel_brightness(vec4 pixel)

@@ -140,7 +136,7 @@ }

void main() { - ivec2 pixel_coords2 = ivec2(texCoord * vec2(screen_res) * 2); + ivec2 pixel_coords2 = ivec2(texCoord * texSize * 2); ivec2 pixel_coords = pixel_coords2 / 2; bool x_even = mod(pixel_coords2.x,2) == 0;
A res/shaders/lcd.shader/lcd.fs

@@ -0,0 +1,40 @@

+/* + LCD Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float boundBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.s * texSize.x * 3.0, 3.0)) == 0 || + int(mod(texCoord.t * texSize.y * 3.0, 3.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness; + } + + gl_FragColor = color; +}
A res/shaders/lcd.shader/manifest.ini

@@ -0,0 +1,18 @@

+[shader] +name=LCD +author=Dominus Iniquitatis +description=Simple LCD emulation. +passes=1 + +[pass.0] +fragmentShader=lcd.fs +blend=1 +width=-3 +height=-3 + +[pass.0.uniform.boundBrightness] +type=float +readableName=Bound brightness +default=0.9 +min=0.0 +max=1.0
A res/shaders/motion_blur.shader/manifest.ini

@@ -0,0 +1,18 @@

+[shader] +name=Motion Blur +author=Dominus Iniquitatis +description=Simple motion blur. +passes=1 + +[pass.0] +fragmentShader=motion_blur.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.amount] +type=float +readableName=Amount +default=0.3 +min=0.0 +max=1.0
A res/shaders/motion_blur.shader/motion_blur.fs

@@ -0,0 +1,35 @@

+/* + Motion Blur Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float amount; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + color.a = 1.0 - amount; + + gl_FragColor = color; +}
A res/shaders/scanlines.shader/manifest.ini

@@ -0,0 +1,18 @@

+[shader] +name=Scanlines +author=Dominus Iniquitatis +description=Simple scanlines. +passes=1 + +[pass.0] +fragmentShader=scanlines.fs +blend=1 +width=-2 +height=-2 + +[pass.0.uniform.lineBrightness] +type=float +readableName=Line brightness +default=0.5 +min=0.0 +max=1.0
A res/shaders/scanlines.shader/scanlines.fs

@@ -0,0 +1,39 @@

+/* + Scanlines Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float lineBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * lineBrightness; + } + + gl_FragColor = color; +}
A res/shaders/soften.shader/manifest.ini

@@ -0,0 +1,18 @@

+[shader] +name=Soften +author=Dominus Iniquitatis +description=Soft image blurring. +passes=1 + +[pass.0] +fragmentShader=soften.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.amount] +type=float +readableName=Amount +default=0.5 +min=0.0 +max=1.0
A res/shaders/soften.shader/soften.fs

@@ -0,0 +1,64 @@

+/* + Soften Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float amount; + +vec2 GetTexelSize() +{ + return vec2(1.0 / texSize.x, 1.0 / texSize.y); +} + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + vec4 northColor = texture2D(tex, texCoord + vec2(0.0, GetTexelSize().y)); + vec4 southColor = texture2D(tex, texCoord - vec2(0.0, GetTexelSize().y)); + vec4 eastColor = texture2D(tex, texCoord + vec2(GetTexelSize().x, 0.0)); + vec4 westColor = texture2D(tex, texCoord - vec2(GetTexelSize().x, 0.0)); + + if (abs(length(color) - length(northColor)) > 0.0) + { + color = mix(color, northColor, amount / 4.0); + } + + if (abs(length(color) - length(southColor)) > 0.0) + { + color = mix(color, southColor, amount / 4.0); + } + + if (abs(length(color) - length(eastColor)) > 0.0) + { + color = mix(color, eastColor, amount / 4.0); + } + + if (abs(length(color) - length(westColor)) > 0.0) + { + color = mix(color, westColor, amount / 4.0); + } + + gl_FragColor = color; +}
A res/shaders/vba_pixelate.shader/manifest.ini

@@ -0,0 +1,18 @@

+[shader] +name=VBA Pixelate +author=Dominus Iniquitatis +description=VisualBoyAdvance-style pixelation. +passes=1 + +[pass.0] +fragmentShader=vba_pixelate.fs +blend=1 +width=-2 +height=-2 + +[pass.0.uniform.boundBrightness] +type=float +readableName=Bound brightness +default=0.5 +min=0.0 +max=1.0
A res/shaders/vba_pixelate.shader/vba_pixelate.fs

@@ -0,0 +1,40 @@

+/* + VBA Pixelate Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float boundBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.s * texSize.x * 2.0, 2.0)) == 0 || + int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness; + } + + gl_FragColor = color; +}
A res/shaders/vignette.shader/manifest.ini

@@ -0,0 +1,18 @@

+[shader] +name=Vignette +author=Dominus Iniquitatis +description=Configurable vignette effect. +passes=1 + +[pass.0] +fragmentShader=vignette.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.intensity] +type=float +readableName=Intensity +default=1.0 +min=0.0 +max=1.0
A res/shaders/vignette.shader/vignette.fs

@@ -0,0 +1,35 @@

+/* + Vignette Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float intensity; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), length(texCoord - 0.5) * intensity); + + gl_FragColor = color; +}
M res/shaders/wiiu.shader/manifest.inires/shaders/wiiu.shader/manifest.ini

@@ -7,5 +7,5 @@

[pass.0] fragmentShader=wiiu.fs blend=1 -width=960 -height=640 +width=-4 +height=-4
M src/arm/debugger/debugger.csrc/arm/debugger/debugger.c

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

#include <mgba/core/core.h> #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>

@@ -54,6 +55,7 @@ static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type);

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); struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct ARMDebugger));

@@ -66,6 +68,7 @@ platform->setWatchpoint = ARMDebuggerSetWatchpoint;

platform->clearWatchpoint = ARMDebuggerClearWatchpoint; platform->checkBreakpoints = ARMDebuggerCheckBreakpoints; platform->hasBreakpoints = ARMDebuggerHasBreakpoints; + platform->trace = ARMDebuggerTrace; return platform; }

@@ -207,3 +210,39 @@ if (!ARMDebugWatchpointListSize(&debugger->watchpoints)) {

ARMDebuggerRemoveMemoryShim(debugger); } } + +static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + + char disassembly[64]; + + struct ARMInstructionInfo info; + if (cpu->executionMode == MODE_ARM) { + uint32_t instruction = cpu->prefetch[0]; + sprintf(disassembly, "%08X: ", instruction); + ARMDecodeARM(instruction, &info); + ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + } else { + struct ARMInstructionInfo info2; + struct ARMInstructionInfo combined; + uint16_t instruction = cpu->prefetch[0]; + uint16_t instruction2 = cpu->prefetch[1]; + ARMDecodeThumb(instruction, &info); + ARMDecodeThumb(instruction2, &info2); + if (ARMDecodeThumbCombine(&info, &info2, &combined)) { + sprintf(disassembly, "%04X%04X: ", instruction, instruction2); + ARMDisassemble(&combined, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + } else { + sprintf(disassembly, " %04X: ", instruction); + ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + } + } + + *length = snprintf(out, *length, "%08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X cpsr: %08X | %s", + cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3], + cpu->gprs[4], cpu->gprs[5], cpu->gprs[6], cpu->gprs[7], + 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); +}
M src/core/config.csrc/core/config.c

@@ -86,6 +86,9 @@ return false;

} char* end; long value = strtol(charValue, &end, 10); + if (end == &charValue[1] && *end == 'x') { + value = strtol(charValue, &end, 16); + } if (*end) { return false; }
M src/core/library.csrc/core/library.c

@@ -339,6 +339,16 @@ sqlite3_bind_text(library->deletePath, 1, entry->filename, -1, SQLITE_TRANSIENT);

sqlite3_step(library->insertPath); } +void mLibraryClear(struct mLibrary* library) { + int result = sqlite3_exec(library->db, + " BEGIN TRANSACTION;" + "\n DELETE FROM roots;" + "\n DELETE FROM roms;" + "\n DELETE FROM paths;" + "\n COMMIT;" + "\n VACUUM;", NULL, NULL, NULL); +} + size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) { sqlite3_clear_bindings(library->count); sqlite3_reset(library->count);
A src/core/mem-search.c

@@ -0,0 +1,496 @@

+/* 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/core/mem-search.h> + +#include <mgba/core/core.h> +#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) { + 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)) { + 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->segment = -1; // TODO + res->guessDivisor = 1; + ++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) { + 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)) { + 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->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; + ++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) { + 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)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++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; + } + } + // TODO: last 7 bytes + return found; +} + +static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, 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)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i; + res->type = mCORE_MEMORY_SEARCH_STRING; + res->segment = -1; // TODO + ++found; + } + } + 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) { + // TODO: As str + + char* end; + uint64_t value; + + size_t found = 0; + + struct mCoreMemorySearchResults tmp; + mCoreMemorySearchResultsInit(&tmp, 0); + + // Decimal: + value = strtoull(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); + } else { + found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + } + + uint32_t divisor = 1; + while (value && !(value % 10)) { + 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); + } else { + found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + } + size_t i; + for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i); + res->guessDivisor = divisor; + *mCoreMemorySearchResultsAppend(out) = *res; + } + } + } + + // Hex: + value = strtoull(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); + } else { + found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + } + + uint32_t divisor = 1; + while (value && !(value & 0xF)) { + 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); + } else { + found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + } + size_t i; + for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i); + res->guessDivisor = divisor; + *mCoreMemorySearchResultsAppend(out) = *res; + } + } + } + + mCoreMemorySearchResultsDeinit(&tmp); + return found; +} + +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_STRING: + return _searchStr(mem, size, block, params->valueStr, out, limit); + case mCORE_MEMORY_SEARCH_GUESS: + return _searchGuess(mem, size, block, params->valueStr, out, limit); + } +} + +void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { + const struct mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + size_t found = 0; + + size_t b; + for (b = 0; (!limit || found < limit) && b < nBlocks; ++b) { + size_t size; + const struct mCoreMemoryBlock* block = &blocks[b]; + if (!(block->flags & params->memoryFlags)) { + continue; + } + void* mem = core->getMemoryBlock(core, block->id, &size); + if (!mem) { + continue; + } + if (size > block->end - block->start) { + size = block->end - block->start; // TOOD: Segments + } + 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; + char* end; + + value = strtoull(params->valueStr, &end, 10); + if (end) { + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + } + + value = strtoull(params->valueStr, &end, 16); + if (end) { + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + } + return false; +} + +void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout) { + 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: + if (!_testGuess(core, res, params)) { + mCoreMemorySearchResultsShift(inout, i, 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; + } + 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: + if (!_testGuess(core, res, params)) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + 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: + case mCORE_MEMORY_SEARCH_GUESS: + // TOOD + break; + } + } +}
M src/core/serialize.csrc/core/serialize.c

@@ -303,6 +303,36 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {

struct mStateExtdata extdata; mStateExtdataInit(&extdata); size_t stateSize = core->stateSize(core); + + if (flags & SAVESTATE_METADATA) { + uint64_t creationUsec; +#ifndef _MSC_VER + struct timeval tv; + if (!gettimeofday(&tv, 0)) { + uint64_t usec = tv.tv_usec; + usec += tv.tv_sec * 1000000LL; + STORE_64LE(usec, 0, &creationUsec); + } +#else + struct timespec ts; + if (timespec_get(&ts, TIME_UTC)) { + uint64_t usec = ts.tv_nsec / 1000; + usec += ts.tv_sec * 1000000LL; + STORE_64LE(usec, 0, &creationUsec); + } +#endif + else { + creationUsec = 0; + } + + struct mStateExtdataItem item = { + .size = sizeof(creationUsec), + .data = &creationUsec, + .clean = NULL + }; + mStateExtdataPut(&extdata, EXTDATA_META_TIME, &item); + } + if (flags & SAVESTATE_SAVEDATA) { void* sram = NULL; size_t size = core->savedataClone(core, &sram);
M src/core/timing.csrc/core/timing.c

@@ -89,5 +89,9 @@ struct mTimingEvent* next = timing->root;

if (!next) { return INT_MAX; } - return next->when - timing->masterCycles; + return next->when - timing->masterCycles - *timing->relativeCycles; +} + +int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent* event) { + return event->when - timing->masterCycles - *timing->relativeCycles; }
M src/debugger/cli-debugger.csrc/debugger/cli-debugger.c

@@ -44,6 +44,7 @@ static void _clearBreakpoint(struct CLIDebugger*, struct CLIDebugVector*);

static void _setWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); static void _setReadWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); 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 _writeWord(struct CLIDebugger*, struct CLIDebugVector*);

@@ -80,6 +81,7 @@ { "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" },

@@ -466,6 +468,27 @@ uint32_t address = dv->intValue;

debugger->d.platform->clearBreakpoint(debugger->d.platform, address, dv->segmentValue); if (debugger->d.platform->clearWatchpoint) { debugger->d.platform->clearWatchpoint(debugger->d.platform, address, dv->segmentValue); + } +} + +static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + return; + } + + char trace[1024]; + trace[sizeof(trace) - 1] = '\0'; + + int i; + for (i = 0; i < dv->intValue; ++i) { + debugger->d.core->step(debugger->d.core); + size_t traceSize = sizeof(trace) - 1; + debugger->d.platform->trace(debugger->d.platform, trace, &traceSize); + if (traceSize + 1 < sizeof(trace)) { + trace[traceSize + 1] = '\0'; + } + debugger->backend->printf(debugger->backend, "%s\n", trace); } }
M src/debugger/debugger.csrc/debugger/debugger.c

@@ -3,7 +3,7 @@ *

* 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/debugger/debugger.h> +#include <mgba/debugger/debugger.h> #include <mgba/core/core.h>

@@ -95,6 +95,13 @@ break;

case DEBUGGER_SHUTDOWN: return; } +} + +void mDebuggerRunFrame(struct mDebugger* debugger) { + int32_t frame = debugger->core->frameCounter(debugger->core); + do { + mDebuggerRun(debugger); + } while (debugger->core->frameCounter(debugger->core) == frame); } void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
M src/feature/gui/gui-runner.csrc/feature/gui/gui-runner.c

@@ -431,7 +431,7 @@ case RUNNER_RESET:

runner->core->reset(runner->core); break; case RUNNER_SAVE_STATE: - mCoreSaveState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA); + mCoreSaveState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); break; case RUNNER_LOAD_STATE: mCoreLoadState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_RTC);
M src/gb/audio.csrc/gb/audio.c

@@ -337,6 +337,7 @@ }

} audio->ch3.window = 0; } + mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingDeschedule(audio->timing, &audio->ch3Event); if (audio->playingCh3) { audio->ch3.readable = audio->style != GB_AUDIO_DMG;

@@ -554,7 +555,7 @@ _updateEnvelope(&audio->ch2.envelope);

if (audio->ch2.envelope.dead == 2) { mTimingDeschedule(timing, &audio->ch2Event); } - _updateSquareSample(&audio->ch1); + _updateSquareSample(&audio->ch2); } }

@@ -708,7 +709,7 @@ return (envelope->initialVolume || envelope->direction) && envelope->dead != 2;

} static void _updateSquareSample(struct GBAudioSquareChannel* ch) { - ch->sample = (ch->control.hi * ch->envelope.currentVolume - 8) * 0x10; + ch->sample = (ch->control.hi * 2 - 1) * ch->envelope.currentVolume * 0x8; } static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {

@@ -863,6 +864,7 @@ ch->sample -= 8;

ch->sample *= volume * 4; audio->ch3.readable = true; if (audio->style == GB_AUDIO_DMG) { + mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingSchedule(timing, &audio->ch3Fade, 2 - cyclesLate); } int cycles = 2 * (2048 - ch->rate);
M src/gb/core.csrc/gb/core.c

@@ -56,6 +56,28 @@ { .name = "VRAM", .start = GB_BASE_VRAM, .end = GB_BASE_EXTERNAL_RAM },

{ 0 } }; +const static struct mCoreMemoryBlock _GBMemoryBlocks[] = { + { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, + { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, + { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 }, + { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2 , GB_SIZE_WORKING_RAM_BANK0 * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, +}; + +const static struct mCoreMemoryBlock _GBCMemoryBlocks[] = { + { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, + { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, + { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, + { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 }, + { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2, GB_SIZE_WORKING_RAM_BANK0 * 8, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 7 }, + { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, +}; + struct mVideoLogContext; struct GBCore { struct mCore d;

@@ -419,11 +441,13 @@

static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) { struct GBCore* gbcore = (struct GBCore*) core; gbcore->keys = keys; + GBTestKeypadIRQ(core->board); } static void _GBCoreAddKeys(struct mCore* core, uint32_t keys) { struct GBCore* gbcore = (struct GBCore*) core; gbcore->keys |= keys; + GBTestKeypadIRQ(core->board); } static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) {

@@ -539,6 +563,48 @@ GBPatch8(cpu, address + 2, value >> 16, NULL, segment);

GBPatch8(cpu, address + 3, value >> 24, NULL, segment); } +size_t _GBListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) { + const struct GB* gb = core->board; + switch (gb->model) { + case GB_MODEL_DMG: + case GB_MODEL_SGB: + default: + *blocks = _GBMemoryBlocks; + return sizeof(_GBMemoryBlocks) / sizeof(*_GBMemoryBlocks); + case GB_MODEL_CGB: + case GB_MODEL_AGB: + *blocks = _GBCMemoryBlocks; + return sizeof(_GBCMemoryBlocks) / sizeof(*_GBCMemoryBlocks); + } +} + +void* _GBGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) { + struct GB* gb = core->board; + bool isCgb = gb->model >= GB_MODEL_CGB; + switch (id) { + default: + return NULL; + case GB_REGION_CART_BANK0: + *sizeOut = gb->memory.romSize; + return gb->memory.rom; + case GB_REGION_VRAM: + *sizeOut = GB_SIZE_WORKING_RAM_BANK0 * (isCgb ? 1 : 2); + return gb->video.vram; + case GB_REGION_EXTERNAL_RAM: + *sizeOut = gb->sramSize; + return gb->memory.sram; + case GB_REGION_WORKING_RAM_BANK0: + *sizeOut = GB_SIZE_VRAM * (isCgb ? 8 : 2); + return gb->memory.wram; + case GB_BASE_OAM: + *sizeOut = GB_SIZE_OAM; + return gb->video.oam.raw; + case GB_BASE_HRAM: + *sizeOut = GB_SIZE_HRAM; + return gb->memory.hram; + } +} + #ifdef USE_DEBUGGERS static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) { UNUSED(core);

@@ -771,6 +837,8 @@ core->rawRead32 = _GBCoreRawRead32;

core->rawWrite8 = _GBCoreRawWrite8; core->rawWrite16 = _GBCoreRawWrite16; core->rawWrite32 = _GBCoreRawWrite32; + core->listMemoryBlocks = _GBListMemoryBlocks; + core->getMemoryBlock = _GBGetMemoryBlock; #ifdef USE_DEBUGGERS core->supportsDebuggerType = _GBCoreSupportsDebuggerType; core->debuggerPlatform = _GBCoreDebuggerPlatform;
M src/gb/debugger/cli.csrc/gb/debugger/cli.c

@@ -118,5 +118,5 @@ }

struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; - mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); + mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC | SAVESTATE_METADATA); }
M src/gb/gb.csrc/gb/gb.c

@@ -433,6 +433,7 @@ cpu->c = 0x13;

cpu->e = 0xD8; cpu->h = 1; cpu->l = 0x4D; + gb->timer.internalDiv = 0x2AF3; break; case GB_MODEL_AGB: cpu->b = 1;

@@ -444,6 +445,7 @@ cpu->c = 0;

cpu->e = 0x08; cpu->h = 0; cpu->l = 0x7C; + gb->timer.internalDiv = 0x7A8; break; }

@@ -710,4 +712,6 @@ struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);

mCheatRefresh(device, cheats); } } + + GBTestKeypadIRQ(gb); }
M src/gb/io.csrc/gb/io.c

@@ -564,6 +564,13 @@ success:

return gb->memory.io[address] | _registerMask[address]; } +void GBTestKeypadIRQ(struct GB* gb) { + if (_readKeys(gb)) { + gb->memory.io[REG_IF] |= (1 << GB_IRQ_KEYPAD); + GBUpdateIRQs(gb); + } +} + struct GBSerializedState; void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state) { memcpy(state->io, gb->memory.io, GB_SIZE_IO);
M src/gb/mbc.csrc/gb/mbc.c

@@ -28,6 +28,10 @@ static void _GBMBC5(struct GB*, uint16_t address, uint8_t value);

static void _GBMBC6(struct GB*, uint16_t address, uint8_t value); static void _GBMBC7(struct GB*, uint16_t address, uint8_t value); static void _GBHuC3(struct GB*, uint16_t address, uint8_t value); +static void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value); + +static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); +static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address); void GBMBCSwitchBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_CART_BANK0;

@@ -75,7 +79,11 @@ }

void GBMBCSwitchSramBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM; - GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM); + if (bankStart + GB_SIZE_EXTERNAL_RAM > gb->sramSize) { + mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid RAM bank: %0X", bank); + bankStart &= (gb->sramSize - 1); + bank = bankStart / GB_SIZE_EXTERNAL_RAM; + } gb->memory.sramBank = &gb->memory.sram[bankStart]; gb->memory.sramCurrentBank = bank; }

@@ -148,6 +156,12 @@ break;

case 0x22: gb->memory.mbcType = GB_MBC7; break; + case 0xFC: + gb->memory.mbcType = GB_POCKETCAM; + break; + case 0xFD: + gb->memory.mbcType = GB_HuC1; + break; case 0xFE: gb->memory.mbcType = GB_HuC3; break;

@@ -156,51 +170,57 @@ }

} else { gb->memory.mbcType = GB_MBC_NONE; } + gb->memory.mbcRead = NULL; switch (gb->memory.mbcType) { case GB_MBC_NONE: - gb->memory.mbc = _GBMBCNone; + gb->memory.mbcWrite = _GBMBCNone; break; case GB_MBC1: - gb->memory.mbc = _GBMBC1; + gb->memory.mbcWrite = _GBMBC1; break; case GB_MBC2: - gb->memory.mbc = _GBMBC2; + gb->memory.mbcWrite = _GBMBC2; gb->sramSize = 0x200; break; case GB_MBC3: - gb->memory.mbc = _GBMBC3; + gb->memory.mbcWrite = _GBMBC3; break; default: mLOG(GB_MBC, WARN, "Unknown MBC type: %02X", cart->type); // Fall through case GB_MBC5: - gb->memory.mbc = _GBMBC5; + gb->memory.mbcWrite = _GBMBC5; break; case GB_MBC6: mLOG(GB_MBC, WARN, "unimplemented MBC: MBC6"); - gb->memory.mbc = _GBMBC6; + gb->memory.mbcWrite = _GBMBC6; break; case GB_MBC7: - gb->memory.mbc = _GBMBC7; - gb->sramSize = GB_SIZE_EXTERNAL_RAM; + gb->memory.mbcWrite = _GBMBC7; + gb->memory.mbcRead = _GBMBC7Read; + gb->sramSize = 0x100; break; case GB_MMM01: mLOG(GB_MBC, WARN, "unimplemented MBC: MMM01"); - gb->memory.mbc = _GBMBC1; + gb->memory.mbcWrite = _GBMBC1; break; case GB_HuC1: mLOG(GB_MBC, WARN, "unimplemented MBC: HuC-1"); - gb->memory.mbc = _GBMBC1; + gb->memory.mbcWrite = _GBMBC1; break; case GB_HuC3: - gb->memory.mbc = _GBHuC3; + gb->memory.mbcWrite = _GBHuC3; break; case GB_MBC3_RTC: memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); - gb->memory.mbc = _GBMBC3; + gb->memory.mbcWrite = _GBMBC3; break; case GB_MBC5_RUMBLE: - gb->memory.mbc = _GBMBC5; + gb->memory.mbcWrite = _GBMBC5; + break; + case GB_POCKETCAM: + gb->memory.mbcWrite = _GBPocketCam; + gb->memory.mbcRead = _GBPocketCamRead; break; }

@@ -350,7 +370,8 @@ default:

// TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value); break; - }} + } +} void _GBMBC3(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory;

@@ -451,12 +472,25 @@

void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { int bank = value & 0x7F; switch (address >> 13) { + case 0x0: + switch (value) { + default: + case 0: + gb->memory.mbcState.mbc7.access = 0; + break; + case 0xA: + gb->memory.mbcState.mbc7.access |= 1; + break; + } + break; case 0x1: GBMBCSwitchBank(gb, bank); break; case 0x2: - if (value < 0x10) { - GBMBCSwitchSramBank(gb, value); + if (value == 0x40) { + gb->memory.mbcState.mbc7.access |= 2; + } else { + gb->memory.mbcState.mbc7.access &= ~2; } break; default:

@@ -466,19 +500,17 @@ break;

} } -uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) { +uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) { struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; + if (mbc7->access != 3) { + return 0xFF; + } switch (address & 0xF0) { - case 0x00: - case 0x10: - case 0x60: - case 0x70: - return 0; case 0x20: if (memory->rotation && memory->rotation->readTiltX) { int32_t x = -memory->rotation->readTiltX(memory->rotation); x >>= 21; - x += 2047; + x += 0x81D0; return x; } return 0xFF;

@@ -486,7 +518,7 @@ case 0x30:

if (memory->rotation && memory->rotation->readTiltX) { int32_t x = -memory->rotation->readTiltX(memory->rotation); x >>= 21; - x += 2047; + x += 0x81D0; return x >> 8; } return 7;

@@ -494,7 +526,7 @@ case 0x40:

if (memory->rotation && memory->rotation->readTiltY) { int32_t y = -memory->rotation->readTiltY(memory->rotation); y >>= 21; - y += 2047; + y += 0x81D0; return y; } return 0xFF;

@@ -502,144 +534,142 @@ case 0x50:

if (memory->rotation && memory->rotation->readTiltY) { int32_t y = -memory->rotation->readTiltY(memory->rotation); y >>= 21; - y += 2047; + y += 0x81D0; return y >> 8; } return 7; + case 0x60: + return 0; case 0x80: - return (mbc7->sr >> 16) & 1; + return mbc7->eeprom; default: return 0xFF; } } void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { - if ((address & 0xF0) != 0x80) { + struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; + if (mbc7->access != 3) { return; } - struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; - GBMBC7Field old = memory->mbcState.mbc7.field; - mbc7->field = GBMBC7FieldClearIO(value); + switch (address & 0xF0) { + case 0x00: + mbc7->latch = (value & 0x55) == 0x55; + return; + case 0x10: + mbc7->latch |= (value & 0xAA); + if (mbc7->latch == 0xFF && memory->rotation && memory->rotation->sample) { + memory->rotation->sample(memory->rotation); + } + mbc7->latch = 0; + return; + default: + mLOG(GB_MBC, STUB, "MBC7 unknown register: %04X:%02X", address, value); + return; + case 0x80: + break; + } + GBMBC7Field old = memory->mbcState.mbc7.eeprom; + value = GBMBC7FieldFillDO(value); // Hi-Z if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) { - if (mbc7->state == GBMBC7_STATE_WRITE) { - if (mbc7->writable) { - memory->sramBank[mbc7->address * 2] = mbc7->sr >> 8; - memory->sramBank[mbc7->address * 2 + 1] = mbc7->sr; - } - mbc7->sr = 0x1FFFF; - mbc7->state = GBMBC7_STATE_NULL; - } else { - mbc7->state = GBMBC7_STATE_IDLE; - } + mbc7->state = GBMBC7_STATE_IDLE; } - if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) { - if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) { + if (!GBMBC7FieldIsCLK(old) && GBMBC7FieldIsCLK(value)) { + if (mbc7->state == GBMBC7_STATE_READ_COMMAND || mbc7->state == GBMBC7_STATE_EEPROM_WRITE || mbc7->state == GBMBC7_STATE_EEPROM_WRAL) { mbc7->sr <<= 1; - mbc7->sr |= GBMBC7FieldGetIO(value); + mbc7->sr |= GBMBC7FieldGetDI(value); ++mbc7->srBits; } switch (mbc7->state) { case GBMBC7_STATE_IDLE: - if (GBMBC7FieldIsIO(value)) { + if (GBMBC7FieldIsDI(value)) { mbc7->state = GBMBC7_STATE_READ_COMMAND; mbc7->srBits = 0; mbc7->sr = 0; } break; case GBMBC7_STATE_READ_COMMAND: - if (mbc7->srBits == 2) { - mbc7->state = GBMBC7_STATE_READ_ADDRESS; + if (mbc7->srBits == 10) { + mbc7->state = 0x10 | (mbc7->sr >> 6); + if (mbc7->state & 0xC) { + mbc7->state &= ~0x3; + } mbc7->srBits = 0; - mbc7->command = mbc7->sr; + mbc7->address = mbc7->sr & 0x7F; } break; - case GBMBC7_STATE_READ_ADDRESS: - if (mbc7->srBits == 8) { - mbc7->state = GBMBC7_STATE_COMMAND_0 + mbc7->command; - mbc7->srBits = 0; - mbc7->address = mbc7->sr; - if (mbc7->state == GBMBC7_STATE_COMMAND_0) { - switch (mbc7->address >> 6) { - case 0: - mbc7->writable = false; - mbc7->state = GBMBC7_STATE_NULL; - break; - case 3: - mbc7->writable = true; - mbc7->state = GBMBC7_STATE_NULL; - break; - } - } + case GBMBC7_STATE_DO: + value = GBMBC7FieldSetDO(value, mbc7->sr >> 15); + mbc7->sr <<= 1; + --mbc7->srBits; + if (!mbc7->srBits) { + mbc7->state = GBMBC7_STATE_IDLE; } break; - case GBMBC7_STATE_COMMAND_0: + default: + break; + } + switch (mbc7->state) { + case GBMBC7_STATE_EEPROM_EWEN: + mbc7->writable = true; + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_EWDS: + mbc7->writable = false; + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_WRITE: if (mbc7->srBits == 16) { - switch (mbc7->address >> 6) { - case 0: - mbc7->writable = false; - mbc7->state = GBMBC7_STATE_NULL; - break; - case 1: - mbc7->state = GBMBC7_STATE_WRITE; - if (mbc7->writable) { - int i; - for (i = 0; i < 256; ++i) { - memory->sramBank[i * 2] = mbc7->sr >> 8; - memory->sramBank[i * 2 + 1] = mbc7->sr; - } - } - break; - case 2: - mbc7->state = GBMBC7_STATE_WRITE; - if (mbc7->writable) { - int i; - for (i = 0; i < 256; ++i) { - memory->sramBank[i * 2] = 0xFF; - memory->sramBank[i * 2 + 1] = 0xFF; - } - } - break; - case 3: - mbc7->writable = true; - mbc7->state = GBMBC7_STATE_NULL; - break; + if (mbc7->writable) { + memory->sram[mbc7->address * 2] = mbc7->sr >> 8; + memory->sram[mbc7->address * 2 + 1] = mbc7->sr; } + mbc7->state = GBMBC7_STATE_IDLE; } break; - case GBMBC7_STATE_COMMAND_SR_WRITE: - if (mbc7->srBits == 16) { - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_WRITE; + case GBMBC7_STATE_EEPROM_ERASE: + if (mbc7->writable) { + memory->sram[mbc7->address * 2] = 0xFF; + memory->sram[mbc7->address * 2 + 1] = 0xFF; } + mbc7->state = GBMBC7_STATE_IDLE; break; - case GBMBC7_STATE_COMMAND_SR_READ: - if (mbc7->srBits == 1) { - mbc7->sr = memory->sramBank[mbc7->address * 2] << 8; - mbc7->sr |= memory->sramBank[mbc7->address * 2 + 1]; - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_READ; - } + case GBMBC7_STATE_EEPROM_READ: + mbc7->srBits = 16; + mbc7->sr = memory->sram[mbc7->address * 2] << 8; + mbc7->sr |= memory->sram[mbc7->address * 2 + 1]; + mbc7->state = GBMBC7_STATE_DO; + value = GBMBC7FieldClearDO(value); break; - case GBMBC7_STATE_COMMAND_SR_FILL: + case GBMBC7_STATE_EEPROM_WRAL: if (mbc7->srBits == 16) { - mbc7->sr = 0xFFFF; - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_WRITE; + if (mbc7->writable) { + int i; + for (i = 0; i < 128; ++i) { + memory->sram[i * 2] = mbc7->sr >> 8; + memory->sram[i * 2 + 1] = mbc7->sr; + } + } + mbc7->state = GBMBC7_STATE_IDLE; } break; + case GBMBC7_STATE_EEPROM_ERAL: + if (mbc7->writable) { + int i; + for (i = 0; i < 128; ++i) { + memory->sram[i * 2] = 0xFF; + memory->sram[i * 2 + 1] = 0xFF; + } + } + mbc7->state = GBMBC7_STATE_IDLE; + break; default: break; } - } else if (GBMBC7FieldIsSK(old) && !GBMBC7FieldIsSK(value)) { - if (mbc7->state == GBMBC7_STATE_READ) { - mbc7->sr <<= 1; - ++mbc7->srBits; - if (mbc7->srBits == 16) { - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_NULL; - } - } + } else if (GBMBC7FieldIsCS(value) && GBMBC7FieldIsCLK(old) && !GBMBC7FieldIsCLK(value)) { + value = GBMBC7FieldSetDO(value, GBMBC7FieldGetDO(old)); } + mbc7->eeprom = value; } void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) {

@@ -672,6 +702,52 @@ // TODO

mLOG(GB_MBC, STUB, "HuC-3 unknown address: %04X:%02X", address, value); break; } +} + +void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { + struct GBMemory* memory = &gb->memory; + int bank = value & 0x3F; + switch (address >> 13) { + case 0x0: + switch (value) { + case 0: + memory->sramAccess = false; + break; + case 0xA: + memory->sramAccess = true; + GBMBCSwitchSramBank(gb, memory->sramCurrentBank); + break; + default: + // TODO + mLOG(GB_MBC, STUB, "Pocket Cam unknown value %02X", value); + break; + } + break; + case 0x1: + GBMBCSwitchBank(gb, bank); + break; + case 0x2: + if (value < 0x10) { + GBMBCSwitchSramBank(gb, value); + memory->mbcState.pocketCam.registersActive = false; + } else { + memory->mbcState.pocketCam.registersActive = true; + } + break; + default: + mLOG(GB_MBC, STUB, "Pocket Cam unknown address: %04X:%02X", address, value); + break; + } +} + +uint8_t _GBPocketCamRead(struct GBMemory* memory, uint16_t address) { + if (memory->mbcState.pocketCam.registersActive) { + return 0; + } + if (!memory->sramAccess) { + return 0xFF; + } + return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } void GBMBCRTCRead(struct GB* gb) {
M src/gb/memory.csrc/gb/memory.c

@@ -99,7 +99,8 @@ gb->memory.romBank = 0;

gb->memory.romSize = 0; gb->memory.sram = 0; gb->memory.mbcType = GB_MBC_AUTODETECT; - gb->memory.mbc = 0; + gb->memory.mbcRead = NULL; + gb->memory.mbcWrite = NULL; gb->memory.rtc = NULL;

@@ -215,10 +216,10 @@ case GB_REGION_EXTERNAL_RAM:

case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { return memory->rtcRegs[memory->activeRtcReg]; + } else if (memory->mbcRead) { + return memory->mbcRead(memory, address); } else if (memory->sramAccess) { return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; - } else if (memory->mbcType == GB_MBC7) { - return GBMBC7Read(memory, address); } else if (memory->mbcType == GB_HuC3) { return 0x01; // TODO: Is this supposed to be the current SRAM bank? }

@@ -274,7 +275,7 @@ case GB_REGION_CART_BANK1:

case GB_REGION_CART_BANK1 + 1: case GB_REGION_CART_BANK1 + 2: case GB_REGION_CART_BANK1 + 3: - memory->mbc(gb, address, value); + memory->mbcWrite(gb, address, value); cpu->memory.setActiveRegion(cpu, cpu->pc); return; case GB_REGION_VRAM:

@@ -391,8 +392,8 @@ return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM];

} else { return 0xFF; } - } else if (memory->mbcType == GB_MBC7) { - return GBMBC7Read(memory, address); + } else if (memory->mbcRead) { + return memory->mbcRead(memory, address); } else if (memory->mbcType == GB_HuC3) { return 0x01; // TODO: Is this supposed to be the current SRAM bank? }

@@ -436,6 +437,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) {

if (base > 0xF100) { return; } + mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent); mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8); if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) { gb->cpu->nextEvent = gb->cpu->cycles + 8;
M src/gb/overrides.csrc/gb/overrides.c

@@ -13,7 +13,7 @@ #include <mgba-util/crc32.h>

static const struct GBCartridgeOverride _overrides[] = { // None yet - { 0, 0, 0 } + { 0, 0, 0, { 0 } } }; bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverride* override) {

@@ -35,6 +35,12 @@ char sectionName[24] = "";

snprintf(sectionName, sizeof(sectionName), "gb.override.%08X", override->headerCrc32); const char* model = ConfigurationGetValue(config, sectionName, "model"); const char* mbc = ConfigurationGetValue(config, sectionName, "mbc"); + const char* pal[4] = { + ConfigurationGetValue(config, sectionName, "pal[0]"), + ConfigurationGetValue(config, sectionName, "pal[1]"), + ConfigurationGetValue(config, sectionName, "pal[2]"), + ConfigurationGetValue(config, sectionName, "pal[3]") + }; if (model) { if (strcasecmp(model, "DMG") == 0) {

@@ -63,6 +69,21 @@ override->mbc = type;

found = true; } } + + if (pal[0] && pal[1] && pal[2] && pal[3]) { + int i; + for (i = 0; i < 4; ++i) { + char* end; + unsigned long value = strtoul(pal[i], &end, 10); + if (end == &pal[i][1] && *end == 'x') { + value = strtoul(pal[i], &end, 16); + } + if (*end) { + continue; + } + override->gbColors[i] = value; + } + } } return found; }

@@ -89,6 +110,12 @@ break;

} ConfigurationSetValue(config, sectionName, "model", model); + if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) { + ConfigurationSetIntValue(config, sectionName, "pal[0]", override->gbColors[0]); + ConfigurationSetIntValue(config, sectionName, "pal[1]", override->gbColors[1]); + ConfigurationSetIntValue(config, sectionName, "pal[2]", override->gbColors[2]); + ConfigurationSetIntValue(config, sectionName, "pal[3]", override->gbColors[3]); + } if (override->mbc != GB_MBC_AUTODETECT) { ConfigurationSetIntValue(config, sectionName, "mbc", override->mbc); } else {

@@ -104,6 +131,13 @@

if (override->mbc != GB_MBC_AUTODETECT) { gb->memory.mbcType = override->mbc; GBMBCInit(gb); + } + + if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) { + GBVideoSetPalette(&gb->video, 0, override->gbColors[0]); + GBVideoSetPalette(&gb->video, 1, override->gbColors[1]); + GBVideoSetPalette(&gb->video, 2, override->gbColors[2]); + GBVideoSetPalette(&gb->video, 3, override->gbColors[3]); } }
M src/gb/renderers/software.csrc/gb/renderers/software.c

@@ -67,8 +67,6 @@ softwareRenderer->wy = 0;

softwareRenderer->currentWy = 0; softwareRenderer->wx = 0; softwareRenderer->model = model; - - _clearScreen(softwareRenderer); } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
M src/gb/serialize.csrc/gb/serialize.c

@@ -59,25 +59,6 @@ GBIOSerialize(gb, state);

GBVideoSerialize(&gb->video, state); GBTimerSerialize(&gb->timer, state); GBAudioSerialize(&gb->audio, state); - -#ifndef _MSC_VER - struct timeval tv; - if (!gettimeofday(&tv, 0)) { - uint64_t usec = tv.tv_usec; - usec += tv.tv_sec * 1000000LL; - STORE_64LE(usec, 0, &state->creationUsec); - } -#else - struct timespec ts; - if (timespec_get(&ts, TIME_UTC)) { - uint64_t usec = ts.tv_nsec / 1000; - usec += ts.tv_sec * 1000000LL; - STORE_64LE(usec, 0, &state->creationUsec); - } -#endif - else { - state->creationUsec = 0; - } } bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {

@@ -188,8 +169,8 @@ gb->audio.style = GB_AUDIO_CGB;

} GBMemoryDeserialize(gb, state); - GBIODeserialize(gb, state); GBVideoDeserialize(&gb->video, state); + GBIODeserialize(gb, state); GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state);
M src/gb/timer.csrc/gb/timer.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/gb/timer.h> +#include <mgba/internal/lr35902/lr35902.h> #include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/io.h> #include <mgba/internal/gb/serialize.h>

@@ -18,22 +19,26 @@ timer->p->memory.io[REG_IF] |= (1 << GB_IRQ_TIMER);

GBUpdateIRQs(timer->p); } -void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) { - struct GBTimer* timer = context; - timer->nextDiv += cyclesLate; - while (timer->nextDiv > 0) { +static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) { + while (timer->nextDiv >= GB_DMG_DIV_PERIOD) { timer->nextDiv -= GB_DMG_DIV_PERIOD; // Make sure to trigger when the correct bit is a falling edge if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) { ++timer->p->memory.io[REG_TIMA]; if (!timer->p->memory.io[REG_TIMA]) { - mTimingSchedule(timing, &timer->irq, 4 - cyclesLate); + mTimingSchedule(&timer->p->timing, &timer->irq, 4 - cyclesLate); } } ++timer->internalDiv; timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; } +} + +void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBTimer* timer = context; + timer->nextDiv += cyclesLate; + _GBTimerDivIncrement(timer, cyclesLate); // Batch div increments int divsToGo = 16 - (timer->internalDiv & 15); int timaToGo = INT_MAX;

@@ -50,7 +55,7 @@

void GBTimerReset(struct GBTimer* timer) { timer->event.context = timer; timer->event.name = "GB Timer"; - timer->event.callback = _GBTimerIncrement; + timer->event.callback = _GBTimerUpdate; timer->event.priority = 0x20; timer->irq.context = timer; timer->irq.name = "GB Timer IRQ";

@@ -63,11 +68,19 @@ timer->internalDiv = 0;

} void GBTimerDivReset(struct GBTimer* timer) { + timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3); + if (timer->internalDiv & (timer->timaPeriod >> 1)) { + ++timer->p->memory.io[REG_TIMA]; + if (!timer->p->memory.io[REG_TIMA]) { + mTimingSchedule(&timer->p->timing, &timer->irq, 4 - (timer->p->cpu->executionState + 1) & 3); + } + } timer->p->memory.io[REG_DIV] = 0; timer->internalDiv = 0; timer->nextDiv = GB_DMG_DIV_PERIOD; - mTimingDeschedule(&timer->p->timing, &timer->event); - mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3)); } uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {

@@ -86,6 +99,12 @@ case 3:

timer->timaPeriod = 256 >> 4; break; } + + timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3); + timer->nextDiv += GB_DMG_DIV_PERIOD; + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); } else { timer->timaPeriod = 0; }
M src/gb/video.csrc/gb/video.c

@@ -185,9 +185,6 @@ video->p->memory.io[REG_LY] = video->ly;

next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { - video->p->memory.rotation->sample(video->p->memory.rotation); - } } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->p->memory.io[REG_LY] = 0; next = GB_VIDEO_HORIZONTAL_LENGTH - 8;

@@ -471,11 +468,11 @@ video->vramBank = &video->vram[value * GB_SIZE_VRAM_BANK0];

video->vramCurrentBank = value; } -void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) { +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color) { if (index >= 4) { return; } - video->dmgPalette[index] = color; + video->dmgPalette[index] = M_RGB8_TO_RGB5(color); } static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {

@@ -628,4 +625,7 @@ memcpy(&video->oam.raw, state->oam, GB_SIZE_OAM);

_cleanOAM(video, video->ly); GBVideoSwitchBank(video, video->vramCurrentBank); + + video->renderer->deinit(video->renderer); + video->renderer->init(video->renderer, video->p->model); }
M src/gba/bios.csrc/gba/bios.c

@@ -256,18 +256,23 @@ }

static void _Div(struct GBA* gba, int32_t num, int32_t denom) { struct ARMCore* cpu = gba->cpu; - if (denom != 0) { + if (denom != 0 && (denom != -1 || num != INT32_MIN)) { div_t result = div(num, denom); cpu->gprs[0] = result.quot; cpu->gprs[1] = result.rem; cpu->gprs[3] = abs(result.quot); - } else { + } else if (denom == 0) { mLOG(GBA_BIOS, GAME_ERROR, "Attempting to divide %i by zero!", num); // If abs(num) > 1, this should hang, but that would be painful to // emulate in HLE, and no game will get into a state where it hangs... cpu->gprs[0] = (num < 0) ? -1 : 1; cpu->gprs[1] = num; cpu->gprs[3] = 1; + } else { + mLOG(GBA_BIOS, GAME_ERROR, "Attempting to divide INT_MIN by -1!"); + cpu->gprs[0] = INT32_MIN; + cpu->gprs[1] = 0; + cpu->gprs[3] = INT32_MIN; } }
M src/gba/core.csrc/gba/core.c

@@ -45,6 +45,80 @@ { 4, "chA", "FIFO Channel A", NULL },

{ 5, "chB", "FIFO Channel B", NULL }, }; +const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM, "sram", "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_SRAM, SIZE_CART_SRAM, true }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH512, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, SIZE_CART_EEPROM, SIZE_CART_EEPROM, mCORE_MEMORY_RW }, +}; + struct mVideoLogContext; struct GBACore { struct mCore d;

@@ -528,6 +602,67 @@ struct ARMCore* cpu = core->cpu;

GBAPatch32(cpu, address, value, NULL); } +size_t _GBAListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) { + const struct GBA* gba = core->board; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + *blocks = _GBAMemoryBlocksSRAM; + return sizeof(_GBAMemoryBlocksSRAM) / sizeof(*_GBAMemoryBlocksSRAM); + case SAVEDATA_FLASH512: + *blocks = _GBAMemoryBlocksFlash512; + return sizeof(_GBAMemoryBlocksFlash512) / sizeof(*_GBAMemoryBlocksFlash512); + case SAVEDATA_FLASH1M: + *blocks = _GBAMemoryBlocksFlash1M; + return sizeof(_GBAMemoryBlocksFlash1M) / sizeof(*_GBAMemoryBlocksFlash1M); + case SAVEDATA_EEPROM: + *blocks = _GBAMemoryBlocksEEPROM; + return sizeof(_GBAMemoryBlocksEEPROM) / sizeof(*_GBAMemoryBlocksEEPROM); + default: + *blocks = _GBAMemoryBlocks; + return sizeof(_GBAMemoryBlocks) / sizeof(*_GBAMemoryBlocks); + } +} + +void* _GBAGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) { + struct GBA* gba = core->board; + switch (id) { + default: + return NULL; + case REGION_BIOS: + *sizeOut = SIZE_BIOS; + return gba->memory.bios; + case REGION_WORKING_RAM: + *sizeOut = SIZE_WORKING_RAM; + return gba->memory.wram; + case REGION_WORKING_IRAM: + *sizeOut = SIZE_WORKING_IRAM; + return gba->memory.iwram; + case REGION_PALETTE_RAM: + *sizeOut = SIZE_PALETTE_RAM; + return gba->video.palette; + case REGION_VRAM: + *sizeOut = SIZE_VRAM; + return gba->video.vram; + case REGION_OAM: + *sizeOut = SIZE_OAM; + return gba->video.oam.raw; + case REGION_CART0: + case REGION_CART1: + case REGION_CART2: + *sizeOut = gba->memory.romSize; + return gba->memory.rom; + case REGION_CART_SRAM: + if (gba->memory.savedata.type == SAVEDATA_FLASH1M) { + *sizeOut = SIZE_CART_FLASH1M; + return gba->memory.savedata.currentBank; + } + // Fall through + case REGION_CART_SRAM_MIRROR: + *sizeOut = GBASavedataSize(&gba->memory.savedata); + return gba->memory.savedata.data; + } +} + #ifdef USE_DEBUGGERS static bool _GBACoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) { UNUSED(core);

@@ -757,6 +892,8 @@ core->rawRead32 = _GBACoreRawRead32;

core->rawWrite8 = _GBACoreRawWrite8; core->rawWrite16 = _GBACoreRawWrite16; core->rawWrite32 = _GBACoreRawWrite32; + core->listMemoryBlocks = _GBAListMemoryBlocks; + core->getMemoryBlock = _GBAGetMemoryBlock; #ifdef USE_DEBUGGERS core->supportsDebuggerType = _GBACoreSupportsDebuggerType; core->debuggerPlatform = _GBACoreDebuggerPlatform;
M src/gba/debugger/cli.csrc/gba/debugger/cli.c

@@ -119,5 +119,5 @@ }

struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; - mCoreSaveState(gbaDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); + mCoreSaveState(gbaDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC | SAVESTATE_METADATA); }
M src/gba/memory.csrc/gba/memory.c

@@ -59,12 +59,8 @@ int i;

for (i = 0; i < 16; ++i) { gba->memory.waitstatesNonseq16[i] = GBA_BASE_WAITSTATES[i]; gba->memory.waitstatesSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i]; - gba->memory.waitstatesPrefetchNonseq16[i] = GBA_BASE_WAITSTATES[i]; - gba->memory.waitstatesPrefetchSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i]; gba->memory.waitstatesNonseq32[i] = GBA_BASE_WAITSTATES_32[i]; gba->memory.waitstatesSeq32[i] = GBA_BASE_WAITSTATES_SEQ_32[i]; - gba->memory.waitstatesPrefetchNonseq32[i] = GBA_BASE_WAITSTATES_32[i]; - gba->memory.waitstatesPrefetchSeq32[i] = GBA_BASE_WAITSTATES_SEQ_32[i]; } for (; i < 256; ++i) { gba->memory.waitstatesNonseq16[i] = 0;

@@ -1505,33 +1501,32 @@ if (dist < 8) {

previousLoads = dist; } - int32_t s = cpu->memory.activeSeqCycles16 + 1; - int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1; + int32_t s = cpu->memory.activeSeqCycles16; + int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; // Figure out how many sequential loads we can jam in int32_t stall = s; int32_t loads = 1; - if (stall > wait && !previousLoads) { - // We might need to stall a bit extra if we haven't finished the first S cycle - wait = stall; - } else { - while (stall < wait) { + if (stall < wait) { + int32_t maxLoads = 8 - previousLoads; + while (stall < wait && loads < maxLoads) { stall += s; ++loads; } - if (loads + previousLoads > 8) { - loads = 8 - previousLoads; - } + } + if (stall > wait) { + // The wait cannot take less time than the prefetch stalls + wait = stall; } + // This instruction used to have an N, convert it to an S. wait -= n2s; - // TODO: Invalidate prefetch on branch - memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * loads; + 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 -= (s - 1) * loads; + cpu->cycles -= stall; return wait; }
M src/gba/serialize.csrc/gba/serialize.c

@@ -69,25 +69,6 @@ GBAVideoSerialize(&gba->video, state);

GBAAudioSerialize(&gba->audio, state); GBASavedataSerialize(&gba->memory.savedata, state); - -#ifndef _MSC_VER - struct timeval tv; - if (!gettimeofday(&tv, 0)) { - uint64_t usec = tv.tv_usec; - usec += tv.tv_sec * 1000000LL; - STORE_64(usec, 0, &state->creationUsec); - } -#else - struct timespec ts; - if (timespec_get(&ts, TIME_UTC)) { - uint64_t usec = ts.tv_nsec / 1000; - usec += ts.tv_sec * 1000000LL; - STORE_64(usec, 0, &state->creationUsec); - } -#endif - else { - state->creationUsec = 0; - } state->associatedStreamId = 0; if (gba->rr) { gba->rr->stateSaved(gba->rr, state);
M src/gba/sharkport.csrc/gba/sharkport.c

@@ -115,24 +115,14 @@ }

uint32_t copySize = size - 0x1C; switch (gba->memory.savedata.type) { - case SAVEDATA_SRAM: - if (copySize > SIZE_CART_SRAM) { - copySize = SIZE_CART_SRAM; - } - break; case SAVEDATA_FLASH512: if (copySize > SIZE_CART_FLASH512) { GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); } // Fall through - case SAVEDATA_FLASH1M: - if (copySize > SIZE_CART_FLASH1M) { - copySize = SIZE_CART_FLASH1M; - } - break; - case SAVEDATA_EEPROM: - if (copySize > SIZE_CART_EEPROM) { - copySize = SAVEDATA_EEPROM; + default: + if (copySize > GBASavedataSize(&gba->memory.savedata)) { + copySize = GBASavedataSize(&gba->memory.savedata); } break; case SAVEDATA_FORCE_NONE:

@@ -141,6 +131,7 @@ goto cleanup;

} memcpy(gba->memory.savedata.data, &payload[0x1C], copySize); + gba->memory.savedata.vf && gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size); free(payload); return true;
M src/gba/timer.csrc/gba/timer.c

@@ -83,13 +83,13 @@

void GBATimerUpdateRegister(struct GBA* gba, int timer) { struct GBATimer* currentTimer = &gba->timers[timer]; if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) { - int32_t prefetchSkew = 0; - if (gba->memory.lastPrefetchedPc >= (uint32_t) gba->cpu->gprs[ARM_PC]) { - prefetchSkew = (gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * (gba->cpu->memory.activeSeqCycles16 + 1) / WORD_SIZE_THUMB; + int32_t prefetchSkew = -2; + if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) { + prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB; } // Reading this takes two cycles (1N+1I), so let's remove them preemptively int32_t diff = gba->cpu->cycles - (currentTimer->lastEvent - gba->timing.masterCycles); - gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff - 2 + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags)); + gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags)); } }
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/lr35902/decoder.h> #include <mgba/internal/lr35902/lr35902.h> #include <mgba/internal/lr35902/debugger/memory-debugger.h>

@@ -48,6 +49,7 @@ static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type);

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); struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct LR35902Debugger));

@@ -60,6 +62,7 @@ platform->setWatchpoint = LR35902DebuggerSetWatchpoint;

platform->clearWatchpoint = LR35902DebuggerClearWatchpoint; platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints; platform->hasBreakpoints = LR35902DebuggerHasBreakpoints; + platform->trace = LR35902DebuggerTrace; return platform; }

@@ -137,3 +140,31 @@ if (!LR35902DebugWatchpointListSize(&debugger->watchpoints)) {

LR35902DebuggerRemoveMemoryShim(debugger); } } + +static void LR35902DebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902Core* cpu = debugger->cpu; + + char disassembly[64]; + + struct LR35902InstructionInfo info = {{0}}; + char* disPtr = disassembly; + uint8_t instruction; + uint16_t address = cpu->pc; + size_t bytesRemaining = 1; + for (bytesRemaining = 1; bytesRemaining; --bytesRemaining) { + instruction = debugger->d.p->core->rawRead8(debugger->d.p->core, address, -1); + disPtr += snprintf(disPtr, sizeof(disassembly) - (disPtr - disassembly), "%02X", instruction); + ++address; + bytesRemaining += LR35902Decode(instruction, &info); + }; + disPtr[0] = ':'; + disPtr[1] = ' '; + disPtr += 2; + LR35902Disassemble(&info, disPtr, sizeof(disassembly) - (disPtr - disassembly)); + + *length = snprintf(out, *length, "A: %02X F: %02X B: %02X C: %02X D: %02X E: %02X H: %02X L: %02X SP: %04X PC: %04X | %s", + cpu->a, cpu->f.packed, cpu->b, cpu->c, + cpu->d, cpu->e, cpu->h, cpu->l, + cpu->sp, cpu->pc, disassembly); +}
M src/platform/opengl/gles2.csrc/platform/opengl/gles2.c

@@ -175,7 +175,7 @@ if (v->lockIntegerScaling) {

drawW -= drawW % v->width; drawH -= drawH % v->height; } - glViewport(0, 0, v->width, v->height); + glViewport(0, 0, w, h); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);

@@ -203,13 +203,13 @@ int drawW = shader->width;

int drawH = shader->height; int padW = 0; int padH = 0; - if (!shader->width) { + if (!drawW) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { drawW = context->d.width * -shader->width; } - if (!shader->height) { + if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) {

@@ -234,7 +234,7 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH); glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glEnableVertexAttribArray(shader->positionLocation); size_t u;

@@ -290,7 +290,6 @@ }

} glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, shader->tex); - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); } void mGLES2ContextDrawFrame(struct VideoBackend* v) {

@@ -298,12 +297,17 @@ struct mGLES2Context* context = (struct mGLES2Context*) v;

glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, context->tex); + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + context->finalShader.filter = v->filter; _drawShader(context, &context->initialShader); size_t n; for (n = 0; n < context->nShaders; ++n) { + glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->shaders[n]); } + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); _drawShader(context, &context->finalShader); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0);
M src/platform/python/CMakeLists.txtsrc/platform/python/CMakeLists.txt

@@ -1,12 +1,12 @@

find_program(PYTHON python) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) - get_property(INCLUDE_DIRECTORIES DIRECTORY PROPERTY INCLUDE_DIRECTORIES) set(INCLUDE_FLAGS) foreach(DIR IN LISTS INCLUDE_DIRECTORIES) list(APPEND INCLUDE_FLAGS "-I${DIR}") endforeach() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
M src/platform/python/_builder.hsrc/platform/python/_builder.h

@@ -28,7 +28,9 @@

#include "flags.h" #include <mgba/core/core.h> +#include <mgba/core/mem-search.h> #include <mgba/core/tile-cache.h> +#include <mgba/core/version.h> #define PYEXPORT extern "Python+C" #include "platform/python/log.h"
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -21,7 +21,9 @@ #include "flags.h"

#include <mgba-util/common.h> #include <mgba/core/core.h> #include <mgba/core/log.h> +#include <mgba/core/mem-search.h> #include <mgba/core/tile-cache.h> +#include <mgba/core/version.h> #include <mgba/internal/arm/arm.h> #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/input.h>
M src/platform/python/mgba/__init__.pysrc/platform/python/mgba/__init__.py

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

# file, You can obtain one at http://mozilla.org/MPL/2.0/. from ._pylib import ffi, lib +from collections import namedtuple + def createCallback(structName, cbName, funcName=None): funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:]) fullStruct = "struct {}*".format(structName)

@@ -13,3 +15,19 @@ h = ffi.cast(fullStruct, handle)

return getattr(ffi.from_handle(h.pyobj), cbName)(*args) return ffi.def_extern(name=funcName)(cb) + +version = ffi.string(lib.projectVersion).decode('utf-8') + +GitInfo = namedtuple("GitInfo", "commit commitShort branch revision") + +git = {} +if lib.gitCommit and lib.gitCommit != "(unknown)": + git['commit'] = ffi.string(lib.gitCommit).decode('utf-8') +if lib.gitCommitShort and lib.gitCommitShort != "(unknown)": + git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') +if lib.gitBranch and lib.gitBranch != "(unknown)": + git['branch'] = ffi.string(lib.gitBranch).decode('utf-8') +if lib.gitRevision > 0: + git['revision'] = lib.gitRevision + +git = GitInfo(**git)
M src/platform/python/mgba/image.pysrc/platform/python/mgba/image.py

@@ -6,64 +6,74 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/.

from ._pylib import ffi, lib from . import png +try: + import PIL.Image as PImage +except ImportError: + pass + class Image: - def __init__(self, width, height, stride=0): - self.width = width - self.height = height - self.stride = stride - self.constitute() + def __init__(self, width, height, stride=0): + self.width = width + self.height = height + self.stride = stride + self.constitute() - def constitute(self): - if self.stride <= 0: - self.stride = self.width - self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) + def constitute(self): + if self.stride <= 0: + self.stride = self.width + self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) - def savePNG(self, f): - p = png.PNG(f) - success = p.writeHeader(self) - success = success and p.writePixels(self) - p.writeClose() - return success + def savePNG(self, f): + p = png.PNG(f) + success = p.writeHeader(self) + success = success and p.writePixels(self) + p.writeClose() + return success + + if 'PImage' in globals(): + def toPIL(self): + return PImage.frombytes("RGBX", (self.width, self.height), ffi.buffer(self.buffer), "raw", + "RGBX", self.stride * 4) def u16ToU32(c): - r = c & 0x1F - g = (c >> 5) & 0x1F - b = (c >> 10) & 0x1F - a = (c >> 15) & 1 - abgr = r << 3 - abgr |= g << 11 - abgr |= b << 19 - abgr |= (a * 0xFF) << 24 - return abgr + r = c & 0x1F + g = (c >> 5) & 0x1F + b = (c >> 10) & 0x1F + a = (c >> 15) & 1 + abgr = r << 3 + abgr |= g << 11 + abgr |= b << 19 + abgr |= (a * 0xFF) << 24 + return abgr def u32ToU16(c): - r = (c >> 3) & 0x1F - g = (c >> 11) & 0x1F - b = (c >> 19) & 0x1F - a = c >> 31 - abgr = r - abgr |= g << 5 - abgr |= b << 10 - abgr |= a << 15 - return abgr + r = (c >> 3) & 0x1F + g = (c >> 11) & 0x1F + b = (c >> 19) & 0x1F + a = c >> 31 + abgr = r + abgr |= g << 5 + abgr |= b << 10 + abgr |= a << 15 + return abgr if ffi.sizeof("color_t") == 2: - def colorToU16(c): - return c + def colorToU16(c): + return c - colorToU32 = u16ToU32 + colorToU32 = u16ToU32 - def u16ToColor(c): - return c + def u16ToColor(c): + return c - u32ToColor = u32ToU16 + u32ToColor = u32ToU16 else: - def colorToU32(c): - return c + def colorToU32(c): + return c - colorToU16 = u32ToU16 + colorToU16 = u32ToU16 - def u32ToColor(c): - return c + def u32ToColor(c): + return c - u16ToColor = u16ToU32 + u16ToColor = u16ToU32
M src/platform/python/mgba/log.pysrc/platform/python/mgba/log.py

@@ -8,29 +8,33 @@ from . import createCallback

createCallback("mLoggerPy", "log", "_pyLog") +defaultLogger = None + def installDefault(logger): - lib.mLogSetDefaultLogger(logger._native) + global defaultLogger + defaultLogger = logger + lib.mLogSetDefaultLogger(logger._native) class Logger(object): - FATAL = lib.mLOG_FATAL - DEBUG = lib.mLOG_DEBUG - INFO = lib.mLOG_INFO - WARN = lib.mLOG_WARN - ERROR = lib.mLOG_ERROR - STUB = lib.mLOG_STUB - GAME_ERROR = lib.mLOG_GAME_ERROR + FATAL = lib.mLOG_FATAL + DEBUG = lib.mLOG_DEBUG + INFO = lib.mLOG_INFO + WARN = lib.mLOG_WARN + ERROR = lib.mLOG_ERROR + STUB = lib.mLOG_STUB + GAME_ERROR = lib.mLOG_GAME_ERROR - def __init__(self): - self._handle = ffi.new_handle(self) - self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) - @staticmethod - def categoryName(category): - return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') + @staticmethod + def categoryName(category): + return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') - def log(self, category, level, message): - print("{}: {}".format(self.categoryName(category), message)) + def log(self, category, level, message): + print("{}: {}".format(self.categoryName(category), message)) class NullLogger(Logger): - def log(self, category, level, message): - pass + def log(self, category, level, message): + pass
M src/platform/python/mgba/memory.pysrc/platform/python/mgba/memory.py

@@ -67,10 +67,53 @@ def rawWrite(self, address, value, segment=-1):

self._addrCheck(address) self._rawWrite(self._core, self._base + address, segment, value & self._mask) + +class MemorySearchResult(object): + def __init__(self, memory, result): + self.address = result.address + self.segment = result.segment + self.guessDivisor = result.guessDivisor + self.type = result.type + + if result.type == Memory.SEARCH_8: + self._memory = memory.u8 + elif result.type == Memory.SEARCH_16: + self._memory = memory.u16 + elif result.type == Memory.SEARCH_32: + self._memory = memory.u32 + elif result.type == Memory.SEARCH_STRING: + self._memory = memory.u8 + else: + raise ValueError("Unknown type: %X" % result.type) + + @property + def value(self): + if self.type == Memory.SEARCH_STRING: + raise ValueError + return self._memory[self.address] * self.guessDivisor + + @value.setter + def value(self, v): + if self.type == Memory.SEARCH_STRING: + raise IndexError + 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_STRING = lib.mCORE_MEMORY_SEARCH_STRING + SEARCH_GUESS = lib.mCORE_MEMORY_SEARCH_GUESS + + READ = lib.mCORE_MEMORY_READ + WRITE = lib.mCORE_MEMORY_READ + RW = lib.mCORE_MEMORY_RW + def __init__(self, core, size, base=0): self.size = size self.base = base + self._core = core self.u8 = MemoryView(core, 1, size, base, "u") self.u16 = MemoryView(core, 2, size, base, "u")

@@ -81,3 +124,32 @@ self.s32 = MemoryView(core, 4, size, base, "s")

def __len__(self): return self._size + + def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]): + results = ffi.new("struct mCoreMemorySearchResults*") + 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) + else: + params.valueStr = ffi.new("char[]", str(value).encode("ascii")) + + for result in old_results: + r = lib.mCoreMemorySearchResultsAppend(results) + r.address = result.address + r.segment = result.segment + r.guessDivisor = result.guessDivisor + r.type = result.type + if old_results: + lib.mCoreMemorySearchRepeat(self._core, params, results) + else: + 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
M src/platform/python/setup.py.insrc/platform/python/setup.py.in

@@ -1,5 +1,10 @@

from setuptools import setup import re +import os + +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",

@@ -18,6 +23,7 @@ url="http://github.com/mgba-emu/mgba/",

packages=["mgba"], setup_requires=['cffi>=1.6'], install_requires=['cffi>=1.6', 'cached-property'], + extras_require={'pil': ['Pillow>=2.3']}, cffi_modules=["_builder.py:ffi"], license="MPL 2.0", classifiers=classifiers
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -85,6 +85,7 @@ LoadSaveState.cpp

LogController.cpp LogView.cpp MemoryModel.cpp + MemorySearch.cpp MemoryView.cpp MessagePainter.cpp MultiplayerController.cpp

@@ -115,6 +116,7 @@ GIFView.ui

IOViewer.ui LoadSaveState.ui LogView.ui + MemorySearch.ui MemoryView.ui ObjView.ui OverrideView.ui
M src/platform/qt/GBAKeyEditor.cppsrc/platform/qt/GBAKeyEditor.cpp

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

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GBAKeyEditor.h" +#include <QApplication> #include <QComboBox> #include <QHBoxLayout> #include <QPaintEvent>

@@ -182,10 +183,20 @@ return QWidget::event(event);

} bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) { + KeyEditor* keyEditor = static_cast<KeyEditor*>(obj); + if (event->type() == QEvent::FocusOut) { + keyEditor->setPalette(QApplication::palette(keyEditor)); + } if (event->type() != QEvent::FocusIn) { return false; } - findFocus(static_cast<KeyEditor*>(obj)); + + QPalette palette = keyEditor->palette(); + palette.setBrush(keyEditor->backgroundRole(), palette.highlight()); + palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText()); + keyEditor->setPalette(palette); + + findFocus(keyEditor); return true; }
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -40,7 +40,7 @@

GameController::GameController(QObject* parent) : QObject(parent) , m_audioProcessor(AudioProcessor::create()) - , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC) + , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA) , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) { #ifdef M_CORE_GBA

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

default: break; } + mTileCacheDeinit(controller->m_tileCache.get()); controller->m_tileCache.reset(); }

@@ -696,6 +697,7 @@

void GameController::frameAdvance() { if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) { setPaused(false); + m_wasPaused = true; } }
M src/platform/qt/LoadSaveState.cppsrc/platform/qt/LoadSaveState.cpp

@@ -188,7 +188,7 @@ mStateExtdataDeinit(&extdata);

return; } - QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO + QDateTime creation; QImage stateImage; unsigned width, height;

@@ -196,6 +196,12 @@ thread->core->desiredVideoDimensions(thread->core, &width, &height);

mStateExtdataItem item; if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= width * height * 4) { stateImage = QImage((uchar*) item.data, width, height, QImage::Format_ARGB32).rgbSwapped(); + } + + if (mStateExtdataGet(&extdata, EXTDATA_META_TIME, &item) && item.size == sizeof(uint64_t)) { + uint64_t creationUsec; + LOAD_64LE(creationUsec, 0, item.data); + creation = QDateTime::fromMSecsSinceEpoch(creationUsec / 1000LL); } if (!stateImage.isNull()) {
A src/platform/qt/MemorySearch.cpp

@@ -0,0 +1,204 @@

+/* 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 "MemorySearch.h" + +#include <mgba/core/core.h> + +#include "GameController.h" +#include "MemoryView.h" + +using namespace QGBA; + +MemorySearch::MemorySearch(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + mCoreMemorySearchResultsInit(&m_results, 0); + connect(m_ui.search, &QPushButton::clicked, this, &MemorySearch::search); + connect(m_ui.searchWithin, &QPushButton::clicked, this, &MemorySearch::searchWithin); + connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory); +} + +MemorySearch::~MemorySearch() { + mCoreMemorySearchResultsDeinit(&m_results); +} + +bool MemorySearch::createParams(mCoreMemorySearchParams* params) { + params->memoryFlags = mCORE_MEMORY_RW; + mCore* core = m_controller->thread()->core; + + QByteArray string; + bool ok = false; + if (m_ui.typeNum->isChecked()) { + if (m_ui.bits8->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_8; + } + if (m_ui.bits16->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_16; + } + if (m_ui.bits32->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_32; + } + 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: + ok = v < 0x100; + params->value8 = v; + break; + case mCORE_MEMORY_SEARCH_16: + ok = v < 0x10000; + params->value16 = v; + break; + case mCORE_MEMORY_SEARCH_32: + params->value32 = v; + break; + default: + ok = false; + } + } + } + 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: + ok = v < 0x100; + params->value8 = v; + break; + case mCORE_MEMORY_SEARCH_16: + ok = v < 0x10000; + params->value16 = v; + break; + case mCORE_MEMORY_SEARCH_32: + params->value32 = v; + break; + default: + ok = false; + } + } + } + if (m_ui.numGuess->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_GUESS; + m_string = m_ui.value->text().toLocal8Bit(); + params->valueStr = m_string.constData(); + ok = true; + } + } + if (m_ui.typeStr->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_STRING; + m_string = m_ui.value->text().toLocal8Bit(); + params->valueStr = m_string.constData(); + ok = true; + } + return ok; +} + +void MemorySearch::search() { + mCoreMemorySearchResultsClear(&m_results); + + mCoreMemorySearchParams params; + + GameController::Interrupter interrupter(m_controller); + if (!m_controller->isLoaded()) { + return; + } + mCore* core = m_controller->thread()->core; + + if (createParams(&params)) { + mCoreMemorySearch(core, &params, &m_results, LIMIT); + } + + refresh(); +} + +void MemorySearch::searchWithin() { + mCoreMemorySearchParams params; + + GameController::Interrupter interrupter(m_controller); + if (!m_controller->isLoaded()) { + return; + } + mCore* core = m_controller->thread()->core; + + if (createParams(&params)) { + mCoreMemorySearchRepeat(core, &params, &m_results); + } + + refresh(); +} + +void MemorySearch::refresh() { + GameController::Interrupter interrupter(m_controller); + if (!m_controller->isLoaded()) { + return; + } + mCore* core = m_controller->thread()->core; + + m_ui.results->clearContents(); + m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results)); + 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); + if (m_ui.numHex->isChecked()) { + switch (result->type) { + case mCORE_MEMORY_SEARCH_8: + item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, result->segment), 2, 16, QChar('0'))); + break; + case mCORE_MEMORY_SEARCH_16: + 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: + 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))); + break; + case mCORE_MEMORY_SEARCH_STRING: + item = new QTableWidgetItem("?"); // TODO + } + } + m_ui.results->setItem(i, 1, item); + } + m_ui.results->sortItems(0); +} + +void MemorySearch::openMemory() { + auto items = m_ui.results->selectedItems(); + if (items.empty()) { + return; + } + QTableWidgetItem* item = items[0]; + uint32_t address = item->text().toUInt(nullptr, 16); + + MemoryView* memView = new MemoryView(m_controller); + memView->jumpToAddress(address); + + connect(m_controller, &GameController::gameStopped, memView, &QWidget::close); + memView->setAttribute(Qt::WA_DeleteOnClose); + memView->show(); +}
A src/platform/qt/MemorySearch.h

@@ -0,0 +1,47 @@

+/* 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/. */ +#ifndef QGBA_MEMORY_SEARCH +#define QGBA_MEMORY_SEARCH + +#include "ui_MemorySearch.h" + +#include <mgba/core/mem-search.h> + +namespace QGBA { + +class GameController; + +class MemorySearch : public QWidget { +Q_OBJECT + +public: + static constexpr size_t LIMIT = 10000; + + MemorySearch(GameController* controller, QWidget* parent = nullptr); + ~MemorySearch(); + +public slots: + void refresh(); + void search(); + void searchWithin(); + +private slots: + void openMemory(); + +private: + bool createParams(mCoreMemorySearchParams*); + + Ui::MemorySearch m_ui; + + GameController* m_controller; + + mCoreMemorySearchResults m_results; + QByteArray m_string; +}; + +} + +#endif
A src/platform/qt/MemorySearch.ui

@@ -0,0 +1,223 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MemorySearch</class> + <widget class="QWidget" name="MemorySearch"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>631</width> + <height>378</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>540</width> + <height>241</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QTableWidget" name="results"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string>Address</string> + </property> + </column> + <column> + <property name="text"> + <string>Current Value</string> + </property> + </column> + <column> + <property name="text"> + <string>Type</string> + </property> + </column> + </widget> + </item> + <item row="0" column="0"> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Value</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="value"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Type</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QRadioButton" name="typeNum"> + <property name="text"> + <string>Numeric</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">type</string> + </attribute> + </widget> + </item> + <item row="2" column="1"> + <widget class="QRadioButton" name="typeStr"> + <property name="text"> + <string>Text</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">type</string> + </attribute> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Width</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QRadioButton" name="bits8"> + <property name="text"> + <string>1 Byte (8-bit)</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">width</string> + </attribute> + </widget> + </item> + <item row="4" column="1"> + <widget class="QRadioButton" name="bits16"> + <property name="text"> + <string>2 Bytes (16-bit)</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">width</string> + </attribute> + </widget> + </item> + <item row="5" column="1"> + <widget class="QRadioButton" name="bits32"> + <property name="text"> + <string>4 Bytes (32-bit)</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">width</string> + </attribute> + </widget> + </item> + <item row="6" 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"> + <property name="text"> + <string>Hexadecimal</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" 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"> + <property name="text"> + <string>Guess</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="search"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="searchWithin"> + <property name="text"> + <string>Search Within</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="viewMem"> + <property name="text"> + <string>Open in Memory Viewer</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="refresh"> + <property name="text"> + <string>Refresh</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> + <buttongroups> + <buttongroup name="type"/> + <buttongroup name="width"/> + <buttongroup name="numType"/> + </buttongroups> +</ui>
M src/platform/qt/MemoryView.cppsrc/platform/qt/MemoryView.cpp

@@ -9,53 +9,9 @@

#include "GameController.h" #include <mgba/core/core.h> -#ifdef M_CORE_GBA -#include <mgba/internal/gba/memory.h> -#endif -#ifdef M_CORE_GB -#include <mgba/internal/gb/memory.h> -#endif using namespace QGBA; -struct IndexInfo { - const char* name; - const char* longName; - uint32_t base; - uint32_t size; - int maxSegment; -}; -#ifdef M_CORE_GBA -const static struct IndexInfo indexInfoGBA[] = { - { "All", "All", 0, 0x10000000 }, - { "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS }, - { "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, SIZE_WORKING_RAM }, - { "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, SIZE_WORKING_IRAM }, - { "MMIO", "Memory-Mapped I/O", BASE_IO, SIZE_IO }, - { "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, SIZE_PALETTE_RAM }, - { "VRAM", "Video RAM (96kiB)", BASE_VRAM, SIZE_VRAM }, - { "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, SIZE_OAM }, - { "ROM", "Game Pak (32MiB)", BASE_CART0, SIZE_CART0 }, - { "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, SIZE_CART1 }, - { "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, SIZE_CART2 }, - { "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, SIZE_CART_SRAM }, - { nullptr, nullptr, 0, 0, 0 } -}; -#endif -#ifdef M_CORE_GB -const static struct IndexInfo indexInfoGB[] = { - { "All", "All", 0, 0x10000 }, - { "ROM", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 511 }, - { "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_SIZE_VRAM, 1 }, - { "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM, 3 }, - { "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_SIZE_WORKING_RAM_BANK0 * 2, 7 }, - { "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_SIZE_OAM }, - { "IO", "Memory-Mapped I/O", GB_BASE_IO, GB_SIZE_IO }, - { "HRAM", "High RAM", GB_BASE_HRAM, GB_SIZE_HRAM }, - { nullptr, nullptr, 0, 0, 0 } -}; -#endif - MemoryView::MemoryView(GameController* controller, QWidget* parent) : QWidget(parent) , m_controller(controller)

@@ -65,21 +21,8 @@

m_ui.hexfield->setController(controller); mCore* core = m_controller->thread()->core; - const IndexInfo* info = nullptr; - switch (core->platform(core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - info = indexInfoGBA; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - info = indexInfoGB; - break; -#endif - default: - break; - } + const mCoreMemoryBlock* info; + size_t nBlocks = core->listMemoryBlocks(core, &info); connect(m_ui.regions, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MemoryView::setIndex);

@@ -87,7 +30,10 @@ connect(m_ui.segments, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),

this, &MemoryView::setSegment); if (info) { - for (size_t i = 0; info[i].name; ++i) { + for (size_t i = 0; i < nBlocks; ++i) { + if (!(info[i].flags & (mCORE_MEMORY_MAPPED | mCORE_MEMORY_VIRTUAL))) { + continue; + } m_ui.regions->addItem(tr(info[i].longName)); } }

@@ -116,44 +62,22 @@ }

void MemoryView::setIndex(int index) { mCore* core = m_controller->thread()->core; - IndexInfo info; - switch (core->platform(core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - info = indexInfoGBA[index]; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - info = indexInfoGB[index]; - break; -#endif - default: - return; - } + const mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + const mCoreMemoryBlock& info = blocks[index]; + m_ui.segments->setValue(-1); m_ui.segments->setVisible(info.maxSegment > 0); m_ui.segments->setMaximum(info.maxSegment); - m_ui.hexfield->setRegion(info.base, info.size, info.name); + m_ui.hexfield->setRegion(info.start, info.end - info.start, info.shortName); } void MemoryView::setSegment(int segment) { mCore* core = m_controller->thread()->core; - IndexInfo info; - switch (core->platform(core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - info = indexInfoGBA[m_ui.regions->currentIndex()]; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - info = indexInfoGB[m_ui.regions->currentIndex()]; - break; -#endif - default: - return; - } + const mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + const mCoreMemoryBlock& info = blocks[m_ui.regions->currentIndex()]; + m_ui.hexfield->setSegment(info.maxSegment < segment ? info.maxSegment : segment); }
M src/platform/qt/MemoryView.hsrc/platform/qt/MemoryView.h

@@ -22,6 +22,7 @@ MemoryView(GameController* controller, QWidget* parent = nullptr);

public slots: void update(); + void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); } private slots: void setIndex(int);
M src/platform/qt/ObjView.cppsrc/platform/qt/ObjView.cpp

@@ -68,6 +68,7 @@ }

#ifdef M_CORE_GBA void ObjView::updateTilesGBA(bool force) { + m_ui.objId->setMaximum(127); const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board); const GBAObj* obj = &gba->video.oam.obj[m_objId];

@@ -172,6 +173,7 @@ #endif

#ifdef M_CORE_GB void ObjView::updateTilesGB(bool force) { + m_ui.objId->setMaximum(39); const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board); const GBObj* obj = &gb->video.oam.obj[m_objId];
M src/platform/qt/OverrideView.cppsrc/platform/qt/OverrideView.cpp

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

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "OverrideView.h" +#include <QColorDialog> #include <QPushButton> #include "ConfigController.h"

@@ -79,6 +80,21 @@

connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); + QPalette palette = m_ui.color0->palette(); + palette.setColor(backgroundRole(), QColor(0xF8, 0xF8, 0xF8)); + m_ui.color0->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0xA8, 0xA8, 0xA8)); + m_ui.color1->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0x50, 0x50, 0x50)); + m_ui.color2->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0x00, 0x00, 0x00)); + m_ui.color3->setPalette(palette); + + m_ui.color0->installEventFilter(this); + m_ui.color1->installEventFilter(this); + m_ui.color2->installEventFilter(this); + m_ui.color3->installEventFilter(this); + connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides); #ifndef M_CORE_GBA m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGBA));

@@ -96,6 +112,42 @@ gameStarted(controller->thread());

} } +bool OverrideView::eventFilter(QObject* obj, QEvent* event) { +#ifdef M_CORE_GB + if (event->type() != QEvent::MouseButtonRelease) { + return false; + } + int colorId; + if (obj == m_ui.color0) { + colorId = 0; + } else if (obj == m_ui.color1) { + colorId = 1; + } else if (obj == m_ui.color2) { + colorId = 2; + } else if (obj == m_ui.color3) { + colorId = 3; + } else { + return false; + } + + QWidget* swatch = static_cast<QWidget*>(obj); + + QColorDialog* colorPicker = new QColorDialog; + colorPicker->setAttribute(Qt::WA_DeleteOnClose); + colorPicker->open(); + connect(colorPicker, &QColorDialog::colorSelected, [this, swatch, colorId](const QColor& color) { + QPalette palette = swatch->palette(); + palette.setColor(backgroundRole(), color); + swatch->setPalette(palette); + m_gbColors[colorId] = color.rgb(); + updateOverrides(); + }); + return true; +#else + return false; +#endif +} + void OverrideView::saveOverride() { if (!m_config) { return;

@@ -155,7 +207,13 @@ if (m_ui.tabWidget->currentWidget() == m_ui.tabGB) {

GBOverride* gb = new GBOverride; gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()]; gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()]; - if (gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT) { + gb->override.gbColors[0] = m_gbColors[0]; + gb->override.gbColors[1] = m_gbColors[1]; + gb->override.gbColors[2] = m_gbColors[2]; + gb->override.gbColors[3] = m_gbColors[3]; + bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT; + hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]); + if (hasOverride) { m_controller->setOverride(gb); } else { m_controller->clearOverride();
M src/platform/qt/OverrideView.hsrc/platform/qt/OverrideView.h

@@ -36,6 +36,9 @@ void updateOverrides();

void gameStarted(mCoreThread*); void gameStopped(); +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + private: Ui::OverrideView m_ui;

@@ -43,6 +46,8 @@ GameController* m_controller;

ConfigController* m_config; #ifdef M_CORE_GB + uint32_t m_gbColors[4]{}; + static QList<enum GBModel> s_gbModelList; static QList<enum GBMemoryBankControllerType> s_mbcList; #endif
M src/platform/qt/OverrideView.uisrc/platform/qt/OverrideView.ui

@@ -6,8 +6,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>443</width> - <height>282</height> + <width>444</width> + <height>284</height> </rect> </property> <property name="sizePolicy">

@@ -325,6 +325,93 @@ <string>HuC-3</string>

</property> </item> </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Colors</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QFrame" name="color0"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="color1"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="color2"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="color3"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + </layout> </item> </layout> </widget>
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -88,6 +88,7 @@ m_ui.patchSameDir->setChecked(false);

m_ui.patchPath->setText(path); } }); + connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() QVariant audioDriver = m_controller->getQtOption("audioDriver");

@@ -212,7 +213,7 @@ loadState |= m_ui.loadStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0;

loadState |= m_ui.loadStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; saveSetting("loadStateExtdata", loadState); - int saveState = SAVESTATE_RTC; + int saveState = SAVESTATE_RTC | SAVESTATE_METADATA; saveState |= m_ui.saveStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0; saveState |= m_ui.saveStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0; saveState |= m_ui.saveStateCheats->isChecked() ? SAVESTATE_CHEATS : 0;

@@ -299,7 +300,7 @@ m_ui.loadStateCheats->setChecked(loadState & SAVESTATE_CHEATS);

int saveState = loadSetting("saveStateExtdata").toInt(&ok); if (!ok) { - saveState = SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC; + saveState = SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA; } m_ui.saveStateScreenshot->setChecked(saveState & SAVESTATE_SCREENSHOT); m_ui.saveStateSave->setChecked(saveState & SAVESTATE_SAVEDATA);
M src/platform/qt/SettingsView.hsrc/platform/qt/SettingsView.h

@@ -29,6 +29,7 @@ void biosLoaded(int platform, const QString&);

void audioDriverChanged(); void displayDriverChanged(); void pathsChanged(); + void libraryCleared(); private slots: void selectBios(QLineEdit*);
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -431,9 +431,6 @@ </widget>

</item> <item row="2" column="1"> <widget class="QPushButton" name="clearCache"> - <property name="enabled"> - <bool>false</bool> - </property> <property name="text"> <string>Clear cache</string> </property>
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -35,6 +35,7 @@ #include "IOViewer.h"

#include "LoadSaveState.h" #include "LogView.h" #include "MultiplayerController.h" +#include "MemorySearch.h" #include "MemoryView.h" #include "OverrideView.h" #include "ObjView.h"

@@ -468,6 +469,7 @@ connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS);

connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver); connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); + connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); openView(settingsWindow); }

@@ -1404,6 +1406,11 @@ QAction* memoryView = new QAction(tr("View memory..."), toolsMenu);

connect(memoryView, &QAction::triggered, openTView<MemoryView>()); m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); + + QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu); + connect(memorySearch, &QAction::triggered, openTView<MemorySearch>()); + m_gameActions.append(memorySearch); + addControlledAction(toolsMenu, memorySearch, "memorySearch"); #ifdef M_CORE_GBA QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu);
M src/platform/qt/library/LibraryController.cppsrc/platform/qt/library/LibraryController.cpp

@@ -24,7 +24,7 @@ }

} void AbstractGameList::removeEntries(QList<LibraryEntryRef> items) { for (LibraryEntryRef o : items) { - addEntry(o); + removeEntry(o); } }

@@ -130,6 +130,20 @@ m_library = nullptr;

m_loaderThread.start(); } +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; + } + } + + mLibraryClear(m_library); + refresh(); +} + void LibraryController::refresh() { if (!m_library) { if (!m_loaderThread.isRunning() && m_loaderThread.m_library) {

@@ -181,7 +195,7 @@ emit doneLoading();

} void LibraryController::selectLastBootedGame() { - if (!m_config) { + if (!m_config || m_config->getMRU().isEmpty()) { return; } const QString lastfile = m_config->getMRU().first();
M src/platform/qt/library/LibraryController.hsrc/platform/qt/library/LibraryController.h

@@ -100,6 +100,9 @@ void selectLastBootedGame();

void addDirectory(const QString& dir); +public slots: + void clear(); + signals: void startGame(); void doneLoading();
M src/platform/sdl/sdl-events.csrc/platform/sdl/sdl-events.c

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

#include <mgba/core/input.h> #include <mgba/core/serialize.h> #include <mgba/core/thread.h> -#include <mgba/internal/debugger/debugger.h> +#include <mgba/debugger/debugger.h> #include <mgba/internal/gba/input.h> #include <mgba-util/configuration.h> #include <mgba-util/formatting.h>