diff --git a/.gitignore b/.gitignore index 20c19fc4154..9bc108b766b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /build /build-* /.vs +/.vscode *.a *.dylib diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 3475c393dfa..51573bdbee8 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -11,6 +11,7 @@ CXX_GUARD_START #include +#include #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 #include #endif @@ -160,6 +161,9 @@ struct mCore { void (*startVideoLog)(struct mCore*, struct mVideoLogContext*); void (*endVideoLog)(struct mCore*); #endif + + size_t (*extDataSerialize)(struct mCore*, enum mStateExtdataTag tag, void** sram); + bool (*extDataDeserialize)(struct mCore*, enum mStateExtdataTag tag, const void* sram, size_t size); }; #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 diff --git a/include/mgba/core/serialize.h b/include/mgba/core/serialize.h index 968f3f16f05..2381d6b3878 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_GBA_EXTENSIONS = 0x80, EXTDATA_META_TIME = 0x101, EXTDATA_MAX }; @@ -25,6 +26,7 @@ enum mStateExtdataTag { #define SAVESTATE_CHEATS 4 #define SAVESTATE_RTC 8 #define SAVESTATE_METADATA 16 +#define SAVESTATE_GBA_EXTENSIONS 32 struct mStateExtdataItem { int32_t size; diff --git a/include/mgba/internal/gba/extra/extensions.h b/include/mgba/internal/gba/extra/extensions.h new file mode 100644 index 00000000000..ddf87113028 --- /dev/null +++ b/include/mgba/internal/gba/extra/extensions.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2013-2021 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_EXTENSIONS_H +#define GBA_EXTENSIONS_H + +#include + +CXX_GUARD_START + +#include +#include + +#include + +#include + +enum GBA_EXTENSIONS_IDS { + GBAEX_ID_EXTRA_RAM = 0, + GBAEX_EXTENSIONS_COUNT +}; + +#define REG_HWEX_VERSION_VALUE GBAEX_EXTENSIONS_COUNT +#define GBAEX_IO_SIZE (REG_HWEX_END - REG_HWEX0_ENABLE) + +struct GBAExtensions { + bool globalEnabled; + bool extensionsEnabled[GBAEX_EXTENSIONS_COUNT]; + bool userGlobalEnabled; + bool userExtensionsEnabled[GBAEX_EXTENSIONS_COUNT]; + + // IO: + uint16_t* io; + + // Other data + uint8_t* extraRam; + uint32_t extraRamSize; + uint32_t extraRamRealSize; +}; + +struct GBAExtensionsStateBlockHeader { + uint32_t id; + uint32_t offset; + uint32_t size; +}; + +struct GBAExtensionsState { + uint32_t version; + uint32_t extensionsBlockCount; + + struct GBAExtensionsStateBlockHeader ioBlockHeader; + // More blocks can come after the IO one +}; + +struct GBA; +void GBAExtensionsInit(struct GBAExtensions* extensions); +void GBAExtensionsReset(struct GBAExtensions* extensions); +void GBAExtensionsDestroy(struct GBAExtensions* extensions); +uint16_t GBAExtensionsIORead(struct GBA* gba, uint32_t address); +uint32_t GBAExtensionsIORead32(struct GBA* gba, uint32_t address); +void GBAExtensionsIOWrite8(struct GBA* gba, uint32_t address, uint8_t value); +void GBAExtensionsIOWrite(struct GBA* gba, uint32_t address, uint16_t value); +size_t GBAExtensionsSerialize(struct GBA* gba, void** sram); +bool GBAExtensionsDeserialize(struct GBA* gba, const struct GBAExtensionsState* state, size_t size); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index 6e8fe499056..2ae5fedc4ef 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/mgba/internal/gba/gba.h @@ -18,6 +18,7 @@ CXX_GUARD_START #include #include #include +#include #define GBA_ARM7TDMI_FREQUENCY 0x1000000U @@ -70,6 +71,7 @@ struct GBA { struct GBAVideo video; struct GBAAudio audio; struct GBASIO sio; + struct GBAExtensions extensions; struct mCoreSync* sync; struct mTiming timing; diff --git a/include/mgba/internal/gba/io.h b/include/mgba/internal/gba/io.h index 9875061f358..dce60108143 100644 --- a/include/mgba/internal/gba/io.h +++ b/include/mgba/internal/gba/io.h @@ -157,6 +157,27 @@ enum GBAIORegisters { REG_DEBUG_STRING = 0xFFF600, REG_DEBUG_FLAGS = 0xFFF700, REG_DEBUG_ENABLE = 0xFFF780, + + // Extensions + REG_HWEX_ENABLE = 0x400A00, + REG_HWEX_VERSION = 0x400A02, + + // Extra RAM + REG_HWEX0_ENABLE = 0x400A04, + REG_HWEX0_CNT = 0x400A06, + REG_HWEX0_RET_CODE = 0x400A08, + REG_HWEX0_UNUSED = 0x400A0A, + REG_HWEX0_P0_LO = 0x400A0C, // command + REG_HWEX0_P0_HI = 0x400A0E, + REG_HWEX0_P1_LO = 0x400A10, // index + REG_HWEX0_P1_HI = 0x400A12, + REG_HWEX0_P2_LO = 0x400A14, // data pointer + REG_HWEX0_P2_HI = 0x400A16, + REG_HWEX0_P3_LO = 0x400A18, // size + REG_HWEX0_P3_HI = 0x400A1A, + + REG_HWEX_END = 0x400A1C, + }; mLOG_DECLARE_CATEGORY(GBA_IO); diff --git a/src/core/serialize.c b/src/core/serialize.c index 446152e0414..1816e5ff6f0 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -421,6 +421,18 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { mStateExtdataPut(&extdata, EXTDATA_RTC, &item); } } + if ((true || flags & SAVESTATE_GBA_EXTENSIONS) && core->extDataSerialize) { + void* sram = NULL; + size_t size = core->extDataSerialize(core, EXTDATA_GBA_EXTENSIONS, &sram); + if (size) { + struct mStateExtdataItem item = { + .size = size, + .data = sram, + .clean = free + }; + mStateExtdataPut(&extdata, EXTDATA_GBA_EXTENSIONS, &item); + } + } #ifdef USE_PNG if (!(flags & SAVESTATE_SCREENSHOT)) { #else @@ -537,6 +549,10 @@ bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { core->rtc.d.deserialize(&core->rtc.d, &item); } } + if ((true || flags & SAVESTATE_GBA_EXTENSIONS) && core->extDataDeserialize && mStateExtdataGet(&extdata, EXTDATA_GBA_EXTENSIONS, &item)) { + mLOG(SAVESTATE, INFO, "Loading GBA extensions"); + core->extDataDeserialize(core, EXTDATA_GBA_EXTENSIONS, item.data, item.size); + } mStateExtdataDeinit(&extdata); return success; } diff --git a/src/feature/commandline.c b/src/feature/commandline.c index 2d3f3e357f3..0e3a7182cbe 100644 --- a/src/feature/commandline.c +++ b/src/feature/commandline.c @@ -248,6 +248,15 @@ void usage(const char* arg0, const char* extraOptions) { puts(" -p, --patch FILE Apply a specified patch file when running"); puts(" -s, --frameskip N Skip every N frames"); puts(" --version Print version and exit"); + + puts("\nHardware extensions options:"); + puts(" --hw-extensions Enable hardware extensions"); + puts(" --no-hw-extensions Disable hardware extensions"); + puts(" --hwex-all Enable all hardware extensions"); + puts(" --hwex-none Disable all hardware extensions"); + puts(" --hwex-more-ram Enable hardware extension \"More RAM\""); + puts(" --no-hwex-more-ram Disable hardware extension \"More RAM\""); + if (extraOptions) { puts(extraOptions); } diff --git a/src/gb/core.c b/src/gb/core.c index 51599eb8283..c8c4231d614 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -1141,6 +1141,8 @@ struct mCore* GBCoreCreate(void) { core->startVideoLog = _GBCoreStartVideoLog; core->endVideoLog = _GBCoreEndVideoLog; #endif + core->extDataSerialize = NULL; + core->extDataDeserialize = NULL; return core; } diff --git a/src/gba/CMakeLists.txt b/src/gba/CMakeLists.txt index 4bf354f3a37..9331244766a 100644 --- a/src/gba/CMakeLists.txt +++ b/src/gba/CMakeLists.txt @@ -13,6 +13,7 @@ set(SOURCE_FILES cheats/parv3.c core.c dma.c + extra/extensions.c gba.c hle-bios.c input.c diff --git a/src/gba/core.c b/src/gba/core.c index 88171fbeb89..d291701fe59 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -307,6 +307,13 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con #endif mCoreConfigCopyValue(&core->config, config, "hwaccelVideo"); mCoreConfigCopyValue(&core->config, config, "videoScale"); + + if (mCoreConfigGetIntValue(config, "gba.extensions", &fakeBool)) { + gba->extensions.userGlobalEnabled = fakeBool; + } + if (mCoreConfigGetIntValue(config, "gba.ext.extraRam", &fakeBool)) { + gba->extensions.userExtensionsEnabled[GBAEX_ID_EXTRA_RAM] = fakeBool; + } } static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, const struct mCoreConfig* config) { @@ -399,6 +406,19 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c GBAVideoAssociateRenderer(&gba->video, renderer); } } + + if (strcmp("gba.extensions", option) == 0) { + if (mCoreConfigGetIntValue(config, "gba.extensions", &fakeBool)) { + gba->extensions.userGlobalEnabled = fakeBool; + } + return; + } + if (strcmp("gba.ext.extraRam", option) == 0) { + if (mCoreConfigGetIntValue(config, "gba.ext.extraRam", &fakeBool)) { + gba->extensions.userExtensionsEnabled[GBAEX_ID_EXTRA_RAM] = fakeBool; + } + return; + } } static void _GBACoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { @@ -1152,6 +1172,25 @@ static void _GBACoreEndVideoLog(struct mCore* core) { } #endif +static size_t _GBAExtDataSerialize(struct mCore* core, enum mStateExtdataTag tag, void** sram) { + size_t size; + if (tag == EXTDATA_GBA_EXTENSIONS) { + return GBAExtensionsSerialize(core->board, sram); + } else { + size = 0; + *sram = NULL; + } + + return size; +} + +static bool _GBAExtDataDeserialize(struct mCore* core, enum mStateExtdataTag tag, const void* sram, size_t size) { + if (tag == EXTDATA_GBA_EXTENSIONS) { + return GBAExtensionsDeserialize(core->board, sram, size); + } + return false; +} + struct mCore* GBACoreCreate(void) { struct GBACore* gbacore = malloc(sizeof(*gbacore)); struct mCore* core = &gbacore->d; @@ -1236,6 +1275,8 @@ struct mCore* GBACoreCreate(void) { core->startVideoLog = _GBACoreStartVideoLog; core->endVideoLog = _GBACoreEndVideoLog; #endif + core->extDataSerialize = _GBAExtDataSerialize; + core->extDataDeserialize = _GBAExtDataDeserialize; return core; } diff --git a/src/gba/extra/extensions.c b/src/gba/extra/extensions.c new file mode 100644 index 00000000000..d55f5b6647d --- /dev/null +++ b/src/gba/extra/extensions.c @@ -0,0 +1,510 @@ +/* Copyright (c) 2013-2021 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 ENABLE_CODE 0xC0DE +#define ENABLED_VALUE 0x1DEA + +enum HWEX_RETURN_CODES { + HWEX_RET_OK = 0, + HWEX_RET_WAIT = 1, + + // Errors + HWEX_RET_ERR_UNKNOWN = 0x100, + HWEX_RET_ERR_BAD_ADDRESS = 0x101, + HWEX_RET_ERR_INVALID_PARAMETERS = 0x102, + HWEX_RET_ERR_WRITE_TO_ROM = 0x103, + HWEX_RET_ERR_ABORTED = 0x104, + HWEX_RET_ERR_DISABLED = 0x105, + HWEX_RET_ERR_DISABLED_BY_USER = 0x106, + HWEX_RET_ERR_NOT_INITIALIZED = 0x107, + HWEX_RET_ERR_ALREADY_INITIALIZED = 0x108, +}; + + +#define GBAEX_EXTRA_RAM_MAX_SIZE 0x4000000 // 64 MB + +#define SIMPLIFY_HWEX_REG_ADDRESS(address) ((address - REG_HWEX0_ENABLE) >> 1) + +const uint16_t extensionIdByRegister[] = { + // Extra RAM + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_ENABLE)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_CNT)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_RET_CODE)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_UNUSED)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P0_LO)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P0_HI)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P1_LO)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P1_HI)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P2_LO)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P2_HI)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P3_LO)] = GBAEX_ID_EXTRA_RAM, + [SIMPLIFY_HWEX_REG_ADDRESS(REG_HWEX0_P3_HI)] = GBAEX_ID_EXTRA_RAM, +}; + +static uint16_t _getExtensionIdFromAddress(uint32_t address) { + return extensionIdByRegister[SIMPLIFY_HWEX_REG_ADDRESS(address)]; +} + +#undef SIMPLIFY_HWEX_REG_ADDRESS + +// CNT flags +// Writable +#define HWEX_CNT_FLAG_CALL 1 +#define HWEX_CNT_ALL_WRITABLE (HWEX_CNT_FLAG_CALL) +// Read only +#define HWEX_CNT_FLAG_PROCESSING (1 << 15) + +struct GBAExtensionHandlers { + uint16_t (*onCall)(struct GBA* gba); + bool (*onAbort)(struct GBA* gba); + void (*onDisable)(struct GBAExtensions* extensions); +}; + +static uint16_t _GBAExtensionExtraRAM(struct GBA* gba); +static void _GBAExtensionExtraRAMDisable(struct GBAExtensions* extensions); + +static const struct GBAExtensionHandlers extensionHandlers[] = { + [GBAEX_ID_EXTRA_RAM] = { .onCall = _GBAExtensionExtraRAM, .onAbort = NULL, .onDisable = _GBAExtensionExtraRAMDisable } +}; + +void GBAExtensionsInit(struct GBAExtensions* extensions) { + extensions->globalEnabled = false; + memset(extensions->extensionsEnabled, 0, sizeof(extensions->extensionsEnabled)); + + extensions->userGlobalEnabled = false; + memset(extensions->userExtensionsEnabled, 0, sizeof(extensions->userExtensionsEnabled)); + + extensions->io = NULL; + + extensions->extraRam = NULL; +} + +static void _GBAExtensionsFreeExtensionsData(struct GBAExtensions* extensions) { + for (uint32_t i = 0; i < GBAEX_EXTENSIONS_COUNT; i++) { + if (extensionHandlers[i].onDisable && extensions->extensionsEnabled[i]) { + extensionHandlers[i].onDisable(extensions); + } + } +} + +static void _GBAExtensionsFreeIO(struct GBAExtensions* extensions) { + if (extensions->io) { + free(extensions->io); + extensions->io = NULL; + } +} + +void GBAExtensionsReset(struct GBAExtensions* extensions) { + _GBAExtensionsFreeExtensionsData(extensions); + + extensions->globalEnabled = false; + memset(extensions->extensionsEnabled, 0, sizeof(extensions->extensionsEnabled)); + + _GBAExtensionsFreeIO(extensions); +} + +void GBAExtensionsDestroy(struct GBAExtensions* extensions) { + _GBAExtensionsFreeExtensionsData(extensions); + + extensions->globalEnabled = false; + memset(extensions->extensionsEnabled, 0, sizeof(extensions->extensionsEnabled)); + + _GBAExtensionsFreeIO(extensions); +} + +static uint16_t* _getHwExIOPointer(struct GBA* gba, uint32_t address) { + return gba->extensions.io + (address - REG_HWEX0_ENABLE) / 2; +} + +static bool GBAExtensionsIsExtensionEnabled(struct GBA* gba, uint32_t extensionId) { + return gba->extensions.extensionsEnabled[extensionId] && gba->extensions.userExtensionsEnabled[extensionId]; +} + +static uint16_t _GBAExtensionsIORead(struct GBA* gba, uint32_t address) { + switch (address) { + case REG_HWEX_ENABLE: + return ENABLED_VALUE; + case REG_HWEX_VERSION: + return REG_HWEX_VERSION_VALUE; + + default: + return *_getHwExIOPointer(gba, address); + } +} + +uint16_t GBAExtensionsIORead(struct GBA* gba, uint32_t address) { + if (gba->extensions.userGlobalEnabled && gba->extensions.globalEnabled) { + return _GBAExtensionsIORead(gba, address); + } + mLOG(GBA_IO, GAME_ERROR, "Read from unused I/O register: %03X", address); + return GBALoadBad(gba->cpu); +} + +static uint32_t _GBAExtensionsIORead32(struct GBA* gba, uint32_t address) { + return (_GBAExtensionsIORead(gba, address + 2) << 16) | _GBAExtensionsIORead(gba, address); +}; + +static void _GBAExtensionsIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { + *_getHwExIOPointer(gba, address) = value; +} + +static void GBAExtensionsHandleCntWrite(struct GBA* gba, uint32_t cntAddress, uint32_t value) { + uint16_t* cnt = _getHwExIOPointer(gba, cntAddress); + uint16_t* returnCode = cnt + 1; + uint16_t currentValue = *cnt; + uint32_t extensionId = _getExtensionIdFromAddress(cntAddress); + + if (!GBAExtensionsIsExtensionEnabled(gba, extensionId)) { + *returnCode = HWEX_RET_ERR_DISABLED; + return; + } + + const struct GBAExtensionHandlers* handlers = extensionHandlers + extensionId; + value &= HWEX_CNT_ALL_WRITABLE; // delete non-writable flags + + if (value != (currentValue & HWEX_CNT_ALL_WRITABLE)) { // check if value changed + if (currentValue & HWEX_CNT_FLAG_PROCESSING) { // check if extension is running + if (!(currentValue & HWEX_CNT_FLAG_CALL)) { + // call flag set to 0 + // abort + if (handlers->onAbort) { + handlers->onAbort(gba); + *cnt = currentValue; + *returnCode = HWEX_RET_ERR_ABORTED; + } + } + } else { + if (value & HWEX_CNT_FLAG_CALL) { // check call the extension + *returnCode = handlers->onCall(gba); + if (*returnCode == HWEX_RET_OK || *returnCode >= HWEX_RET_ERR_UNKNOWN) { // execution finished + *cnt = 0; + } else { // processing + *cnt = currentValue | HWEX_CNT_FLAG_CALL | HWEX_CNT_FLAG_PROCESSING; + } + } + } + } +} + +void GBAExtensionsIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { + if ((!gba->extensions.userGlobalEnabled || !gba->extensions.globalEnabled) && address != REG_HWEX_ENABLE) { + mLOG(GBA_IO, GAME_ERROR, "Write to unused I/O register: %03X", address); + return; + } + switch (address) { + case REG_HWEX_ENABLE: { + gba->extensions.globalEnabled = value == ENABLE_CODE; + if (gba->extensions.globalEnabled && !gba->extensions.io) { + gba->extensions.io = malloc(GBAEX_IO_SIZE); + memset(gba->extensions.io, 0, GBAEX_IO_SIZE); + } else if (!gba->extensions.globalEnabled && gba->extensions.io) { + _GBAExtensionsFreeExtensionsData(&gba->extensions); + _GBAExtensionsFreeIO(&gba->extensions); + } + break; + } + // Enable flags + case REG_HWEX0_ENABLE: { + uint32_t extensionId = _getExtensionIdFromAddress(address); + bool enabled = value == ENABLE_CODE; + bool wasEnabled = gba->extensions.extensionsEnabled[extensionId]; + gba->extensions.extensionsEnabled[extensionId] = enabled; + _GBAExtensionsIOWrite(gba, address, enabled ? ENABLED_VALUE : 0); + if (wasEnabled && !enabled && extensionHandlers[extensionId].onDisable) { + extensionHandlers[extensionId].onDisable(&gba->extensions); + } + break; + } + // Return codes + case REG_HWEX0_RET_CODE: + mLOG(GBA_IO, GAME_ERROR, "Write to read-only hardware extensions I/O register: %06X", address); + break; + + // CNT + case REG_HWEX0_CNT: + GBAExtensionsHandleCntWrite(gba, address, value); + break; + + // Parameters + case REG_HWEX0_P0_LO: + case REG_HWEX0_P0_HI: + case REG_HWEX0_P1_LO: + case REG_HWEX0_P1_HI: + case REG_HWEX0_P2_LO: + case REG_HWEX0_P2_HI: + case REG_HWEX0_P3_LO: + case REG_HWEX0_P3_HI: + if (!(_GBAExtensionsIORead(gba, REG_HWEX0_CNT) & HWEX_CNT_FLAG_PROCESSING)) { + _GBAExtensionsIOWrite(gba, address, value); + } + break; + default: + mLOG(GBA_IO, GAME_ERROR, "Write non hardware extensions I/O register: %06X", address); + } +} + +void GBAExtensionsIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) { + if (!gba->extensions.userGlobalEnabled || !gba->extensions.globalEnabled) { + mLOG(GBA_IO, GAME_ERROR, "Write to unused I/O register: %03X", address); + return; + } + uint32_t address16 = address & 0xFFFFFE; + uint16_t* reg = _getHwExIOPointer(gba, address16); + GBAExtensionsIOWrite(gba, address16, address & 1 ? (value << 8) | (*reg & 0xFF) : (value | (*reg & 0xFF00))); +} + +#define IO_BLOCK_HEADER_ID 0xFFFFFFFF + +size_t GBAExtensionsSerialize(struct GBA* gba, void** sram) { + if (!gba->extensions.io) { + // Disabled + return 0; + } + + struct GBAExtensionsStateBlockHeader tmpHeaders[GBAEX_EXTENSIONS_COUNT + 1]; + void* blocksDataSrc[GBAEX_EXTENSIONS_COUNT + 1]; + uint32_t blocksCount = 1; + size_t blocksDataSize = 0; + + // Serialize IO + tmpHeaders[0].id = IO_BLOCK_HEADER_ID; + tmpHeaders[0].size = GBAEX_IO_SIZE; + blocksDataSrc[0] = gba->extensions.io; + blocksDataSize += GBAEX_IO_SIZE; + + // Serialize extra RAM + if (gba->extensions.extraRam) { + tmpHeaders[blocksCount].id = GBAEX_ID_EXTRA_RAM; + tmpHeaders[blocksCount].size = gba->extensions.extraRamSize; + blocksDataSrc[blocksCount++] = gba->extensions.extraRam; + blocksDataSize += gba->extensions.extraRamSize; + } + + // Alloc state + uint32_t offset = sizeof(struct GBAExtensionsState) + sizeof(struct GBAExtensionsStateBlockHeader) * (blocksCount - 1); + size_t stateSize = offset + blocksDataSize; + struct GBAExtensionsState* state = malloc(stateSize); + struct GBAExtensionsStateBlockHeader* blockHeaders = &state->ioBlockHeader; + + // Copy data blocks + for (size_t i = 0; i < blocksCount; i++) { + blockHeaders[i].id = tmpHeaders[i].id; + blockHeaders[i].size = tmpHeaders[i].size; + blockHeaders[i].offset = offset; + memcpy(((uint8_t*) state) + offset, blocksDataSrc[i], tmpHeaders[i].size); + offset += tmpHeaders[i].size; + } + + state->version = REG_HWEX_VERSION_VALUE; + state->extensionsBlockCount = blocksCount; + + *sram = state; + return stateSize; +} + +bool GBAExtensionsDeserialize(struct GBA* gba, const struct GBAExtensionsState* state, size_t size) { + _GBAExtensionsFreeExtensionsData(&gba->extensions); + memset(gba->extensions.extensionsEnabled, 0, sizeof(gba->extensions.extensionsEnabled)); + + if (!state || state->extensionsBlockCount == 0 || state->ioBlockHeader.id != IO_BLOCK_HEADER_ID + || state->ioBlockHeader.size == 0) { + // No data, extensions disabled + _GBAExtensionsFreeIO(&gba->extensions); + gba->extensions.globalEnabled = false; + return false; + } + + // Deserialize IO + if (!gba->extensions.io) { + gba->extensions.io = malloc(GBAEX_IO_SIZE); + memset(gba->extensions.io, 0, GBAEX_IO_SIZE); + gba->extensions.globalEnabled = true; + } + uint8_t* blocksData = (uint8_t*) state; + size_t ioSerializedSize = GBAEX_IO_SIZE; + if (state->ioBlockHeader.size < GBAEX_IO_SIZE) { + memset(((uint8_t*) gba->extensions.io) + state->ioBlockHeader.size, 0, GBAEX_IO_SIZE - state->ioBlockHeader.size); + ioSerializedSize = state->ioBlockHeader.size; + } + memcpy(gba->extensions.io, blocksData + state->ioBlockHeader.offset, ioSerializedSize); + + // Deserialize extensions + const struct GBAExtensionsStateBlockHeader* extBlocksHeaders = (&state->ioBlockHeader) + 1; + for (size_t i = 0; i < (state->extensionsBlockCount - 1); i++) { + if (extBlocksHeaders[i].size > 0 && (extBlocksHeaders[i].offset + extBlocksHeaders[i].size) <= size) { + switch(extBlocksHeaders[i].id) { + case GBAEX_ID_EXTRA_RAM: { + size_t extraRAMSize = extBlocksHeaders[i].size <= GBAEX_EXTRA_RAM_MAX_SIZE ? extBlocksHeaders[i].size : GBAEX_EXTRA_RAM_MAX_SIZE; + gba->extensions.extraRam = malloc(extraRAMSize); + memcpy(gba->extensions.extraRam, blocksData + extBlocksHeaders[i].offset, extraRAMSize); + gba->extensions.extraRamSize = extraRAMSize; + gba->extensions.extraRamRealSize = extraRAMSize; + gba->extensions.extensionsEnabled[GBAEX_ID_EXTRA_RAM] = true; + break; + } + } + } + } + + return true; +} + +// Extension handlers +static void* GBAGetMemoryPointer(struct GBA* gba, uint32_t address, uint32_t* memoryMaxSize, bool* isRom) { + uint8_t* pointer = NULL; + *isRom = false; + + switch (address >> 24) { + case REGION_WORKING_RAM: + if ((address & 0xFFFFFF) < SIZE_WORKING_RAM) { + pointer = (uint8_t*) gba->memory.wram; + pointer += address & 0xFFFFFF; + *memoryMaxSize = SIZE_WORKING_RAM - (address & 0xFFFFFF); + } + break; + case REGION_WORKING_IRAM: + if ((address & 0xFFFFFF) < SIZE_WORKING_IRAM) { + pointer = (uint8_t*) gba->memory.iwram; + pointer += address & 0xFFFFFF; + *memoryMaxSize = SIZE_WORKING_IRAM - (address & 0xFFFFFF); + } + break; + case REGION_CART0: + case REGION_CART0_EX: + if ((address & 0x1FFFFFF) < gba->memory.romSize) { + *isRom = true; + pointer = (uint8_t*) gba->memory.rom; + pointer += address & 0x1FFFFFF; + *memoryMaxSize = gba->memory.romSize - (address & 0x1FFFFFF); + } + } + + return pointer; +} + +enum GBAEX_EXTRA_RAM_COMMANDS { + GBAEX_EXTRA_RAM_CMD_WRITE = 0, + GBAEX_EXTRA_RAM_CMD_READ = 1, + GBAEX_EXTRA_RAM_CMD_SWAP = 2, + GBAEX_EXTRA_RAM_CMD_INIT = 3, + GBAEX_EXTRA_RAM_CMD_RESIZE = 4, + GBAEX_EXTRA_RAM_CMD_DESTROY = 5, +}; + +static uint16_t _GBAExtensionExtraRAM(struct GBA* gba) { + uint32_t command = _GBAExtensionsIORead32(gba, REG_HWEX0_P0_LO); + uint32_t index = _GBAExtensionsIORead32(gba, REG_HWEX0_P1_LO); + uint32_t dataPointer = _GBAExtensionsIORead32(gba, REG_HWEX0_P2_LO); + uint32_t dataSize = _GBAExtensionsIORead32(gba, REG_HWEX0_P3_LO); + void* data; + bool isRom; + uint32_t memoryMaxSize; + + // Validate parameters + switch (command) { + case GBAEX_EXTRA_RAM_CMD_WRITE: + case GBAEX_EXTRA_RAM_CMD_READ: + case GBAEX_EXTRA_RAM_CMD_SWAP: + if (!gba->extensions.extraRam) { + return HWEX_RET_ERR_NOT_INITIALIZED; + } + + // Check if the pointer is valid + data = GBAGetMemoryPointer(gba, dataPointer, &memoryMaxSize, &isRom); + if (data == NULL) { + return HWEX_RET_ERR_BAD_ADDRESS; + } + + // Check if index and size are valid + if (index >= gba->extensions.extraRamSize || (index + dataSize) >= gba->extensions.extraRamSize || dataSize >= memoryMaxSize) { + return HWEX_RET_ERR_INVALID_PARAMETERS; + } + break; + case GBAEX_EXTRA_RAM_CMD_INIT: + if (gba->extensions.extraRam) { + return HWEX_RET_ERR_ALREADY_INITIALIZED; + } + // Check if size is valid + if (index == 0 || index > GBAEX_EXTRA_RAM_MAX_SIZE) { + return HWEX_RET_ERR_INVALID_PARAMETERS; + } + break; + case GBAEX_EXTRA_RAM_CMD_RESIZE: + if (!gba->extensions.extraRam) { + return HWEX_RET_ERR_NOT_INITIALIZED; + } + // Check if size is valid + if (index == 0 || index > GBAEX_EXTRA_RAM_MAX_SIZE) { + return HWEX_RET_ERR_INVALID_PARAMETERS; + } + break; + case GBAEX_EXTRA_RAM_CMD_DESTROY: + if (!gba->extensions.extraRam) { + return HWEX_RET_ERR_NOT_INITIALIZED; + } + break; + default: + // invalid command + return HWEX_RET_ERR_INVALID_PARAMETERS; + } + + // Execute command + switch (command) { + case GBAEX_EXTRA_RAM_CMD_WRITE: + memcpy(((uint8_t*)gba->extensions.extraRam) + index, data, dataSize); + break; + case GBAEX_EXTRA_RAM_CMD_READ: + if (isRom) { + return HWEX_RET_ERR_WRITE_TO_ROM; + } + memcpy(data, ((uint8_t*)gba->extensions.extraRam) + index, dataSize); + break; + case GBAEX_EXTRA_RAM_CMD_SWAP: + if (isRom) { + return HWEX_RET_ERR_WRITE_TO_ROM; + } + // TODO: make this more efficient + uint8_t* data1 = data; + uint8_t* data2 = (uint8_t*) gba->extensions.extraRam; + data2 += index; + for (uint32_t i = 0; i < dataSize; i++) { + uint8_t aux = data1[i]; + data1[i] = data2[i]; + data2[i] = aux; + } + break; + case GBAEX_EXTRA_RAM_CMD_INIT: + gba->extensions.extraRam = malloc(index); + gba->extensions.extraRamSize = index; + gba->extensions.extraRamRealSize = index; + break; + case GBAEX_EXTRA_RAM_CMD_RESIZE: + if (index > gba->extensions.extraRamRealSize) { + uint8_t* newExtraRam = malloc(index); + memcpy(newExtraRam, gba->extensions.extraRam, gba->extensions.extraRamSize); + free(gba->extensions.extraRam); + gba->extensions.extraRam = newExtraRam; + gba->extensions.extraRamRealSize = index; + } + gba->extensions.extraRamSize = index; + break; + case GBAEX_EXTRA_RAM_CMD_DESTROY: + free(gba->extensions.extraRam); + break; + } + + return HWEX_RET_OK; +} + +static void _GBAExtensionExtraRAMDisable(struct GBAExtensions* extensions) { + if (extensions->extraRam) { + free(extensions->extraRam); + extensions->extraRam = NULL; + } +} \ No newline at end of file diff --git a/src/gba/gba.c b/src/gba/gba.c index b73fe9d8668..f4e917dc3e2 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -124,6 +124,8 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { gba->irqEvent.callback = _triggerIRQ; gba->irqEvent.context = gba; gba->irqEvent.priority = 0; + + GBAExtensionsInit(&gba->extensions); } void GBAUnloadROM(struct GBA* gba) { @@ -171,6 +173,7 @@ void GBADestroy(struct GBA* gba) { GBAVideoDeinit(&gba->video); GBAAudioDeinit(&gba->audio); GBASIODeinit(&gba->sio); + GBAExtensionsDestroy(&gba->extensions); mTimingDeinit(&gba->timing); mCoreCallbacksListDeinit(&gba->coreCallbacks); } @@ -214,6 +217,7 @@ void GBAReset(struct ARMCore* cpu) { GBAAudioReset(&gba->audio); GBAIOInit(gba); GBATimerInit(gba); + GBAExtensionsReset(&gba->extensions); GBASIOReset(&gba->sio); diff --git a/src/gba/io.c b/src/gba/io.c index cc39e119263..a669eb261a8 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -9,6 +9,7 @@ #include #include #include +#include mLOG_DEFINE_CATEGORY(GBA_IO, "GBA I/O", "gba.io"); @@ -585,6 +586,10 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { STORE_16LE(value, address - REG_DEBUG_STRING, gba->debugString); return; } + if (address >= REG_HWEX_ENABLE && address < REG_HWEX_END) { + GBAExtensionsIOWrite(gba, address, value); + return; + } mLOG(GBA_IO, STUB, "Stub I/O register write: %03X", address); if (address >= REG_MAX) { mLOG(GBA_IO, GAME_ERROR, "Write to unused I/O register: %03X", address); @@ -613,6 +618,10 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) { gba->debugString[address - REG_DEBUG_STRING] = value; return; } + if (address >= REG_HWEX_ENABLE && address < REG_HWEX_END) { + GBAExtensionsIOWrite8(gba, address, value); + return; + } if (address > SIZE_IO) { return; } @@ -937,6 +946,9 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { } // Fall through default: + if (address >= REG_HWEX_ENABLE && address < REG_HWEX_END) { + return GBAExtensionsIORead(gba, address); + } mLOG(GBA_IO, GAME_ERROR, "Read from unused I/O register: %03X", address); return GBALoadBad(gba->cpu); } diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 5de04fa9d3c..4a15af68b14 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -336,6 +336,56 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC shortcutView->setController(shortcutController); shortcutView->setInputController(inputController); addPage(tr("Shortcuts"), shortcutView, Page::SHORTCUTS); + + // GBA extensions + m_gbaExtCheckboxesCounter = 0; + m_gbaExtCheckboxes[m_gbaExtCheckboxesCounter++] = m_ui.gbaExtExtraRamCheckBox; + + m_enabledExtensionsCounter = 0; + for (size_t i = 0; i < m_gbaExtCheckboxesCounter; i++) { + if (m_gbaExtCheckboxes[i] && Qt::Checked == m_gbaExtCheckboxes[i]->checkState()) { + m_enabledExtensionsCounter++; + } + } + if (m_enabledExtensionsCounter == m_gbaExtCheckboxesCounter) { + m_ui.gbaExtAllCheckBox->setCheckState(Qt::Checked); + } + + // connect "All" checkbox + connect(m_ui.gbaExtAllCheckBox, &QCheckBox::stateChanged, this, [this](int state) { + for (size_t i = 0; i < m_gbaExtCheckboxesCounter; i++) { + m_enabledExtensionsCounter = 0; + if (m_gbaExtCheckboxes[i] && state != m_gbaExtCheckboxes[i]->checkState()) { + m_gbaExtCheckboxes[i]->blockSignals(true); + m_gbaExtCheckboxes[i]->setCheckState((Qt::CheckState) state); + m_gbaExtCheckboxes[i]->blockSignals(false); + m_enabledExtensionsCounter++; + } + } + }); + for (size_t i = 0; i < m_gbaExtCheckboxesCounter; i++) { + if (m_gbaExtCheckboxes[i]) { + connect(m_gbaExtCheckboxes[i], &QCheckBox::stateChanged, this, [this](int state) { + // update "All" checkbox + m_enabledExtensionsCounter += (state == Qt::Checked) ? 1 : -1; + switch (m_ui.gbaExtAllCheckBox->checkState()) { + case Qt::Checked: + if (m_enabledExtensionsCounter < m_gbaExtCheckboxesCounter) { + m_ui.gbaExtAllCheckBox->blockSignals(true); + m_ui.gbaExtAllCheckBox->setCheckState(Qt::Unchecked); + m_ui.gbaExtAllCheckBox->blockSignals(false); + } + break; + default: + if (m_enabledExtensionsCounter == m_gbaExtCheckboxesCounter) { + m_ui.gbaExtAllCheckBox->blockSignals(true); + m_ui.gbaExtAllCheckBox->setCheckState(Qt::Checked); + m_ui.gbaExtAllCheckBox->blockSignals(false); + } + } + }); + } + } } SettingsView::~SettingsView() { @@ -601,6 +651,9 @@ void SettingsView::updateConfig() { saveSetting("gb.colors", gbColors); #endif + saveSetting("gba.extensions", m_ui.gbaExtensionsCheckBox); + saveSetting("gba.ext.extraRam", m_ui.gbaExtExtraRamCheckBox); + m_controller->write(); emit pathsChanged(); @@ -779,6 +832,9 @@ void SettingsView::reloadConfig() { } else { m_ui.multiplayerAudioAll->setChecked(true); } + + loadSetting("gba.extensions", m_ui.gbaExtensionsCheckBox, false); + loadSetting("gba.ext.extraRam", m_ui.gbaExtExtraRamCheckBox, false); } void SettingsView::addPage(const QString& name, QWidget* view, Page index) { diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 880db5f14c9..30b84634b74 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -17,6 +17,8 @@ #include #endif +#include + #include "ui_SettingsView.h" namespace QGBA { @@ -43,6 +45,7 @@ Q_OBJECT CONTROLLERS, SHORTCUTS, SHADERS, + HARDWARE_EXTENSIONS, }; SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, LogController* logController, QWidget* parent = nullptr); @@ -78,6 +81,10 @@ private slots: ShaderSelector* m_shader = nullptr; LogConfigModel m_logModel; + unsigned int m_enabledExtensionsCounter; + QCheckBox* m_gbaExtCheckboxes[GBAEX_EXTENSIONS_COUNT]; + unsigned int m_gbaExtCheckboxesCounter; + #ifdef M_CORE_GB uint32_t m_gbColors[12]{}; ColorPicker m_colorPickers[12]; diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 7589e06f3a3..6562ebed0bd 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -1149,6 +1149,73 @@ + + + + GBA extensions + + + + QFormLayout::ExpandingFieldsGrow + + + + + General: + + + + + + + Enable + + + + + + + Qt::Horizontal + + + + + + + Individual: + + + + + + + All + + + + + + + Extra RAM + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +