diff --git a/CHANGES b/CHANGES
index db620fc67..55ce1fd69 100644
--- a/CHANGES
+++ b/CHANGES
@@ -21,8 +21,10 @@ Features:
- Debugger: Segment/bank support
- GB: Symbol table support
- GB MBC: Add MBC1 multicart support
- - GBA: Implement keypad interrupts
+ - Implement keypad interrupts
- LR35902: Watchpoints
+ - Memory search
+ - Debugger: Execution tracing
Bugfixes:
- LR35902: Fix core never exiting with certain event patterns
- GB Timer: Improve DIV reset behavior
@@ -64,6 +66,13 @@ Bugfixes:
- LR35902: Fix decoding LD r, $imm and 0-valued immediates (fixes mgba.io/i/735)
- GB: Fix STAT blocking
- GB MBC: Fix swapping carts not detect new MBC
+ - GB Timer: Fix DIV batching if TAC changes
+ - GB Video: Reset renderer when loading state
+ - GBA BIOS: Fix INT_MIN/-1 crash
+ - GBA Savedata: Update and fix Sharkport importing (fixes mgba.io/i/658)
+ - OpenGL: Fix some shaders causing offset graphics
+ - Qt: Fix game unpausing after frame advancing and refocusing
+ - GB Timer: Fix sub-M-cycle DIV reset timing and edge triggering
Misc:
- SDL: Remove scancode key input
- GBA Video: Clean up unused timers
@@ -124,6 +133,15 @@ Misc:
- Util: Tune patch-fast extent sizes
- Qt: Relax hard dependency on OpenGL
- GB Video: Improved video timings
+ - Core: List memory segments in the core
+ - Core: Move savestate creation time to extdata
+ - Debugger: Add mDebuggerRunFrame convenience function
+ - GBA Memory: Remove unused prefetch cruft
+ - GB: Trust ROM header for number of SRAM banks (fixes mgba.io/i/726)
+ - Core: Config values can now be hexadecimal
+ - GB: Reset with initial state of DIV register
+ - GB MBC: New MBC7 implementation
+ - Qt: Better highlight active key in control binding
0.5.2: (2016-12-31)
Bugfixes:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e40465c6e..7504ae4da 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,8 +155,6 @@ add_custom_target(version-info ALL
include(${CMAKE_CURRENT_SOURCE_DIR}/version.cmake)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c)
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME})
list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c)
source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c)
@@ -200,6 +198,7 @@ if(WIN32)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
endif()
elseif(UNIX)
+ set(USE_PTHREADS ON)
add_definitions(-DUSE_PTHREADS)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
@@ -844,6 +843,10 @@ if(BUILD_EXAMPLE)
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})
set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR})
diff --git a/README.md b/README.md
index 61c69c8ac..3b52e1573 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ Features
- Remappable controls for both keyboards and gamepads.
- Loading from ZIP and 7z files.
- IPS, UPS and BPS patch support.
-- Game debugging via a command-line interface (not available with Qt port) and GDB remote support, compatible with IDA Pro.
+- Game debugging via a command-line interface and GDB remote support, compatible with IDA Pro.
- Configurable emulation rewinding.
- Support for loading and exporting GameShark and Action Replay snapshots.
- Cores available for RetroArch/Libretro and OpenEmu.
@@ -94,6 +94,16 @@ Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to
This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them.
+If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are:
+
+ brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit
+ mkdir build
+ cd build
+ cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
+ make
+
+Note that you should not do a `make install` on macOS, as it will not work properly.
+
#### Windows developer building
To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 500MiB of packages, so it will take a long time):
@@ -133,7 +143,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are
- ImageMagick: for GIF recording.
- SQLite3: for game databases.
-Both libpng and zlib are included with the emulator, so they do not need to be externally compiled first.
+SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first.
Footnotes
---------
@@ -145,7 +155,7 @@ Footnotes
[2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
-[3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
+[3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.5, and may work on older.
[downloads]: http://mgba.io/downloads.html
[source]: https://github.com/mgba-emu/mgba/
@@ -153,7 +163,7 @@ Footnotes
Copyright
---------
-mGBA is Copyright © 2013 – 2016 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
+mGBA is Copyright © 2013 – 2017 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
mGBA contains the following third-party libraries:
diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h
index 5507a54a0..17c9de9f3 100644
--- a/include/mgba/core/core.h
+++ b/include/mgba/core/core.h
@@ -19,8 +19,7 @@ CXX_GUARD_START
#endif
#include
#ifdef USE_DEBUGGERS
-// TODO: Fix layering violation
-#include
+#include
#endif
enum mPlatform {
@@ -132,6 +131,9 @@ struct mCore {
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);
struct mDebuggerPlatform* (*debuggerPlatform)(struct mCore*);
diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h
index 7f0515a60..44c7e13d5 100644
--- a/include/mgba/core/interface.h
+++ b/include/mgba/core/interface.h
@@ -31,6 +31,10 @@ typedef uint32_t color_t;
#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5)
#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5)
+#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19))
+#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9))
+#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19))
+
struct blip_t;
struct mCoreCallbacks {
@@ -112,6 +116,27 @@ struct mCoreChannelInfo {
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
diff --git a/include/mgba/core/library.h b/include/mgba/core/library.h
index 1408f24d3..d55ad4094 100644
--- a/include/mgba/core/library.h
+++ b/include/mgba/core/library.h
@@ -36,6 +36,7 @@ void mLibraryDestroy(struct mLibrary*);
struct VDir;
struct VFile;
void mLibraryLoadDirectory(struct mLibrary* library, const char* base);
+void mLibraryClear(struct mLibrary* library);
size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints);
size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints);
diff --git a/include/mgba/core/mem-search.h b/include/mgba/core/mem-search.h
new file mode 100644
index 000000000..57084b4ae
--- /dev/null
+++ b/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
+
+CXX_GUARD_START
+
+#include
+
+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
diff --git a/include/mgba/core/serialize.h b/include/mgba/core/serialize.h
index 0951fc4c3..afbf8de54 100644
--- a/include/mgba/core/serialize.h
+++ b/include/mgba/core/serialize.h
@@ -16,6 +16,7 @@ enum mStateExtdataTag {
EXTDATA_SAVEDATA = 2,
EXTDATA_CHEATS = 3,
EXTDATA_RTC = 4,
+ EXTDATA_META_TIME = 0x101,
EXTDATA_MAX
};
@@ -23,6 +24,7 @@ enum mStateExtdataTag {
#define SAVESTATE_SAVEDATA 2
#define SAVESTATE_CHEATS 4
#define SAVESTATE_RTC 8
+#define SAVESTATE_METADATA 16
struct mStateExtdataItem {
int32_t size;
diff --git a/include/mgba/core/timing.h b/include/mgba/core/timing.h
index 19de59dc7..634631bed 100644
--- a/include/mgba/core/timing.h
+++ b/include/mgba/core/timing.h
@@ -38,6 +38,7 @@ bool mTimingIsScheduled(const struct mTiming* timing, const struct mTimingEvent*
int32_t mTimingTick(struct mTiming* timing, int32_t cycles);
int32_t mTimingCurrentTime(const struct mTiming* timing);
int32_t mTimingNextEvent(struct mTiming* timing);
+int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent*);
CXX_GUARD_END
diff --git a/include/mgba/internal/debugger/debugger.h b/include/mgba/debugger/debugger.h
similarity index 92%
rename from include/mgba/internal/debugger/debugger.h
rename to include/mgba/debugger/debugger.h
index d73afd39e..bef61c208 100644
--- a/include/mgba/internal/debugger/debugger.h
+++ b/include/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
#include
-#include
mLOG_DECLARE_CATEGORY(DEBUGGER);
@@ -53,14 +52,6 @@ enum mDebuggerEntryReason {
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 @@ struct mDebuggerPlatform {
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 {
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
diff --git a/include/mgba/feature/commandline.h b/include/mgba/feature/commandline.h
index b47442c21..854afdb77 100644
--- a/include/mgba/feature/commandline.h
+++ b/include/mgba/feature/commandline.h
@@ -12,7 +12,7 @@ CXX_GUARD_START
#include
-#include
+#include
struct mArguments {
char* fname;
diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h
index 03b836865..ef0771bca 100644
--- a/include/mgba/gb/interface.h
+++ b/include/mgba/gb/interface.h
@@ -30,6 +30,7 @@ enum GBMemoryBankControllerType {
GB_MMM01 = 0x10,
GB_HuC1 = 0x11,
GB_HuC3 = 0x12,
+ GB_POCKETCAM = 0x13,
GB_MBC3_RTC = 0x103,
GB_MBC5_RUMBLE = 0x105
};
diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h
index d5a05e6fd..d35dbed13 100644
--- a/include/mgba/internal/arm/debugger/debugger.h
+++ b/include/mgba/internal/arm/debugger/debugger.h
@@ -10,9 +10,10 @@
CXX_GUARD_START
-#include
+#include
#include
+#include
struct ARMDebugBreakpoint {
uint32_t address;
diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h
index 2f86590ee..6eb28b1dc 100644
--- a/include/mgba/internal/debugger/cli-debugger.h
+++ b/include/mgba/internal/debugger/cli-debugger.h
@@ -10,7 +10,10 @@
CXX_GUARD_START
-#include
+#include
+
+extern const char* ERROR_MISSING_ARGS;
+extern const char* ERROR_OVERFLOW;
struct CLIDebugger;
diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h
index 9e6be2239..5743834a2 100644
--- a/include/mgba/internal/debugger/gdb-stub.h
+++ b/include/mgba/internal/debugger/gdb-stub.h
@@ -10,7 +10,7 @@
CXX_GUARD_START
-#include
+#include
#include
diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h
index c5ab01446..178254b69 100644
--- a/include/mgba/internal/debugger/parser.h
+++ b/include/mgba/internal/debugger/parser.h
@@ -10,7 +10,7 @@
CXX_GUARD_START
-#include
+#include
enum LexState {
LEX_ERROR = -1,
diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h
index 05362c7bd..37050e613 100644
--- a/include/mgba/internal/gb/gb.h
+++ b/include/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
diff --git a/include/mgba/internal/gb/mbc.h b/include/mgba/internal/gb/mbc.h
index 6242343e8..477364967 100644
--- a/include/mgba/internal/gb/mbc.h
+++ b/include/mgba/internal/gb/mbc.h
@@ -36,7 +36,6 @@ struct GBMBCRTCSaveBuffer {
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
diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h
index 11f1e929f..d28338cff 100644
--- a/include/mgba/internal/gb/memory.h
+++ b/include/mgba/internal/gb/memory.h
@@ -63,24 +63,27 @@ enum {
};
struct GBMemory;
-typedef void (*GBMemoryBankController)(struct GB*, uint16_t address, uint8_t value);
+typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value);
+typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address);
DECL_BITFIELD(GBMBC7Field, uint8_t);
-DECL_BIT(GBMBC7Field, SK, 6);
DECL_BIT(GBMBC7Field, CS, 7);
-DECL_BIT(GBMBC7Field, IO, 1);
+DECL_BIT(GBMBC7Field, CLK, 6);
+DECL_BIT(GBMBC7Field, DI, 1);
+DECL_BIT(GBMBC7Field, DO, 0);
enum GBMBC7MachineState {
- GBMBC7_STATE_NULL = -1,
GBMBC7_STATE_IDLE = 0,
GBMBC7_STATE_READ_COMMAND = 1,
- GBMBC7_STATE_READ_ADDRESS = 2,
- GBMBC7_STATE_COMMAND_0 = 3,
- GBMBC7_STATE_COMMAND_SR_WRITE = 4,
- GBMBC7_STATE_COMMAND_SR_READ = 5,
- GBMBC7_STATE_COMMAND_SR_FILL = 6,
- GBMBC7_STATE_READ = 7,
- GBMBC7_STATE_WRITE = 8,
+ GBMBC7_STATE_DO = 2,
+
+ GBMBC7_STATE_EEPROM_EWDS = 0x10,
+ GBMBC7_STATE_EEPROM_WRAL = 0x11,
+ GBMBC7_STATE_EEPROM_ERAL = 0x12,
+ GBMBC7_STATE_EEPROM_EWEN = 0x13,
+ GBMBC7_STATE_EEPROM_WRITE = 0x14,
+ GBMBC7_STATE_EEPROM_READ = 0x18,
+ GBMBC7_STATE_EEPROM_ERASE = 0x1C,
};
struct GBMBC1State {
@@ -90,17 +93,23 @@ struct GBMBC1State {
struct GBMBC7State {
enum GBMBC7MachineState state;
- uint32_t sr;
+ uint16_t sr;
uint8_t address;
bool writable;
int srBits;
- int command;
- GBMBC7Field field;
+ uint8_t access;
+ uint8_t latch;
+ GBMBC7Field eeprom;
+};
+
+struct GBPocketCamState {
+ bool registersActive;
};
union GBMBCState {
struct GBMBC1State mbc1;
struct GBMBC7State mbc7;
+ struct GBPocketCamState pocketCam;
};
struct mRotationSource;
@@ -109,7 +118,8 @@ struct GBMemory {
uint8_t* romBase;
uint8_t* romBank;
enum GBMemoryBankControllerType mbcType;
- GBMemoryBankController mbc;
+ GBMemoryBankControllerWrite mbcWrite;
+ GBMemoryBankControllerRead mbcRead;
union GBMBCState mbcState;
int currentBank;
diff --git a/include/mgba/internal/gb/overrides.h b/include/mgba/internal/gb/overrides.h
index fd787163c..44d012183 100644
--- a/include/mgba/internal/gb/overrides.h
+++ b/include/mgba/internal/gb/overrides.h
@@ -16,6 +16,8 @@ struct GBCartridgeOverride {
int headerCrc32;
enum GBModel model;
enum GBMemoryBankControllerType mbc;
+
+ uint32_t gbColors[4];
};
struct Configuration;
diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h
index e370c997b..c55f97600 100644
--- a/include/mgba/internal/gb/serialize.h
+++ b/include/mgba/internal/gb/serialize.h
@@ -154,8 +154,7 @@ mLOG_DECLARE_CATEGORY(GB_STATE);
* | 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 @@ struct GBSerializedState {
uint16_t reserved;
} memory;
- uint64_t creationUsec;
-
- uint32_t reserved[48];
+ uint32_t reserved[50];
uint8_t oam[GB_SIZE_OAM];
diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h
index 60534636b..440a90ccf 100644
--- a/include/mgba/internal/gb/video.h
+++ b/include/mgba/internal/gb/video.h
@@ -144,7 +144,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value);
void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value);
void GBVideoSwitchBank(struct GBVideo* video, uint8_t value);
-void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color);
+void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color);
struct GBSerializedState;
void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state);
diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h
index 9507f0e29..1e5c89890 100644
--- a/include/mgba/internal/gba/memory.h
+++ b/include/mgba/internal/gba/memory.h
@@ -132,10 +132,6 @@ struct GBAMemory {
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;
diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h
index 14bddf6e2..4f24198dd 100644
--- a/include/mgba/internal/gba/serialize.h
+++ b/include/mgba/internal/gba/serialize.h
@@ -190,8 +190,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | 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 @@ struct GBASerializedState {
uint32_t cpuPrefetch[2];
uint32_t associatedStreamId;
- uint32_t reservedRr[3];
-
- uint64_t creationUsec;
+ uint32_t reservedRr[5];
uint32_t lastPrefetchedPc;
GBASerializedMiscFlags miscFlags;
diff --git a/include/mgba/internal/lr35902/debugger/debugger.h b/include/mgba/internal/lr35902/debugger/debugger.h
index 85712ee96..340e13aac 100644
--- a/include/mgba/internal/lr35902/debugger/debugger.h
+++ b/include/mgba/internal/lr35902/debugger/debugger.h
@@ -10,9 +10,10 @@
CXX_GUARD_START
-#include
+#include
#include
+#include
struct LR35902DebugBreakpoint {
diff --git a/res/shaders/agb001.shader/manifest.ini b/res/shaders/agb001.shader/manifest.ini
index 62280af9f..4c1a8f5b3 100644
--- a/res/shaders/agb001.shader/manifest.ini
+++ b/res/shaders/agb001.shader/manifest.ini
@@ -7,5 +7,5 @@ passes=1
[pass.0]
fragmentShader=agb001.fs
blend=1
-width=960
-height=640
+width=-4
+height=-4
diff --git a/res/shaders/ags001.shader/manifest.ini b/res/shaders/ags001.shader/manifest.ini
index 85398cce6..2d5b7c491 100644
--- a/res/shaders/ags001.shader/manifest.ini
+++ b/res/shaders/ags001.shader/manifest.ini
@@ -7,13 +7,13 @@ passes=2
[pass.0]
fragmentShader=ags001.fs
blend=1
-width=960
-height=640
+width=-4
+height=-4
[pass.1]
fragmentShader=ags001-light.fs
-width=960
-height=640
+width=-4
+height=-4
[pass.1.uniform.lightBrightness]
type=float
diff --git a/res/shaders/fish.shader/fish.fs b/res/shaders/fish.shader/fish.fs
index 7d750fd7b..96a1b5f46 100644
--- a/res/shaders/fish.shader/fish.fs
+++ b/res/shaders/fish.shader/fish.fs
@@ -23,19 +23,15 @@
THE SOFTWARE.
*/
-precision highp float;
-
varying vec2 texCoord;
uniform sampler2D tex;
uniform vec2 texSize;
uniform float similarity_threshold;
-#define screen_res 240,160
-
vec4 texel_fetch(sampler2D t, ivec2 c) // because GLSL TexelFetch is not supported
{
- return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * vec2(screen_res)) );
+ return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * texSize) );
}
float pixel_brightness(vec4 pixel)
@@ -140,7 +136,7 @@ vec4 interpolate_diagonal(vec4 a, vec4 b, vec4 c, vec4 d)
void main()
{
- ivec2 pixel_coords2 = ivec2(texCoord * vec2(screen_res) * 2);
+ ivec2 pixel_coords2 = ivec2(texCoord * texSize * 2);
ivec2 pixel_coords = pixel_coords2 / 2;
bool x_even = mod(pixel_coords2.x,2) == 0;
diff --git a/res/shaders/lcd.shader/lcd.fs b/res/shaders/lcd.shader/lcd.fs
new file mode 100644
index 000000000..ceedd8eb7
--- /dev/null
+++ b/res/shaders/lcd.shader/lcd.fs
@@ -0,0 +1,40 @@
+/*
+ LCD Shader
+
+ Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+uniform sampler2D tex;
+uniform vec2 texSize;
+varying vec2 texCoord;
+
+uniform float boundBrightness;
+
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+
+ if (int(mod(texCoord.s * texSize.x * 3.0, 3.0)) == 0 ||
+ int(mod(texCoord.t * texSize.y * 3.0, 3.0)) == 0)
+ {
+ color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness;
+ }
+
+ gl_FragColor = color;
+}
diff --git a/res/shaders/lcd.shader/manifest.ini b/res/shaders/lcd.shader/manifest.ini
new file mode 100644
index 000000000..81061d8b0
--- /dev/null
+++ b/res/shaders/lcd.shader/manifest.ini
@@ -0,0 +1,18 @@
+[shader]
+name=LCD
+author=Dominus Iniquitatis
+description=Simple LCD emulation.
+passes=1
+
+[pass.0]
+fragmentShader=lcd.fs
+blend=1
+width=-3
+height=-3
+
+[pass.0.uniform.boundBrightness]
+type=float
+readableName=Bound brightness
+default=0.9
+min=0.0
+max=1.0
diff --git a/res/shaders/motion_blur.shader/manifest.ini b/res/shaders/motion_blur.shader/manifest.ini
new file mode 100644
index 000000000..5f485a041
--- /dev/null
+++ b/res/shaders/motion_blur.shader/manifest.ini
@@ -0,0 +1,18 @@
+[shader]
+name=Motion Blur
+author=Dominus Iniquitatis
+description=Simple motion blur.
+passes=1
+
+[pass.0]
+fragmentShader=motion_blur.fs
+blend=1
+width=-1
+height=-1
+
+[pass.0.uniform.amount]
+type=float
+readableName=Amount
+default=0.3
+min=0.0
+max=1.0
diff --git a/res/shaders/motion_blur.shader/motion_blur.fs b/res/shaders/motion_blur.shader/motion_blur.fs
new file mode 100644
index 000000000..850107355
--- /dev/null
+++ b/res/shaders/motion_blur.shader/motion_blur.fs
@@ -0,0 +1,35 @@
+/*
+ Motion Blur Shader
+
+ Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+uniform sampler2D tex;
+uniform vec2 texSize;
+varying vec2 texCoord;
+
+uniform float amount;
+
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+ color.a = 1.0 - amount;
+
+ gl_FragColor = color;
+}
diff --git a/res/shaders/scanlines.shader/manifest.ini b/res/shaders/scanlines.shader/manifest.ini
new file mode 100644
index 000000000..8df4604d1
--- /dev/null
+++ b/res/shaders/scanlines.shader/manifest.ini
@@ -0,0 +1,18 @@
+[shader]
+name=Scanlines
+author=Dominus Iniquitatis
+description=Simple scanlines.
+passes=1
+
+[pass.0]
+fragmentShader=scanlines.fs
+blend=1
+width=-2
+height=-2
+
+[pass.0.uniform.lineBrightness]
+type=float
+readableName=Line brightness
+default=0.5
+min=0.0
+max=1.0
diff --git a/res/shaders/scanlines.shader/scanlines.fs b/res/shaders/scanlines.shader/scanlines.fs
new file mode 100644
index 000000000..4f25fd0f6
--- /dev/null
+++ b/res/shaders/scanlines.shader/scanlines.fs
@@ -0,0 +1,39 @@
+/*
+ Scanlines Shader
+
+ Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+uniform sampler2D tex;
+uniform vec2 texSize;
+varying vec2 texCoord;
+
+uniform float lineBrightness;
+
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+
+ if (int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0)
+ {
+ color.rgb *= vec3(1.0, 1.0, 1.0) * lineBrightness;
+ }
+
+ gl_FragColor = color;
+}
diff --git a/res/shaders/soften.shader/manifest.ini b/res/shaders/soften.shader/manifest.ini
new file mode 100644
index 000000000..6c2312796
--- /dev/null
+++ b/res/shaders/soften.shader/manifest.ini
@@ -0,0 +1,18 @@
+[shader]
+name=Soften
+author=Dominus Iniquitatis
+description=Soft image blurring.
+passes=1
+
+[pass.0]
+fragmentShader=soften.fs
+blend=1
+width=-1
+height=-1
+
+[pass.0.uniform.amount]
+type=float
+readableName=Amount
+default=0.5
+min=0.0
+max=1.0
diff --git a/res/shaders/soften.shader/soften.fs b/res/shaders/soften.shader/soften.fs
new file mode 100644
index 000000000..41cfa1083
--- /dev/null
+++ b/res/shaders/soften.shader/soften.fs
@@ -0,0 +1,64 @@
+/*
+ Soften Shader
+
+ Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+uniform sampler2D tex;
+uniform vec2 texSize;
+varying vec2 texCoord;
+
+uniform float amount;
+
+vec2 GetTexelSize()
+{
+ return vec2(1.0 / texSize.x, 1.0 / texSize.y);
+}
+
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+
+ vec4 northColor = texture2D(tex, texCoord + vec2(0.0, GetTexelSize().y));
+ vec4 southColor = texture2D(tex, texCoord - vec2(0.0, GetTexelSize().y));
+ vec4 eastColor = texture2D(tex, texCoord + vec2(GetTexelSize().x, 0.0));
+ vec4 westColor = texture2D(tex, texCoord - vec2(GetTexelSize().x, 0.0));
+
+ if (abs(length(color) - length(northColor)) > 0.0)
+ {
+ color = mix(color, northColor, amount / 4.0);
+ }
+
+ if (abs(length(color) - length(southColor)) > 0.0)
+ {
+ color = mix(color, southColor, amount / 4.0);
+ }
+
+ if (abs(length(color) - length(eastColor)) > 0.0)
+ {
+ color = mix(color, eastColor, amount / 4.0);
+ }
+
+ if (abs(length(color) - length(westColor)) > 0.0)
+ {
+ color = mix(color, westColor, amount / 4.0);
+ }
+
+ gl_FragColor = color;
+}
diff --git a/res/shaders/vba_pixelate.shader/manifest.ini b/res/shaders/vba_pixelate.shader/manifest.ini
new file mode 100644
index 000000000..ae5d58d1b
--- /dev/null
+++ b/res/shaders/vba_pixelate.shader/manifest.ini
@@ -0,0 +1,18 @@
+[shader]
+name=VBA Pixelate
+author=Dominus Iniquitatis
+description=VisualBoyAdvance-style pixelation.
+passes=1
+
+[pass.0]
+fragmentShader=vba_pixelate.fs
+blend=1
+width=-2
+height=-2
+
+[pass.0.uniform.boundBrightness]
+type=float
+readableName=Bound brightness
+default=0.5
+min=0.0
+max=1.0
diff --git a/res/shaders/vba_pixelate.shader/vba_pixelate.fs b/res/shaders/vba_pixelate.shader/vba_pixelate.fs
new file mode 100644
index 000000000..c676a3afd
--- /dev/null
+++ b/res/shaders/vba_pixelate.shader/vba_pixelate.fs
@@ -0,0 +1,40 @@
+/*
+ VBA Pixelate Shader
+
+ Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+uniform sampler2D tex;
+uniform vec2 texSize;
+varying vec2 texCoord;
+
+uniform float boundBrightness;
+
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+
+ if (int(mod(texCoord.s * texSize.x * 2.0, 2.0)) == 0 ||
+ int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0)
+ {
+ color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness;
+ }
+
+ gl_FragColor = color;
+}
diff --git a/res/shaders/vignette.shader/manifest.ini b/res/shaders/vignette.shader/manifest.ini
new file mode 100644
index 000000000..f486dbc44
--- /dev/null
+++ b/res/shaders/vignette.shader/manifest.ini
@@ -0,0 +1,18 @@
+[shader]
+name=Vignette
+author=Dominus Iniquitatis
+description=Configurable vignette effect.
+passes=1
+
+[pass.0]
+fragmentShader=vignette.fs
+blend=1
+width=-1
+height=-1
+
+[pass.0.uniform.intensity]
+type=float
+readableName=Intensity
+default=1.0
+min=0.0
+max=1.0
diff --git a/res/shaders/vignette.shader/vignette.fs b/res/shaders/vignette.shader/vignette.fs
new file mode 100644
index 000000000..fb6e11c3c
--- /dev/null
+++ b/res/shaders/vignette.shader/vignette.fs
@@ -0,0 +1,35 @@
+/*
+ Vignette Shader
+
+ Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+uniform sampler2D tex;
+uniform vec2 texSize;
+varying vec2 texCoord;
+
+uniform float intensity;
+
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+ color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), length(texCoord - 0.5) * intensity);
+
+ gl_FragColor = color;
+}
diff --git a/res/shaders/wiiu.shader/manifest.ini b/res/shaders/wiiu.shader/manifest.ini
index 2b92c1d74..e69751ed3 100644
--- a/res/shaders/wiiu.shader/manifest.ini
+++ b/res/shaders/wiiu.shader/manifest.ini
@@ -7,5 +7,5 @@ passes=1
[pass.0]
fragmentShader=wiiu.fs
blend=1
-width=960
-height=640
+width=-4
+height=-4
diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c
index 03ae0e90e..fde61a8a8 100644
--- a/src/arm/debugger/debugger.c
+++ b/src/arm/debugger/debugger.c
@@ -7,6 +7,7 @@
#include
#include
+#include
#include
#include
@@ -54,6 +55,7 @@ static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address
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 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) {
platform->clearWatchpoint = ARMDebuggerClearWatchpoint;
platform->checkBreakpoints = ARMDebuggerCheckBreakpoints;
platform->hasBreakpoints = ARMDebuggerHasBreakpoints;
+ platform->trace = ARMDebuggerTrace;
return platform;
}
@@ -207,3 +210,39 @@ static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t add
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);
+}
diff --git a/src/core/config.c b/src/core/config.c
index cbb6f32ee..950607469 100644
--- a/src/core/config.c
+++ b/src/core/config.c
@@ -86,6 +86,9 @@ static bool _lookupIntValue(const struct mCoreConfig* config, const char* key, i
}
char* end;
long value = strtol(charValue, &end, 10);
+ if (end == &charValue[1] && *end == 'x') {
+ value = strtol(charValue, &end, 16);
+ }
if (*end) {
return false;
}
diff --git a/src/core/library.c b/src/core/library.c
index d230f60a6..b16afe3ce 100644
--- a/src/core/library.c
+++ b/src/core/library.c
@@ -339,6 +339,16 @@ static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry*
sqlite3_step(library->insertPath);
}
+void mLibraryClear(struct mLibrary* library) {
+ int result = sqlite3_exec(library->db,
+ " BEGIN TRANSACTION;"
+ "\n DELETE FROM roots;"
+ "\n DELETE FROM roms;"
+ "\n DELETE FROM paths;"
+ "\n COMMIT;"
+ "\n VACUUM;", NULL, NULL, NULL);
+}
+
size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) {
sqlite3_clear_bindings(library->count);
sqlite3_reset(library->count);
diff --git a/src/core/mem-search.c b/src/core/mem-search.c
new file mode 100644
index 000000000..5cf74140f
--- /dev/null
+++ b/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
+
+#include
+#include
+
+DEFINE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult);
+
+static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, struct mCoreMemorySearchResults* out, size_t limit) {
+ const uint32_t* mem32 = mem;
+ size_t found = 0;
+ uint32_t start = block->start;
+ uint32_t end = size; // TODO: Segments
+ size_t i;
+ // TODO: Big endian
+ for (i = 0; (!limit || found < limit) && i < end; i += 16) {
+ int mask = 0;
+ mask |= (mem32[(i >> 2) + 0] == value32) << 0;
+ mask |= (mem32[(i >> 2) + 1] == value32) << 1;
+ mask |= (mem32[(i >> 2) + 2] == value32) << 2;
+ mask |= (mem32[(i >> 2) + 3] == value32) << 3;
+ if (!mask) {
+ continue;
+ }
+ if ((mask & 1) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i;
+ res->type = mCORE_MEMORY_SEARCH_32;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 2) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 4;
+ res->type = mCORE_MEMORY_SEARCH_32;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 4) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 8;
+ res->type = mCORE_MEMORY_SEARCH_32;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 8) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 12;
+ res->type = mCORE_MEMORY_SEARCH_32;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ }
+ // TODO: last 12 bytes
+ return found;
+}
+
+static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, struct mCoreMemorySearchResults* out, size_t limit) {
+ const uint16_t* mem16 = mem;
+ size_t found = 0;
+ uint32_t start = block->start;
+ uint32_t end = size; // TODO: Segments
+ size_t i;
+ // TODO: Big endian
+ for (i = 0; (!limit || found < limit) && i < end; i += 16) {
+ int mask = 0;
+ mask |= (mem16[(i >> 1) + 0] == value16) << 0;
+ mask |= (mem16[(i >> 1) + 1] == value16) << 1;
+ mask |= (mem16[(i >> 1) + 2] == value16) << 2;
+ mask |= (mem16[(i >> 1) + 3] == value16) << 3;
+ mask |= (mem16[(i >> 1) + 4] == value16) << 4;
+ mask |= (mem16[(i >> 1) + 5] == value16) << 5;
+ mask |= (mem16[(i >> 1) + 6] == value16) << 6;
+ mask |= (mem16[(i >> 1) + 7] == value16) << 7;
+ if (!mask) {
+ continue;
+ }
+ if ((mask & 1) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 2) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 2;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 4) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 4;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 8) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 6;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 16) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 8;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 32) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 10;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 64) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 12;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 128) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 14;
+ res->type = mCORE_MEMORY_SEARCH_16;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ }
+ // TODO: last 14 bytes
+ return found;
+}
+
+static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, struct mCoreMemorySearchResults* out, size_t limit) {
+ const uint8_t* mem8 = mem;
+ size_t found = 0;
+ uint32_t start = block->start;
+ uint32_t end = size; // TODO: Segments
+ size_t i;
+ for (i = 0; (!limit || found < limit) && i < end; i += 8) {
+ int mask = 0;
+ mask |= (mem8[i + 0] == value8) << 0;
+ mask |= (mem8[i + 1] == value8) << 1;
+ mask |= (mem8[i + 2] == value8) << 2;
+ mask |= (mem8[i + 3] == value8) << 3;
+ mask |= (mem8[i + 4] == value8) << 4;
+ mask |= (mem8[i + 5] == value8) << 5;
+ mask |= (mem8[i + 6] == value8) << 6;
+ mask |= (mem8[i + 7] == value8) << 7;
+ if (!mask) {
+ continue;
+ }
+ if ((mask & 1) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 2) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 1;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 4) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 2;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 8) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 3;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 16) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 4;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 32) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 5;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 64) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 6;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ if ((mask & 128) && (!limit || found < limit)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i + 7;
+ res->type = mCORE_MEMORY_SEARCH_8;
+ res->segment = -1; // TODO
+ res->guessDivisor = 1;
+ ++found;
+ }
+ }
+ // TODO: last 7 bytes
+ return found;
+}
+
+static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) {
+ const char* memStr = mem;
+ size_t found = 0;
+ size_t len = strlen(valueStr);
+ uint32_t start = block->start;
+ uint32_t end = size; // TODO: Segments
+ size_t i;
+ for (i = 0; (!limit || found < limit) && i < end - len; ++i) {
+ if (!strncmp(valueStr, &memStr[i], len)) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
+ res->address = start + i;
+ res->type = mCORE_MEMORY_SEARCH_STRING;
+ res->segment = -1; // TODO
+ ++found;
+ }
+ }
+ return found;
+}
+
+static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) {
+ // TODO: As str
+
+ char* end;
+ uint64_t value;
+
+ size_t found = 0;
+
+ struct mCoreMemorySearchResults tmp;
+ mCoreMemorySearchResultsInit(&tmp, 0);
+
+ // Decimal:
+ value = strtoull(valueStr, &end, 10);
+ if (end && !end[0]) {
+ if (value > 0x10000) {
+ found += _search32(mem, size, block, value, out, limit ? limit - found : 0);
+ } else if (value > 0x100) {
+ found += _search16(mem, size, block, value, out, limit ? limit - found : 0);
+ } else {
+ found += _search8(mem, size, block, value, out, limit ? limit - found : 0);
+ }
+
+ uint32_t divisor = 1;
+ while (value && !(value % 10)) {
+ mCoreMemorySearchResultsClear(&tmp);
+ value /= 10;
+ divisor *= 10;
+
+ if (value > 0x10000) {
+ found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0);
+ } else if (value > 0x100) {
+ found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0);
+ } else {
+ found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0);
+ }
+ size_t i;
+ for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i);
+ res->guessDivisor = divisor;
+ *mCoreMemorySearchResultsAppend(out) = *res;
+ }
+ }
+ }
+
+ // Hex:
+ value = strtoull(valueStr, &end, 16);
+ if (end && !end[0]) {
+ if (value > 0x10000) {
+ found += _search32(mem, size, block, value, out, limit ? limit - found : 0);
+ } else if (value > 0x100) {
+ found += _search16(mem, size, block, value, out, limit ? limit - found : 0);
+ } else {
+ found += _search8(mem, size, block, value, out, limit ? limit - found : 0);
+ }
+
+ uint32_t divisor = 1;
+ while (value && !(value & 0xF)) {
+ mCoreMemorySearchResultsClear(&tmp);
+ value >>= 4;
+ divisor <<= 4;
+
+ if (value > 0x10000) {
+ found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0);
+ } else if (value > 0x100) {
+ found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0);
+ } else {
+ found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0);
+ }
+ size_t i;
+ for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i);
+ res->guessDivisor = divisor;
+ *mCoreMemorySearchResultsAppend(out) = *res;
+ }
+ }
+ }
+
+ mCoreMemorySearchResultsDeinit(&tmp);
+ return found;
+}
+
+static size_t _search(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) {
+ switch (params->type) {
+ case mCORE_MEMORY_SEARCH_32:
+ return _search32(mem, size, block, params->value32, out, limit);
+ case mCORE_MEMORY_SEARCH_16:
+ return _search16(mem, size, block, params->value16, out, limit);
+ case mCORE_MEMORY_SEARCH_8:
+ return _search8(mem, size, block, params->value8, out, limit);
+ case mCORE_MEMORY_SEARCH_STRING:
+ return _searchStr(mem, size, block, params->valueStr, out, limit);
+ case mCORE_MEMORY_SEARCH_GUESS:
+ return _searchGuess(mem, size, block, params->valueStr, out, limit);
+ }
+}
+
+void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) {
+ const struct mCoreMemoryBlock* blocks;
+ size_t nBlocks = core->listMemoryBlocks(core, &blocks);
+ size_t found = 0;
+
+ size_t b;
+ for (b = 0; (!limit || found < limit) && b < nBlocks; ++b) {
+ size_t size;
+ const struct mCoreMemoryBlock* block = &blocks[b];
+ if (!(block->flags & params->memoryFlags)) {
+ continue;
+ }
+ void* mem = core->getMemoryBlock(core, block->id, &size);
+ if (!mem) {
+ continue;
+ }
+ if (size > block->end - block->start) {
+ size = block->end - block->start; // TOOD: Segments
+ }
+ found += _search(mem, size, block, params, out, limit ? limit - found : 0);
+ }
+}
+
+bool _testGuess(struct mCore* core, const struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) {
+ uint64_t value;
+ char* end;
+
+ value = strtoull(params->valueStr, &end, 10);
+ if (end) {
+ if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) {
+ return true;
+ }
+ if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) {
+ return true;
+ }
+ if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) {
+ return true;
+ }
+ }
+
+ value = strtoull(params->valueStr, &end, 16);
+ if (end) {
+ if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) {
+ return true;
+ }
+ if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) {
+ return true;
+ }
+ if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout) {
+ size_t i;
+ for (i = 0; i < mCoreMemorySearchResultsSize(inout); ++i) {
+ struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i);
+ switch (res->type) {
+ case mCORE_MEMORY_SEARCH_8:
+ switch (params->type) {
+ case mCORE_MEMORY_SEARCH_8:
+ if (core->rawRead8(core, res->address, res->segment) != params->value8) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_16:
+ if (core->rawRead8(core, res->address, res->segment) != params->value16) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_32:
+ if (core->rawRead32(core, res->address, res->segment) != params->value32) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_GUESS:
+ if (!_testGuess(core, res, params)) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_16:
+ switch (params->type) {
+ case mCORE_MEMORY_SEARCH_16:
+ if (core->rawRead16(core, res->address, res->segment) != params->value16) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_32:
+ if (core->rawRead32(core, res->address, res->segment) != params->value32) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_GUESS:
+ if (!_testGuess(core, res, params)) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_32:
+ switch (params->type) {
+ case mCORE_MEMORY_SEARCH_32:
+ if (core->rawRead32(core, res->address, res->segment) != params->value32) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_GUESS:
+ if (!_testGuess(core, res, params)) {
+ mCoreMemorySearchResultsShift(inout, i, 1);
+ --i;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case mCORE_MEMORY_SEARCH_STRING:
+ case mCORE_MEMORY_SEARCH_GUESS:
+ // TOOD
+ break;
+ }
+ }
+}
diff --git a/src/core/serialize.c b/src/core/serialize.c
index bd6abd112..9efca53f7 100644
--- a/src/core/serialize.c
+++ b/src/core/serialize.c
@@ -303,6 +303,36 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
struct mStateExtdata extdata;
mStateExtdataInit(&extdata);
size_t stateSize = core->stateSize(core);
+
+ if (flags & SAVESTATE_METADATA) {
+ uint64_t creationUsec;
+#ifndef _MSC_VER
+ struct timeval tv;
+ if (!gettimeofday(&tv, 0)) {
+ uint64_t usec = tv.tv_usec;
+ usec += tv.tv_sec * 1000000LL;
+ STORE_64LE(usec, 0, &creationUsec);
+ }
+#else
+ struct timespec ts;
+ if (timespec_get(&ts, TIME_UTC)) {
+ uint64_t usec = ts.tv_nsec / 1000;
+ usec += ts.tv_sec * 1000000LL;
+ STORE_64LE(usec, 0, &creationUsec);
+ }
+#endif
+ else {
+ creationUsec = 0;
+ }
+
+ struct mStateExtdataItem item = {
+ .size = sizeof(creationUsec),
+ .data = &creationUsec,
+ .clean = NULL
+ };
+ mStateExtdataPut(&extdata, EXTDATA_META_TIME, &item);
+ }
+
if (flags & SAVESTATE_SAVEDATA) {
void* sram = NULL;
size_t size = core->savedataClone(core, &sram);
diff --git a/src/core/timing.c b/src/core/timing.c
index 11b808a93..1d22a93cb 100644
--- a/src/core/timing.c
+++ b/src/core/timing.c
@@ -89,5 +89,9 @@ int32_t mTimingNextEvent(struct mTiming* timing) {
if (!next) {
return INT_MAX;
}
- return next->when - timing->masterCycles;
+ return next->when - timing->masterCycles - *timing->relativeCycles;
+}
+
+int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent* event) {
+ return event->when - timing->masterCycles - *timing->relativeCycles;
}
diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c
index 70cff2a5f..cc911d8c7 100644
--- a/src/debugger/cli-debugger.c
+++ b/src/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 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "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" },
@@ -469,6 +471,27 @@ static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector
}
}
+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);
+ }
+}
+
static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
UNUSED(dv);
debugger->system->printStatus(debugger->system);
diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c
index bf7b9d99c..bac157f7c 100644
--- a/src/debugger/debugger.c
+++ b/src/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
+#include
#include
@@ -97,6 +97,13 @@ void mDebuggerRun(struct mDebugger* debugger) {
}
}
+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) {
debugger->state = DEBUGGER_PAUSED;
if (debugger->platform->entered) {
diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c
index ad59ae893..0f997f395 100644
--- a/src/feature/gui/gui-runner.c
+++ b/src/feature/gui/gui-runner.c
@@ -431,7 +431,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
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);
diff --git a/src/gb/audio.c b/src/gb/audio.c
index 8fbca6d9b..3b384cdf8 100644
--- a/src/gb/audio.c
+++ b/src/gb/audio.c
@@ -337,6 +337,7 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
}
audio->ch3.window = 0;
}
+ mTimingDeschedule(audio->timing, &audio->ch3Fade);
mTimingDeschedule(audio->timing, &audio->ch3Event);
if (audio->playingCh3) {
audio->ch3.readable = audio->style != GB_AUDIO_DMG;
@@ -554,7 +555,7 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) {
if (audio->ch2.envelope.dead == 2) {
mTimingDeschedule(timing, &audio->ch2Event);
}
- _updateSquareSample(&audio->ch1);
+ _updateSquareSample(&audio->ch2);
}
}
@@ -708,7 +709,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value) {
}
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
- ch->sample = (ch->control.hi * ch->envelope.currentVolume - 8) * 0x10;
+ ch->sample = (ch->control.hi * 2 - 1) * ch->envelope.currentVolume * 0x8;
}
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
@@ -863,6 +864,7 @@ static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesL
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);
diff --git a/src/gb/core.c b/src/gb/core.c
index 339eaa477..018517837 100644
--- a/src/gb/core.c
+++ b/src/gb/core.c
@@ -56,6 +56,28 @@ const static struct LR35902Segment _GBCSegments[] = {
{ 0 }
};
+const static struct mCoreMemoryBlock _GBMemoryBlocks[] = {
+ { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL },
+ { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 },
+ { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 },
+ { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2 , GB_SIZE_WORKING_RAM_BANK0 * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+};
+
+const static struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
+ { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL },
+ { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 },
+ { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
+ { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 },
+ { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2, GB_SIZE_WORKING_RAM_BANK0 * 8, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 7 },
+ { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+};
+
struct mVideoLogContext;
struct GBCore {
struct mCore d;
@@ -419,11 +441,13 @@ static bool _GBCoreSaveState(struct mCore* core, void* state) {
static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {
struct GBCore* gbcore = (struct GBCore*) core;
gbcore->keys = keys;
+ GBTestKeypadIRQ(core->board);
}
static void _GBCoreAddKeys(struct mCore* core, uint32_t keys) {
struct GBCore* gbcore = (struct GBCore*) core;
gbcore->keys |= keys;
+ GBTestKeypadIRQ(core->board);
}
static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) {
@@ -539,6 +563,48 @@ static void _GBCoreRawWrite32(struct mCore* core, uint32_t address, int segment,
GBPatch8(cpu, address + 3, value >> 24, NULL, segment);
}
+size_t _GBListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) {
+ const struct GB* gb = core->board;
+ switch (gb->model) {
+ case GB_MODEL_DMG:
+ case GB_MODEL_SGB:
+ default:
+ *blocks = _GBMemoryBlocks;
+ return sizeof(_GBMemoryBlocks) / sizeof(*_GBMemoryBlocks);
+ case GB_MODEL_CGB:
+ case GB_MODEL_AGB:
+ *blocks = _GBCMemoryBlocks;
+ return sizeof(_GBCMemoryBlocks) / sizeof(*_GBCMemoryBlocks);
+ }
+}
+
+void* _GBGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
+ struct GB* gb = core->board;
+ bool isCgb = gb->model >= GB_MODEL_CGB;
+ switch (id) {
+ default:
+ return NULL;
+ case GB_REGION_CART_BANK0:
+ *sizeOut = gb->memory.romSize;
+ return gb->memory.rom;
+ case GB_REGION_VRAM:
+ *sizeOut = GB_SIZE_WORKING_RAM_BANK0 * (isCgb ? 1 : 2);
+ return gb->video.vram;
+ case GB_REGION_EXTERNAL_RAM:
+ *sizeOut = gb->sramSize;
+ return gb->memory.sram;
+ case GB_REGION_WORKING_RAM_BANK0:
+ *sizeOut = GB_SIZE_VRAM * (isCgb ? 8 : 2);
+ return gb->memory.wram;
+ case GB_BASE_OAM:
+ *sizeOut = GB_SIZE_OAM;
+ return gb->video.oam.raw;
+ case GB_BASE_HRAM:
+ *sizeOut = GB_SIZE_HRAM;
+ return gb->memory.hram;
+ }
+}
+
#ifdef USE_DEBUGGERS
static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
UNUSED(core);
@@ -771,6 +837,8 @@ struct mCore* GBCoreCreate(void) {
core->rawWrite8 = _GBCoreRawWrite8;
core->rawWrite16 = _GBCoreRawWrite16;
core->rawWrite32 = _GBCoreRawWrite32;
+ core->listMemoryBlocks = _GBListMemoryBlocks;
+ core->getMemoryBlock = _GBGetMemoryBlock;
#ifdef USE_DEBUGGERS
core->supportsDebuggerType = _GBCoreSupportsDebuggerType;
core->debuggerPlatform = _GBCoreDebuggerPlatform;
diff --git a/src/gb/debugger/cli.c b/src/gb/debugger/cli.c
index 809961290..2f59293d1 100644
--- a/src/gb/debugger/cli.c
+++ b/src/gb/debugger/cli.c
@@ -118,5 +118,5 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
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);
}
diff --git a/src/gb/gb.c b/src/gb/gb.c
index a97fe97ef..7338701cc 100644
--- a/src/gb/gb.c
+++ b/src/gb/gb.c
@@ -433,6 +433,7 @@ void GBReset(struct LR35902Core* cpu) {
cpu->e = 0xD8;
cpu->h = 1;
cpu->l = 0x4D;
+ gb->timer.internalDiv = 0x2AF3;
break;
case GB_MODEL_AGB:
cpu->b = 1;
@@ -444,6 +445,7 @@ void GBReset(struct LR35902Core* cpu) {
cpu->e = 0x08;
cpu->h = 0;
cpu->l = 0x7C;
+ gb->timer.internalDiv = 0x7A8;
break;
}
@@ -710,4 +712,6 @@ void GBFrameEnded(struct GB* gb) {
mCheatRefresh(device, cheats);
}
}
+
+ GBTestKeypadIRQ(gb);
}
diff --git a/src/gb/io.c b/src/gb/io.c
index 8b4102c0d..3be656499 100644
--- a/src/gb/io.c
+++ b/src/gb/io.c
@@ -564,6 +564,13 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
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);
diff --git a/src/gb/mbc.c b/src/gb/mbc.c
index 0710f10c0..87fae1510 100644
--- a/src/gb/mbc.c
+++ b/src/gb/mbc.c
@@ -28,6 +28,10 @@ static void _GBMBC5(struct GB*, uint16_t address, uint8_t value);
static void _GBMBC6(struct GB*, uint16_t address, uint8_t value);
static void _GBMBC7(struct GB*, uint16_t address, uint8_t value);
static void _GBHuC3(struct GB*, uint16_t address, uint8_t value);
+static void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value);
+
+static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address);
+static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address);
void GBMBCSwitchBank(struct GB* gb, int bank) {
size_t bankStart = bank * GB_SIZE_CART_BANK0;
@@ -75,7 +79,11 @@ static bool _isMulticart(const uint8_t* mem) {
void GBMBCSwitchSramBank(struct GB* gb, int bank) {
size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM;
- GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM);
+ if (bankStart + GB_SIZE_EXTERNAL_RAM > gb->sramSize) {
+ mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid RAM bank: %0X", bank);
+ bankStart &= (gb->sramSize - 1);
+ bank = bankStart / GB_SIZE_EXTERNAL_RAM;
+ }
gb->memory.sramBank = &gb->memory.sram[bankStart];
gb->memory.sramCurrentBank = bank;
}
@@ -148,6 +156,12 @@ void GBMBCInit(struct GB* gb) {
case 0x22:
gb->memory.mbcType = GB_MBC7;
break;
+ case 0xFC:
+ gb->memory.mbcType = GB_POCKETCAM;
+ break;
+ case 0xFD:
+ gb->memory.mbcType = GB_HuC1;
+ break;
case 0xFE:
gb->memory.mbcType = GB_HuC3;
break;
@@ -156,51 +170,57 @@ void GBMBCInit(struct GB* gb) {
} else {
gb->memory.mbcType = GB_MBC_NONE;
}
+ gb->memory.mbcRead = NULL;
switch (gb->memory.mbcType) {
case GB_MBC_NONE:
- gb->memory.mbc = _GBMBCNone;
+ gb->memory.mbcWrite = _GBMBCNone;
break;
case GB_MBC1:
- gb->memory.mbc = _GBMBC1;
+ gb->memory.mbcWrite = _GBMBC1;
break;
case GB_MBC2:
- gb->memory.mbc = _GBMBC2;
+ gb->memory.mbcWrite = _GBMBC2;
gb->sramSize = 0x200;
break;
case GB_MBC3:
- gb->memory.mbc = _GBMBC3;
+ gb->memory.mbcWrite = _GBMBC3;
break;
default:
mLOG(GB_MBC, WARN, "Unknown MBC type: %02X", cart->type);
// Fall through
case GB_MBC5:
- gb->memory.mbc = _GBMBC5;
+ gb->memory.mbcWrite = _GBMBC5;
break;
case GB_MBC6:
mLOG(GB_MBC, WARN, "unimplemented MBC: MBC6");
- gb->memory.mbc = _GBMBC6;
+ gb->memory.mbcWrite = _GBMBC6;
break;
case GB_MBC7:
- gb->memory.mbc = _GBMBC7;
- gb->sramSize = GB_SIZE_EXTERNAL_RAM;
+ gb->memory.mbcWrite = _GBMBC7;
+ gb->memory.mbcRead = _GBMBC7Read;
+ gb->sramSize = 0x100;
break;
case GB_MMM01:
mLOG(GB_MBC, WARN, "unimplemented MBC: MMM01");
- gb->memory.mbc = _GBMBC1;
+ gb->memory.mbcWrite = _GBMBC1;
break;
case GB_HuC1:
mLOG(GB_MBC, WARN, "unimplemented MBC: HuC-1");
- gb->memory.mbc = _GBMBC1;
+ gb->memory.mbcWrite = _GBMBC1;
break;
case GB_HuC3:
- gb->memory.mbc = _GBHuC3;
+ gb->memory.mbcWrite = _GBHuC3;
break;
case GB_MBC3_RTC:
memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs));
- gb->memory.mbc = _GBMBC3;
+ gb->memory.mbcWrite = _GBMBC3;
break;
case GB_MBC5_RUMBLE:
- gb->memory.mbc = _GBMBC5;
+ gb->memory.mbcWrite = _GBMBC5;
+ break;
+ case GB_POCKETCAM:
+ gb->memory.mbcWrite = _GBPocketCam;
+ gb->memory.mbcRead = _GBPocketCamRead;
break;
}
@@ -350,7 +370,8 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) {
// TODO
mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value);
break;
- }}
+ }
+}
void _GBMBC3(struct GB* gb, uint16_t address, uint8_t value) {
struct GBMemory* memory = &gb->memory;
@@ -451,12 +472,25 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) {
void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
int bank = value & 0x7F;
switch (address >> 13) {
+ case 0x0:
+ switch (value) {
+ default:
+ case 0:
+ gb->memory.mbcState.mbc7.access = 0;
+ break;
+ case 0xA:
+ gb->memory.mbcState.mbc7.access |= 1;
+ break;
+ }
+ break;
case 0x1:
GBMBCSwitchBank(gb, bank);
break;
case 0x2:
- if (value < 0x10) {
- GBMBCSwitchSramBank(gb, value);
+ if (value == 0x40) {
+ gb->memory.mbcState.mbc7.access |= 2;
+ } else {
+ gb->memory.mbcState.mbc7.access &= ~2;
}
break;
default:
@@ -466,19 +500,17 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
}
}
-uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) {
+uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) {
struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
+ if (mbc7->access != 3) {
+ return 0xFF;
+ }
switch (address & 0xF0) {
- case 0x00:
- case 0x10:
- case 0x60:
- case 0x70:
- return 0;
case 0x20:
if (memory->rotation && memory->rotation->readTiltX) {
int32_t x = -memory->rotation->readTiltX(memory->rotation);
x >>= 21;
- x += 2047;
+ x += 0x81D0;
return x;
}
return 0xFF;
@@ -486,7 +518,7 @@ uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) {
if (memory->rotation && memory->rotation->readTiltX) {
int32_t x = -memory->rotation->readTiltX(memory->rotation);
x >>= 21;
- x += 2047;
+ x += 0x81D0;
return x >> 8;
}
return 7;
@@ -494,7 +526,7 @@ uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) {
if (memory->rotation && memory->rotation->readTiltY) {
int32_t y = -memory->rotation->readTiltY(memory->rotation);
y >>= 21;
- y += 2047;
+ y += 0x81D0;
return y;
}
return 0xFF;
@@ -502,144 +534,142 @@ uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) {
if (memory->rotation && memory->rotation->readTiltY) {
int32_t y = -memory->rotation->readTiltY(memory->rotation);
y >>= 21;
- y += 2047;
+ y += 0x81D0;
return y >> 8;
}
return 7;
+ case 0x60:
+ return 0;
case 0x80:
- return (mbc7->sr >> 16) & 1;
+ return mbc7->eeprom;
default:
return 0xFF;
}
}
void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) {
- if ((address & 0xF0) != 0x80) {
+ struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
+ if (mbc7->access != 3) {
return;
}
- struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
- GBMBC7Field old = memory->mbcState.mbc7.field;
- mbc7->field = GBMBC7FieldClearIO(value);
- if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) {
- if (mbc7->state == GBMBC7_STATE_WRITE) {
- if (mbc7->writable) {
- memory->sramBank[mbc7->address * 2] = mbc7->sr >> 8;
- memory->sramBank[mbc7->address * 2 + 1] = mbc7->sr;
- }
- mbc7->sr = 0x1FFFF;
- mbc7->state = GBMBC7_STATE_NULL;
- } else {
- mbc7->state = GBMBC7_STATE_IDLE;
+ switch (address & 0xF0) {
+ case 0x00:
+ mbc7->latch = (value & 0x55) == 0x55;
+ return;
+ case 0x10:
+ mbc7->latch |= (value & 0xAA);
+ if (mbc7->latch == 0xFF && memory->rotation && memory->rotation->sample) {
+ memory->rotation->sample(memory->rotation);
}
+ mbc7->latch = 0;
+ return;
+ default:
+ mLOG(GB_MBC, STUB, "MBC7 unknown register: %04X:%02X", address, value);
+ return;
+ case 0x80:
+ break;
}
- if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) {
- if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) {
+ GBMBC7Field old = memory->mbcState.mbc7.eeprom;
+ value = GBMBC7FieldFillDO(value); // Hi-Z
+ if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) {
+ mbc7->state = GBMBC7_STATE_IDLE;
+ }
+ if (!GBMBC7FieldIsCLK(old) && GBMBC7FieldIsCLK(value)) {
+ if (mbc7->state == GBMBC7_STATE_READ_COMMAND || mbc7->state == GBMBC7_STATE_EEPROM_WRITE || mbc7->state == GBMBC7_STATE_EEPROM_WRAL) {
mbc7->sr <<= 1;
- mbc7->sr |= GBMBC7FieldGetIO(value);
+ mbc7->sr |= GBMBC7FieldGetDI(value);
++mbc7->srBits;
}
switch (mbc7->state) {
case GBMBC7_STATE_IDLE:
- if (GBMBC7FieldIsIO(value)) {
+ if (GBMBC7FieldIsDI(value)) {
mbc7->state = GBMBC7_STATE_READ_COMMAND;
mbc7->srBits = 0;
mbc7->sr = 0;
}
break;
case GBMBC7_STATE_READ_COMMAND:
- if (mbc7->srBits == 2) {
- mbc7->state = GBMBC7_STATE_READ_ADDRESS;
- mbc7->srBits = 0;
- mbc7->command = mbc7->sr;
- }
- break;
- case GBMBC7_STATE_READ_ADDRESS:
- if (mbc7->srBits == 8) {
- mbc7->state = GBMBC7_STATE_COMMAND_0 + mbc7->command;
- mbc7->srBits = 0;
- mbc7->address = mbc7->sr;
- if (mbc7->state == GBMBC7_STATE_COMMAND_0) {
- switch (mbc7->address >> 6) {
- case 0:
- mbc7->writable = false;
- mbc7->state = GBMBC7_STATE_NULL;
- break;
- case 3:
- mbc7->writable = true;
- mbc7->state = GBMBC7_STATE_NULL;
- break;
- }
+ if (mbc7->srBits == 10) {
+ mbc7->state = 0x10 | (mbc7->sr >> 6);
+ if (mbc7->state & 0xC) {
+ mbc7->state &= ~0x3;
}
- }
- break;
- case GBMBC7_STATE_COMMAND_0:
- if (mbc7->srBits == 16) {
- switch (mbc7->address >> 6) {
- case 0:
- mbc7->writable = false;
- mbc7->state = GBMBC7_STATE_NULL;
- break;
- case 1:
- mbc7->state = GBMBC7_STATE_WRITE;
- if (mbc7->writable) {
- int i;
- for (i = 0; i < 256; ++i) {
- memory->sramBank[i * 2] = mbc7->sr >> 8;
- memory->sramBank[i * 2 + 1] = mbc7->sr;
- }
- }
- break;
- case 2:
- mbc7->state = GBMBC7_STATE_WRITE;
- if (mbc7->writable) {
- int i;
- for (i = 0; i < 256; ++i) {
- memory->sramBank[i * 2] = 0xFF;
- memory->sramBank[i * 2 + 1] = 0xFF;
- }
- }
- break;
- case 3:
- mbc7->writable = true;
- mbc7->state = GBMBC7_STATE_NULL;
- break;
- }
- }
- break;
- case GBMBC7_STATE_COMMAND_SR_WRITE:
- if (mbc7->srBits == 16) {
mbc7->srBits = 0;
- mbc7->state = GBMBC7_STATE_WRITE;
+ mbc7->address = mbc7->sr & 0x7F;
}
break;
- case GBMBC7_STATE_COMMAND_SR_READ:
- if (mbc7->srBits == 1) {
- mbc7->sr = memory->sramBank[mbc7->address * 2] << 8;
- mbc7->sr |= memory->sramBank[mbc7->address * 2 + 1];
- mbc7->srBits = 0;
- mbc7->state = GBMBC7_STATE_READ;
- }
- break;
- case GBMBC7_STATE_COMMAND_SR_FILL:
- if (mbc7->srBits == 16) {
- mbc7->sr = 0xFFFF;
- mbc7->srBits = 0;
- mbc7->state = GBMBC7_STATE_WRITE;
+ case GBMBC7_STATE_DO:
+ value = GBMBC7FieldSetDO(value, mbc7->sr >> 15);
+ mbc7->sr <<= 1;
+ --mbc7->srBits;
+ if (!mbc7->srBits) {
+ mbc7->state = GBMBC7_STATE_IDLE;
}
break;
default:
break;
}
- } else if (GBMBC7FieldIsSK(old) && !GBMBC7FieldIsSK(value)) {
- if (mbc7->state == GBMBC7_STATE_READ) {
- mbc7->sr <<= 1;
- ++mbc7->srBits;
+ switch (mbc7->state) {
+ case GBMBC7_STATE_EEPROM_EWEN:
+ mbc7->writable = true;
+ mbc7->state = GBMBC7_STATE_IDLE;
+ break;
+ case GBMBC7_STATE_EEPROM_EWDS:
+ mbc7->writable = false;
+ mbc7->state = GBMBC7_STATE_IDLE;
+ break;
+ case GBMBC7_STATE_EEPROM_WRITE:
if (mbc7->srBits == 16) {
- mbc7->srBits = 0;
- mbc7->state = GBMBC7_STATE_NULL;
+ if (mbc7->writable) {
+ memory->sram[mbc7->address * 2] = mbc7->sr >> 8;
+ memory->sram[mbc7->address * 2 + 1] = mbc7->sr;
+ }
+ mbc7->state = GBMBC7_STATE_IDLE;
}
+ break;
+ case GBMBC7_STATE_EEPROM_ERASE:
+ if (mbc7->writable) {
+ memory->sram[mbc7->address * 2] = 0xFF;
+ memory->sram[mbc7->address * 2 + 1] = 0xFF;
+ }
+ mbc7->state = GBMBC7_STATE_IDLE;
+ break;
+ case GBMBC7_STATE_EEPROM_READ:
+ mbc7->srBits = 16;
+ mbc7->sr = memory->sram[mbc7->address * 2] << 8;
+ mbc7->sr |= memory->sram[mbc7->address * 2 + 1];
+ mbc7->state = GBMBC7_STATE_DO;
+ value = GBMBC7FieldClearDO(value);
+ break;
+ case GBMBC7_STATE_EEPROM_WRAL:
+ if (mbc7->srBits == 16) {
+ if (mbc7->writable) {
+ int i;
+ for (i = 0; i < 128; ++i) {
+ memory->sram[i * 2] = mbc7->sr >> 8;
+ memory->sram[i * 2 + 1] = mbc7->sr;
+ }
+ }
+ mbc7->state = GBMBC7_STATE_IDLE;
+ }
+ break;
+ case GBMBC7_STATE_EEPROM_ERAL:
+ if (mbc7->writable) {
+ int i;
+ for (i = 0; i < 128; ++i) {
+ memory->sram[i * 2] = 0xFF;
+ memory->sram[i * 2 + 1] = 0xFF;
+ }
+ }
+ mbc7->state = GBMBC7_STATE_IDLE;
+ break;
+ default:
+ break;
}
+ } else if (GBMBC7FieldIsCS(value) && GBMBC7FieldIsCLK(old) && !GBMBC7FieldIsCLK(value)) {
+ value = GBMBC7FieldSetDO(value, GBMBC7FieldGetDO(old));
}
+ mbc7->eeprom = value;
}
void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) {
@@ -674,6 +704,52 @@ void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) {
}
}
+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) {
struct GBMBCRTCSaveBuffer rtcBuffer;
struct VFile* vf = gb->sramVf;
diff --git a/src/gb/memory.c b/src/gb/memory.c
index 8484941b7..c424f5a92 100644
--- a/src/gb/memory.c
+++ b/src/gb/memory.c
@@ -99,7 +99,8 @@ void GBMemoryInit(struct GB* gb) {
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 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) {
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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) {
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 @@ uint8_t GBView8(struct LR35902Core* cpu, uint16_t address, int segment) {
} else {
return 0xFF;
}
- } else if (memory->mbcType == GB_MBC7) {
- return GBMBC7Read(memory, address);
+ } else if (memory->mbcRead) {
+ return memory->mbcRead(memory, address);
} else if (memory->mbcType == GB_HuC3) {
return 0x01; // TODO: Is this supposed to be the current SRAM bank?
}
@@ -436,6 +437,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) {
if (base > 0xF100) {
return;
}
+ mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent);
mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8);
if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) {
gb->cpu->nextEvent = gb->cpu->cycles + 8;
diff --git a/src/gb/overrides.c b/src/gb/overrides.c
index 70d11cd22..d8265800b 100644
--- a/src/gb/overrides.c
+++ b/src/gb/overrides.c
@@ -13,7 +13,7 @@
static const struct GBCartridgeOverride _overrides[] = {
// None yet
- { 0, 0, 0 }
+ { 0, 0, 0, { 0 } }
};
bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverride* override) {
@@ -35,6 +35,12 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri
snprintf(sectionName, sizeof(sectionName), "gb.override.%08X", override->headerCrc32);
const char* model = ConfigurationGetValue(config, sectionName, "model");
const char* mbc = ConfigurationGetValue(config, sectionName, "mbc");
+ const char* pal[4] = {
+ ConfigurationGetValue(config, sectionName, "pal[0]"),
+ ConfigurationGetValue(config, sectionName, "pal[1]"),
+ ConfigurationGetValue(config, sectionName, "pal[2]"),
+ ConfigurationGetValue(config, sectionName, "pal[3]")
+ };
if (model) {
if (strcasecmp(model, "DMG") == 0) {
@@ -63,6 +69,21 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri
found = true;
}
}
+
+ if (pal[0] && pal[1] && pal[2] && pal[3]) {
+ int i;
+ for (i = 0; i < 4; ++i) {
+ char* end;
+ unsigned long value = strtoul(pal[i], &end, 10);
+ if (end == &pal[i][1] && *end == 'x') {
+ value = strtoul(pal[i], &end, 16);
+ }
+ if (*end) {
+ continue;
+ }
+ override->gbColors[i] = value;
+ }
+ }
}
return found;
}
@@ -89,6 +110,12 @@ void GBOverrideSave(struct Configuration* config, const struct GBCartridgeOverri
}
ConfigurationSetValue(config, sectionName, "model", model);
+ if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) {
+ ConfigurationSetIntValue(config, sectionName, "pal[0]", override->gbColors[0]);
+ ConfigurationSetIntValue(config, sectionName, "pal[1]", override->gbColors[1]);
+ ConfigurationSetIntValue(config, sectionName, "pal[2]", override->gbColors[2]);
+ ConfigurationSetIntValue(config, sectionName, "pal[3]", override->gbColors[3]);
+ }
if (override->mbc != GB_MBC_AUTODETECT) {
ConfigurationSetIntValue(config, sectionName, "mbc", override->mbc);
} else {
@@ -105,6 +132,13 @@ void GBOverrideApply(struct GB* gb, const struct GBCartridgeOverride* override)
gb->memory.mbcType = override->mbc;
GBMBCInit(gb);
}
+
+ if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) {
+ GBVideoSetPalette(&gb->video, 0, override->gbColors[0]);
+ GBVideoSetPalette(&gb->video, 1, override->gbColors[1]);
+ GBVideoSetPalette(&gb->video, 2, override->gbColors[2]);
+ GBVideoSetPalette(&gb->video, 3, override->gbColors[3]);
+ }
}
void GBOverrideApplyDefaults(struct GB* gb) {
diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c
index 1474a86a5..2d2dbe787 100644
--- a/src/gb/renderers/software.c
+++ b/src/gb/renderers/software.c
@@ -67,8 +67,6 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum G
softwareRenderer->currentWy = 0;
softwareRenderer->wx = 0;
softwareRenderer->model = model;
-
- _clearScreen(softwareRenderer);
}
static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) {
diff --git a/src/gb/serialize.c b/src/gb/serialize.c
index 1cacc4224..d9f192092 100644
--- a/src/gb/serialize.c
+++ b/src/gb/serialize.c
@@ -59,25 +59,6 @@ void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
GBVideoSerialize(&gb->video, state);
GBTimerSerialize(&gb->timer, state);
GBAudioSerialize(&gb->audio, state);
-
-#ifndef _MSC_VER
- struct timeval tv;
- if (!gettimeofday(&tv, 0)) {
- uint64_t usec = tv.tv_usec;
- usec += tv.tv_sec * 1000000LL;
- STORE_64LE(usec, 0, &state->creationUsec);
- }
-#else
- struct timespec ts;
- if (timespec_get(&ts, TIME_UTC)) {
- uint64_t usec = ts.tv_nsec / 1000;
- usec += ts.tv_sec * 1000000LL;
- STORE_64LE(usec, 0, &state->creationUsec);
- }
-#endif
- else {
- state->creationUsec = 0;
- }
}
bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
@@ -188,8 +169,8 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
}
GBMemoryDeserialize(gb, state);
- GBIODeserialize(gb, state);
GBVideoDeserialize(&gb->video, state);
+ GBIODeserialize(gb, state);
GBTimerDeserialize(&gb->timer, state);
GBAudioDeserialize(&gb->audio, state);
diff --git a/src/gb/timer.c b/src/gb/timer.c
index e5bda7113..3729e313f 100644
--- a/src/gb/timer.c
+++ b/src/gb/timer.c
@@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include
+#include
#include
#include
#include
@@ -18,22 +19,26 @@ void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBUpdateIRQs(timer->p);
}
-void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) {
- struct GBTimer* timer = context;
- timer->nextDiv += cyclesLate;
- while (timer->nextDiv > 0) {
+static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) {
+ while (timer->nextDiv >= GB_DMG_DIV_PERIOD) {
timer->nextDiv -= GB_DMG_DIV_PERIOD;
// Make sure to trigger when the correct bit is a falling edge
if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) {
++timer->p->memory.io[REG_TIMA];
if (!timer->p->memory.io[REG_TIMA]) {
- mTimingSchedule(timing, &timer->irq, 4 - cyclesLate);
+ mTimingSchedule(&timer->p->timing, &timer->irq, 4 - cyclesLate);
}
}
++timer->internalDiv;
timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4;
}
+}
+
+void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) {
+ struct GBTimer* timer = context;
+ timer->nextDiv += cyclesLate;
+ _GBTimerDivIncrement(timer, cyclesLate);
// Batch div increments
int divsToGo = 16 - (timer->internalDiv & 15);
int timaToGo = INT_MAX;
@@ -50,7 +55,7 @@ void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLat
void GBTimerReset(struct GBTimer* timer) {
timer->event.context = timer;
timer->event.name = "GB Timer";
- timer->event.callback = _GBTimerIncrement;
+ timer->event.callback = _GBTimerUpdate;
timer->event.priority = 0x20;
timer->irq.context = timer;
timer->irq.name = "GB Timer IRQ";
@@ -63,11 +68,19 @@ void GBTimerReset(struct GBTimer* timer) {
}
void GBTimerDivReset(struct GBTimer* timer) {
+ timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
+ mTimingDeschedule(&timer->p->timing, &timer->event);
+ _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3);
+ if (timer->internalDiv & (timer->timaPeriod >> 1)) {
+ ++timer->p->memory.io[REG_TIMA];
+ if (!timer->p->memory.io[REG_TIMA]) {
+ mTimingSchedule(&timer->p->timing, &timer->irq, 4 - (timer->p->cpu->executionState + 1) & 3);
+ }
+ }
timer->p->memory.io[REG_DIV] = 0;
timer->internalDiv = 0;
timer->nextDiv = GB_DMG_DIV_PERIOD;
- mTimingDeschedule(&timer->p->timing, &timer->event);
- mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
+ mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3));
}
uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
@@ -86,6 +99,12 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
timer->timaPeriod = 256 >> 4;
break;
}
+
+ timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
+ mTimingDeschedule(&timer->p->timing, &timer->event);
+ _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3);
+ timer->nextDiv += GB_DMG_DIV_PERIOD;
+ mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
} else {
timer->timaPeriod = 0;
}
diff --git a/src/gb/video.c b/src/gb/video.c
index 8794a953c..b495a96b1 100644
--- a/src/gb/video.c
+++ b/src/gb/video.c
@@ -185,9 +185,6 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7);
video->mode = 2;
video->modeEvent.callback = _endMode2;
- if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) {
- video->p->memory.rotation->sample(video->p->memory.rotation);
- }
} else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) {
video->p->memory.io[REG_LY] = 0;
next = GB_VIDEO_HORIZONTAL_LENGTH - 8;
@@ -471,11 +468,11 @@ void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) {
video->vramCurrentBank = value;
}
-void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) {
+void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color) {
if (index >= 4) {
return;
}
- video->dmgPalette[index] = color;
+ video->dmgPalette[index] = M_RGB8_TO_RGB5(color);
}
static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {
@@ -628,4 +625,7 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* s
_cleanOAM(video, video->ly);
GBVideoSwitchBank(video, video->vramCurrentBank);
+
+ video->renderer->deinit(video->renderer);
+ video->renderer->init(video->renderer, video->p->model);
}
diff --git a/src/gba/bios.c b/src/gba/bios.c
index 199ffab8f..ae3b23ed2 100644
--- a/src/gba/bios.c
+++ b/src/gba/bios.c
@@ -256,18 +256,23 @@ static void _MidiKey2Freq(struct GBA* gba) {
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;
}
}
diff --git a/src/gba/core.c b/src/gba/core.c
index 2064ee2d0..4c212598f 100644
--- a/src/gba/core.c
+++ b/src/gba/core.c
@@ -45,6 +45,80 @@ const static struct mCoreChannelInfo _GBAAudioChannels[] = {
{ 5, "chB", "FIFO Channel B", NULL },
};
+const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = {
+ { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
+ { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+};
+
+const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = {
+ { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
+ { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART_SRAM, "sram", "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_SRAM, SIZE_CART_SRAM, true },
+};
+
+const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = {
+ { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
+ { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH512, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+};
+
+const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = {
+ { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
+ { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
+};
+
+const static struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
+ { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL },
+ { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
+ { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED },
+ { REGION_CART_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, SIZE_CART_EEPROM, SIZE_CART_EEPROM, mCORE_MEMORY_RW },
+};
+
struct mVideoLogContext;
struct GBACore {
struct mCore d;
@@ -528,6 +602,67 @@ static void _GBACoreRawWrite32(struct mCore* core, uint32_t address, int segment
GBAPatch32(cpu, address, value, NULL);
}
+size_t _GBAListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) {
+ const struct GBA* gba = core->board;
+ switch (gba->memory.savedata.type) {
+ case SAVEDATA_SRAM:
+ *blocks = _GBAMemoryBlocksSRAM;
+ return sizeof(_GBAMemoryBlocksSRAM) / sizeof(*_GBAMemoryBlocksSRAM);
+ case SAVEDATA_FLASH512:
+ *blocks = _GBAMemoryBlocksFlash512;
+ return sizeof(_GBAMemoryBlocksFlash512) / sizeof(*_GBAMemoryBlocksFlash512);
+ case SAVEDATA_FLASH1M:
+ *blocks = _GBAMemoryBlocksFlash1M;
+ return sizeof(_GBAMemoryBlocksFlash1M) / sizeof(*_GBAMemoryBlocksFlash1M);
+ case SAVEDATA_EEPROM:
+ *blocks = _GBAMemoryBlocksEEPROM;
+ return sizeof(_GBAMemoryBlocksEEPROM) / sizeof(*_GBAMemoryBlocksEEPROM);
+ default:
+ *blocks = _GBAMemoryBlocks;
+ return sizeof(_GBAMemoryBlocks) / sizeof(*_GBAMemoryBlocks);
+ }
+}
+
+void* _GBAGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
+ struct GBA* gba = core->board;
+ switch (id) {
+ default:
+ return NULL;
+ case REGION_BIOS:
+ *sizeOut = SIZE_BIOS;
+ return gba->memory.bios;
+ case REGION_WORKING_RAM:
+ *sizeOut = SIZE_WORKING_RAM;
+ return gba->memory.wram;
+ case REGION_WORKING_IRAM:
+ *sizeOut = SIZE_WORKING_IRAM;
+ return gba->memory.iwram;
+ case REGION_PALETTE_RAM:
+ *sizeOut = SIZE_PALETTE_RAM;
+ return gba->video.palette;
+ case REGION_VRAM:
+ *sizeOut = SIZE_VRAM;
+ return gba->video.vram;
+ case REGION_OAM:
+ *sizeOut = SIZE_OAM;
+ return gba->video.oam.raw;
+ case REGION_CART0:
+ case REGION_CART1:
+ case REGION_CART2:
+ *sizeOut = gba->memory.romSize;
+ return gba->memory.rom;
+ case REGION_CART_SRAM:
+ if (gba->memory.savedata.type == SAVEDATA_FLASH1M) {
+ *sizeOut = SIZE_CART_FLASH1M;
+ return gba->memory.savedata.currentBank;
+ }
+ // Fall through
+ case REGION_CART_SRAM_MIRROR:
+ *sizeOut = GBASavedataSize(&gba->memory.savedata);
+ return gba->memory.savedata.data;
+ }
+}
+
#ifdef USE_DEBUGGERS
static bool _GBACoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
UNUSED(core);
@@ -757,6 +892,8 @@ struct mCore* GBACoreCreate(void) {
core->rawWrite8 = _GBACoreRawWrite8;
core->rawWrite16 = _GBACoreRawWrite16;
core->rawWrite32 = _GBACoreRawWrite32;
+ core->listMemoryBlocks = _GBAListMemoryBlocks;
+ core->getMemoryBlock = _GBAGetMemoryBlock;
#ifdef USE_DEBUGGERS
core->supportsDebuggerType = _GBACoreSupportsDebuggerType;
core->debuggerPlatform = _GBACoreDebuggerPlatform;
diff --git a/src/gba/debugger/cli.c b/src/gba/debugger/cli.c
index 44ae46917..4208d9021 100644
--- a/src/gba/debugger/cli.c
+++ b/src/gba/debugger/cli.c
@@ -119,5 +119,5 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
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);
}
diff --git a/src/gba/memory.c b/src/gba/memory.c
index db4a30122..be35f4104 100644
--- a/src/gba/memory.c
+++ b/src/gba/memory.c
@@ -59,12 +59,8 @@ void GBAMemoryInit(struct GBA* gba) {
for (i = 0; i < 16; ++i) {
gba->memory.waitstatesNonseq16[i] = GBA_BASE_WAITSTATES[i];
gba->memory.waitstatesSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i];
- gba->memory.waitstatesPrefetchNonseq16[i] = GBA_BASE_WAITSTATES[i];
- gba->memory.waitstatesPrefetchSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i];
gba->memory.waitstatesNonseq32[i] = GBA_BASE_WAITSTATES_32[i];
gba->memory.waitstatesSeq32[i] = GBA_BASE_WAITSTATES_SEQ_32[i];
- gba->memory.waitstatesPrefetchNonseq32[i] = GBA_BASE_WAITSTATES_32[i];
- gba->memory.waitstatesPrefetchSeq32[i] = GBA_BASE_WAITSTATES_SEQ_32[i];
}
for (; i < 256; ++i) {
gba->memory.waitstatesNonseq16[i] = 0;
@@ -1505,33 +1501,32 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) {
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;
}
diff --git a/src/gba/serialize.c b/src/gba/serialize.c
index 663fcc44c..5917fa5f1 100644
--- a/src/gba/serialize.c
+++ b/src/gba/serialize.c
@@ -69,25 +69,6 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* 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);
diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c
index 4ada2360c..d9ca483c9 100644
--- a/src/gba/sharkport.c
+++ b/src/gba/sharkport.c
@@ -115,24 +115,14 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
uint32_t copySize = size - 0x1C;
switch (gba->memory.savedata.type) {
- case SAVEDATA_SRAM:
- if (copySize > SIZE_CART_SRAM) {
- copySize = SIZE_CART_SRAM;
- }
- break;
case SAVEDATA_FLASH512:
if (copySize > SIZE_CART_FLASH512) {
GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming);
}
// Fall through
- case SAVEDATA_FLASH1M:
- if (copySize > SIZE_CART_FLASH1M) {
- copySize = SIZE_CART_FLASH1M;
- }
- break;
- case SAVEDATA_EEPROM:
- if (copySize > SIZE_CART_EEPROM) {
- copySize = SAVEDATA_EEPROM;
+ default:
+ if (copySize > GBASavedataSize(&gba->memory.savedata)) {
+ copySize = GBASavedataSize(&gba->memory.savedata);
}
break;
case SAVEDATA_FORCE_NONE:
@@ -141,6 +131,7 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec
}
memcpy(gba->memory.savedata.data, &payload[0x1C], copySize);
+ gba->memory.savedata.vf && gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size);
free(payload);
return true;
diff --git a/src/gba/timer.c b/src/gba/timer.c
index c62f64506..03af4fe34 100644
--- a/src/gba/timer.c
+++ b/src/gba/timer.c
@@ -83,13 +83,13 @@ void GBATimerInit(struct GBA* gba) {
void GBATimerUpdateRegister(struct GBA* gba, int timer) {
struct GBATimer* currentTimer = &gba->timers[timer];
if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
- int32_t prefetchSkew = 0;
- if (gba->memory.lastPrefetchedPc >= (uint32_t) gba->cpu->gprs[ARM_PC]) {
- prefetchSkew = (gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * (gba->cpu->memory.activeSeqCycles16 + 1) / WORD_SIZE_THUMB;
+ int32_t prefetchSkew = -2;
+ if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) {
+ prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB;
}
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
int32_t diff = gba->cpu->cycles - (currentTimer->lastEvent - gba->timing.masterCycles);
- gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff - 2 + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags));
+ gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags));
}
}
diff --git a/src/lr35902/debugger/debugger.c b/src/lr35902/debugger/debugger.c
index 5d3f3a6ce..ae6345b79 100644
--- a/src/lr35902/debugger/debugger.c
+++ b/src/lr35902/debugger/debugger.c
@@ -6,6 +6,7 @@
#include
#include
+#include
#include
#include
@@ -48,6 +49,7 @@ static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t add
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 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) {
platform->clearWatchpoint = LR35902DebuggerClearWatchpoint;
platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints;
platform->hasBreakpoints = LR35902DebuggerHasBreakpoints;
+ platform->trace = LR35902DebuggerTrace;
return platform;
}
@@ -137,3 +140,31 @@ static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t
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);
+}
diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c
index 47842273e..2dfa7448b 100644
--- a/src/platform/opengl/gles2.c
+++ b/src/platform/opengl/gles2.c
@@ -175,7 +175,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
drawW -= drawW % v->width;
drawH -= drawH % v->height;
}
- glViewport(0, 0, v->width, v->height);
+ glViewport(0, 0, w, h);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
@@ -203,13 +203,13 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
int drawH = shader->height;
int padW = 0;
int padH = 0;
- if (!shader->width) {
+ if (!drawW) {
drawW = viewport[2];
padW = viewport[0];
} else if (shader->width < 0) {
drawW = context->d.width * -shader->width;
}
- if (!shader->height) {
+ if (!drawH) {
drawH = viewport[3];
padH = viewport[1];
} else if (shader->height < 0) {
@@ -234,7 +234,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
glUseProgram(shader->program);
glUniform1i(shader->texLocation, 0);
- glUniform2f(shader->texSizeLocation, context->d.width, context->d.height);
+ glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH);
glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
glEnableVertexAttribArray(shader->positionLocation);
size_t u;
@@ -290,7 +290,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
}
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindTexture(GL_TEXTURE_2D, shader->tex);
- glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
}
void mGLES2ContextDrawFrame(struct VideoBackend* v) {
@@ -298,12 +297,17 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, context->tex);
+ GLint viewport[4];
+ glGetIntegerv(GL_VIEWPORT, viewport);
+
context->finalShader.filter = v->filter;
_drawShader(context, &context->initialShader);
size_t n;
for (n = 0; n < context->nShaders; ++n) {
+ glViewport(0, 0, viewport[2], viewport[3]);
_drawShader(context, &context->shaders[n]);
}
+ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
_drawShader(context, &context->finalShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glUseProgram(0);
diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt
index 7b7605e7d..ad0d7536a 100644
--- a/src/platform/python/CMakeLists.txt
+++ b/src/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}
diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h
index 8820a8c08..7d7703bb5 100644
--- a/src/platform/python/_builder.h
+++ b/src/platform/python/_builder.h
@@ -28,7 +28,9 @@ void free(void*);
#include "flags.h"
#include
+#include
#include
+#include
#define PYEXPORT extern "Python+C"
#include "platform/python/log.h"
diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py
index ccf35a880..f93766b22 100644
--- a/src/platform/python/_builder.py
+++ b/src/platform/python/_builder.py
@@ -21,7 +21,9 @@ ffi.set_source("mgba._pylib", """
#include
#include
#include
+#include
#include
+#include
#include
#include
#include
diff --git a/src/platform/python/mgba/__init__.py b/src/platform/python/mgba/__init__.py
index aab19b4d2..a8c0ddce4 100644
--- a/src/platform/python/mgba/__init__.py
+++ b/src/platform/python/mgba/__init__.py
@@ -5,6 +5,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from ._pylib import ffi, lib
+from collections import namedtuple
+
def createCallback(structName, cbName, funcName=None):
funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:])
fullStruct = "struct {}*".format(structName)
@@ -13,3 +15,19 @@ def createCallback(structName, cbName, funcName=None):
return getattr(ffi.from_handle(h.pyobj), cbName)(*args)
return ffi.def_extern(name=funcName)(cb)
+
+version = ffi.string(lib.projectVersion).decode('utf-8')
+
+GitInfo = namedtuple("GitInfo", "commit commitShort branch revision")
+
+git = {}
+if lib.gitCommit and lib.gitCommit != "(unknown)":
+ git['commit'] = ffi.string(lib.gitCommit).decode('utf-8')
+if lib.gitCommitShort and lib.gitCommitShort != "(unknown)":
+ git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8')
+if lib.gitBranch and lib.gitBranch != "(unknown)":
+ git['branch'] = ffi.string(lib.gitBranch).decode('utf-8')
+if lib.gitRevision > 0:
+ git['revision'] = lib.gitRevision
+
+git = GitInfo(**git)
diff --git a/src/platform/python/mgba/image.py b/src/platform/python/mgba/image.py
index f76945b32..948edeeb4 100644
--- a/src/platform/python/mgba/image.py
+++ b/src/platform/python/mgba/image.py
@@ -6,64 +6,74 @@
from ._pylib import ffi, lib
from . import png
+try:
+ import PIL.Image as PImage
+except ImportError:
+ pass
+
class Image:
- def __init__(self, width, height, stride=0):
- self.width = width
- self.height = height
- self.stride = stride
- self.constitute()
+ def __init__(self, width, height, stride=0):
+ self.width = width
+ self.height = height
+ self.stride = stride
+ self.constitute()
- def constitute(self):
- if self.stride <= 0:
- self.stride = self.width
- self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height))
+ def constitute(self):
+ if self.stride <= 0:
+ self.stride = self.width
+ self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height))
- def savePNG(self, f):
- p = png.PNG(f)
- success = p.writeHeader(self)
- success = success and p.writePixels(self)
- p.writeClose()
- return success
+ def savePNG(self, f):
+ p = png.PNG(f)
+ success = p.writeHeader(self)
+ success = success and p.writePixels(self)
+ p.writeClose()
+ return success
+
+ if 'PImage' in globals():
+ def toPIL(self):
+ return PImage.frombytes("RGBX", (self.width, self.height), ffi.buffer(self.buffer), "raw",
+ "RGBX", self.stride * 4)
def u16ToU32(c):
- r = c & 0x1F
- g = (c >> 5) & 0x1F
- b = (c >> 10) & 0x1F
- a = (c >> 15) & 1
- abgr = r << 3
- abgr |= g << 11
- abgr |= b << 19
- abgr |= (a * 0xFF) << 24
- return abgr
+ r = c & 0x1F
+ g = (c >> 5) & 0x1F
+ b = (c >> 10) & 0x1F
+ a = (c >> 15) & 1
+ abgr = r << 3
+ abgr |= g << 11
+ abgr |= b << 19
+ abgr |= (a * 0xFF) << 24
+ return abgr
def u32ToU16(c):
- r = (c >> 3) & 0x1F
- g = (c >> 11) & 0x1F
- b = (c >> 19) & 0x1F
- a = c >> 31
- abgr = r
- abgr |= g << 5
- abgr |= b << 10
- abgr |= a << 15
- return abgr
+ r = (c >> 3) & 0x1F
+ g = (c >> 11) & 0x1F
+ b = (c >> 19) & 0x1F
+ a = c >> 31
+ abgr = r
+ abgr |= g << 5
+ abgr |= b << 10
+ abgr |= a << 15
+ return abgr
if ffi.sizeof("color_t") == 2:
- def colorToU16(c):
- return c
+ def colorToU16(c):
+ return c
- colorToU32 = u16ToU32
+ colorToU32 = u16ToU32
- def u16ToColor(c):
- return c
+ def u16ToColor(c):
+ return c
- u32ToColor = u32ToU16
+ u32ToColor = u32ToU16
else:
- def colorToU32(c):
- return c
+ def colorToU32(c):
+ return c
- colorToU16 = u32ToU16
+ colorToU16 = u32ToU16
- def u32ToColor(c):
- return c
+ def u32ToColor(c):
+ return c
- u16ToColor = u16ToU32
+ u16ToColor = u16ToU32
diff --git a/src/platform/python/mgba/log.py b/src/platform/python/mgba/log.py
index 4514e67a9..168627a0e 100644
--- a/src/platform/python/mgba/log.py
+++ b/src/platform/python/mgba/log.py
@@ -8,29 +8,33 @@ from . import createCallback
createCallback("mLoggerPy", "log", "_pyLog")
+defaultLogger = None
+
def installDefault(logger):
- lib.mLogSetDefaultLogger(logger._native)
+ global defaultLogger
+ defaultLogger = logger
+ lib.mLogSetDefaultLogger(logger._native)
class Logger(object):
- FATAL = lib.mLOG_FATAL
- DEBUG = lib.mLOG_DEBUG
- INFO = lib.mLOG_INFO
- WARN = lib.mLOG_WARN
- ERROR = lib.mLOG_ERROR
- STUB = lib.mLOG_STUB
- GAME_ERROR = lib.mLOG_GAME_ERROR
+ FATAL = lib.mLOG_FATAL
+ DEBUG = lib.mLOG_DEBUG
+ INFO = lib.mLOG_INFO
+ WARN = lib.mLOG_WARN
+ ERROR = lib.mLOG_ERROR
+ STUB = lib.mLOG_STUB
+ GAME_ERROR = lib.mLOG_GAME_ERROR
- def __init__(self):
- self._handle = ffi.new_handle(self)
- self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free)
+ def __init__(self):
+ self._handle = ffi.new_handle(self)
+ self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free)
- @staticmethod
- def categoryName(category):
- return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8')
+ @staticmethod
+ def categoryName(category):
+ return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8')
- def log(self, category, level, message):
- print("{}: {}".format(self.categoryName(category), message))
+ def log(self, category, level, message):
+ print("{}: {}".format(self.categoryName(category), message))
class NullLogger(Logger):
- def log(self, category, level, message):
- pass
+ def log(self, category, level, message):
+ pass
diff --git a/src/platform/python/mgba/memory.py b/src/platform/python/mgba/memory.py
index a7f52e22e..c59f5a792 100644
--- a/src/platform/python/mgba/memory.py
+++ b/src/platform/python/mgba/memory.py
@@ -67,10 +67,53 @@ class MemoryView(object):
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 @@ class Memory(object):
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
diff --git a/src/platform/python/setup.py.in b/src/platform/python/setup.py.in
index 31ce6cd52..04c1c0c29 100644
--- a/src/platform/python/setup.py.in
+++ b/src/platform/python/setup.py.in
@@ -1,5 +1,10 @@
from setuptools import setup
import re
+import os
+
+os.environ["BINDIR"] = "${CMAKE_BINARY_DIR}"
+os.environ["CPPFLAGS"] = " ".join([d for d in "${INCLUDE_FLAGS}".split(";") if d])
+os.chdir("${CMAKE_CURRENT_SOURCE_DIR}")
classifiers = [
"Programming Language :: C",
@@ -18,6 +23,7 @@ setup(name="${BINARY_NAME}",
packages=["mgba"],
setup_requires=['cffi>=1.6'],
install_requires=['cffi>=1.6', 'cached-property'],
+ extras_require={'pil': ['Pillow>=2.3']},
cffi_modules=["_builder.py:ffi"],
license="MPL 2.0",
classifiers=classifiers
diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt
index 86e1af59d..2569d5b14 100644
--- a/src/platform/qt/CMakeLists.txt
+++ b/src/platform/qt/CMakeLists.txt
@@ -85,6 +85,7 @@ set(SOURCE_FILES
LogController.cpp
LogView.cpp
MemoryModel.cpp
+ MemorySearch.cpp
MemoryView.cpp
MessagePainter.cpp
MultiplayerController.cpp
@@ -115,6 +116,7 @@ set(UI_FILES
IOViewer.ui
LoadSaveState.ui
LogView.ui
+ MemorySearch.ui
MemoryView.ui
ObjView.ui
OverrideView.ui
diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp
index 0b58760eb..064bd0f58 100644
--- a/src/platform/qt/GBAKeyEditor.cpp
+++ b/src/platform/qt/GBAKeyEditor.cpp
@@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GBAKeyEditor.h"
+#include
#include
#include
#include
@@ -182,10 +183,20 @@ bool GBAKeyEditor::event(QEvent* event) {
}
bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) {
+ KeyEditor* keyEditor = static_cast(obj);
+ if (event->type() == QEvent::FocusOut) {
+ keyEditor->setPalette(QApplication::palette(keyEditor));
+ }
if (event->type() != QEvent::FocusIn) {
return false;
}
- findFocus(static_cast(obj));
+
+ QPalette palette = keyEditor->palette();
+ palette.setBrush(keyEditor->backgroundRole(), palette.highlight());
+ palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText());
+ keyEditor->setPalette(palette);
+
+ findFocus(keyEditor);
return true;
}
diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp
index 0b48ac302..ef1b3dddb 100644
--- a/src/platform/qt/GameController.cpp
+++ b/src/platform/qt/GameController.cpp
@@ -40,7 +40,7 @@ using namespace std;
GameController::GameController(QObject* parent)
: QObject(parent)
, m_audioProcessor(AudioProcessor::create())
- , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC)
+ , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA)
, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
{
#ifdef M_CORE_GBA
@@ -162,6 +162,7 @@ GameController::GameController(QObject* parent)
default:
break;
}
+ mTileCacheDeinit(controller->m_tileCache.get());
controller->m_tileCache.reset();
}
@@ -696,6 +697,7 @@ void GameController::threadContinue() {
void GameController::frameAdvance() {
if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) {
setPaused(false);
+ m_wasPaused = true;
}
}
diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp
index 40777000f..a381c3d22 100644
--- a/src/platform/qt/LoadSaveState.cpp
+++ b/src/platform/qt/LoadSaveState.cpp
@@ -188,7 +188,7 @@ void LoadSaveState::loadState(int slot) {
return;
}
- QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO
+ QDateTime creation;
QImage stateImage;
unsigned width, height;
@@ -198,6 +198,12 @@ void LoadSaveState::loadState(int slot) {
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()) {
QPixmap statePixmap;
statePixmap.convertFromImage(stateImage);
diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp
new file mode 100644
index 000000000..0f7df5ece
--- /dev/null
+++ b/src/platform/qt/MemorySearch.cpp
@@ -0,0 +1,204 @@
+/* Copyright (c) 2013-2017 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemorySearch.h"
+
+#include
+
+#include "GameController.h"
+#include "MemoryView.h"
+
+using namespace QGBA;
+
+MemorySearch::MemorySearch(GameController* controller, QWidget* parent)
+ : QWidget(parent)
+ , m_controller(controller)
+{
+ m_ui.setupUi(this);
+
+ mCoreMemorySearchResultsInit(&m_results, 0);
+ connect(m_ui.search, &QPushButton::clicked, this, &MemorySearch::search);
+ connect(m_ui.searchWithin, &QPushButton::clicked, this, &MemorySearch::searchWithin);
+ connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh);
+ connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh);
+ connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh);
+ connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory);
+}
+
+MemorySearch::~MemorySearch() {
+ mCoreMemorySearchResultsDeinit(&m_results);
+}
+
+bool MemorySearch::createParams(mCoreMemorySearchParams* params) {
+ params->memoryFlags = mCORE_MEMORY_RW;
+ mCore* core = m_controller->thread()->core;
+
+ QByteArray string;
+ bool ok = false;
+ if (m_ui.typeNum->isChecked()) {
+ if (m_ui.bits8->isChecked()) {
+ params->type = mCORE_MEMORY_SEARCH_8;
+ }
+ if (m_ui.bits16->isChecked()) {
+ params->type = mCORE_MEMORY_SEARCH_16;
+ }
+ if (m_ui.bits32->isChecked()) {
+ params->type = mCORE_MEMORY_SEARCH_32;
+ }
+ if (m_ui.numHex->isChecked()) {
+ uint32_t v = m_ui.value->text().toUInt(&ok, 16);
+ if (ok) {
+ switch (params->type) {
+ case mCORE_MEMORY_SEARCH_8:
+ ok = v < 0x100;
+ params->value8 = v;
+ break;
+ case mCORE_MEMORY_SEARCH_16:
+ ok = v < 0x10000;
+ params->value16 = v;
+ break;
+ case mCORE_MEMORY_SEARCH_32:
+ params->value32 = v;
+ break;
+ default:
+ ok = false;
+ }
+ }
+ }
+ if (m_ui.numDec->isChecked()) {
+ uint32_t v = m_ui.value->text().toUInt(&ok, 10);
+ if (ok) {
+ switch (params->type) {
+ case mCORE_MEMORY_SEARCH_8:
+ ok = v < 0x100;
+ params->value8 = v;
+ break;
+ case mCORE_MEMORY_SEARCH_16:
+ ok = v < 0x10000;
+ params->value16 = v;
+ break;
+ case mCORE_MEMORY_SEARCH_32:
+ params->value32 = v;
+ break;
+ default:
+ ok = false;
+ }
+ }
+ }
+ if (m_ui.numGuess->isChecked()) {
+ params->type = mCORE_MEMORY_SEARCH_GUESS;
+ m_string = m_ui.value->text().toLocal8Bit();
+ params->valueStr = m_string.constData();
+ ok = true;
+ }
+ }
+ if (m_ui.typeStr->isChecked()) {
+ params->type = mCORE_MEMORY_SEARCH_STRING;
+ m_string = m_ui.value->text().toLocal8Bit();
+ params->valueStr = m_string.constData();
+ ok = true;
+ }
+ return ok;
+}
+
+void MemorySearch::search() {
+ mCoreMemorySearchResultsClear(&m_results);
+
+ mCoreMemorySearchParams params;
+
+ GameController::Interrupter interrupter(m_controller);
+ if (!m_controller->isLoaded()) {
+ return;
+ }
+ mCore* core = m_controller->thread()->core;
+
+ if (createParams(¶ms)) {
+ mCoreMemorySearch(core, ¶ms, &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(¶ms)) {
+ mCoreMemorySearchRepeat(core, ¶ms, &m_results);
+ }
+
+ refresh();
+}
+
+void MemorySearch::refresh() {
+ GameController::Interrupter interrupter(m_controller);
+ if (!m_controller->isLoaded()) {
+ return;
+ }
+ mCore* core = m_controller->thread()->core;
+
+ m_ui.results->clearContents();
+ m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results));
+ for (size_t i = 0; i < mCoreMemorySearchResultsSize(&m_results); ++i) {
+ mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i);
+ QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0')));
+ m_ui.results->setItem(i, 0, item);
+ if (m_ui.numHex->isChecked()) {
+ switch (result->type) {
+ case mCORE_MEMORY_SEARCH_8:
+ item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, result->segment), 2, 16, QChar('0')));
+ break;
+ case mCORE_MEMORY_SEARCH_16:
+ item = new QTableWidgetItem(QString("%1").arg(core->rawRead16(core, result->address, result->segment), 4, 16, QChar('0')));
+ break;
+ case mCORE_MEMORY_SEARCH_GUESS:
+ case mCORE_MEMORY_SEARCH_32:
+ item = new QTableWidgetItem(QString("%1").arg(core->rawRead32(core, result->address, result->segment), 8, 16, QChar('0')));
+ break;
+ case mCORE_MEMORY_SEARCH_STRING:
+ item = new QTableWidgetItem("?"); // TODO
+ }
+ } else {
+ switch (result->type) {
+ case mCORE_MEMORY_SEARCH_8:
+ item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment)));
+ break;
+ case mCORE_MEMORY_SEARCH_16:
+ item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment)));
+ break;
+ case mCORE_MEMORY_SEARCH_GUESS:
+ case mCORE_MEMORY_SEARCH_32:
+ item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment)));
+ break;
+ case mCORE_MEMORY_SEARCH_STRING:
+ item = new QTableWidgetItem("?"); // TODO
+ }
+ }
+ m_ui.results->setItem(i, 1, item);
+ }
+ m_ui.results->sortItems(0);
+}
+
+void MemorySearch::openMemory() {
+ auto items = m_ui.results->selectedItems();
+ if (items.empty()) {
+ return;
+ }
+ QTableWidgetItem* item = items[0];
+ uint32_t address = item->text().toUInt(nullptr, 16);
+
+ MemoryView* memView = new MemoryView(m_controller);
+ memView->jumpToAddress(address);
+
+ connect(m_controller, &GameController::gameStopped, memView, &QWidget::close);
+ memView->setAttribute(Qt::WA_DeleteOnClose);
+ memView->show();
+}
diff --git a/src/platform/qt/MemorySearch.h b/src/platform/qt/MemorySearch.h
new file mode 100644
index 000000000..65f365f44
--- /dev/null
+++ b/src/platform/qt/MemorySearch.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2013-2017 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef QGBA_MEMORY_SEARCH
+#define QGBA_MEMORY_SEARCH
+
+#include "ui_MemorySearch.h"
+
+#include
+
+namespace QGBA {
+
+class GameController;
+
+class MemorySearch : public QWidget {
+Q_OBJECT
+
+public:
+ static constexpr size_t LIMIT = 10000;
+
+ MemorySearch(GameController* controller, QWidget* parent = nullptr);
+ ~MemorySearch();
+
+public slots:
+ void refresh();
+ void search();
+ void searchWithin();
+
+private slots:
+ void openMemory();
+
+private:
+ bool createParams(mCoreMemorySearchParams*);
+
+ Ui::MemorySearch m_ui;
+
+ GameController* m_controller;
+
+ mCoreMemorySearchResults m_results;
+ QByteArray m_string;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/MemorySearch.ui b/src/platform/qt/MemorySearch.ui
new file mode 100644
index 000000000..ccd7a05e9
--- /dev/null
+++ b/src/platform/qt/MemorySearch.ui
@@ -0,0 +1,223 @@
+
+
+ MemorySearch
+
+
+
+ 0
+ 0
+ 631
+ 378
+
+
+
+
+ 540
+ 241
+
+
+
+ Form
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ QAbstractItemView::SelectRows
+
+
+ false
+
+
+ false
+
+
+
+ Address
+
+
+
+
+ Current Value
+
+
+
+
+ Type
+
+
+
+
+ -
+
+
-
+
+
+ Value
+
+
+
+ -
+
+
+ -
+
+
+ Type
+
+
+
+ -
+
+
+ Numeric
+
+
+ true
+
+
+ type
+
+
+
+ -
+
+
+ Text
+
+
+ type
+
+
+
+ -
+
+
+ Width
+
+
+
+ -
+
+
+ 1 Byte (8-bit)
+
+
+ width
+
+
+
+ -
+
+
+ 2 Bytes (16-bit)
+
+
+ width
+
+
+
+ -
+
+
+ 4 Bytes (32-bit)
+
+
+ true
+
+
+ width
+
+
+
+ -
+
+
+ Number type
+
+
+
+ -
+
+
+ Hexadecimal
+
+
+ true
+
+
+
+ -
+
+
+ Decimal
+
+
+
+ -
+
+
+ Guess
+
+
+
+
+
+ -
+
+
+ QDialogButtonBox::Close
+
+
+
+ -
+
+
-
+
+
+ Search
+
+
+
+ -
+
+
+ Search Within
+
+
+
+ -
+
+
+ Open in Memory Viewer
+
+
+
+ -
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/platform/qt/MemoryView.cpp b/src/platform/qt/MemoryView.cpp
index b343060fa..543dbe6bf 100644
--- a/src/platform/qt/MemoryView.cpp
+++ b/src/platform/qt/MemoryView.cpp
@@ -9,53 +9,9 @@
#include "GameController.h"
#include
-#ifdef M_CORE_GBA
-#include
-#endif
-#ifdef M_CORE_GB
-#include
-#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 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent)
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(&QComboBox::currentIndexChanged),
this, &MemoryView::setIndex);
@@ -87,7 +30,10 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent)
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 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent)
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);
}
diff --git a/src/platform/qt/MemoryView.h b/src/platform/qt/MemoryView.h
index 9882ce32b..04a492ca9 100644
--- a/src/platform/qt/MemoryView.h
+++ b/src/platform/qt/MemoryView.h
@@ -22,6 +22,7 @@ public:
public slots:
void update();
+ void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); }
private slots:
void setIndex(int);
diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp
index 0fa19df8c..35fb9be0b 100644
--- a/src/platform/qt/ObjView.cpp
+++ b/src/platform/qt/ObjView.cpp
@@ -68,6 +68,7 @@ void ObjView::translateIndex(int index) {
#ifdef M_CORE_GBA
void ObjView::updateTilesGBA(bool force) {
+ m_ui.objId->setMaximum(127);
const GBA* gba = static_cast(m_controller->thread()->core->board);
const GBAObj* obj = &gba->video.oam.obj[m_objId];
@@ -172,6 +173,7 @@ void ObjView::updateTilesGBA(bool force) {
#ifdef M_CORE_GB
void ObjView::updateTilesGB(bool force) {
+ m_ui.objId->setMaximum(39);
const GB* gb = static_cast(m_controller->thread()->core->board);
const GBObj* obj = &gb->video.oam.obj[m_objId];
diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp
index 42f90f5eb..6b7652f81 100644
--- a/src/platform/qt/OverrideView.cpp
+++ b/src/platform/qt/OverrideView.cpp
@@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "OverrideView.h"
+#include
#include
#include "ConfigController.h"
@@ -79,6 +80,21 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config,
connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides);
connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides);
+ QPalette palette = m_ui.color0->palette();
+ palette.setColor(backgroundRole(), QColor(0xF8, 0xF8, 0xF8));
+ m_ui.color0->setPalette(palette);
+ palette.setColor(backgroundRole(), QColor(0xA8, 0xA8, 0xA8));
+ m_ui.color1->setPalette(palette);
+ palette.setColor(backgroundRole(), QColor(0x50, 0x50, 0x50));
+ m_ui.color2->setPalette(palette);
+ palette.setColor(backgroundRole(), QColor(0x00, 0x00, 0x00));
+ m_ui.color3->setPalette(palette);
+
+ m_ui.color0->installEventFilter(this);
+ m_ui.color1->installEventFilter(this);
+ m_ui.color2->installEventFilter(this);
+ m_ui.color3->installEventFilter(this);
+
connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides);
#ifndef M_CORE_GBA
m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGBA));
@@ -96,6 +112,42 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config,
}
}
+bool OverrideView::eventFilter(QObject* obj, QEvent* event) {
+#ifdef M_CORE_GB
+ if (event->type() != QEvent::MouseButtonRelease) {
+ return false;
+ }
+ int colorId;
+ if (obj == m_ui.color0) {
+ colorId = 0;
+ } else if (obj == m_ui.color1) {
+ colorId = 1;
+ } else if (obj == m_ui.color2) {
+ colorId = 2;
+ } else if (obj == m_ui.color3) {
+ colorId = 3;
+ } else {
+ return false;
+ }
+
+ QWidget* swatch = static_cast(obj);
+
+ QColorDialog* colorPicker = new QColorDialog;
+ colorPicker->setAttribute(Qt::WA_DeleteOnClose);
+ colorPicker->open();
+ connect(colorPicker, &QColorDialog::colorSelected, [this, swatch, colorId](const QColor& color) {
+ QPalette palette = swatch->palette();
+ palette.setColor(backgroundRole(), color);
+ swatch->setPalette(palette);
+ m_gbColors[colorId] = color.rgb();
+ updateOverrides();
+ });
+ return true;
+#else
+ return false;
+#endif
+}
+
void OverrideView::saveOverride() {
if (!m_config) {
return;
@@ -155,7 +207,13 @@ void OverrideView::updateOverrides() {
GBOverride* gb = new GBOverride;
gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()];
gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()];
- if (gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT) {
+ gb->override.gbColors[0] = m_gbColors[0];
+ gb->override.gbColors[1] = m_gbColors[1];
+ gb->override.gbColors[2] = m_gbColors[2];
+ gb->override.gbColors[3] = m_gbColors[3];
+ bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT;
+ hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]);
+ if (hasOverride) {
m_controller->setOverride(gb);
} else {
m_controller->clearOverride();
diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h
index c7f3fad99..3d1c8a3aa 100644
--- a/src/platform/qt/OverrideView.h
+++ b/src/platform/qt/OverrideView.h
@@ -36,6 +36,9 @@ private slots:
void gameStarted(mCoreThread*);
void gameStopped();
+protected:
+ bool eventFilter(QObject* obj, QEvent* event) override;
+
private:
Ui::OverrideView m_ui;
@@ -43,6 +46,8 @@ private:
ConfigController* m_config;
#ifdef M_CORE_GB
+ uint32_t m_gbColors[4]{};
+
static QList s_gbModelList;
static QList s_mbcList;
#endif
diff --git a/src/platform/qt/OverrideView.ui b/src/platform/qt/OverrideView.ui
index 2d383ad47..3d8238cd8 100644
--- a/src/platform/qt/OverrideView.ui
+++ b/src/platform/qt/OverrideView.ui
@@ -6,8 +6,8 @@
0
0
- 443
- 282
+ 444
+ 284
@@ -326,6 +326,93 @@
+ -
+
+
+ Colors
+
+
+
+ -
+
+
-
+
+
+
+ 30
+ 30
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+
+ 30
+ 30
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+
+ 30
+ 30
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+
+ 30
+ 30
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+
+
diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp
index 161568fa6..1ea0ca131 100644
--- a/src/platform/qt/SettingsView.cpp
+++ b/src/platform/qt/SettingsView.cpp
@@ -88,6 +88,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
m_ui.patchPath->setText(path);
}
});
+ connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared);
// TODO: Move to reloadConfig()
QVariant audioDriver = m_controller->getQtOption("audioDriver");
@@ -212,7 +213,7 @@ void SettingsView::updateConfig() {
loadState |= m_ui.loadStateCheats->isChecked() ? SAVESTATE_CHEATS : 0;
saveSetting("loadStateExtdata", loadState);
- int saveState = SAVESTATE_RTC;
+ int saveState = SAVESTATE_RTC | SAVESTATE_METADATA;
saveState |= m_ui.saveStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0;
saveState |= m_ui.saveStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0;
saveState |= m_ui.saveStateCheats->isChecked() ? SAVESTATE_CHEATS : 0;
@@ -299,7 +300,7 @@ void SettingsView::reloadConfig() {
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);
diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h
index 511e2346c..c292b4a0f 100644
--- a/src/platform/qt/SettingsView.h
+++ b/src/platform/qt/SettingsView.h
@@ -29,6 +29,7 @@ signals:
void audioDriverChanged();
void displayDriverChanged();
void pathsChanged();
+ void libraryCleared();
private slots:
void selectBios(QLineEdit*);
diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui
index 8fca6efaf..6f27a1fd5 100644
--- a/src/platform/qt/SettingsView.ui
+++ b/src/platform/qt/SettingsView.ui
@@ -431,9 +431,6 @@
-
-
- false
-
Clear cache
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index 750bcff79..74904cc3b 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -35,6 +35,7 @@
#include "LoadSaveState.h"
#include "LogView.h"
#include "MultiplayerController.h"
+#include "MemorySearch.h"
#include "MemoryView.h"
#include "OverrideView.h"
#include "ObjView.h"
@@ -468,6 +469,7 @@ void Window::openSettingsWindow() {
connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver);
connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart);
connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig);
+ connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear);
openView(settingsWindow);
}
@@ -1405,6 +1407,11 @@ void Window::setupMenu(QMenuBar* menubar) {
m_gameActions.append(memoryView);
addControlledAction(toolsMenu, memoryView, "memoryView");
+ QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu);
+ connect(memorySearch, &QAction::triggered, openTView());
+ m_gameActions.append(memorySearch);
+ addControlledAction(toolsMenu, memorySearch, "memorySearch");
+
#ifdef M_CORE_GBA
QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu);
connect(ioViewer, &QAction::triggered, openTView());
diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp
index ecb8a1fab..bb7acac36 100644
--- a/src/platform/qt/library/LibraryController.cpp
+++ b/src/platform/qt/library/LibraryController.cpp
@@ -24,7 +24,7 @@ void AbstractGameList::addEntries(QList items) {
}
void AbstractGameList::removeEntries(QList items) {
for (LibraryEntryRef o : items) {
- addEntry(o);
+ removeEntry(o);
}
}
@@ -130,6 +130,20 @@ void LibraryController::addDirectory(const QString& dir) {
m_loaderThread.start();
}
+void LibraryController::clear() {
+ if (!m_library) {
+ if (!m_loaderThread.isRunning() && m_loaderThread.m_library) {
+ m_library = m_loaderThread.m_library;
+ m_loaderThread.m_library = nullptr;
+ } else {
+ return;
+ }
+ }
+
+ mLibraryClear(m_library);
+ refresh();
+}
+
void LibraryController::refresh() {
if (!m_library) {
if (!m_loaderThread.isRunning() && m_loaderThread.m_library) {
@@ -181,7 +195,7 @@ void LibraryController::refresh() {
}
void LibraryController::selectLastBootedGame() {
- if (!m_config) {
+ if (!m_config || m_config->getMRU().isEmpty()) {
return;
}
const QString lastfile = m_config->getMRU().first();
diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h
index 3cc82c8b5..7a7799b5b 100644
--- a/src/platform/qt/library/LibraryController.h
+++ b/src/platform/qt/library/LibraryController.h
@@ -100,6 +100,9 @@ public:
void addDirectory(const QString& dir);
+public slots:
+ void clear();
+
signals:
void startGame();
void doneLoading();
diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c
index 896d473ca..38ada79a5 100644
--- a/src/platform/sdl/sdl-events.c
+++ b/src/platform/sdl/sdl-events.c
@@ -9,7 +9,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include