all repos — mgba @ 48cb8abc219e66183d7d32fbb7055e48a72c5cff

mGBA Game Boy Advance Emulator

Merge branch 'master' into medusa
Vicki Pfau vi@endrift.com
Tue, 13 Jun 2017 20:57:22 -0700
commit

48cb8abc219e66183d7d32fbb7055e48a72c5cff

parent

f45ff4d35f53b35f0cc094e5082060eca0021ffa

60 files changed, 1663 insertions(+), 223 deletions(-)

jump to
M CHANGESCHANGES

@@ -30,8 +30,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

@@ -73,6 +75,9 @@ - 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 Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers

@@ -133,6 +138,10 @@ - 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 medusa alpha 2: (2017-04-26) Features:
M CMakeLists.txtCMakeLists.txt

@@ -160,8 +160,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)

@@ -205,6 +203,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")

@@ -874,6 +873,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 include/mgba-util/circle-buffer.hinclude/mgba-util/circle-buffer.h

@@ -30,7 +30,6 @@ size_t CircleBufferWrite(struct CircleBuffer* buffer, const void* input, size_t length);

int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value); int CircleBufferRead16(struct CircleBuffer* buffer, int16_t* value); int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value); -size_t CircleBufferWrite(struct CircleBuffer* buffer, const void* input, size_t length); size_t CircleBufferRead(struct CircleBuffer* buffer, void* output, size_t length); size_t CircleBufferDump(const struct CircleBuffer* buffer, void* output, size_t length);
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 {

@@ -137,6 +136,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

@@ -113,6 +113,27 @@ 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 #endif
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/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,7 +63,8 @@ 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);

@@ -98,9 +99,14 @@ int command;

GBMBC7Field field; }; +struct GBPocketCamState { + bool registersActive; +}; + union GBMBCState { struct GBMBC1State mbc1; struct GBMBC7State mbc7; + struct GBPocketCamState pocketCam; }; struct mRotationSource;

@@ -109,7 +115,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/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/gba/memory.hinclude/mgba/internal/gba/memory.h

@@ -97,10 +97,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 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); +}
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) { + 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) { + 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

@@ -302,6 +302,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/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;

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

@@ -422,11 +444,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) {

@@ -553,6 +577,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);

@@ -787,6 +853,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

@@ -710,4 +710,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;

@@ -148,6 +152,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 +166,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->memory.mbcWrite = _GBMBC7; + gb->memory.mbcRead = _GBMBC7Read; gb->sramSize = GB_SIZE_EXTERNAL_RAM; 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 +366,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;

@@ -466,7 +483,7 @@ 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; switch (address & 0xF0) { case 0x00:

@@ -672,6 +689,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? }
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) {
M src/gb/timer.csrc/gb/timer.c

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

#include <mgba/internal/gb/io.h> #include <mgba/internal/gb/serialize.h> +static void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate); + void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); UNUSED(cyclesLate);

@@ -34,8 +36,12 @@ }

++timer->internalDiv; timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; } + _GBTimerUpdateDIV(timer, cyclesLate); +} + +void _GBTimerUpdateDIV(struct GBTimer* timer, uint32_t cyclesLate) { // Batch div increments - int divsToGo = 16 - (timer->internalDiv & 15); + int divsToGo = 16 - (timer->internalDiv & 15) + (timer->nextDiv / GB_DMG_DIV_PERIOD); int timaToGo = INT_MAX; if (timer->timaPeriod) { timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1));

@@ -43,8 +49,12 @@ }

if (timaToGo < divsToGo) { divsToGo = timaToGo; } - timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo; - mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate); + if (divsToGo > 16) { + divsToGo = 16; + } + timer->nextDiv &= GB_DMG_DIV_PERIOD - 1; + timer->nextDiv += GB_DMG_DIV_PERIOD * divsToGo; + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - cyclesLate); } void GBTimerReset(struct GBTimer* timer) {

@@ -89,6 +99,8 @@ }

} else { timer->timaPeriod = 0; } + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerUpdateDIV(timer, 0); return tac; }
M src/gb/video.csrc/gb/video.c

@@ -628,4 +628,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;

@@ -542,6 +616,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);

@@ -773,6 +908,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

@@ -1501,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/timer.csrc/gba/timer.c

@@ -105,18 +105,18 @@

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; + // Reading this takes two cycles (1N+1I), so let's remove them preemptively + 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; } GBATimerUpdateRegisterInternal(currentTimer, &gba->timing, gba->cpu, &gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1], prefetchSkew); } } void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew) { - // Reading this takes two cycles (1N+1I), so let's remove them preemptively int32_t diff = cpu->cycles - (timer->lastEvent - timing->masterCycles); - *io = timer->oldReload + ((diff - 2 + skew) >> GBATimerFlagsGetPrescaleBits(timer->flags)); + *io = timer->oldReload + ((diff + skew) >> GBATimerFlagsGetPrescaleBits(timer->flags)); } void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload) {
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/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,6 +28,7 @@

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

@@ -21,6 +21,7 @@ #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/internal/arm/arm.h> #include <mgba/internal/gba/gba.h>
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",
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/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
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,187 @@

+/* 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" + +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); +} + +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()) { + bool ok; + 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); +}
A src/platform/qt/MemorySearch.h

@@ -0,0 +1,44 @@

+/* 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: + 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="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="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>View in Memory View</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/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -224,7 +224,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;

@@ -314,7 +314,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/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"

@@ -1500,6 +1501,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

@@ -181,7 +181,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/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>