diff --git a/CHANGES b/CHANGES index a5c80f5c5..47adeff9a 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Features: - Map viewer - Automatic cheat loading and saving - GameShark and Action Replay button support + - AGBPrint support Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index 259813b67..abb6525ca 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -69,7 +69,9 @@ enum { SIZE_CART_FLASH512 = 0x00010000, SIZE_CART_FLASH1M = 0x00020000, SIZE_CART_EEPROM = 0x00002000, - SIZE_CART_EEPROM512 = 0x00000200 + SIZE_CART_EEPROM512 = 0x00000200, + + SIZE_AGB_PRINT = 0x10000 }; enum { @@ -77,8 +79,23 @@ enum { BASE_OFFSET = 24 }; +enum { + AGB_PRINT_BASE = 0x00FD0000, + AGB_PRINT_TOP = 0x00FE0000, + AGB_PRINT_PROTECT = 0x00FE2FFE, + AGB_PRINT_STRUCT = 0x01FE20F8, + AGB_PRINT_FLUSH_ADDR = 0x01FE209C, +}; + mLOG_DECLARE_CATEGORY(GBA_MEM); +struct GBAPrintContext { + uint16_t request; + uint16_t bank; + uint16_t get; + uint16_t put; +}; + struct GBAMemory { uint32_t* bios; uint32_t* wram; @@ -108,6 +125,10 @@ struct GBAMemory { int activeDMA; uint32_t dmaTransferRegister; + uint16_t agbPrint; + struct GBAPrintContext agbPrintCtx; + uint16_t* agbPrintBuffer; + bool mirroring; }; @@ -146,6 +167,8 @@ struct GBASerializedState; void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state); void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state); +void GBAPrintFlush(struct GBA* gba); + CXX_GUARD_END #endif diff --git a/src/gba/bios.c b/src/gba/bios.c index 6d4835bed..debee1e05 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -333,6 +333,12 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { mLOG(GBA_BIOS, DEBUG, "SWI: %02X r0: %08X r1: %08X r2: %08X r3: %08X", immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); + switch (immediate) { + case 0xFA: + GBAPrintFlush(gba); + return; + } + if (gba->memory.fullBios) { ARMRaiseSWI(cpu); return; diff --git a/src/gba/memory.c b/src/gba/memory.c index 3498d722d..079372689 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -22,7 +22,10 @@ mLOG_DEFINE_CATEGORY(GBA_MEM, "GBA Memory", "gba.memory"); static void _pristineCow(struct GBA* gba); -static uint32_t _deadbeef[1] = { 0xE710B710 }; // Illegal instruction on both ARM and Thumb +static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value); +static int16_t _agbPrintLoad(struct GBA* gba, uint32_t address); +static uint8_t _deadbeef[4] = { 0x10, 0xB7, 0x10, 0xE7 }; // Illegal instruction on both ARM and Thumb +static uint8_t _agbPrintFunc[4] = { 0xFA, 0xDF /* swi 0xFF */, 0x70, 0x47 /* bx lr */ }; static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait); @@ -80,6 +83,10 @@ void GBAMemoryInit(struct GBA* gba) { gba->memory.biosPrefetch = 0; gba->memory.mirroring = false; + gba->memory.agbPrint = 0; + memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); + gba->memory.agbPrintBuffer = NULL; + gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); @@ -116,6 +123,12 @@ void GBAMemoryReset(struct GBA* gba) { memset(gba->memory.io, 0, sizeof(gba->memory.io)); + gba->memory.agbPrint = 0; + memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); + if (gba->memory.agbPrintBuffer) { + gba->memory.agbPrintBuffer = NULL; + } + gba->memory.prefetch = false; gba->memory.lastPrefetchedPc = 0; @@ -295,10 +308,15 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { if ((address & (SIZE_CART0 - 1)) < memory->romSize) { break; } + if ((address & (SIZE_CART0 - 1)) == AGB_PRINT_FLUSH_ADDR && memory->agbPrint == 0x20) { + cpu->memory.activeRegion = (uint32_t*) _agbPrintFunc; + cpu->memory.activeMask = sizeof(_agbPrintFunc) - 1; + + } // Fall through default: memory->activeRegion = -1; - cpu->memory.activeRegion = _deadbeef; + cpu->memory.activeRegion = (uint32_t*) _deadbeef; cpu->memory.activeMask = 0; if (!gba->yankedRomSize && mCoreCallbacksListSize(&gba->coreCallbacks)) { @@ -528,6 +546,16 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { LOAD_16(value, address & memory->romMask, memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); + } else if ((address & (SIZE_CART0 - 1)) >= AGB_PRINT_BASE) { + uint32_t agbPrintAddr = address & 0x00FFFFFF; + if (agbPrintAddr == AGB_PRINT_PROTECT) { + value = memory->agbPrint; + } else if (agbPrintAddr < AGB_PRINT_TOP || (agbPrintAddr & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + value = _agbPrintLoad(gba, address); + } else { + mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); + value = (address >> 1) & 0xFFFF; + } } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF; @@ -838,9 +866,23 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFE)) { uint32_t reg = address & 0xFFFFFE; GBAHardwareGPIOWrite(&memory->hw, reg, value); - } else { - mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); + break; } + // Fall through + case REGION_CART0_EX: + if ((address & 0x00FFFFFF) >= AGB_PRINT_BASE) { + uint32_t agbPrintAddr = address & 0x00FFFFFF; + if (agbPrintAddr == AGB_PRINT_PROTECT) { + memory->agbPrint = value; + _agbPrintStore(gba, address, value); + break; + } + if (memory->agbPrint == 0x20 && (agbPrintAddr < AGB_PRINT_TOP || (agbPrintAddr & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8))) { + _agbPrintStore(gba, address, value); + break; + } + } + mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); break; case REGION_CART2_EX: if (memory->savedata.type == SAVEDATA_AUTODETECT) { @@ -1595,3 +1637,50 @@ void _pristineCow(struct GBA* gba) { gba->memory.rom = newRom; gba->memory.hw.gpioBase = &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]; } + +void GBAPrintFlush(struct GBA* gba) { + char oolBuf[0x101]; + size_t i; + for (i = 0; gba->memory.agbPrintCtx.get != gba->memory.agbPrintCtx.put && i < 0x100; ++i) { + int16_t value; + LOAD_16(value, gba->memory.agbPrintCtx.get & -2, gba->memory.agbPrintBuffer); + if (gba->memory.agbPrintCtx.get & 1) { + value >>= 8; + } else { + value &= 0xFF; + } + oolBuf[i] = value; + ++gba->memory.agbPrintCtx.get; + } + _agbPrintStore(gba, AGB_PRINT_STRUCT + 4, gba->memory.agbPrintCtx.get); + + mLOG(GBA_DEBUG, INFO, "%s", oolBuf); +} + +static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value) { + struct GBAMemory* memory = &gba->memory; + if ((address & 0x00FFFFFF) < AGB_PRINT_TOP) { + if (!memory->agbPrintBuffer) { + memory->agbPrintBuffer = anonymousMemoryMap(SIZE_AGB_PRINT); + } + STORE_16(value, address & (SIZE_AGB_PRINT - 2), memory->agbPrintBuffer); + } else if ((address & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + (&memory->agbPrintCtx.request)[(address & 7) >> 1] = value; + } + if (memory->romSize == SIZE_CART0) { + _pristineCow(gba); + memcpy(&memory->rom[AGB_PRINT_FLUSH_ADDR >> 2], _agbPrintFunc, sizeof(_agbPrintFunc)); + STORE_16(value, address & (SIZE_CART0 - 2), memory->rom); + } +} + +static int16_t _agbPrintLoad(struct GBA* gba, uint32_t address) { + struct GBAMemory* memory = &gba->memory; + int16_t value = 0xFFFF; + if (address < AGB_PRINT_TOP) { + LOAD_16(value, address & (SIZE_AGB_PRINT - 1), memory->agbPrintBuffer); + } else if ((address & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + value = (&memory->agbPrintCtx.request)[(address & 7) >> 1]; + } + return value; +}