From c8028e1a60749fe27fea50ae4fc0a2fc7a6b6c2d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Aug 2020 16:14:00 -0700 Subject: [PATCH 01/20] Util: Fill out and fix big-endian byteswap load/stores --- include/mgba-util/common.h | 31 +++++++++++++++++++++++-------- src/debugger/gdb-stub.c | 4 ---- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 119509c4d..2c152f3f1 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -126,7 +126,12 @@ typedef intptr_t ssize_t; #endif #if defined __BIG_ENDIAN__ +#define LOAD_64BE(DEST, ADDR, ARR) DEST = *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) #define LOAD_32BE(DEST, ADDR, ARR) DEST = *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) +#define LOAD_16BE(DEST, ADDR, ARR) DEST = *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) +#define STORE_64BE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#define STORE_32BE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#define STORE_16BE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #if defined(__PPC__) || defined(__POWERPC__) #define LOAD_32LE(DEST, ADDR, ARR) { \ size_t _addr = (ADDR); \ @@ -200,12 +205,12 @@ typedef intptr_t ssize_t; #endif #elif defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) -#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3]) -#define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) -#define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(((uint16_t*) ARR)[(ADDR) >> 1]) -#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = __builtin_bswap64(SRC) -#define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = __builtin_bswap32(SRC) -#define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = __builtin_bswap16(SRC) +#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(*(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(*(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(*(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap64(SRC) +#define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap32(SRC) +#define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap16(SRC) #else #error Big endian build not supported on this platform. #endif @@ -217,9 +222,19 @@ typedef intptr_t ssize_t; #define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #ifdef _MSC_VER -#define LOAD_32BE(DEST, ADDR, ARR) DEST = _byteswap_ulong(((uint32_t*) ARR)[(ADDR) >> 2]) +#define LOAD_64BE(DEST, ADDR, ARR) DEST = _byteswap_uint64(*(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_32BE(DEST, ADDR, ARR) DEST = _byteswap_ulong(*(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_16BE(DEST, ADDR, ARR) DEST = _byteswap_ushort(*(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define STORE_64BE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = _byteswap_uint64(SRC) +#define STORE_32BE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = _byteswap_ulong(SRC) +#define STORE_16BE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = _byteswap_ushort(SRC) #else -#define LOAD_32BE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) +#define LOAD_64BE(DEST, ADDR, ARR) DEST = __builtin_bswap64(*(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_32BE(DEST, ADDR, ARR) DEST = __builtin_bswap32(*(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_16BE(DEST, ADDR, ARR) DEST = __builtin_bswap16(*(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define STORE_64BE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap64(SRC) +#define STORE_32BE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap32(SRC) +#define STORE_16BE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap16(SRC) #endif #endif diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 3c0a77f37..518425b83 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -362,11 +362,7 @@ static void _writeRegister(struct GDBStub* stub, const char* message) { uint32_t value = _readHex(readAddress, &i); -#ifdef _MSC_VER - value = _byteswap_ulong(value); -#else LOAD_32BE(value, 0, &value); -#endif if (reg <= ARM_PC) { cpu->gprs[reg] = value; From 1d186dd9ea95d3e699569ce8870f83146580d677 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Aug 2020 16:15:39 -0700 Subject: [PATCH 02/20] Util: Use __builtin_bswap on PPC big-endian platforms that support it --- include/mgba-util/common.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 2c152f3f1..461c314d7 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -132,7 +132,14 @@ typedef intptr_t ssize_t; #define STORE_64BE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_32BE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_16BE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC -#if defined(__PPC__) || defined(__POWERPC__) +#if defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(*(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(*(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(*(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) +#define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap64(SRC) +#define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap32(SRC) +#define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap16(SRC) +#elif defined(__PPC__) || defined(__POWERPC__) #define LOAD_32LE(DEST, ADDR, ARR) { \ size_t _addr = (ADDR); \ const void* _ptr = (ARR); \ @@ -203,14 +210,6 @@ typedef intptr_t ssize_t; __asm__("stdbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr) : "memory"); \ } #endif - -#elif defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) -#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(*(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) -#define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(*(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) -#define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(*(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR))) -#define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap64(SRC) -#define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap32(SRC) -#define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = __builtin_bswap16(SRC) #else #error Big endian build not supported on this platform. #endif From 6a97b1dbf50f8b4dc9a5e58aa6b8acc95f3f622a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Aug 2020 22:51:53 -0700 Subject: [PATCH 03/20] GBA Memory: Minor code simplification --- src/gba/memory.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 8a0238a9f..aabe8641a 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -484,7 +484,7 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { if (cycleCounter) { wait += 2; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -603,7 +603,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { if (cycleCounter) { wait += 2; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -714,7 +714,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { if (cycleCounter) { wait += 2; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -840,7 +840,7 @@ void GBAStore32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycle if (cycleCounter) { ++wait; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -956,7 +956,7 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle if (cycleCounter) { ++wait; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -1039,7 +1039,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo if (cycleCounter) { ++wait; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -1454,7 +1454,7 @@ uint32_t GBALoadMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum L if (cycleCounter) { ++wait; - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; @@ -1572,7 +1572,7 @@ uint32_t GBAStoreMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum } if (cycleCounter) { - if (address >> BASE_OFFSET < REGION_CART0) { + if (address < BASE_CART0) { wait = GBAMemoryStall(cpu, wait); } *cycleCounter += wait; From c825c57281734bddefa16c05ec9d61a387029cf5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 31 Aug 2020 22:28:19 -0700 Subject: [PATCH 04/20] GBA Video: Implement green swap (fixes #1609) --- CHANGES | 1 + include/mgba/core/interface.h | 17 +++++++++ .../internal/gba/renderers/video-software.h | 1 + src/gba/io.c | 2 +- src/gba/renderers/video-software.c | 35 +++++++++++++------ 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index a52464063..eae5153f9 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Emulation fixes: - GBA Video: Fix Hblank timing - GBA Video: Invalidate map cache when modifying BGCNT (fixes mgba.io/i/1846) - GBA Video: Don't draw sprites using unmapped VRAM in GL renderer (fixes mgba.io/i/1865) + - GBA Video: Implement green swap (fixes mgba.io/i/1609) - SM83: Emulate HALT bug Other fixes: - 3DS: Redo video sync to be more precise diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 1d19c6e79..acbb76c13 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -36,6 +36,23 @@ typedef uint32_t color_t; #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)) +#ifndef COLOR_16_BIT +#define M_COLOR_RED 0x000000FF +#define M_COLOR_GREEN 0x0000FF00 +#define M_COLOR_BLUE 0x00FF0000 +#define M_COLOR_ALPHA 0xFF000000 +#elif defined(COLOR_5_6_5) +#define M_COLOR_RED 0x001F +#define M_COLOR_GREEN 0x07E0 +#define M_COLOR_BLUE 0xF800 +#define M_COLOR_ALPHA 0x0000 +#else +#define M_COLOR_RED 0x001F +#define M_COLOR_GREEN 0x03E0 +#define M_COLOR_BLUE 0x7C00 +#define M_COLOR_ALPHA 0x1000 +#endif + #ifndef PYCPARSE static inline color_t mColorFrom555(uint16_t value) { #ifdef COLOR_16_BIT diff --git a/include/mgba/internal/gba/renderers/video-software.h b/include/mgba/internal/gba/renderers/video-software.h index ad5aa5932..aeb536d90 100644 --- a/include/mgba/internal/gba/renderers/video-software.h +++ b/include/mgba/internal/gba/renderers/video-software.h @@ -114,6 +114,7 @@ struct GBAVideoSoftwareRenderer { uint16_t bldy; GBAMosaicControl mosaic; + bool greenswap; struct WindowN { struct GBAVideoWindowRegion h; diff --git a/src/gba/io.c b/src/gba/io.c index 2e0fb576e..87c519d4d 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -341,7 +341,7 @@ void GBAIOInit(struct GBA* gba) { } void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { - if (address < REG_SOUND1CNT_LO && (address > REG_VCOUNT || address == REG_DISPCNT)) { + if (address < REG_SOUND1CNT_LO && (address > REG_VCOUNT || address < REG_DISPSTAT)) { value = gba->video.renderer->writeVideoRegister(gba->video.renderer, address, value); } else { switch (address) { diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 20783292d..9df7983e7 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -122,6 +122,7 @@ static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) { softwareRenderer->oamMax = 0; softwareRenderer->mosaic = 0; + softwareRenderer->greenswap = false; softwareRenderer->nextY = 0; softwareRenderer->objOffsetX = 0; @@ -179,6 +180,9 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender softwareRenderer->dispcnt = value; GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer); break; + case REG_GREENSWP: + softwareRenderer->greenswap = value & 1; + break; case REG_BG0CNT: value &= 0xDFFF; GBAVideoSoftwareRendererWriteBGCNT(softwareRenderer, &softwareRenderer->bg[0], value); @@ -389,9 +393,6 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender case REG_MOSAIC: softwareRenderer->mosaic = value; break; - case REG_GREENSWP: - mLOG(GBA_VIDEO, STUB, "Stub video register write: 0x%03X", address); - break; default: mLOG(GBA_VIDEO, GAME_ERROR, "Invalid video register: 0x%03X", address); } @@ -675,16 +676,30 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render } } + if (softwareRenderer->greenswap) { + for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; x += 4) { + row[x] = softwareRenderer->row[x] & (M_COLOR_RED | M_COLOR_BLUE); + row[x] |= softwareRenderer->row[x + 1] & M_COLOR_GREEN; + row[x + 1] = softwareRenderer->row[x + 1] & (M_COLOR_RED | M_COLOR_BLUE); + row[x + 1] |= softwareRenderer->row[x] & M_COLOR_GREEN; + row[x + 2] = softwareRenderer->row[x + 2] & (M_COLOR_RED | M_COLOR_BLUE); + row[x + 2] |= softwareRenderer->row[x + 3] & M_COLOR_GREEN; + row[x + 3] = softwareRenderer->row[x + 3] & (M_COLOR_RED | M_COLOR_BLUE); + row[x + 3] |= softwareRenderer->row[x + 2] & M_COLOR_GREEN; + + } + } else { #ifdef COLOR_16_BIT - for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; x += 4) { - row[x] = softwareRenderer->row[x]; - row[x + 1] = softwareRenderer->row[x + 1]; - row[x + 2] = softwareRenderer->row[x + 2]; - row[x + 3] = softwareRenderer->row[x + 3]; - } + for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; x += 4) { + row[x] = softwareRenderer->row[x]; + row[x + 1] = softwareRenderer->row[x + 1]; + row[x + 2] = softwareRenderer->row[x + 2]; + row[x + 3] = softwareRenderer->row[x + 3]; + } #else - memcpy(row, softwareRenderer->row, GBA_VIDEO_HORIZONTAL_PIXELS * sizeof(*row)); + memcpy(row, softwareRenderer->row, GBA_VIDEO_HORIZONTAL_PIXELS * sizeof(*row)); #endif + } } static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) { From 2251d939386d3ff90606e77bd43753e94886f4b0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 3 Sep 2020 17:18:28 -0700 Subject: [PATCH 05/20] CMake: Install cinema with tests --- src/platform/test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/test/CMakeLists.txt b/src/platform/test/CMakeLists.txt index c81b339f8..2517e83bf 100644 --- a/src/platform/test/CMakeLists.txt +++ b/src/platform/test/CMakeLists.txt @@ -44,4 +44,5 @@ if(BUILD_CINEMA) target_link_libraries(${BINARY_NAME}-cinema ${BINARY_NAME} ${PLATFORM_LIBRARY}) set_target_properties(${BINARY_NAME}-cinema PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") add_test(cinema ${BINARY_NAME}-cinema -v) + install(TARGETS ${BINARY_NAME}-cinema DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-test) endif() From 347c5f2de5b37f4e4aacd92b9d9ef81115c8a03f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 3 Sep 2020 23:43:47 -0700 Subject: [PATCH 06/20] GB MBC: Add MBC6 flash support (read-only currently) --- CHANGES | 5 +-- README.md | 2 +- include/mgba/internal/gb/memory.h | 4 ++ src/gb/mbc.c | 66 ++++++++++++++++++++++++++----- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index eae5153f9..dc2e600c4 100644 --- a/CHANGES +++ b/CHANGES @@ -5,9 +5,8 @@ Features: - Separate overrides for GBC games that can also run on SGB or regular GB - Mute option in homebrew ports - Status indicators for fast-forward and mute in homebrew ports - - Support for unlicensed Pokemon Jade/Diamond Game Boy mapper - - Support for unlicensed BBD Game Boy mapper - - Support for unlicensed Hitek Game Boy mapper + - Read-only support for MBC6 flash memory + - New unlicensed GB mappers: Pokémon Jade/Diamond, BBD, and Hitek - Stack tracing tools in ARM debugger (by ahigerd) - Command scripts for CLI debugger (by ahigerd) Emulation fixes: diff --git a/README.md b/README.md index 580319425..e1777f40e 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ The following mappers are fully supported: The following mappers are partially supported: -- MBC6 (missing flash memory support) +- MBC6 (missing flash memory write support) - MMM01 - Pocket Cam - TAMA5 (missing RTC support) diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 676a8d5bb..d11260bac 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -61,6 +61,8 @@ enum { GB_SIZE_OAM = 0xA0, GB_SIZE_IO = 0x80, GB_SIZE_HRAM = 0x7F, + + GB_SIZE_MBC6_FLASH = 0x100000, }; enum { @@ -118,6 +120,8 @@ struct GBMBC6State { bool sramAccess; int currentSramBank1; uint8_t* sramBank1; + bool flashBank0; + bool flashBank1; }; struct GBMBC7State { diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 310c8bb95..cdbbbef44 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -53,6 +53,8 @@ static uint8_t _GBHitekRead(struct GBMemory*, uint16_t address); static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address); static void _GBPocketCamCapture(struct GBMemory*); +static void _GBMBC6MapChip(struct GB*, int half, uint8_t value); + void GBMBCSwitchBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_CART_BANK0; if (bankStart + GB_SIZE_CART_BANK0 > gb->memory.romSize) { @@ -81,19 +83,37 @@ void GBMBCSwitchBank0(struct GB* gb, int bank) { void GBMBCSwitchHalfBank(struct GB* gb, int half, int bank) { size_t bankStart = bank * GB_SIZE_CART_HALFBANK; - if (bankStart + GB_SIZE_CART_HALFBANK > gb->memory.romSize) { - mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank); - bankStart &= (gb->memory.romSize - 1); - bank = bankStart / GB_SIZE_CART_HALFBANK; - if (!bank) { - ++bank; + bool isFlash = half ? gb->memory.mbcState.mbc6.flashBank1 : gb->memory.mbcState.mbc6.flashBank0; + if (isFlash) { + if (bankStart + GB_SIZE_CART_HALFBANK > GB_SIZE_MBC6_FLASH) { + mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid Flash bank: %0X", bank); + bankStart &= GB_SIZE_MBC6_FLASH - 1; + bank = bankStart / GB_SIZE_CART_HALFBANK; + } + bankStart += gb->sramSize - GB_SIZE_MBC6_FLASH; + } else { + if (bankStart + GB_SIZE_CART_HALFBANK > gb->memory.romSize) { + mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank); + bankStart &= gb->memory.romSize - 1; + bank = bankStart / GB_SIZE_CART_HALFBANK; + if (!bank) { + ++bank; + } } } if (!half) { - gb->memory.romBank = &gb->memory.rom[bankStart]; + if (isFlash) { + gb->memory.romBank = &gb->memory.sram[bankStart]; + } else { + gb->memory.romBank = &gb->memory.rom[bankStart]; + } gb->memory.currentBank = bank; } else { - gb->memory.mbcState.mbc6.romBank1 = &gb->memory.rom[bankStart]; + if (isFlash) { + gb->memory.mbcState.mbc6.romBank1 = &gb->memory.sram[bankStart]; + } else { + gb->memory.mbcState.mbc6.romBank1 = &gb->memory.rom[bankStart]; + } gb->memory.mbcState.mbc6.currentBank1 = bank; } if (gb->cpu->pc < GB_BASE_VRAM) { @@ -187,9 +207,10 @@ void GBMBCSwitchSramBank(struct GB* gb, int bank) { void GBMBCSwitchSramHalfBank(struct GB* gb, int half, int bank) { size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM_HALFBANK; - if (bankStart + GB_SIZE_EXTERNAL_RAM_HALFBANK > gb->sramSize) { + size_t sramSize = gb->sramSize - GB_SIZE_MBC6_FLASH; + if (bankStart + GB_SIZE_EXTERNAL_RAM_HALFBANK > sramSize) { mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid RAM bank: %0X", bank); - bankStart &= (gb->sramSize - 1); + bankStart &= (sramSize - 1); bank = bankStart / GB_SIZE_EXTERNAL_RAM_HALFBANK; } if (!half) { @@ -334,6 +355,7 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcWrite = _GBMBC6; gb->memory.mbcRead = _GBMBC6Read; gb->memory.directSramAccess = false; + gb->sramSize += GB_SIZE_MBC6_FLASH; // Flash is concatenated at the end break; case GB_MBC7: gb->memory.mbcWrite = _GBMBC7; @@ -689,14 +711,28 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) { case 0x2: GBMBCSwitchSramHalfBank(gb, 1, bank); break; + case 0x3: + mLOG(GB_MBC, STUB, "MBC6 unimplemented flash OE write: %04X:%02X", address, value); + break; + case 0x4: + mLOG(GB_MBC, STUB, "MBC6 unimplemented flash WE write: %04X:%02X", address, value); + break; case 0x8: case 0x9: GBMBCSwitchHalfBank(gb, 0, bank); break; + case 0xA: + case 0xB: + _GBMBC6MapChip(gb, 0, value); + break; case 0xC: case 0xD: GBMBCSwitchHalfBank(gb, 1, bank); break; + case 0xE: + case 0xF: + _GBMBC6MapChip(gb, 1, value); + break; case 0x28: case 0x29: case 0x2A: @@ -732,6 +768,16 @@ uint8_t _GBMBC6Read(struct GBMemory* memory, uint16_t address) { return 0xFF; } +static void _GBMBC6MapChip(struct GB* gb, int half, uint8_t value) { + if (!half) { + gb->memory.mbcState.mbc6.flashBank0 = !!(value & 0x08); + GBMBCSwitchHalfBank(gb, half, gb->memory.currentBank); + } else { + gb->memory.mbcState.mbc6.flashBank1 = !!(value & 0x08); + GBMBCSwitchHalfBank(gb, half, gb->memory.mbcState.mbc6.currentBank1); + } +} + void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { int bank = value & 0x7F; switch (address >> 13) { From ca5e11533c5430669475bfa15a9292ea822d10da Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Fri, 4 Sep 2020 10:48:17 +0200 Subject: [PATCH 07/20] Doc: Update German README file --- README_DE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_DE.md b/README_DE.md index 781aaa284..0f90b6b8a 100644 --- a/README_DE.md +++ b/README_DE.md @@ -57,7 +57,7 @@ Die folgenden Mapper werden vollständig unterstützt: Die folgenden Mapper werden teilweise unterstützt: -- MBC6 (fehlende Flash-Unterstützung) +- MBC6 (fehlende Unterstützung für Schreibzugriffe auf den Flash-Speicher) - MMM01 - Pocket Cam - TAMA5 (fehlende RTC-Unterstützung) From c2ee50288bb14a2b7673154f67d9bf8868b4d502 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 5 Sep 2020 15:53:31 -0700 Subject: [PATCH 08/20] GBA I/O: Green swap register should be readable --- CHANGES | 1 + src/gba/io.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index dc2e600c4..899042f7b 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Emulation fixes: - GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320) - GBA DMA: Fix ordering and timing of overlapping DMAs - GBA Hardware: Fix GB Player detection on big endian platforms + - GBA I/O: Green swap register should be readable - GBA Memory: Improve gamepak prefetch timing - GBA Memory: Stall on VRAM access in mode 2 (fixes mgba.io/i/190) - GBA Memory: Improve robustness of Matrix memory support diff --git a/src/gba/io.c b/src/gba/io.c index 87c519d4d..6a1037bf3 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -850,6 +850,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { } // Fall through case REG_DISPCNT: + case REG_GREENSWP: case REG_DISPSTAT: case REG_VCOUNT: case REG_BG0CNT: From 756c6e18abfb2691a1ceac371624a5956823d303 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 5 Sep 2020 16:13:04 -0700 Subject: [PATCH 09/20] AppVeyor: Attempt cleanup if the cache goes awry --- .appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0b4b5fb39..f498d666c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,7 +7,8 @@ configuration: cache: - C:\Tools\vcpkg install: -- git -C C:\Tools\vcpkg pull --quiet +- git -C C:\Tools\vcpkg clean -dfq ports toolsrc +- git -C C:\Tools\vcpkg pull --force --quiet - C:\Tools\vcpkg\bootstrap-vcpkg - vcpkg --triplet x64-windows --recurse install ffmpeg libepoxy libpng libzip sdl2 sqlite3 - vcpkg --no-dry-run upgrade From d1178689cbd690b3f1d19a8f1bd93f6b2ffc246e Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 6 Sep 2020 13:02:12 +0200 Subject: [PATCH 10/20] Qt: Update French translations This pull request update the French translations for the Qt platform. --- src/platform/qt/ts/mgba-fr.ts | 364 +++++++++++++++++----------------- 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/src/platform/qt/ts/mgba-fr.ts b/src/platform/qt/ts/mgba-fr.ts index cb91feeb9..fd510dd55 100644 --- a/src/platform/qt/ts/mgba-fr.ts +++ b/src/platform/qt/ts/mgba-fr.ts @@ -135,32 +135,32 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Chip name - + Nom de la puce Insert - + Insérer Save - Sauvegarder + Sauvegarder Load - Charger + Charger Add - Ajouter + Ajouter Remove - Supprimer + Supprimer @@ -185,22 +185,22 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Inserted - + Inséré Chip ID - + ID de la puce Update Chip data - + Mettre à jour les données de la puce Show advanced - Paramètres avancés + Paramètres avancés @@ -239,7 +239,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Enter codes here... - + Entrez les codes ici… @@ -265,42 +265,42 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Inspect frame - + Inspecter l'image × - × + × Magnification - Agrandissement + Agrandissement Freeze frame - + Figer l'image Backdrop color - + Couleur de fond Disable scanline effects - + Désactiver les effets de lignes de balayage Export - Exporter + Exporter Reset - Réinitialiser + Réinitialiser @@ -308,12 +308,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Record GIF/APNG - + Enregistrer GIF/APNG Loop - + Boucle @@ -333,17 +333,17 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. APNG - + APNG GIF - + GIF WebP - + WebP @@ -506,7 +506,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Cancel - + Annuler @@ -627,7 +627,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Copy - + Copier @@ -635,33 +635,33 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Save Memory Range - + Sauvegarder la plage de mémoire Start Address: - + Adresse de départ : : - : + : 0x - 0x + 0x Byte Count: - + Nombre d'octets : Dump across banks - + Dump across banks @@ -746,57 +746,57 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Search type - + Type de recherche Equal to value - + Égale à la valeur Greater than value - + Supérieur à la valeur Less than value - + Inférieur à la valeur Unknown/changed - + Inconnu/changé Changed by value - + Modifié par la valeur Unchanged - + Inchangé Increased - + Augmentation Decreased - + Diminution Search ROM - + Recherche dans la ROM New Search - + Nouvelle recherche @@ -829,7 +829,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. : - : + : @@ -844,17 +844,17 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. &1 Byte - + &1 Octet &2 Bytes - + &2 Octets &4 Bytes - + &4 Octets @@ -894,7 +894,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Save Range - + Sauvegarder la plage @@ -956,24 +956,24 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Copy - + Copier +0.00 - + +0.00 +1.00 - + +1.00 Matrix - + Matrice @@ -1224,7 +1224,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. MBC6 - MBC6 + MBC6 @@ -1234,7 +1234,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. MMM01 - + MMM01 @@ -1249,7 +1249,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. HuC-1 - HuC-1 + HuC-1 @@ -1259,12 +1259,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Wisdom Tree (Unlicensed) - + Wisdom Tree (sans licence) Pokémon Jade/Diamond (Unlicensed) - + Pokémon Jade/Diamond (sans licence) @@ -1354,7 +1354,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. 0x000 (000) - 0x000 (000) {0x?} + 0x000 (000) @@ -1410,17 +1410,17 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Tear off - Tear off + Déchirer (Tear off) × - × + × Magnification - Agrandissement + Agrandissement @@ -1514,12 +1514,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Could not load game. Are you sure it's in the correct format? - Impossible de charger le jeu. Êtes-vous sûr qu'il est dans le bon format ? + Impossible de charger le jeu. Êtes-vous sûr qu'il est dans le bon format ? Failed to open save file. Is the save directory writable? - + Impossible d'ouvrir le fichier de sauvegarde. Le répertoire de sauvegarde est-il accessible en écriture ? @@ -1527,42 +1527,42 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Export frame - + Exporter l'image Portable Network Graphics (*.png) - Portable Network Graphics (*.png) + Portable Network Graphics (*.png) None - Aucun + Aucun Background - Arrière plan + Arrière plan Window - + Fenêtre Sprite - + Sprite Backdrop - + Toile de fond %1 %2 - %1x {1 %2?} + %1 %2 @@ -1570,7 +1570,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Enable Discord Rich Presence - + Activer intégration avec Discord @@ -1644,7 +1644,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Failed to open output file: %1 - Impossible d'ouvrir le fichier de sortie : %1 + Impossible d'ouvrir le fichier de sortie : %1 @@ -1654,7 +1654,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Graphics Interchange Format (*.gif);;Animated Portable Network Graphics (*.png *.webp *.apng) - + Graphics Interchange Format (*.gif);;Animated Portable Network Graphics (*.png *.webp *.apng) @@ -1667,17 +1667,17 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Mode 0: 4 tile layers - Mode 0: 4 tile layers + Mode 0: 4 tile layers Mode 1: 2 tile layers + 1 rotated/scaled tile layer - Mode 1: 2 tile layers + 1 rotated/scaled tile layer + Mode 1: 2 tile layers + 1 rotated/scaled tile layer Mode 2: 2 rotated/scaled tile layers - Mode 2: 2 rotated/scaled tile layers + Mode 2: 2 rotated/scaled tile layers @@ -2384,12 +2384,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Loud channel A - Canal fort A + Canal fort A Loud channel B - Canal fort B + Canal fort B @@ -3047,17 +3047,17 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Super (L) - + Super (L) Super (R) - + Super (R) Menu - + Menu @@ -3094,42 +3094,42 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Default - + Défaut Fatal - Fatal + Fatal Error - Erreur + Erreur Warning - Avertissement + Avertissement Info - Info + Info Debug - Débogage + Débogage Stub - Stub + Stub Game Error - Erreur du jeu + Erreur du jeu @@ -3137,12 +3137,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. [%1] %2: %3 - + [%1] %2: %3 An error occurred - + Une erreur est survenue @@ -3192,35 +3192,35 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Priority - Priorité + Priorité Map base - + Fond de carte Tile base - + Fond de tuile Size - Taille + Taille Offset - Offset + Compensation Xform - + Xform @@ -3257,7 +3257,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. N/A - N/A + N/A @@ -3275,12 +3275,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Save memory region - + Sauvegarder la région de la mémoire Failed to open output file: %1 - Impossible d'ouvrir le fichier de sortie : %1 + Impossible d'ouvrir le fichier de sortie : %1 @@ -3392,7 +3392,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. --- - --- + --- @@ -3402,7 +3402,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Trans - Trans + Trans @@ -3552,7 +3552,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. (%1×%2) - + (%1×%2) @@ -3593,17 +3593,17 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Action - Action + Action Keyboard - Clavier + Clavier Gamepad - Manette de jeu + Manette de jeu @@ -3611,18 +3611,18 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Export tiles - + Exporter les tuiles Portable Network Graphics (*.png) - Portable Network Graphics (*.png) + Portable Network Graphics (*.png) Export tile - + Exporter une tuile @@ -3697,13 +3697,13 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. mGBA savestate files (%1) - + mGBA fichiers d'état (%1) Select savestate - + Sélectionner un fichier d'état @@ -3718,12 +3718,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Select e-Reader dotcode - + Sélectionnez le numéro de point du e-Reader e-Reader card (*.raw *.bin *.bmp) - + e-Reader carte (*.raw *.bin *.bmp) @@ -3954,7 +3954,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Yank game pak - Extraire game pak + Yank game pak @@ -3984,7 +3984,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Unbounded - Non lié + Sans limites @@ -4034,7 +4034,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Brightest solar level - Tester le niveau solaire + Tester le niveau solaire @@ -4134,47 +4134,47 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Game &overrides... - Passer &outre le jeu… + Couldn't Start - + N'a pas pu démarrer Could not start game. - + Impossible de démarrer le jeu. Scan e-Reader dotcodes... - + Scanner les dotcodes e-Reader... Load state file... - + Charger le fichier d'état... Save state file... - + Enregistrer le fichier d'état... Import GameShark Save... - + Importer la sauvegarde de GameShark... Export GameShark Save... - + Exporter la sauvegarde de GameShark... About... - + À propos de... @@ -4184,32 +4184,32 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. %1× - %1x {1×?} + %1× Interframe blending - + Mélange d'images Native (59.7275) - Natif (59.7) {59.7275)?} + Natif (59.7275) Record A/V... - + Enregistrer A/V... Record GIF/WebP/APNG... - + Enregistrer GIF/WebP/APNG... Game Pak sensors... - + Capteurs de la Game Pak... @@ -4254,7 +4254,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. &Frame inspector... - + Inspecteur de &frame... @@ -4274,12 +4274,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Record debug video log... - + Enregistrer le journal vidéo de débogage... Stop debug video log - + Arrêtez le journal vidéo de débogage @@ -4349,7 +4349,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Clear - Vider + Vider @@ -4375,22 +4375,22 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Shift - + Shift Control - + Control Alt - + Alt Meta - + Méta/Super @@ -4552,7 +4552,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Enhancements - + Améliorations @@ -4567,7 +4567,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Logging - + Journalisation @@ -4670,7 +4670,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Fast forward volume: - + Volume en avance rapide : @@ -4731,42 +4731,42 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Log to file - + Journalisation vers le fichier Log to console - + Journalisation vers la console Select Log File - + Sélectionner le fichier de journalisation Game Boy model: - + Modèle de Game Boy : Super Game Boy model: - + Modèle de Super Game Boy : Game Boy Color model: - + Modèle de Game Boy Color : Use GBC colors in GB games - + Utiliser les couleurs GBC dans les jeux en GB Camera: - + Caméra : @@ -4846,22 +4846,22 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Native (59.7275) - Natif (59.7) {59.7275)?} + Natif (59.7275) Interframe blending - + Mélange d'images Pause when minimized - + Pause lorsqu'elle est minimisée Enable Discord Rich Presence - + Activer l'intégration avec Discord @@ -4871,12 +4871,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Show OSD messages - + Afficher les messages OSD Show filename instead of ROM name in title bar - + Afficher le nom du fichier au lieu du nom de la ROM dans la barre de titre @@ -4899,7 +4899,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Fast forward (held) speed: - + Vitesse d'avance rapide (maintenue) : @@ -4972,37 +4972,37 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Video renderer: - + Rendu vidéo : Software - + Logiciel(s) OpenGL - OpenGL + OpenGL OpenGL enhancements - + Améliorations OpenGL High-resolution scale: - + Échelle haute résolution : (240×160) - + (240×160) XQ GBA audio (experimental) - + XQ GBA audio (expérimental) @@ -5213,12 +5213,12 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Export Selected - + Exportation sélectionnée Export All - + Exporter tous @@ -5238,22 +5238,22 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. Tiles per row - + Tuiles par rangée Fit to window - + Adaptation à la fenêtre Copy Selected - + Copier la sélection Copy All - + Copier tout @@ -5313,42 +5313,42 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. High &Quality - + Haute &Qualité &YouTube - + &YouTube &Lossless - + &Lossless 4K - 480p {4K?} + 4K &1080p - + &1080p &720p - + &720p &480p - + &480p &Native - + &Natif @@ -5368,7 +5368,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. HEVC (NVENC) - + HEVC (NVENC) @@ -5378,7 +5378,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. VP9 - VP9 + VP9 @@ -5389,7 +5389,7 @@ Game Boy Advance est une marque de fabrique enregistré par Nintendo Co., Ltd. None - Aucun + Aucun From 17ca8f524a165cb15856b76925a17d68b2cdb09d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 7 Sep 2020 01:20:22 -0700 Subject: [PATCH 11/20] GBA Video: Fix rare regression blending semitransparent sprites (fixes #1876) --- CHANGES | 1 + .../gba/blend/sma2-semitrans/baseline_0000.png | Bin 0 -> 10114 bytes .../gba/blend/sma2-semitrans/baseline_0001.png | Bin 0 -> 10114 bytes .../gba/blend/sma2-semitrans/baseline_0002.png | Bin 0 -> 10114 bytes .../gba/blend/sma2-semitrans/baseline_0003.png | Bin 0 -> 10324 bytes .../gba/blend/sma2-semitrans/baseline_0004.png | Bin 0 -> 10324 bytes .../gba/blend/sma2-semitrans/baseline_0005.png | Bin 0 -> 10324 bytes cinema/gba/blend/sma2-semitrans/test.mvl | Bin 0 -> 22109 bytes src/gba/renderers/software-obj.c | 2 +- 9 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 cinema/gba/blend/sma2-semitrans/baseline_0000.png create mode 100644 cinema/gba/blend/sma2-semitrans/baseline_0001.png create mode 100644 cinema/gba/blend/sma2-semitrans/baseline_0002.png create mode 100644 cinema/gba/blend/sma2-semitrans/baseline_0003.png create mode 100644 cinema/gba/blend/sma2-semitrans/baseline_0004.png create mode 100644 cinema/gba/blend/sma2-semitrans/baseline_0005.png create mode 100644 cinema/gba/blend/sma2-semitrans/test.mvl diff --git a/CHANGES b/CHANGES index 899042f7b..48471e3d1 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,7 @@ Emulation fixes: - GBA Video: Invalidate map cache when modifying BGCNT (fixes mgba.io/i/1846) - GBA Video: Don't draw sprites using unmapped VRAM in GL renderer (fixes mgba.io/i/1865) - GBA Video: Implement green swap (fixes mgba.io/i/1609) + - GBA Video: Fix rare regression blending semitransparent sprites (fixes mgba.io/i/1876) - SM83: Emulate HALT bug Other fixes: - 3DS: Redo video sync to be more precise diff --git a/cinema/gba/blend/sma2-semitrans/baseline_0000.png b/cinema/gba/blend/sma2-semitrans/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..89538bf1e1a81c4a38c47270fd6911a0eb25cc61 GIT binary patch literal 10114 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 m%FojE&8?Fy9V;b1HU2+hwMMpjyW~Fr0000S0 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 C^U1#e literal 0 HcmV?d00001 diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index d533de9ee..514adc724 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -162,7 +162,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re } int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); - int variant = (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) && + int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) { From 87ec3f3d4a8a28f4d7dde5b1069ae86183fcb12a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 7 Sep 2020 15:54:49 -0700 Subject: [PATCH 12/20] ARM Debugger: Disassembler now resolves addresses to symbol names --- CHANGES | 1 + include/mgba/internal/arm/decoder.h | 3 +- src/arm/debugger/cli-debugger.c | 13 ++--- src/arm/debugger/debugger.c | 7 +-- src/arm/decoder.c | 80 +++++++++++++++++++++++------ 5 files changed, 78 insertions(+), 26 deletions(-) diff --git a/CHANGES b/CHANGES index 48471e3d1..9cd28ed38 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Features: - New unlicensed GB mappers: Pokémon Jade/Diamond, BBD, and Hitek - Stack tracing tools in ARM debugger (by ahigerd) - Command scripts for CLI debugger (by ahigerd) + - ARM disassembler now resolves addresses to symbol names Emulation fixes: - ARM: Fix ALU reading PC after shifting - ARM: Fix STR storing PC after address calculation diff --git a/include/mgba/internal/arm/decoder.h b/include/mgba/internal/arm/decoder.h index dffa7042a..1a3f9e7e9 100644 --- a/include/mgba/internal/arm/decoder.h +++ b/include/mgba/internal/arm/decoder.h @@ -212,11 +212,12 @@ struct ARMInstructionInfo { unsigned nDataCycles : 10; }; +struct mDebuggerSymbols; void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info); void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info); bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, struct ARMInstructionInfo* out); -int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, int blen); +int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* core, const struct mDebuggerSymbols* symbols, uint32_t pc, char* buffer, int blen); uint32_t ARMResolveMemoryAccess(struct ARMInstructionInfo* info, struct ARMRegisterFile* regs, uint32_t pc); CXX_GUARD_END diff --git a/src/arm/debugger/cli-debugger.c b/src/arm/debugger/cli-debugger.c index d5ee883be..df3e04c1c 100644 --- a/src/arm/debugger/cli-debugger.c +++ b/src/arm/debugger/cli-debugger.c @@ -98,28 +98,29 @@ static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) { struct CLIDebuggerBackend* be = debugger->backend; + struct mCore* core = debugger->d.core; char disassembly[64]; struct ARMInstructionInfo info; be->printf(be, "%08X: ", address); if (mode == MODE_ARM) { - uint32_t instruction = debugger->d.core->busRead32(debugger->d.core, address); + uint32_t instruction = core->busRead32(core, address); ARMDecodeARM(instruction, &info); - ARMDisassemble(&info, address + WORD_SIZE_ARM * 2, disassembly, sizeof(disassembly)); + ARMDisassemble(&info, core->cpu, core->symbolTable, address + WORD_SIZE_ARM * 2, disassembly, sizeof(disassembly)); be->printf(be, "%08X\t%s\n", instruction, disassembly); return WORD_SIZE_ARM; } else { struct ARMInstructionInfo info2; struct ARMInstructionInfo combined; - uint16_t instruction = debugger->d.core->busRead16(debugger->d.core, address); - uint16_t instruction2 = debugger->d.core->busRead16(debugger->d.core, address + WORD_SIZE_THUMB); + uint16_t instruction = core->busRead16(core, address); + uint16_t instruction2 = core->busRead16(core, address + WORD_SIZE_THUMB); ARMDecodeThumb(instruction, &info); ARMDecodeThumb(instruction2, &info2); if (ARMDecodeThumbCombine(&info, &info2, &combined)) { - ARMDisassemble(&combined, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); + ARMDisassemble(&combined, core->cpu, core->symbolTable, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); be->printf(be, "%04X %04X\t%s\n", instruction, instruction2, disassembly); return WORD_SIZE_THUMB * 2; } else { - ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); + ARMDisassemble(&info, core->cpu, core->symbolTable, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); be->printf(be, "%04X \t%s\n", instruction, disassembly); return WORD_SIZE_THUMB; } diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index b7f345b3d..fe3d56117 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -467,6 +467,7 @@ static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mWatc static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMCore* cpu = debugger->cpu; + struct mCore* core = d->p->core; char disassembly[64]; @@ -475,17 +476,17 @@ static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* len if (cpu->executionMode == MODE_ARM) { uint32_t instruction = cpu->prefetch[0]; sprintf(disassembly, "%08X: ", instruction); - ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + ARMDisassemble(&info, cpu, core->symbolTable, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); } else { uint16_t instruction = cpu->prefetch[0]; ARMDecodeThumb(instruction, &info); if (isWideInstruction) { uint16_t instruction2 = cpu->prefetch[1]; sprintf(disassembly, "%04X%04X: ", instruction, instruction2); - ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + ARMDisassemble(&info, cpu, core->symbolTable, 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: ")); + ARMDisassemble(&info, cpu, core->symbolTable, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); } } diff --git a/src/arm/decoder.c b/src/arm/decoder.c index 3cb87d77c..81bf9f483 100644 --- a/src/arm/decoder.c +++ b/src/arm/decoder.c @@ -6,6 +6,7 @@ #include #include +#include #include #define ADVANCE(AMOUNT) \ @@ -20,8 +21,8 @@ static int _decodeRegister(int reg, char* buffer, int blen); static int _decodeRegisterList(int list, char* buffer, int blen); static int _decodePSR(int bits, char* buffer, int blen); -static int _decodePCRelative(uint32_t address, uint32_t pc, char* buffer, int blen); -static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, int blen); +static int _decodePCRelative(uint32_t address, const struct mDebuggerSymbols* symbols, uint32_t pc, bool thumbBranch, char* buffer, int blen); +static int _decodeMemory(struct ARMMemoryAccess memory, struct ARMCore* cpu, const struct mDebuggerSymbols* symbols, int pc, char* buffer, int blen); static int _decodeShift(union ARMOperand operand, bool reg, char* buffer, int blen); static const char* _armConditions[] = { @@ -141,23 +142,66 @@ static int _decodePSR(int psrBits, char* buffer, int blen) { return total; } -static int _decodePCRelative(uint32_t address, uint32_t pc, char* buffer, int blen) { - return snprintf(buffer, blen, "$%08X", address + pc); +static int _decodePCRelative(uint32_t address, const struct mDebuggerSymbols* symbols, uint32_t pc, bool thumbBranch, char* buffer, int blen) { + address += pc; + const char* label = NULL; + if (symbols) { + label = mDebuggerSymbolReverseLookup(symbols, address, -1); + if (!label && thumbBranch) { + label = mDebuggerSymbolReverseLookup(symbols, address | 1, -1); + } + } + if (label) { + return strlcpy(buffer, label, blen); + } else { + return snprintf(buffer, blen, "0x%08X", address); + } } -static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, int blen) { +static int _decodeMemory(struct ARMMemoryAccess memory, struct ARMCore* cpu, const struct mDebuggerSymbols* symbols, int pc, char* buffer, int blen) { if (blen <= 1) { return 0; } int total = 0; - strlcpy(buffer, "[", blen); - ADVANCE(1); + bool elideClose = false; int written; if (memory.format & ARM_MEMORY_REGISTER_BASE) { if (memory.baseReg == ARM_PC && memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) { - written = _decodePCRelative(memory.format & ARM_MEMORY_OFFSET_SUBTRACT ? -memory.offset.immediate : memory.offset.immediate, pc & 0xFFFFFFFC, buffer, blen); - ADVANCE(written); + uint32_t addrBase = memory.format & ARM_MEMORY_OFFSET_SUBTRACT ? -memory.offset.immediate : memory.offset.immediate; + if (!cpu) { + strlcpy(buffer, "[", blen); + ADVANCE(1); + written = _decodePCRelative(addrBase, symbols, pc & 0xFFFFFFFC, false, buffer, blen); + ADVANCE(written); + } else { + uint32_t value; + addrBase += pc & 0xFFFFFFFC; // Thumb does not have PC-relative LDRH/LDRB + switch (memory.width & 7) { + case 1: + value = cpu->memory.load8(cpu, addrBase, NULL); + break; + case 2: + value = cpu->memory.load16(cpu, addrBase, NULL); + break; + case 4: + value = cpu->memory.load32(cpu, addrBase, NULL); + break; + } + const char* label = NULL; + if (symbols) { + label = mDebuggerSymbolReverseLookup(symbols, value, -1); + } + if (label) { + written = snprintf(buffer, blen, "=%s", label); + } else { + written = snprintf(buffer, blen, "=0x%08X", value); + } + ADVANCE(written); + elideClose = true; + } } else { + strlcpy(buffer, "[", blen); + ADVANCE(1); written = _decodeRegister(memory.baseReg, buffer, blen); ADVANCE(written); if (memory.format & (ARM_MEMORY_REGISTER_OFFSET | ARM_MEMORY_IMMEDIATE_OFFSET) && !(memory.format & ARM_MEMORY_POST_INCREMENT)) { @@ -165,10 +209,14 @@ static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, in ADVANCE(2); } } + } else { + strlcpy(buffer, "[", blen); + ADVANCE(1); } if (memory.format & ARM_MEMORY_POST_INCREMENT) { strlcpy(buffer, "], ", blen); ADVANCE(3); + elideClose = true; } if (memory.format & ARM_MEMORY_IMMEDIATE_OFFSET && memory.baseReg != ARM_PC) { if (memory.format & ARM_MEMORY_OFFSET_SUBTRACT) { @@ -191,7 +239,7 @@ static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, in ADVANCE(written); } - if (!(memory.format & ARM_MEMORY_POST_INCREMENT)) { + if (!elideClose) { strlcpy(buffer, "]", blen); ADVANCE(1); } @@ -322,7 +370,7 @@ static const char* _armAccessTypeStrings[] = { "" }; -int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, int blen) { +int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* cpu, const struct mDebuggerSymbols* symbols, uint32_t pc, char* buffer, int blen) { const char* mnemonic = _armMnemonicStrings[info->mnemonic]; int written; int total = 0; @@ -394,7 +442,7 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i case ARM_MN_B: case ARM_MN_BL: if (info->operandFormat & ARM_OPERAND_IMMEDIATE_1) { - written = _decodePCRelative(info->op1.immediate, pc, buffer, blen); + written = _decodePCRelative(info->op1.immediate, symbols, pc, true, buffer, blen); ADVANCE(written); } break; @@ -403,7 +451,7 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i written = snprintf(buffer, blen, "#%i", info->op1.immediate); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_MEMORY_1) { - written = _decodeMemory(info->memory, pc, buffer, blen); + written = _decodeMemory(info->memory, cpu, symbols, pc, buffer, blen); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_REGISTER_1) { written = _decodeRegister(info->op1.reg, buffer, blen); @@ -428,7 +476,7 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i written = snprintf(buffer, blen, "#%i", info->op2.immediate); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_MEMORY_2) { - written = _decodeMemory(info->memory, pc, buffer, blen); + written = _decodeMemory(info->memory, cpu, symbols, pc, buffer, blen); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_REGISTER_2) { written = _decodeRegister(info->op2.reg, buffer, blen); @@ -449,7 +497,7 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i written = snprintf(buffer, blen, "#%i", info->op3.immediate); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_MEMORY_3) { - written = _decodeMemory(info->memory, pc, buffer, blen); + written = _decodeMemory(info->memory, cpu, symbols, pc, buffer, blen); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_REGISTER_3) { written = _decodeRegister(info->op3.reg, buffer, blen); @@ -470,7 +518,7 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i written = snprintf(buffer, blen, "#%i", info->op4.immediate); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_MEMORY_4) { - written = _decodeMemory(info->memory, pc, buffer, blen); + written = _decodeMemory(info->memory, cpu, symbols, pc, buffer, blen); ADVANCE(written); } else if (info->operandFormat & ARM_OPERAND_REGISTER_4) { written = _decodeRegister(info->op4.reg, buffer, blen); From 11f105eec78e2744ccd20554171c7e0f7794ba14 Mon Sep 17 00:00:00 2001 From: Kevin <6500490+Zeturic@users.noreply.github.com> Date: Mon, 7 Sep 2020 20:00:03 -0400 Subject: [PATCH 13/20] support variant symfile format with function sizes --- src/debugger/symbols.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/debugger/symbols.c b/src/debugger/symbols.c index 1cfa246a3..8b76bd5a0 100644 --- a/src/debugger/symbols.c +++ b/src/debugger/symbols.c @@ -97,6 +97,13 @@ void mDebuggerLoadARMIPSSymbols(struct mDebuggerSymbols* st, struct VFile* vf) { continue; } + char* buf2 = strchr(buf, ','); + + if (buf2 != NULL) { + // Commas separate names from function sizes + *buf2 = '\0'; + } + mDebuggerSymbolAdd(st, buf, address, -1); } } From 94fc963c89460a508e6c569aadaf715981768fa1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Sep 2020 19:57:41 -0700 Subject: [PATCH 14/20] CMake: Fix PGO on non-GNU platforms --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0754c160..9517e225d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,10 @@ mark_as_advanced(BUILD_LTO BUILD_PGO PGO_STAGE_2 PGO_DIR) set(PGO_PRE_FLAGS "-fprofile-generate=${PGO_DIR} -fprofile-arcs") set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities") +if(BUILD_PGO AND CMAKE_SYSTEM_NAME STREQUAL "Generic") + add_definitions(-DTARGET_POSIX_IO) +endif() + if(BUILD_PGO AND NOT PGO_STAGE_2) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${PGO_PRE_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${PGO_PRE_FLAGS}") From ad7146a801324caee68c5bbef25ea448bc962fa1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Sep 2020 22:23:11 -0700 Subject: [PATCH 15/20] GB I/O: Add BANK register name --- include/mgba/internal/gb/io.h | 1 + src/gb/io.c | 9 +++++---- src/gb/serialize.c | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/mgba/internal/gb/io.h b/include/mgba/internal/gb/io.h index 92917d70a..169199230 100644 --- a/include/mgba/internal/gb/io.h +++ b/include/mgba/internal/gb/io.h @@ -87,6 +87,7 @@ enum GBIORegisters { REG_KEY0 = 0x4C, REG_KEY1 = 0x4D, REG_VBK = 0x4F, + REG_BANK = 0x50, REG_HDMA1 = 0x51, REG_HDMA2 = 0x52, REG_HDMA3 = 0x53, diff --git a/src/gb/io.c b/src/gb/io.c index ea9eb7b2e..280ca1ddf 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -56,6 +56,7 @@ MGBA_EXPORT const char* const GBIORegisterNames[] = { [REG_KEY0] = "KEY0", [REG_KEY1] = "KEY1", [REG_VBK] = "VBK", + [REG_BANK] = "BANK", [REG_HDMA1] = "HDMA1", [REG_HDMA2] = "HDMA2", [REG_HDMA3] = "HDMA3", @@ -185,10 +186,10 @@ void GBIOReset(struct GB* gb) { GBIOWrite(gb, REG_NR51, 0xF3); if (!gb->biosVf) { GBIOWrite(gb, REG_LCDC, 0x91); - gb->memory.io[0x50] = 1; + gb->memory.io[REG_BANK] = 1; } else { GBIOWrite(gb, REG_LCDC, 0x00); - gb->memory.io[0x50] = 0xFF; + gb->memory.io[REG_BANK] = 0xFF; } GBIOWrite(gb, REG_SCY, 0x00); GBIOWrite(gb, REG_SCX, 0x00); @@ -459,8 +460,8 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { GBVideoWriteSTAT(&gb->video, value); value = gb->video.stat; break; - case 0x50: - if (gb->memory.io[0x50] != 0xFF) { + case REG_BANK: + if (gb->memory.io[REG_BANK] != 0xFF) { break; } GBUnmapBIOS(gb); diff --git a/src/gb/serialize.c b/src/gb/serialize.c index e6b053b9b..002b5a650 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -139,7 +139,7 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { mLOG(GB_STATE, WARN, "Savestate is corrupted: OCPS is out of range"); } bool differentBios = !gb->biosVf || gb->model != state->model; - if (state->io[0x50] == 0xFF) { + if (state->io[REG_BANK] == 0xFF) { if (differentBios) { mLOG(GB_STATE, WARN, "Incompatible savestate, please restart with correct BIOS in %s mode", GBModelToName(state->model)); error = true; @@ -206,7 +206,7 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state); - if (gb->memory.io[0x50] == 0xFF) { + if (gb->memory.io[REG_BANK] == 0xFF) { GBMapBIOS(gb); } else { GBUnmapBIOS(gb); From a8999958dafb0486c4d5474ed13d21e36e17cc3c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Sep 2020 22:48:07 -0700 Subject: [PATCH 16/20] Core: Add shutdown callback --- CHANGES | 2 ++ include/mgba/core/interface.h | 1 + src/core/thread.c | 9 +++++++++ src/gba/gba.c | 6 +++++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9cd28ed38..82a65d1ac 100644 --- a/CHANGES +++ b/CHANGES @@ -71,6 +71,8 @@ Other fixes: - VFS: Fix directory node listing on some filesystems Misc: - 3DS: Use "wide mode" where applicable for slightly better filtering + - Core: Add savedataUpdated callback + - Core: Add shutdown callback - GB: Allow pausing event loop while CPU is blocked - GBA: Allow pausing event loop while CPU is blocked - Debugger: Keep track of global cycle count diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index acbb76c13..a4f2d1598 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -107,6 +107,7 @@ struct mCoreCallbacks { void (*videoFrameEnded)(void* context); void (*coreCrashed)(void* context); void (*sleep)(void* context); + void (*shutdown)(void* context); void (*keysRead)(void* context); void (*savedataUpdated)(void* context); }; diff --git a/src/core/thread.c b/src/core/thread.c index 218dc92cf..4fbe6e95c 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -139,6 +139,14 @@ void _coreSleep(void* context) { } } +void _coreShutdown(void* context) { + struct mCoreThread* thread = context; + if (!thread) { + return; + } + _changeState(thread->impl, THREAD_EXITING, true); +} + static THREAD_ENTRY _mCoreThreadRun(void* context) { struct mCoreThread* threadContext = context; #ifdef USE_PTHREADS @@ -162,6 +170,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { .videoFrameEnded = _frameEnded, .coreCrashed = _crashed, .sleep = _coreSleep, + .shutdown = _coreShutdown, .context = threadContext }; core->addCoreCallbacks(core, &callbacks); diff --git a/src/gba/gba.c b/src/gba/gba.c index 693a583b5..d47dcd8ef 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -525,11 +525,15 @@ void GBAHalt(struct GBA* gba) { } void GBAStop(struct GBA* gba) { + int validIrqs = (1 << IRQ_GAMEPAK) | (1 << IRQ_KEYPAD) | (1 << IRQ_SIO); + int sleep = gba->memory.io[REG_IE >> 1] & validIrqs; size_t c; for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) { struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c); - if (callbacks->sleep) { + if (sleep && callbacks->sleep) { callbacks->sleep(callbacks->context); + } else if (callbacks->shutdown) { + callbacks->shutdown(callbacks->context); } } gba->cpu->nextEvent = gba->cpu->cycles; From 30e0be098fdab754da67838db1c319cfea7aeb4f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Sep 2020 22:57:34 -0700 Subject: [PATCH 17/20] GB: Add support for sleep and shutdown callbacks --- CHANGES | 1 + src/gb/gb.c | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 82a65d1ac..134cf5dda 100644 --- a/CHANGES +++ b/CHANGES @@ -74,6 +74,7 @@ Misc: - Core: Add savedataUpdated callback - Core: Add shutdown callback - GB: Allow pausing event loop while CPU is blocked + - GB: Add support for sleep and shutdown callbacks - GBA: Allow pausing event loop while CPU is blocked - Debugger: Keep track of global cycle count - FFmpeg: Add looping option for GIF/APNG diff --git a/src/gb/gb.c b/src/gb/gb.c index fb407425e..bc7221957 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -783,29 +783,23 @@ void GBHalt(struct SM83Core* cpu) { void GBStop(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; - if (cpu->bus) { - mLOG(GB, GAME_ERROR, "Hit illegal stop at address %04X:%02X", cpu->pc, cpu->bus); - } - if (gb->memory.io[REG_KEY1] & 1) { + if (gb->model >= GB_MODEL_CGB && gb->memory.io[REG_KEY1] & 1) { gb->doubleSpeed ^= 1; gb->audio.timingFactor = gb->doubleSpeed + 1; gb->memory.io[REG_KEY1] = 0; gb->memory.io[REG_KEY1] |= gb->doubleSpeed << 7; - } else if (cpu->bus) { -#ifdef USE_DEBUGGERS - if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) { - struct mDebuggerEntryInfo info = { - .address = cpu->pc - 1, - .type.bp.opcode = 0x1000 | cpu->bus, - }; - mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info); + } else { + int sleep = ~(gb->memory.io[REG_JOYP] & 0x30); + size_t c; + for (c = 0; c < mCoreCallbacksListSize(&gb->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gb->coreCallbacks, c); + if (sleep && callbacks->sleep) { + callbacks->sleep(callbacks->context); + } else if (callbacks->shutdown) { + callbacks->shutdown(callbacks->context); + } } -#endif - // Hang forever - gb->memory.ime = 0; - cpu->pc -= 2; } - // TODO: Actually stop } void GBIllegal(struct SM83Core* cpu) { From ee698cc311f37c184e8f0fdf553d271cf6f6bf51 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Sep 2020 23:02:39 -0700 Subject: [PATCH 18/20] SM83: Disassemble STOP as one byte --- CHANGES | 1 + src/sm83/decoder.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 134cf5dda..03eb606a4 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,7 @@ Other fixes: - mGUI: Fix closing down a game if an exit is signalled - mVL: Fix injecting accidentally draining non-injection buffer - SM83: Simplify register pair access on big endian + - SM83: Disassemble STOP as one byte - VFS: Fix directory node listing on some filesystems Misc: - 3DS: Use "wide mode" where applicable for slightly better filtering diff --git a/src/sm83/decoder.c b/src/sm83/decoder.c index 7ad565d68..1240d5841 100644 --- a/src/sm83/decoder.c +++ b/src/sm83/decoder.c @@ -365,7 +365,7 @@ DEFINE_DECODER_SM83(DI, info->mnemonic = SM83_MN_DI) DEFINE_DECODER_SM83(EI, info->mnemonic = SM83_MN_EI) DEFINE_DECODER_SM83(HALT, info->mnemonic = SM83_MN_HALT) DEFINE_DECODER_SM83(ILL, info->mnemonic = SM83_MN_ILL) -DEFINE_DECODER_SM83(STOP, info->mnemonic = SM83_MN_STOP; return 1) +DEFINE_DECODER_SM83(STOP, info->mnemonic = SM83_MN_STOP) #define DEFINE_RST_DECODER_SM83(VEC) \ DEFINE_DECODER_SM83(RST ## VEC, info->op1.immediate = 0x ## VEC;) From 94c637ee341f6ea130a2c51b3e44ab3b77077b8d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 10 Sep 2020 00:18:05 -0700 Subject: [PATCH 19/20] README: Update Ubuntu Docker image list --- README_DE.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README_DE.md b/README_DE.md index 0f90b6b8a..d1b3d5d84 100644 --- a/README_DE.md +++ b/README_DE.md @@ -128,10 +128,8 @@ Dieser Befehl erzeugt ein Verzeichnis `build-win32` mit den erzeugten Programmda - mgba/switch - mgba/ubuntu:xenial - mgba/ubuntu:bionic -- mgba/ubuntu:cosmic -- mgba/ubuntu:disco -- mgba/ubuntu:eoan - mgba/ubuntu:focal +- mgba/ubuntu:groovy - mgba/vita - mgba/wii - mgba/windows:w32 From f636b88f275aeb76a44a204c2e95c8d948eac799 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 10 Sep 2020 00:19:10 -0700 Subject: [PATCH 20/20] README: Update Ubuntu Docker image list --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index e1777f40e..67a163d59 100644 --- a/README.md +++ b/README.md @@ -128,10 +128,8 @@ This will produce a `build-win32` directory with the build products. Replace `mg - mgba/switch - mgba/ubuntu:xenial - mgba/ubuntu:bionic -- mgba/ubuntu:cosmic -- mgba/ubuntu:disco -- mgba/ubuntu:eoan - mgba/ubuntu:focal +- mgba/ubuntu:groovy - mgba/vita - mgba/wii - mgba/windows:w32