From 1c10743995da4951a245cb66f7b2c67e0abd6518 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 21 Mar 2015 18:15:06 -0700 Subject: [PATCH 01/92] GBA: Loosen checks on idle loops --- src/gba/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 3ea0b7ece..bc5a32e20 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -197,7 +197,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { int newRegion = address >> BASE_OFFSET; if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) { - if (address == gba->lastJump && address == gba->idleLoop) { + if (address == gba->idleLoop) { GBAHalt(gba); } else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) { if (address == gba->lastJump) { From 6e16b2992c8df8d8380cf2757711fda261fc7e09 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 21 Mar 2015 18:16:50 -0700 Subject: [PATCH 02/92] GBA: Add idle loops for Advance Wars --- src/gba/supervisor/overrides.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index 2c5759040..f2f15d280 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -11,6 +11,10 @@ #include "util/configuration.h" static const struct GBACartridgeOverride _overrides[] = { + // Advance Wars + { "AWRE", SAVEDATA_FLASH512, HW_NONE, 0x8038810 }, + { "AWRP", SAVEDATA_FLASH512, HW_NONE, 0x8038810 }, + // Boktai: The Sun is in Your Hand { "U3IJ", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, { "U3IE", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, From f0cebb3fe72b7b4044cd746a46c6ecedd369e17a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 21 Mar 2015 18:21:05 -0700 Subject: [PATCH 03/92] GBA: Add some idle loops for different regions of the same game --- src/gba/supervisor/overrides.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index f2f15d280..1afff6c70 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -73,13 +73,13 @@ static const struct GBACartridgeOverride _overrides[] = { { "AXPF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, // Pokemon Emerald - { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, { "BPEE", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, - { "BPEP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPEI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPES", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPED", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, - { "BPEF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEP", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPEI", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPES", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPED", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, // Pokemon Mystery Dungeon { "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, @@ -119,10 +119,14 @@ static const struct GBACartridgeOverride _overrides[] = { { "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, // Super Mario Advance 2 + { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, + { "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, // Super Mario Advance 3 + { "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, { "A3AE", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, + { "A3AP", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, // Super Mario Advance 4 { "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, From 475954aff13370093efa3303b36d0e8b975152e7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 21 Mar 2015 18:22:37 -0700 Subject: [PATCH 04/92] GBA: Add idle loops for Super Mario Advance 4 --- src/gba/supervisor/overrides.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index 1afff6c70..1140a3b42 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -129,9 +129,9 @@ static const struct GBACartridgeOverride _overrides[] = { { "A3AP", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, // Super Mario Advance 4 - { "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, - { "AX4E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, - { "AX4P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "AX4J", SAVEDATA_FLASH1M, HW_NONE, 0x800072A }, + { "AX4E", SAVEDATA_FLASH1M, HW_NONE, 0x800072A }, + { "AX4P", SAVEDATA_FLASH1M, HW_NONE, 0x800072A }, // Top Gun - Combat Zones { "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE }, From 64cbdf8aa0cf86281f78037403db58219d6f9a4a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 23 Mar 2015 01:15:56 -0700 Subject: [PATCH 05/92] GBA: Ensure idle loops are not removed on the first iteration --- src/gba/gba.c | 1 + src/gba/gba.h | 1 + src/gba/io.c | 2 +- src/gba/memory.c | 7 ++++++- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index 97b4668fe..e3023ebc8 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -87,6 +87,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) { gba->idleOptimization = IDLE_LOOP_REMOVE; gba->idleLoop = IDLE_LOOP_NONE; gba->lastJump = 0; + gba->haltPending = false; gba->idleDetectionStep = 0; gba->idleDetectionFailures = 0; gba->performingDMA = false; diff --git a/src/gba/gba.h b/src/gba/gba.h index 3594b4ab8..1366fd1e6 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -157,6 +157,7 @@ struct GBA { enum GBAIdleLoopOptimization idleOptimization; uint32_t idleLoop; uint32_t lastJump; + bool haltPending; int idleDetectionStep; int idleDetectionFailures; int32_t cachedRegisters[16]; diff --git a/src/gba/io.c b/src/gba/io.c index 76305cb1d..65f178fde 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -567,7 +567,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) { } uint16_t GBAIORead(struct GBA* gba, uint32_t address) { - gba->lastJump = -1; // IO reads need to invalidate detected idle loops + gba->haltPending = false; // IO reads need to invalidate detected idle loops switch (address) { case REG_TM0CNT_LO: GBATimerUpdateRegister(gba, 0); diff --git a/src/gba/memory.c b/src/gba/memory.c index bc5a32e20..eddc33601 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -198,7 +198,12 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { int newRegion = address >> BASE_OFFSET; if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) { if (address == gba->idleLoop) { - GBAHalt(gba); + if (gba->haltPending) { + gba->haltPending = false; + GBAHalt(gba); + } else { + gba->haltPending = true; + } } else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) { if (address == gba->lastJump) { switch (gba->idleDetectionStep) { From 817dec97c2c54ffb63ce536525f55bfa7b746b9b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 23 Mar 2015 01:16:15 -0700 Subject: [PATCH 06/92] GBA: Add Mega Man Zero override --- src/gba/supervisor/overrides.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index 1140a3b42..fc9b5cbb3 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -51,6 +51,9 @@ static const struct GBACartridgeOverride _overrides[] = { // Mega Man Battle Network { "AREE", SAVEDATA_SRAM, HW_NONE, 0x800032E }, + // Mega Man Zero + { "AZCE", SAVEDATA_SRAM, HW_NONE, 0x80004E8 }, + // Metal Slug Advance { "BSME", SAVEDATA_EEPROM, HW_NONE, 0x8000290 }, From 811ada598e2ba8595e114f5026328a95b2717bcd Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 27 Mar 2015 01:34:49 -0700 Subject: [PATCH 07/92] GBA: Fix timer initialization --- src/gba/gba.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index 97b4668fe..2402fcc77 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -464,7 +464,7 @@ void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) { } gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload; currentTimer->oldReload = currentTimer->reload; - currentTimer->lastEvent = 0; + currentTimer->lastEvent = gba->cpu->cycles; gba->timersEnabled |= 1 << timer; } else if (wasEnabled && !currentTimer->enable) { if (!currentTimer->countUp) { From 8e87e3a528941bc5104f28a1a94e6c7a4d820b5a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 27 Mar 2015 01:35:10 -0700 Subject: [PATCH 08/92] GBA Memory: Fix I cycles that had been moved to ARM7 core --- src/gba/memory.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 3ea0b7ece..93431f259 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -380,7 +380,7 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { } if (cycleCounter) { - *cycleCounter += 2 + wait; + *cycleCounter += 1 + wait; } // Unaligned 32-bit loads are "rotated" so they make some semblance of sense int rotate = (address & 3) << 3; @@ -483,7 +483,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { } if (cycleCounter) { - *cycleCounter += 2 + wait; + *cycleCounter += 1 + wait; } // Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too. int rotate = (address & 1) << 3; @@ -588,7 +588,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { } if (cycleCounter) { - *cycleCounter += 2 + wait; + *cycleCounter += 1 + wait; } return value; } From 325b1dc35beb85131b44f3fc900f94c9ba08681f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 27 Mar 2015 01:37:36 -0700 Subject: [PATCH 09/92] All: Update CHANGES --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 95bd66cb1..9ee549a65 100644 --- a/CHANGES +++ b/CHANGES @@ -56,6 +56,8 @@ Bugfixes: - Qt: Fix crash when loading a game after stopping GDB server - GBA BIOS: Fix BIOS decompression routines with invalid source addresses - GBA: Initialize gba.sync to null + - GBA: Fix timer initialization + - GBA Memory: Fix I cycles that had been moved to ARM7 core Misc: - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - GBA Memory: Simplify memory API and use fixed bus width From afff253928e9364cc0632fc5dfa8ec56f49f9dba Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 27 Mar 2015 21:20:13 -0700 Subject: [PATCH 10/92] GBA Memory: Fix cycle counting for 32-bit load/stores --- CHANGES | 1 + src/gba/memory.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9ee549a65..b05565cf8 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,7 @@ Bugfixes: - GBA: Initialize gba.sync to null - GBA: Fix timer initialization - GBA Memory: Fix I cycles that had been moved to ARM7 core + - GBA Memory: Fix cycle counting for 32-bit load/stores Misc: - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - GBA Memory: Simplify memory API and use fixed bus width diff --git a/src/gba/memory.c b/src/gba/memory.c index 93431f259..cde2abb43 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1180,9 +1180,9 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) { memory->waitstatesSeq16[REGION_CART1] = memory->waitstatesSeq16[REGION_CART1_EX] = GBA_ROM_WAITSTATES_SEQ[ws1seq + 2]; memory->waitstatesSeq16[REGION_CART2] = memory->waitstatesSeq16[REGION_CART2_EX] = GBA_ROM_WAITSTATES_SEQ[ws2seq + 4]; - memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesSeq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0]; - memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesSeq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1]; - memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesSeq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2]; + memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesNonseq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0]; + memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1]; + memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2]; memory->waitstatesSeq32[REGION_CART0] = memory->waitstatesSeq32[REGION_CART0_EX] = 2 * memory->waitstatesSeq16[REGION_CART0] + 1; memory->waitstatesSeq32[REGION_CART1] = memory->waitstatesSeq32[REGION_CART1_EX] = 2 * memory->waitstatesSeq16[REGION_CART1] + 1; From 45473bf7bcf33c58c408d75ff8d06f84774eb79d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 27 Mar 2015 21:21:17 -0700 Subject: [PATCH 11/92] ARM7: Fix cycle counting for loads --- CHANGES | 1 + src/arm/isa-arm.c | 6 +++--- src/arm/isa-thumb.c | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b05565cf8..2b5718244 100644 --- a/CHANGES +++ b/CHANGES @@ -59,6 +59,7 @@ Bugfixes: - GBA: Fix timer initialization - GBA Memory: Fix I cycles that had been moved to ARM7 core - GBA Memory: Fix cycle counting for 32-bit load/stores + - ARM7: Fix cycle counting for loads Misc: - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - GBA Memory: Simplify memory API and use fixed bus width diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index 6b0506af4..fe691b1d9 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -254,7 +254,7 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) { #define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address; #define ARM_LOAD_POST_BODY \ - ++currentCycles; \ + currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; \ if (rd == ARM_PC) { \ ARM_WRITE_PC; \ } @@ -562,14 +562,14 @@ DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(STRT, DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(LDM, load, - ++currentCycles; + currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; if (rs & 0x8000) { ARM_WRITE_PC; }) DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(STM, store, - currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32) + ARM_STORE_POST_BODY;) DEFINE_INSTRUCTION_ARM(SWP, int rm = opcode & 0xF; diff --git a/src/arm/isa-thumb.c b/src/arm/isa-thumb.c index 3c12072fa..ef9ad3eca 100644 --- a/src/arm/isa-thumb.c +++ b/src/arm/isa-thumb.c @@ -41,7 +41,8 @@ #define THUMB_PREFETCH_CYCLES (1 + cpu->memory.activeSeqCycles16) -#define THUMB_LOAD_POST_BODY ++currentCycles; +#define THUMB_LOAD_POST_BODY \ + currentCycles += 1 + cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; #define THUMB_STORE_POST_BODY \ currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; From c665ed78e6df97c0f45fc1bc18c3ab0f63964c05 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 27 Mar 2015 22:42:17 -0700 Subject: [PATCH 12/92] GBA Memory: Add timing information to bad cart stores --- src/gba/memory.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/memory.c b/src/gba/memory.c index cde2abb43..85b2809af 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -623,6 +623,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 4)) >> 1) + 1); #define STORE_CART \ + wait += waitstatesRegion[address >> BASE_OFFSET]; \ GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store32: 0x%08X", address); #define STORE_SRAM \ From e93240f90c75a5f174efaf1647a18a8102ce95e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 Mar 2015 03:25:46 -0700 Subject: [PATCH 13/92] GBA: Add somewhat more realistic flash timings, disabled currently --- src/gba/gba.c | 3 +++ src/gba/gba.h | 2 ++ src/gba/memory.c | 2 +- src/gba/savedata.c | 25 +++++++++++++++++++++---- src/gba/savedata.h | 8 ++++++-- src/gba/serialize.h | 9 +++++---- src/gba/supervisor/overrides.c | 2 +- 7 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index 2402fcc77..e2aabd148 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -89,6 +89,9 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) { gba->lastJump = 0; gba->idleDetectionStep = 0; gba->idleDetectionFailures = 0; + + gba->realisticTiming = false; + gba->performingDMA = false; } diff --git a/src/gba/gba.h b/src/gba/gba.h index 3594b4ab8..1694919e4 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -161,6 +161,8 @@ struct GBA { int idleDetectionFailures; int32_t cachedRegisters[16]; bool taintedRegisters[16]; + + bool realisticTiming; }; struct GBACartridge { diff --git a/src/gba/memory.c b/src/gba/memory.c index 85b2809af..0923edd1e 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -778,7 +778,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo if (memory->savedata.type == SAVEDATA_AUTODETECT) { if (address == SAVEDATA_FLASH_BASE) { GBALog(gba, GBA_LOG_INFO, "Detected Flash savegame"); - GBASavedataInitFlash(&memory->savedata); + GBASavedataInitFlash(&memory->savedata, gba->realisticTiming); } else { GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame"); GBASavedataInitSRAM(&memory->savedata); diff --git a/src/gba/savedata.c b/src/gba/savedata.c index bc9a34851..0d1c82475 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -14,6 +14,8 @@ #include #include +#define FLASH_SETTLE_CYCLES 18000 + static void _flashSwitchBank(struct GBASavedata* savedata, int bank); static void _flashErase(struct GBASavedata* savedata); static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart); @@ -113,7 +115,7 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) { return true; } -void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) { +void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) { if (savedata->type != SAVEDATA_AUTODETECT) { struct VFile* vf = savedata->vf; GBASavedataDeinit(savedata); @@ -123,7 +125,7 @@ void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) case SAVEDATA_FLASH512: case SAVEDATA_FLASH1M: savedata->type = type; - GBASavedataInitFlash(savedata); + GBASavedataInitFlash(savedata, realisticTiming); break; case SAVEDATA_EEPROM: GBASavedataInitEEPROM(savedata); @@ -139,7 +141,7 @@ void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) } } -void GBASavedataInitFlash(struct GBASavedata* savedata) { +void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming) { if (savedata->type == SAVEDATA_AUTODETECT) { savedata->type = SAVEDATA_FLASH512; } @@ -162,6 +164,8 @@ void GBASavedataInitFlash(struct GBASavedata* savedata) { } savedata->currentBank = savedata->data; + savedata->dust = 0; + savedata->realisticTiming = realisticTiming; if (end < SIZE_CART_FLASH512) { memset(&savedata->data[end], 0xFF, flashSize - end); } @@ -226,6 +230,10 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) { } } } + if (savedata->dust > 0 && (address >> 12) == savedata->settling) { + --savedata->dust; + return 0x5F; + } return savedata->currentBank[address]; } @@ -384,6 +392,8 @@ void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializ state->savedata.readBitsRemaining = savedata->readBitsRemaining; state->savedata.readAddress = savedata->readAddress; state->savedata.writeAddress = savedata->writeAddress; + state->savedata.settlingSector = savedata->settling; + state->savedata.settlingDust = savedata->dust; UNUSED(includeData); // TODO } @@ -393,13 +403,16 @@ void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerial return; } if (savedata->type != state->savedata.type) { - GBASavedataForceType(savedata, state->savedata.type); + GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming); } savedata->command = state->savedata.command; savedata->flashState = state->savedata.flashState; savedata->readBitsRemaining = state->savedata.readBitsRemaining; savedata->readAddress = state->savedata.readAddress; savedata->writeAddress = state->savedata.writeAddress; + savedata->settling = state->savedata.settlingSector; + savedata->dust = state->savedata.settlingDust; + if (savedata->type == SAVEDATA_FLASH1M) { _flashSwitchBank(savedata, state->savedata.flashBank); } @@ -434,5 +447,9 @@ void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) { if (savedata->type == SAVEDATA_FLASH1M) { GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart); } + savedata->settling = sectorStart >> 12; + if (savedata->realisticTiming) { + savedata->dust = FLASH_SETTLE_CYCLES; + } memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size); } diff --git a/src/gba/savedata.h b/src/gba/savedata.h index d1614283a..a3030eb03 100644 --- a/src/gba/savedata.h +++ b/src/gba/savedata.h @@ -73,6 +73,10 @@ struct GBASavedata { uint8_t* currentBank; + bool realisticTiming; + unsigned settling; + int dust; + enum FlashStateMachine flashState; }; @@ -82,9 +86,9 @@ void GBASavedataDeinit(struct GBASavedata* savedata); void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataUnmask(struct GBASavedata* savedata); bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out); -void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type); +void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming); -void GBASavedataInitFlash(struct GBASavedata* savedata); +void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming); void GBASavedataInitEEPROM(struct GBASavedata* savedata); void GBASavedataInitSRAM(struct GBASavedata* savedata); diff --git a/src/gba/serialize.h b/src/gba/serialize.h index 5a2d9a34b..3bb416655 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -162,8 +162,9 @@ extern const uint32_t GBA_SAVESTATE_MAGIC; * | 0x002E3 - 0x002E3: Reserved * | 0x002E4 - 0x002E7: EEPROM read bits remaining * | 0x002E8 - 0x002EB: EEPROM read address - * | 0x002EC - 0x002EBF EEPROM write address - * 0x002F0 - 0x002F3: Reserved (leave zero) + * | 0x002EC - 0x002EF: EEPROM write address + * | 0x002F0 - 0x002F1: Flash settling sector + * | 0x002F2 - 0x002F3: Flash settling remaining * 0x002F4 - 0x002FF: Prefetch * | 0x002F4 - 0x002F7: GBA BIOS bus prefetch * | 0x002F8 - 0x002FB: CPU prefecth (decode slot) @@ -297,10 +298,10 @@ struct GBASerializedState { int32_t readBitsRemaining; uint32_t readAddress; uint32_t writeAddress; + uint16_t settlingSector; + uint16_t settlingDust; } savedata; - uint32_t reservedPadding; - uint32_t biosPrefetch; uint32_t cpuPrefetch[2]; diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index 2c5759040..9a0ff9bea 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -249,7 +249,7 @@ void GBAOverrideSave(struct Configuration* config, const struct GBACartridgeOver void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* override) { if (override->savetype != SAVEDATA_AUTODETECT) { - GBASavedataForceType(&gba->memory.savedata, override->savetype); + GBASavedataForceType(&gba->memory.savedata, override->savetype, gba->realisticTiming); } if (override->hardware != HW_NO_OVERRIDE) { From 3fe32281c9f419c0b14044153324624614d15208 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 Mar 2015 23:14:10 -0700 Subject: [PATCH 14/92] Qt: Remember window position --- CHANGES | 1 + src/platform/qt/Window.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 2b5718244..eaf6bff2b 100644 --- a/CHANGES +++ b/CHANGES @@ -84,6 +84,7 @@ Misc: - GBA Thread: Make GBASyncWaitFrameStart time out - GBA: Move A/V stream interface into core - GBA: Savestates now take into account savedata state machines (fixes #109) + - Qt: Remember window position 0.1.1: (2015-01-24) Bugfixes: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index eab756f56..76e169319 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -79,6 +79,11 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); setCentralWidget(m_screenWidget); + QVariant windowPos = m_config->getQtOption("windowPos"); + if (!windowPos.isNull()) { + move(windowPos.toPoint()); + } + connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*))); connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing())); connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped())); @@ -344,6 +349,7 @@ void Window::resizeEvent(QResizeEvent*) { void Window::closeEvent(QCloseEvent* event) { emit shutdown(); + m_config->setQtOption("windowPos", pos()); QMainWindow::closeEvent(event); } From 7a9807f030b80c3c38b6587a385ad7d2e44d16f7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 Mar 2015 23:19:21 -0700 Subject: [PATCH 15/92] GBA: Fix BIOS loading setting --- src/gba/supervisor/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c index 9f3427af0..7b4058028 100644 --- a/src/gba/supervisor/config.c +++ b/src/gba/supervisor/config.c @@ -190,7 +190,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { } int fakeBool; - if (_lookupIntValue(config, "useSync", &fakeBool)) { + if (_lookupIntValue(config, "useBios", &fakeBool)) { opts->useBios = fakeBool; } if (_lookupIntValue(config, "audioSync", &fakeBool)) { From 44d3718eb06629a71d76e8b8e709915f29e97610 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 Mar 2015 23:23:32 -0700 Subject: [PATCH 16/92] Qt: Save fullscreen setting --- src/platform/qt/Window.cpp | 29 ++++++++++++++++++++--------- src/platform/qt/Window.h | 1 + 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 76e169319..9d2f0efe8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -181,6 +181,10 @@ void Window::loadConfig() { resizeFrame(opts->width, opts->height); } + if (opts->fullscreen) { + enterFullScreen(); + } + m_mruFiles = m_config->getMRU(); updateMRU(); @@ -345,6 +349,7 @@ void Window::keyReleaseEvent(QKeyEvent* event) { void Window::resizeEvent(QResizeEvent*) { m_config->setOption("height", m_screenWidget->height()); m_config->setOption("width", m_screenWidget->width()); + m_config->setOption("fullscreen", isFullScreen()); } void Window::closeEvent(QCloseEvent* event) { @@ -380,6 +385,18 @@ void Window::dropEvent(QDropEvent* event) { m_controller->loadGame(url.path()); } +void Window::enterFullScreen() { + if (isFullScreen()) { + return; + } + showFullScreen(); +#ifndef Q_OS_MAC + if (m_controller->isLoaded() && !m_controller->isPaused()) { + menuBar()->hide(); + } +#endif +} + void Window::exitFullScreen() { if (!isFullScreen()) { return; @@ -390,15 +407,9 @@ void Window::exitFullScreen() { void Window::toggleFullScreen() { if (isFullScreen()) { - showNormal(); - menuBar()->show(); + exitFullScreen(); } else { - showFullScreen(); -#ifndef Q_OS_MAC - if (m_controller->isLoaded() && !m_controller->isPaused()) { - menuBar()->hide(); - } -#endif + enterFullScreen(); } } @@ -831,7 +842,7 @@ void Window::setupMenu(QMenuBar* menubar) { m_controller->setTurbo(false, false); }, QKeySequence(Qt::Key_Tab), tr("Fast Forward (held)"), "holdFastForward"); - addControlledAction(other, other->addAction(tr("Exit fullscreen"), this, SLOT(exitFullScreen()), QKeySequence("Esc")), "exitFullscreen"); + addControlledAction(other, other->addAction(tr("Exit fullscreen"), this, SLOT(exitFullScreen()), QKeySequence("Esc")), "exitFullScreen"); foreach (QAction* action, m_gameActions) { action->setDisabled(true); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index d2d569e00..85a3f1907 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -59,6 +59,7 @@ public slots: void selectROM(); void selectBIOS(); void selectPatch(); + void enterFullScreen(); void exitFullScreen(); void toggleFullScreen(); void loadConfig(); From 178f9a83bb120db15d2b7450b63ed0dd5766a08b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 Mar 2015 04:38:13 -0700 Subject: [PATCH 17/92] GBA: Almost entirely untested Pro Action Replay v3 code support --- src/gba/cheats.c | 285 ++++++++++++++++++++++++++++++++- src/gba/cheats.h | 23 ++- src/platform/qt/CheatsView.cpp | 6 +- src/platform/qt/CheatsView.ui | 3 - src/util/vector.h | 4 + 5 files changed, 306 insertions(+), 15 deletions(-) diff --git a/src/gba/cheats.c b/src/gba/cheats.c index d5584d9d5..a70ebf207 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -294,6 +294,7 @@ static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { return true; case GSA_BUTTON: // TODO: Implement button + GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); return false; case GSA_IF_EQ: if (op1 == 0xDEADFACE) { @@ -331,14 +332,225 @@ static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { return true; } +static uint32_t _parAddr(uint32_t x) { + return (x & 0xFFFFF) | ((x << 4) & 0x0F000000); +} + +static void _parEndBlock(struct GBACheatSet* cheats) { + size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); + if (cheats->currentBlock->repeat) { + cheats->currentBlock->negativeRepeat = size - cheats->currentBlock->repeat; + } else { + cheats->currentBlock->repeat = size; + } + cheats->currentBlock = 0; +} + +static void _parElseBlock(struct GBACheatSet* cheats) { + size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); + cheats->currentBlock->repeat = size; +} + +static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAActionReplay3Condition condition = op1 & PAR3_COND; + int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); + if (width > 4) { + // TODO: Always false conditions + return false; + } + if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) { + // TODO: Codes that disable + return false; + } + + struct GBACheat* cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op1); + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); + cheat->addressOffset = 0; + cheat->operandOffset = 0; + + switch (op1 & PAR3_ACTION) { + case PAR3_ACTION_NEXT: + cheat->repeat = 1; + cheat->negativeRepeat = 0; + break; + case PAR3_ACTION_NEXT_TWO: + cheat->repeat = 2; + cheat->negativeRepeat = 0; + break; + case PAR3_ACTION_BLOCK: + cheat->repeat = 0; + cheat->negativeRepeat = 0; + if (cheats->currentBlock) { + _parEndBlock(cheats); + } + cheats->currentBlock = cheat; + break; + } + + switch (condition) { + case PAR3_COND_OTHER: + // We shouldn't be able to get here + GBALog(0, GBA_LOG_ERROR, "Unexpectedly created 'other' PARv3 code"); + cheat->type = CHEAT_IF_LAND; + cheat->operand = 0; + break; + case PAR3_COND_EQ: + cheat->type = CHEAT_IF_EQ; + break; + case PAR3_COND_NE: + cheat->type = CHEAT_IF_NE; + break; + case PAR3_COND_LT: + cheat->type = CHEAT_IF_LT; + break; + case PAR3_COND_GT: + cheat->type = CHEAT_IF_GT; + break; + case PAR3_COND_ULT: + cheat->type = CHEAT_IF_ULT; + break; + case PAR3_COND_UGT: + cheat->type = CHEAT_IF_UGT; + break; + case PAR3_COND_LAND: + cheat->type = CHEAT_IF_LAND; + break; + } + return true; +} + +static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { + struct GBACheat* cheat; + switch (op2 & 0xFF000000) { + case PAR3_OTHER_SLOWDOWN: + // TODO: Slowdown + return false; + case PAR3_OTHER_BUTTON_1: + case PAR3_OTHER_BUTTON_2: + case PAR3_OTHER_BUTTON_4: + // TODO: Button + GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); + return false; + // TODO: Fix overriding existing patches + case PAR3_OTHER_PATCH_1: + cheats->romPatches[0].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[0].applied = false; + cheats->romPatches[0].exists = true; + cheats->incompletePatch = &cheats->romPatches[0]; + break; + case PAR3_OTHER_PATCH_2: + cheats->romPatches[1].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[1].applied = false; + cheats->romPatches[1].exists = true; + cheats->incompletePatch = &cheats->romPatches[1]; + break; + case PAR3_OTHER_PATCH_3: + cheats->romPatches[2].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[2].applied = false; + cheats->romPatches[2].exists = true; + cheats->incompletePatch = &cheats->romPatches[2]; + break; + case PAR3_OTHER_PATCH_4: + cheats->romPatches[3].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[3].applied = false; + cheats->romPatches[3].exists = true; + cheats->incompletePatch = &cheats->romPatches[3]; + break; + case PAR3_OTHER_ENDIF: + if (cheats->currentBlock) { + _parEndBlock(cheats); + return true; + } + return false; + case PAR3_OTHER_ELSE: + if (cheats->currentBlock) { + _parElseBlock(cheats); + return true; + } + return false; + case PAR3_OTHER_FILL_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 1; + cheats->incompleteCheat = cheat; + break; + case PAR3_OTHER_FILL_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case PAR3_OTHER_FILL_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 3; + cheats->incompleteCheat = cheat; + break; + } + return true; +} + static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { - // TODO - UNUSED(cheats); - UNUSED(op1); - UNUSED(op2); - UNUSED(_par3T1); - UNUSED(_par3T2); - return false; + if (cheats->incompletePatch) { + cheats->incompletePatch->newValue = op1; + cheats->incompletePatch = 0; + return true; + } + if (cheats->incompleteCheat) { + cheats->incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - cheats->incompleteCheat->width) * 8)); + cheats->incompleteCheat->addressOffset = op2 >> 24; + cheats->incompleteCheat->repeat = (op2 >> 16) & 0xFF; + cheats->incompleteCheat->addressOffset = (op2 & 0xFFFF) * cheats->incompleteCheat->width; + cheats->incompleteCheat = 0; + return true; + } + + switch (op1) { + case 0x00000000: + return _addPAR3Special(cheats, op2); + case 0xDEADFACE: + _reseedGameShark(cheats->gsaSeeds, op2, _par3T1, _par3T2); + return true; + } + + if (op1 & PAR3_COND) { + return _addPAR3Cond(cheats, op1, op2); + } + + int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); + struct GBACheat* cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op1); + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); + cheat->operandOffset = 0; + cheat->addressOffset = 0; + cheat->repeat = 1; + + switch (op1 & PAR3_BASE) { + case PAR3_BASE_ASSIGN: + cheat->type = CHEAT_ASSIGN; + cheat->addressOffset = width; + if (width < 4) { + cheat->repeat = (op2 >> (width * 8)) + 1; + } + break; + case PAR3_BASE_INDIRECT: + cheat->type = CHEAT_ASSIGN_INDIRECT; + if (width < 4) { + cheat->addressOffset = (op2 >> (width * 8)) * width; + } + break; + case PAR3_BASE_ADD: + cheat->type = CHEAT_ADD; + break; + case PAR3_BASE_OTHER: + cheat->type = CHEAT_ASSIGN; + cheat->address = BASE_IO | (op1 & OFFSET_MASK); + break; + } + return true; } static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) { @@ -415,6 +627,8 @@ void GBACheatSetInit(struct GBACheatSet* set, const char* name) { GBACheatListInit(&set->list, 4); StringListInit(&set->lines, 4); set->incompleteCheat = 0; + set->incompletePatch = 0; + set->currentBlock = 0; set->gsaVersion = 0; set->remainingAddresses = 0; set->hook = 0; @@ -587,6 +801,7 @@ bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t o cheat->address = op1 & 0x0FFFFFFF; cheat->operand = op2; cheat->repeat = 1; + cheat->negativeRepeat = 0; return true; } @@ -642,6 +857,41 @@ bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { return GBACheatAddGameShark(cheats, op1, op2); } +bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + _registerLine(set, line); + + switch (set->gsaVersion) { + case 0: + _setGameSharkVersion(set, 3); + // Fall through + case 1: + _decryptGameShark(&o1, &o2, set->gsaSeeds); + return _addPAR3(set, o1, o2); + } + return false; +} + +bool GBACheatAddProActionReplayLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = _hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddProActionReplay(cheats, op1, op2); +} + bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { uint32_t o1 = op1; uint32_t o2 = op2; @@ -849,6 +1099,7 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) } bool condition = true; int conditionRemaining = 0; + int negativeConditionRemaining = 0; _patchROM(device, cheats); size_t nCodes = GBACheatListSize(&cheats->list); @@ -859,6 +1110,13 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) if (!condition) { continue; } + } else if (negativeConditionRemaining > 0) { + conditionRemaining = negativeConditionRemaining - 1; + negativeConditionRemaining = 0; + condition = !condition; + if (!condition) { + continue; + } } else { condition = true; } @@ -874,6 +1132,11 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) value = operand; performAssignment = true; break; + case CHEAT_ASSIGN_INDIRECT: + value = operand; + address = _readMem(device->p->cpu, address + cheat->addressOffset, 4); + performAssignment = true; + break; case CHEAT_AND: value = _readMem(device->p->cpu, address, cheat->width) & operand; performAssignment = true; @@ -889,34 +1152,42 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) case CHEAT_IF_EQ: condition = _readMem(device->p->cpu, address, cheat->width) == operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_NE: condition = _readMem(device->p->cpu, address, cheat->width) != operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_LT: condition = _readMem(device->p->cpu, address, cheat->width) < operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_GT: condition = _readMem(device->p->cpu, address, cheat->width) > operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_ULT: condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_UGT: condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_AND: condition = _readMem(device->p->cpu, address, cheat->width) & operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; case CHEAT_IF_LAND: condition = _readMem(device->p->cpu, address, cheat->width) && operand; conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; break; } diff --git a/src/gba/cheats.h b/src/gba/cheats.h index 3acbcebeb..61290c2d7 100644 --- a/src/gba/cheats.h +++ b/src/gba/cheats.h @@ -15,6 +15,7 @@ enum GBACheatType { CHEAT_ASSIGN, + CHEAT_ASSIGN_INDIRECT, CHEAT_AND, CHEAT_ADD, CHEAT_OR, @@ -85,6 +86,11 @@ enum GBAActionReplay3Action { }; enum GBAActionReplay3Base { + PAR3_BASE_ASSIGN = 0x00000000, + PAR3_BASE_INDIRECT = 0x40000000, + PAR3_BASE_ADD = 0x80000000, + PAR3_BASE_OTHER = 0xC0000000, + PAR3_BASE_ASSIGN_1 = 0x00000000, PAR3_BASE_ASSIGN_2 = 0x02000000, PAR3_BASE_ASSIGN_4 = 0x04000000, @@ -119,7 +125,10 @@ enum GBAActionReplay3Other { enum { PAR3_COND = 0x38000000, PAR3_WIDTH = 0x06000000, - PAR3_ACTION = 0xC0000000 + PAR3_ACTION = 0xC0000000, + PAR3_BASE = 0xC0000000, + + PAR3_WIDTH_BASE = 25 }; struct GBACheat { @@ -128,6 +137,7 @@ struct GBACheat { uint32_t address; uint32_t operand; uint32_t repeat; + uint32_t negativeRepeat; int32_t addressOffset; int32_t operandOffset; @@ -148,8 +158,6 @@ struct GBACheatSet { struct GBACheatHook* hook; struct GBACheatList list; - struct GBACheat* incompleteCheat; - struct GBACheatPatch { uint32_t address; int16_t newValue; @@ -158,6 +166,10 @@ struct GBACheatSet { bool exists; } romPatches[MAX_ROM_PATCHES]; + struct GBACheat* incompleteCheat; + struct GBACheatPatch* incompletePatch; + struct GBACheat* currentBlock; + int gsaVersion; uint32_t gsaSeeds[4]; int remainingAddresses; @@ -196,6 +208,9 @@ bool GBACheatAddCodeBreakerLine(struct GBACheatSet*, const char* line); bool GBACheatAddGameShark(struct GBACheatSet*, uint32_t op1, uint32_t op2); bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line); +bool GBACheatAddProActionReplay(struct GBACheatSet*, uint32_t op1, uint32_t op2); +bool GBACheatAddProActionReplayLine(struct GBACheatSet*, const char* line); + bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2); bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line); @@ -206,4 +221,4 @@ bool GBACheatAddLine(struct GBACheatSet*, const char* line); void GBACheatRefresh(struct GBACheatDevice*, struct GBACheatSet*); -#endif \ No newline at end of file +#endif diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index 444e336c7..40e1be426 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -40,6 +40,10 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent) enterCheat(GBACheatAddGameSharkLine); }); + connect(m_ui.addPAR, &QPushButton::clicked, [this]() { + enterCheat(GBACheatAddProActionReplayLine); + }); + connect(m_ui.addCB, &QPushButton::clicked, [this]() { enterCheat(GBACheatAddCodeBreakerLine); }); @@ -113,4 +117,4 @@ void CheatsView::enterCheat(std::function callb } m_controller->threadContinue(); m_ui.codeEntry->clear(); -} \ No newline at end of file +} diff --git a/src/platform/qt/CheatsView.ui b/src/platform/qt/CheatsView.ui index b5a1afdc0..a272e8e8f 100644 --- a/src/platform/qt/CheatsView.ui +++ b/src/platform/qt/CheatsView.ui @@ -37,9 +37,6 @@ - - false - Add Pro Action Replay diff --git a/src/util/vector.h b/src/util/vector.h index 80223540c..f809f0084 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -24,6 +24,7 @@ void NAME ## Unshift(struct NAME* vector, size_t location, size_t difference); \ void NAME ## EnsureCapacity(struct NAME* vector, size_t capacity); \ size_t NAME ## Size(const struct NAME* vector); \ + size_t NAME ## Index(const struct NAME* vector, const TYPE* member); #define DEFINE_VECTOR(NAME, TYPE) \ void NAME ## Init(struct NAME* vector, size_t capacity) { \ @@ -75,5 +76,8 @@ size_t NAME ## Size(const struct NAME* vector) { \ return vector->size; \ } \ + size_t NAME ## Index(const struct NAME* vector, const TYPE* member) { \ + return member - (const TYPE*) vector->vector; \ + } \ #endif From d38f99e04172915dcd8a2520fd3888dc8a23a47a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 Mar 2015 04:48:42 -0700 Subject: [PATCH 18/92] GBA: Fix PARv3 IO codes, add master code support --- src/gba/cheats.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/gba/cheats.c b/src/gba/cheats.c index a70ebf207..d2892765a 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -507,6 +507,10 @@ static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { return true; } + if (op2 == 0x001DC0DE) { + return true; + } + switch (op1) { case 0x00000000: return _addPAR3Special(cheats, op2); @@ -515,6 +519,18 @@ static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { return true; } + if (op1 >> 24 == 0xC4) { + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + } + if (op1 & PAR3_COND) { return _addPAR3Cond(cheats, op1, op2); } @@ -522,8 +538,6 @@ static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); struct GBACheat* cheat = GBACheatListAppend(&cheats->list); cheat->address = _parAddr(op1); - cheat->width = width; - cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); cheat->operandOffset = 0; cheat->addressOffset = 0; cheat->repeat = 1; @@ -546,10 +560,14 @@ static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { cheat->type = CHEAT_ADD; break; case PAR3_BASE_OTHER: + width = ((op1 >> 24) & 1) + 1; cheat->type = CHEAT_ASSIGN; cheat->address = BASE_IO | (op1 & OFFSET_MASK); break; } + + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); return true; } From 3a3b7dffdb9e69763a9567119e45312d4b1453df Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 Mar 2015 13:56:46 -0700 Subject: [PATCH 19/92] GBA: Fix Pro Action Replay ROM patches --- src/gba/cheats.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gba/cheats.c b/src/gba/cheats.c index d2892765a..1369391cc 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -435,25 +435,25 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { return false; // TODO: Fix overriding existing patches case PAR3_OTHER_PATCH_1: - cheats->romPatches[0].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); cheats->romPatches[0].applied = false; cheats->romPatches[0].exists = true; cheats->incompletePatch = &cheats->romPatches[0]; break; case PAR3_OTHER_PATCH_2: - cheats->romPatches[1].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); cheats->romPatches[1].applied = false; cheats->romPatches[1].exists = true; cheats->incompletePatch = &cheats->romPatches[1]; break; case PAR3_OTHER_PATCH_3: - cheats->romPatches[2].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); cheats->romPatches[2].applied = false; cheats->romPatches[2].exists = true; cheats->incompletePatch = &cheats->romPatches[2]; break; case PAR3_OTHER_PATCH_4: - cheats->romPatches[3].address = (op2 & 0xFFFFFF) << 1; + cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); cheats->romPatches[3].applied = false; cheats->romPatches[3].exists = true; cheats->incompletePatch = &cheats->romPatches[3]; From cab9f3343b64865761243cf3014cbcb4d6771c56 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 Mar 2015 17:12:39 -0700 Subject: [PATCH 20/92] Qt: Double-clicking on the window toggles full screen --- CHANGES | 1 + src/platform/qt/Window.cpp | 7 +++++++ src/platform/qt/Window.h | 1 + 3 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index eaf6bff2b..e274ccf02 100644 --- a/CHANGES +++ b/CHANGES @@ -85,6 +85,7 @@ Misc: - GBA: Move A/V stream interface into core - GBA: Savestates now take into account savedata state machines (fixes #109) - Qt: Remember window position + - Qt: Double-clicking on the window toggles full screen 0.1.1: (2015-01-24) Bugfixes: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 9d2f0efe8..732270c0b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -385,6 +385,13 @@ void Window::dropEvent(QDropEvent* event) { m_controller->loadGame(url.path()); } +void Window::mouseDoubleClickEvent(QMouseEvent* event) { + if (event->button() != Qt::LeftButton) { + return; + } + toggleFullScreen(); +} + void Window::enterFullScreen() { if (isFullScreen()) { return; diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 85a3f1907..ac1b5f688 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -97,6 +97,7 @@ protected: virtual void focusOutEvent(QFocusEvent*) override; virtual void dragEnterEvent(QDragEnterEvent*) override; virtual void dropEvent(QDropEvent*) override; + virtual void mouseDoubleClickEvent(QMouseEvent*) override; private slots: void gameStarted(GBAThread*); From 8fcd095b037f332efe00cc6002507ad2252d7f39 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 Mar 2015 02:22:29 -0700 Subject: [PATCH 21/92] GBA Audio: #define some macros BEFORE we use them --- src/gba/audio.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gba/audio.h b/src/gba/audio.h index 2aa5953e5..91cc84b1b 100644 --- a/src/gba/audio.h +++ b/src/gba/audio.h @@ -11,6 +11,9 @@ #include "util/circle-buffer.h" +#define RESAMPLE_NN 0 +#define RESAMPLE_BLIP_BUF 2 + #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF #include "third-party/blip_buf/blip_buf.h" #endif @@ -19,9 +22,6 @@ struct GBADMA; extern const unsigned GBA_AUDIO_SAMPLES; -#define RESAMPLE_NN 0 -#define RESAMPLE_BLIP_BUF 2 - DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t); DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6); DECL_BITS(GBAAudioRegisterEnvelope, Duty, 6, 2); From 2eb765eacc646b67440c021a2925951700dda460 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 31 Mar 2015 21:38:55 -0700 Subject: [PATCH 22/92] SDL: Fix pixman build if includes aren't in base path --- src/platform/sdl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index 3134ffc63..c9a6f8ec5 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -35,7 +35,7 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VE file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c) set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${PIXMAN-1_LIBRARIES}) -include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${PIXMAN-1_INCLUDE_DIR} ${SDL_INCLUDE_DIR}) set(SDL_INCLUDE_DIR "${SDL_INCLUDE_DIR}" PARENT_SCOPE) set(SDL_LIBRARY "${SDL_LIBRARY}" PARENT_SCOPE) From bed6a0c1302d49ae673b6df31cdbb4410abba4f5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 31 Mar 2015 21:41:53 -0700 Subject: [PATCH 23/92] SDL: Allow runtime switching of rendering backend --- src/platform/sdl/CMakeLists.txt | 13 +++++++------ src/platform/sdl/gl-sdl.c | 16 +++++++++++++--- src/platform/sdl/main.c | 24 +++++++++++++++--------- src/platform/sdl/main.h | 16 +++++++++------- src/platform/sdl/sw-sdl.c | 30 ++++++++++++++++++++---------- 5 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index c9a6f8ec5..19c4867a1 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -55,13 +55,14 @@ endif() if(BUILD_PANDORA) list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/pandora-sdl.c) -elseif(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL) - list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c) else() - list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c) - add_definitions(-DBUILD_GL) - find_package(OpenGL REQUIRED) - include_directories(${OPENGL_INCLUDE_DIR}) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c) + if(BUILD_GL) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c) + add_definitions(-DBUILD_GL) + find_package(OpenGL REQUIRED) + include_directories(${OPENGL_INCLUDE_DIR}) + endif() endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index fd73a43c3..975166be7 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -49,7 +49,17 @@ static void _doViewport(int w, int h, struct SDLSoftwareRenderer* renderer) { glClear(GL_COLOR_BUFFER_BIT); } -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { +static bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLGLInit; + renderer->deinit = GBASDLGLDeinit; + renderer->runloop = GBASDLGLRunloop; +} + +bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) { #ifndef COLOR_16_BIT SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); @@ -110,7 +120,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { return true; } -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { +void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { SDL_Event event; glEnable(GL_TEXTURE_2D); @@ -159,6 +169,6 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render } } -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { +void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer) { free(renderer->d.outputBuffer); } diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index c52d8d342..79067e09b 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -28,8 +28,8 @@ #define PORT "sdl" -static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer); -static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer); +static bool GBASDLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer); int main(int argc, char** argv) { struct SDLSoftwareRenderer renderer; @@ -83,7 +83,13 @@ int main(int argc, char** argv) { renderer.lockAspectRatio = opts.lockAspectRatio; renderer.filter = opts.resampleVideo; - if (!_GBASDLInit(&renderer)) { +#ifdef BUILD_GL + GBASDLGLCreate(&renderer); +#else + GBASDLSWCreate(&renderer); +#endif + + if (!GBASDLInit(&renderer)) { freeArguments(&args); GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config); @@ -113,7 +119,7 @@ int main(int argc, char** argv) { int didFail = 0; if (GBAThreadStart(&context)) { - GBASDLRunloop(&context, &renderer); + renderer.runloop(&context, &renderer); GBAThreadJoin(&context); } else { didFail = 1; @@ -130,28 +136,28 @@ int main(int argc, char** argv) { free(context.debugger); GBAInputMapDeinit(&inputMap); - _GBASDLDeinit(&renderer); + GBASDLDeinit(&renderer); return didFail; } -static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer) { +static bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("Could not initialize video: %s\n", SDL_GetError()); return false; } - return GBASDLInit(renderer); + return renderer->init(renderer); } -static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { +static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { GBASDLDeinitEvents(&renderer->events); GBASDLDeinitAudio(&renderer->audio); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_DestroyWindow(renderer->window); #endif - GBASDLDeinit(renderer); + renderer->deinit(renderer); SDL_Quit(); diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index 07587c281..80e271245 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -41,12 +41,14 @@ struct SDLSoftwareRenderer { struct GBASDLEvents events; struct GBASDLPlayer player; + bool (*init)(struct SDLSoftwareRenderer* renderer); + void (*runloop)(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); + void (*deinit)(struct SDLSoftwareRenderer* renderer); + #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Window* window; -#ifndef BUILD_GL - SDL_Texture* tex; + SDL_Texture* sdlTex; SDL_Renderer* sdlRenderer; -#endif #endif int viewportWidth; @@ -86,9 +88,9 @@ struct SDLSoftwareRenderer { #endif }; -bool GBASDLInit(struct SDLSoftwareRenderer* renderer); -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer); -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer); +#ifdef BUILD_GL +void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer); +#endif #endif - diff --git a/src/platform/sdl/sw-sdl.c b/src/platform/sdl/sw-sdl.c index 971710554..f44fad38d 100644 --- a/src/platform/sdl/sw-sdl.c +++ b/src/platform/sdl/sw-sdl.c @@ -8,7 +8,17 @@ #include "gba/supervisor/thread.h" #include "util/arm-algo.h" -bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { +static bool GBASDLSWInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLSWDeinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLSWInit; + renderer->deinit = GBASDLSWDeinit; + renderer->runloop = GBASDLSWRunloop; +} + +bool GBASDLSWInit(struct SDLSoftwareRenderer* renderer) { #if !SDL_VERSION_ATLEAST(2, 0, 0) #ifdef COLOR_16_BIT SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); @@ -24,15 +34,15 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { renderer->sdlRenderer = SDL_CreateRenderer(renderer->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #else - renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR1555, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR1555, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif #else - renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif - SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); + SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); renderer->d.outputBufferStride /= BYTES_PER_PIXEL; #else SDL_Surface* surface = SDL_GetVideoSurface(); @@ -72,7 +82,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { return true; } -void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { +void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) { SDL_Event event; #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_Surface* surface = SDL_GetVideoSurface(); @@ -85,10 +95,10 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_UnlockTexture(renderer->tex); - SDL_RenderCopy(renderer->sdlRenderer, renderer->tex, 0, 0); + SDL_UnlockTexture(renderer->sdlTex); + SDL_RenderCopy(renderer->sdlRenderer, renderer->sdlTex, 0, 0); SDL_RenderPresent(renderer->sdlRenderer); - SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); + SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); renderer->d.outputBufferStride /= BYTES_PER_PIXEL; #else #ifdef USE_PIXMAN @@ -122,7 +132,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render } } -void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) { +void GBASDLSWDeinit(struct SDLSoftwareRenderer* renderer) { if (renderer->ratio > 1) { free(renderer->d.outputBuffer); } From 4fb121bbe0e78bb664f19c463518c0513ee4d196 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 31 Mar 2015 22:58:29 -0700 Subject: [PATCH 24/92] Qt: Start modularizing Qt drawing --- src/platform/qt/CMakeLists.txt | 1 + src/platform/qt/Display.cpp | 278 +------------------------------ src/platform/qt/Display.h | 72 ++------ src/platform/qt/DisplayGL.cpp | 291 +++++++++++++++++++++++++++++++++ src/platform/qt/DisplayGL.h | 87 ++++++++++ src/platform/qt/Window.cpp | 10 +- src/platform/qt/Window.h | 2 +- 7 files changed, 400 insertions(+), 341 deletions(-) create mode 100644 src/platform/qt/DisplayGL.cpp create mode 100644 src/platform/qt/DisplayGL.h diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 69cf68712..897fc26a8 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -41,6 +41,7 @@ set(SOURCE_FILES CheatsView.cpp ConfigController.cpp Display.cpp + DisplayGL.cpp GBAApp.cpp GBAKeyEditor.cpp GIFView.cpp diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 989d967b7..fd4870d21 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -5,283 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Display.h" -#include -#include - -extern "C" { -#include "gba/supervisor/thread.h" -} - using namespace QGBA; -static const GLint _glVertices[] = { - 0, 0, - 256, 0, - 256, 256, - 0, 256 -}; - -static const GLint _glTexCoords[] = { - 0, 0, - 1, 0, - 1, 1, - 0, 1 -}; - -Display::Display(QGLFormat format, QWidget* parent) - : QGLWidget(format, parent) - , m_painter(nullptr) - , m_started(false) +Display::Display(QWidget* parent) + : QWidget(parent) { - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - setAutoBufferSwap(false); - setCursor(Qt::BlankCursor); -} - -void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) { - if (m_started) { - return; - } - m_painter = new Painter(this); - m_painter->setContext(thread); - m_painter->setBacking(buffer); - m_context = thread; - doneCurrent(); - m_painter->start(); - m_started = true; - - lockAspectRatio(m_lockAspectRatio); - filter(m_filter); -} - -void Display::stopDrawing() { - if (m_started) { - if (GBAThreadIsActive(m_context)) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - } - m_painter->stop(); - m_started = false; - if (GBAThreadIsActive(m_context)) { - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } - } -} - -void Display::pauseDrawing() { - if (m_started) { - if (GBAThreadIsActive(m_context)) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - } - m_painter->pause(); - if (GBAThreadIsActive(m_context)) { - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } - } -} - -void Display::unpauseDrawing() { - if (m_started) { - if (GBAThreadIsActive(m_context)) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - } - m_painter->unpause(); - if (GBAThreadIsActive(m_context)) { - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } - } -} - -void Display::forceDraw() { - if (m_started) { - m_painter->forceDraw(); - } -} - -void Display::lockAspectRatio(bool lock) { - m_lockAspectRatio = lock; - if (m_started) { - m_painter->lockAspectRatio(lock); - } -} - -void Display::filter(bool filter) { - m_filter = filter; - if (m_started) { - m_painter->filter(filter); - } -} - -#ifdef USE_PNG -void Display::screenshot() { - GBAThreadInterrupt(m_context); - GBAThreadTakeScreenshot(m_context); - GBAThreadContinue(m_context); -} -#endif - -void Display::initializeGL() { - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - swapBuffers(); -} - -void Display::resizeEvent(QResizeEvent* event) { - if (m_started) { - GBAThreadInterrupt(m_context); - GBASyncSuspendDrawing(&m_context->sync); - m_painter->resize(event->size()); - GBASyncResumeDrawing(&m_context->sync); - GBAThreadContinue(m_context); - } -} - -Painter::Painter(Display* parent) - : m_gl(parent) - , m_lockAspectRatio(false) - , m_filter(false) -{ - m_size = parent->size(); -} - -void Painter::setContext(GBAThread* context) { - m_context = context; -} - -void Painter::setBacking(const uint32_t* backing) { - m_backing = backing; -} - -void Painter::resize(const QSize& size) { - m_size = size; - forceDraw(); - forceDraw(); -} - -void Painter::lockAspectRatio(bool lock) { - m_lockAspectRatio = lock; - forceDraw(); - forceDraw(); -} - -void Painter::filter(bool filter) { - m_filter = filter; - m_gl->makeCurrent(); - if (m_filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - m_gl->doneCurrent(); - forceDraw(); -} - -void Painter::start() { - m_gl->makeCurrent(); - glEnable(GL_TEXTURE_2D); - glGenTextures(1, &m_tex); - glBindTexture(GL_TEXTURE_2D, m_tex); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - if (m_filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_INT, 0, _glVertices); - glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, 240, 160, 0, 0, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - m_gl->doneCurrent(); - - m_drawTimer = new QTimer; - m_drawTimer->moveToThread(QThread::currentThread()); - m_drawTimer->setInterval(0); - connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw())); - m_drawTimer->start(); -} - -void Painter::draw() { - m_gl->makeCurrent(); - GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip); - performDraw(); - GBASyncWaitFrameEnd(&m_context->sync); - m_gl->swapBuffers(); - m_gl->doneCurrent(); -} - -void Painter::forceDraw() { - m_gl->makeCurrent(); - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glClear(GL_COLOR_BUFFER_BIT); - performDraw(); - m_gl->swapBuffers(); - m_gl->doneCurrent(); -} - -void Painter::stop() { - m_drawTimer->stop(); - delete m_drawTimer; - m_gl->makeCurrent(); - glDeleteTextures(1, &m_tex); - glClear(GL_COLOR_BUFFER_BIT); - m_gl->swapBuffers(); - m_gl->doneCurrent(); - m_gl->context()->moveToThread(QApplication::instance()->thread()); -} - -void Painter::pause() { - m_drawTimer->stop(); - // Make sure both buffers are filled - forceDraw(); - forceDraw(); -} - -void Painter::unpause() { - m_drawTimer->start(); -} - -void Painter::performDraw() { - int w = m_size.width() * m_gl->devicePixelRatio(); - int h = m_size.height() * m_gl->devicePixelRatio(); -#ifndef Q_OS_MAC - // TODO: This seems to cause framerates to drag down to 120 FPS on OS X, - // even if the emulator can go faster. Look into why. - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glClear(GL_COLOR_BUFFER_BIT); -#endif - int drawW = w; - int drawH = h; - if (m_lockAspectRatio) { - if (w * 2 > h * 3) { - drawW = h * 3 / 2; - } else if (w * 2 < h * 3) { - drawH = w * 2 / 3; - } - } - glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); -#endif -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); -#endif - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - if (m_context->sync.videoFrameWait) { - glFlush(); - } } diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 07c22e087..7cad04307 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 @@ -6,77 +6,29 @@ #ifndef QGBA_DISPLAY #define QGBA_DISPLAY -#include -#include -#include +#include struct GBAThread; namespace QGBA { -class Painter; -class Display : public QGLWidget { +class Display : public QWidget { Q_OBJECT public: - Display(QGLFormat format, QWidget* parent = nullptr); + Display(QWidget* parent = nullptr); public slots: - void startDrawing(const uint32_t* buffer, GBAThread* context); - void stopDrawing(); - void pauseDrawing(); - void unpauseDrawing(); - void forceDraw(); - void lockAspectRatio(bool lock); - void filter(bool filter); + virtual void startDrawing(const uint32_t* buffer, GBAThread* context) = 0; + virtual void stopDrawing() = 0; + virtual void pauseDrawing() = 0; + virtual void unpauseDrawing() = 0; + virtual void forceDraw() = 0; + virtual void lockAspectRatio(bool lock) = 0; + virtual void filter(bool filter) = 0; #ifdef USE_PNG - void screenshot(); + virtual void screenshot() = 0; #endif - -protected: - virtual void initializeGL() override; - virtual void paintEvent(QPaintEvent*) override {}; - virtual void resizeEvent(QResizeEvent*) override; - -private: - Painter* m_painter; - bool m_started; - GBAThread* m_context; - bool m_lockAspectRatio; - bool m_filter; -}; - -class Painter : public QObject { -Q_OBJECT - -public: - Painter(Display* parent); - - void setContext(GBAThread*); - void setBacking(const uint32_t*); - -public slots: - void forceDraw(); - void draw(); - void start(); - void stop(); - void pause(); - void unpause(); - void resize(const QSize& size); - void lockAspectRatio(bool lock); - void filter(bool filter); - -private: - void performDraw(); - - QTimer* m_drawTimer; - GBAThread* m_context; - const uint32_t* m_backing; - GLuint m_tex; - QGLWidget* m_gl; - QSize m_size; - bool m_lockAspectRatio; - bool m_filter; }; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp new file mode 100644 index 000000000..f708287a3 --- /dev/null +++ b/src/platform/qt/DisplayGL.cpp @@ -0,0 +1,291 @@ +/* Copyright (c) 2013-2015 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 "DisplayGL.h" + +#include +#include + +extern "C" { +#include "gba/supervisor/thread.h" +} + +using namespace QGBA; + +static const GLint _glVertices[] = { + 0, 0, + 256, 0, + 256, 256, + 0, 256 +}; + +static const GLint _glTexCoords[] = { + 0, 0, + 1, 0, + 1, 1, + 0, 1 +}; + +DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) + : Display(parent) + , m_painter(new Painter(format, this)) + , m_started(false) +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + setCursor(Qt::BlankCursor); +} + +void DisplayGL::startDrawing(const uint32_t* buffer, GBAThread* thread) { + if (m_started) { + return; + } + m_painter->setContext(thread); + m_painter->setBacking(buffer); + m_context = thread; + m_painter->start(); + m_painter->resize(size()); + m_painter->move(0, 0); + m_started = true; + + lockAspectRatio(m_lockAspectRatio); + filter(m_filter); +} + +void DisplayGL::stopDrawing() { + if (m_started) { + if (GBAThreadIsActive(m_context)) { + GBAThreadInterrupt(m_context); + GBASyncSuspendDrawing(&m_context->sync); + } + m_painter->stop(); + m_started = false; + if (GBAThreadIsActive(m_context)) { + GBASyncResumeDrawing(&m_context->sync); + GBAThreadContinue(m_context); + } + } +} + +void DisplayGL::pauseDrawing() { + if (m_started) { + if (GBAThreadIsActive(m_context)) { + GBAThreadInterrupt(m_context); + GBASyncSuspendDrawing(&m_context->sync); + } + m_painter->pause(); + if (GBAThreadIsActive(m_context)) { + GBASyncResumeDrawing(&m_context->sync); + GBAThreadContinue(m_context); + } + } +} + +void DisplayGL::unpauseDrawing() { + if (m_started) { + if (GBAThreadIsActive(m_context)) { + GBAThreadInterrupt(m_context); + GBASyncSuspendDrawing(&m_context->sync); + } + m_painter->unpause(); + if (GBAThreadIsActive(m_context)) { + GBASyncResumeDrawing(&m_context->sync); + GBAThreadContinue(m_context); + } + } +} + +void DisplayGL::forceDraw() { + if (m_started) { + m_painter->forceDraw(); + } +} + +void DisplayGL::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + if (m_started) { + m_painter->lockAspectRatio(lock); + } +} + +void DisplayGL::filter(bool filter) { + m_filter = filter; + if (m_started) { + m_painter->filter(filter); + } +} + +#ifdef USE_PNG +void DisplayGL::screenshot() { + GBAThreadInterrupt(m_context); + GBAThreadTakeScreenshot(m_context); + GBAThreadContinue(m_context); +} +#endif + +void DisplayGL::resizeEvent(QResizeEvent* event) { + m_painter->resize(event->size()); +} + +Painter::Painter(const QGLFormat& format, QWidget* parent) + : QGLWidget(format, parent) + , m_drawTimer(nullptr) + , m_lockAspectRatio(false) + , m_filter(false) +{ + setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + m_size = parent->size(); + setAutoBufferSwap(false); +} + +void Painter::setContext(GBAThread* context) { + m_context = context; +} + +void Painter::setBacking(const uint32_t* backing) { + m_backing = backing; +} + +void Painter::resize(const QSize& size) { + m_size = size; + QWidget::resize(size); + if (m_drawTimer) { + forceDraw(); + forceDraw(); + } +} + +void Painter::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + if (m_drawTimer) { + forceDraw(); + forceDraw(); + } +} + +void Painter::filter(bool filter) { + m_filter = filter; + makeCurrent(); + if (m_filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + doneCurrent(); + if (m_drawTimer) { + forceDraw(); + forceDraw(); + } +} + +void Painter::start() { + makeCurrent(); + glEnable(GL_TEXTURE_2D); + glGenTextures(1, &m_tex); + glBindTexture(GL_TEXTURE_2D, m_tex); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (m_filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_INT, 0, _glVertices); + glTexCoordPointer(2, GL_INT, 0, _glTexCoords); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, 240, 160, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + doneCurrent(); + + m_drawTimer = new QTimer; + m_drawTimer->moveToThread(QThread::currentThread()); + m_drawTimer->setInterval(0); + connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw())); + m_drawTimer->start(); +} + +void Painter::draw() { + makeCurrent(); + GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip); + performDraw(); + GBASyncWaitFrameEnd(&m_context->sync); + swapBuffers(); + doneCurrent(); +} + +void Painter::forceDraw() { + makeCurrent(); + glViewport(0, 0, m_size.width() * devicePixelRatio(), m_size.height() * devicePixelRatio()); + glClear(GL_COLOR_BUFFER_BIT); + performDraw(); + swapBuffers(); + doneCurrent(); +} + +void Painter::stop() { + m_drawTimer->stop(); + delete m_drawTimer; + m_drawTimer = nullptr; + makeCurrent(); + glDeleteTextures(1, &m_tex); + glClear(GL_COLOR_BUFFER_BIT); + swapBuffers(); + doneCurrent(); +} + +void Painter::pause() { + m_drawTimer->stop(); + // Make sure both buffers are filled + forceDraw(); + forceDraw(); +} + +void Painter::unpause() { + m_drawTimer->start(); +} + +void Painter::initializeGL() { + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + swapBuffers(); +} + +void Painter::performDraw() { + int w = m_size.width() * devicePixelRatio(); + int h = m_size.height() * devicePixelRatio(); +#ifndef Q_OS_MAC + // TODO: This seems to cause framerates to drag down to 120 FPS on OS X, + // even if the emulator can go faster. Look into why. + glViewport(0, 0, m_size.width() * devicePixelRatio(), m_size.height() * devicePixelRatio()); + glClear(GL_COLOR_BUFFER_BIT); +#endif + int drawW = w; + int drawH = h; + if (m_lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); +#endif + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + if (m_context->sync.videoFrameWait) { + glFlush(); + } +} diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h new file mode 100644 index 000000000..e7c0b9bc6 --- /dev/null +++ b/src/platform/qt/DisplayGL.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_DISPLAY_GL +#define QGBA_DISPLAY_GL + +#include "Display.h" + +#include +#include +#include + +struct GBAThread; + +namespace QGBA { + +class Painter; +class DisplayGL : public Display { +Q_OBJECT + +public: + DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); + +public slots: + void startDrawing(const uint32_t* buffer, GBAThread* context); + void stopDrawing(); + void pauseDrawing(); + void unpauseDrawing(); + void forceDraw(); + void lockAspectRatio(bool lock); + void filter(bool filter); +#ifdef USE_PNG + void screenshot(); +#endif + +protected: + virtual void paintEvent(QPaintEvent*) override {}; + virtual void resizeEvent(QResizeEvent*) override; + +private: + Painter* m_painter; + bool m_started; + GBAThread* m_context; + bool m_lockAspectRatio; + bool m_filter; +}; + +class Painter : public QGLWidget { +Q_OBJECT + +public: + Painter(const QGLFormat& format, QWidget* parent); + + void setContext(GBAThread*); + void setBacking(const uint32_t*); + +public slots: + void forceDraw(); + void draw(); + void start(); + void stop(); + void pause(); + void unpause(); + void resize(const QSize& size); + void lockAspectRatio(bool lock); + void filter(bool filter); + +protected: + virtual void initializeGL() override; + +private: + void performDraw(); + + QTimer* m_drawTimer; + GBAThread* m_context; + const uint32_t* m_backing; + GLuint m_tex; + QSize m_size; + bool m_lockAspectRatio; + bool m_filter; +}; + +} + +#endif diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 732270c0b..1eb8b52ad 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -16,6 +16,7 @@ #include "CheatsView.h" #include "ConfigController.h" +#include "DisplayGL.h" #include "GameController.h" #include "GBAKeyEditor.h" #include "GDBController.h" @@ -67,7 +68,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); format.setSwapInterval(1); - m_display = new Display(format); + m_display = new DisplayGL(format); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap @@ -857,6 +858,7 @@ void Window::setupMenu(QMenuBar* menubar) { } void Window::attachWidget(QWidget* widget) { + m_screenWidget->clear(); m_screenWidget->layout()->addWidget(widget); static_cast(m_screenWidget->layout())->setCurrentWidget(widget); } @@ -924,13 +926,13 @@ void WindowBackground::setLockAspectRatio(int width, int height) { } void WindowBackground::paintEvent(QPaintEvent*) { - QPainter painter(this); - painter.setRenderHint(QPainter::SmoothPixmapTransform); const QPixmap* logo = pixmap(); - painter.fillRect(QRect(QPoint(), size()), Qt::black); if (!logo) { return; } + QPainter painter(this); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + painter.fillRect(QRect(QPoint(), size()), Qt::black); QSize s = size(); QSize ds = s; if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index ac1b5f688..e868147d3 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -18,7 +18,6 @@ extern "C" { } #include "GDBController.h" -#include "Display.h" #include "InputController.h" #include "LoadSaveState.h" @@ -28,6 +27,7 @@ struct GBAArguments; namespace QGBA { class ConfigController; +class Display; class GameController; class GIFView; class LogView; From 0d6cc88f9fc1169dd9549647af8eb6b013813162 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 31 Mar 2015 23:37:14 -0700 Subject: [PATCH 25/92] Qt: Remove unneeded second draws --- src/platform/qt/DisplayGL.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index f708287a3..074639c95 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -153,7 +153,6 @@ void Painter::resize(const QSize& size) { QWidget::resize(size); if (m_drawTimer) { forceDraw(); - forceDraw(); } } @@ -161,7 +160,6 @@ void Painter::lockAspectRatio(bool lock) { m_lockAspectRatio = lock; if (m_drawTimer) { forceDraw(); - forceDraw(); } } @@ -176,7 +174,6 @@ void Painter::filter(bool filter) { doneCurrent(); if (m_drawTimer) { forceDraw(); - forceDraw(); } } From 11eab66247ed0ac13508921a9fd6b3a3ae14ed0e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 31 Mar 2015 23:43:47 -0700 Subject: [PATCH 26/92] Qt: Move screenshot to GameController --- src/platform/qt/Display.h | 3 --- src/platform/qt/DisplayGL.cpp | 8 -------- src/platform/qt/DisplayGL.h | 3 --- src/platform/qt/GameController.cpp | 8 ++++++++ src/platform/qt/GameController.h | 4 ++++ src/platform/qt/Window.cpp | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 7cad04307..85e799c9e 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -26,9 +26,6 @@ public slots: virtual void forceDraw() = 0; virtual void lockAspectRatio(bool lock) = 0; virtual void filter(bool filter) = 0; -#ifdef USE_PNG - virtual void screenshot() = 0; -#endif }; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 074639c95..f9847e5c5 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -117,14 +117,6 @@ void DisplayGL::filter(bool filter) { } } -#ifdef USE_PNG -void DisplayGL::screenshot() { - GBAThreadInterrupt(m_context); - GBAThreadTakeScreenshot(m_context); - GBAThreadContinue(m_context); -} -#endif - void DisplayGL::resizeEvent(QResizeEvent* event) { m_painter->resize(event->size()); } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index e7c0b9bc6..d247c8de0 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -31,9 +31,6 @@ public slots: void forceDraw(); void lockAspectRatio(bool lock); void filter(bool filter); -#ifdef USE_PNG - void screenshot(); -#endif protected: virtual void paintEvent(QPaintEvent*) override {}; diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 38bcc0ff8..20d8e30a1 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -520,6 +520,14 @@ void GameController::clearAVStream() { threadContinue(); } +#ifdef USE_PNG +void GameController::screenshot() { + GBAThreadInterrupt(&m_threadContext); + GBAThreadTakeScreenshot(&m_threadContext); + GBAThreadContinue(&m_threadContext); +} +#endif + void GameController::reloadAudioDriver() { QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); int samples = m_audioProcessor->getBufferSamples(); diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index a7036f7f8..0869a59a0 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -119,6 +119,10 @@ public slots: void clearAVStream(); void reloadAudioDriver(); +#ifdef USE_PNG + void screenshot(); +#endif + void setLuminanceValue(uint8_t value); uint8_t luminanceValue() const { return m_luxValue; } void setLuminanceLevel(int level); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 1eb8b52ad..979ad8f6f 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -738,7 +738,7 @@ void Window::setupMenu(QMenuBar* menubar) { #ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); screenshot->setShortcut(tr("F12")); - connect(screenshot, SIGNAL(triggered()), m_display, SLOT(screenshot())); + connect(screenshot, SIGNAL(triggered()), m_controller, SLOT(screenshot())); m_gameActions.append(screenshot); addControlledAction(avMenu, screenshot, "screenshot"); #endif From f2559ad169ac3136e694e5482f1129d7d8341641 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 1 Apr 2015 00:35:55 -0700 Subject: [PATCH 27/92] Qt: Add pure-Qt display backend --- src/platform/qt/CMakeLists.txt | 1 + src/platform/qt/DisplayQt.cpp | 82 ++++++++++++++++++++++++++++++++++ src/platform/qt/DisplayQt.h | 46 +++++++++++++++++++ src/platform/qt/Window.cpp | 1 + 4 files changed, 130 insertions(+) create mode 100644 src/platform/qt/DisplayQt.cpp create mode 100644 src/platform/qt/DisplayQt.h diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 897fc26a8..202431f37 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -42,6 +42,7 @@ set(SOURCE_FILES ConfigController.cpp Display.cpp DisplayGL.cpp + DisplayQt.cpp GBAApp.cpp GBAKeyEditor.cpp GIFView.cpp diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp new file mode 100644 index 000000000..34c43bda6 --- /dev/null +++ b/src/platform/qt/DisplayQt.cpp @@ -0,0 +1,82 @@ +/* Copyright (c) 2013-2015 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 "DisplayQt.h" + +#include + +using namespace QGBA; + +DisplayQt::DisplayQt(QWidget* parent) + : Display(parent) + , m_lockAspectRatio(false) + , m_filter(false) +{ + connect(&m_drawTimer, SIGNAL(timeout()), this, SLOT(update())); + m_drawTimer.setInterval(12); // Give update time roughly 4.6ms of clearance +} + +void DisplayQt::startDrawing(const uint32_t* buffer, GBAThread* context) { + m_context = context; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB16); +#else + m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB555); +#endif +#else + m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB32); +#endif + m_drawTimer.start(); +} + +void DisplayQt::stopDrawing() { + m_drawTimer.stop(); +} + +void DisplayQt::pauseDrawing() { + m_drawTimer.stop(); +} + +void DisplayQt::unpauseDrawing() { + m_drawTimer.start(); +} + +void DisplayQt::forceDraw() { + update(); +} + +void DisplayQt::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + update(); +} + +void DisplayQt::filter(bool filter) { + m_filter = filter; + update(); +} + +void DisplayQt::paintEvent(QPaintEvent*) { + QPainter painter(this); + painter.fillRect(QRect(QPoint(), size()), Qt::black); + if (m_filter) { + painter.setRenderHint(QPainter::SmoothPixmapTransform); + } + QSize s = size(); + QSize ds = s; + if (s.width() * 2 > s.height() * 3) { + ds.setWidth(s.height() * 3 / 2); + } else if (s.width() * 2 < s.height() * 3) { + ds.setHeight(s.width() * 2 / 3); + } + QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); + QRect full(origin, ds); + +#ifdef COLOR_5_6_5 + painter.drawImage(full, m_backing, QRect(0, 0, 240, 160)); +#else + painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, 240, 160)); +#endif +} diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h new file mode 100644 index 000000000..8476861f1 --- /dev/null +++ b/src/platform/qt/DisplayQt.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_DISPLAY_QT +#define QGBA_DISPLAY_QT + +#include "Display.h" + +#include +#include + +struct GBAThread; + +namespace QGBA { + +class DisplayQt : public Display { +Q_OBJECT + +public: + DisplayQt(QWidget* parent = nullptr); + +public slots: + void startDrawing(const uint32_t* buffer, GBAThread* context); + void stopDrawing(); + void pauseDrawing(); + void unpauseDrawing(); + void forceDraw(); + void lockAspectRatio(bool lock); + void filter(bool filter); + +protected: + virtual void paintEvent(QPaintEvent*) override; + +private: + QTimer m_drawTimer; + GBAThread* m_context; + QImage m_backing; + bool m_lockAspectRatio; + bool m_filter; +}; + +} + +#endif diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 979ad8f6f..cb7c87a0c 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "CheatsView.h" From 7bb17bc99ddd1764c99ce209cc017442e30e339f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 1 Apr 2015 21:13:16 -0700 Subject: [PATCH 28/92] Util: Split vfs.c into vfs.c, vfs-fd.c and vfs-dirent.c --- CMakeLists.txt | 8 +- src/gba/supervisor/config.c | 4 +- src/util/vfs.c | 378 +----------------------------------- src/util/vfs.h | 17 ++ src/util/vfs/vfs-dirent.c | 219 +++++++++++++++++++++ src/util/vfs/vfs-fd.c | 159 +++++++++++++++ 6 files changed, 404 insertions(+), 381 deletions(-) create mode 100644 src/util/vfs/vfs-dirent.c create mode 100644 src/util/vfs/vfs-fd.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 519261192..123cbb8f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,15 +21,15 @@ file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) -file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c) file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) +set(VFS_SRC) source_group("ARM core" FILES ${ARM_SRC}) source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC}) source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) -source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}}) +source_group("Utilities" FILES ${UTIL_SRC}) include_directories(${CMAKE_SOURCE_DIR}/src/arm) include_directories(${CMAKE_SOURCE_DIR}/src) @@ -117,6 +117,7 @@ if(WIN32) set(WIN32_VERSION "${LIB_VERSION_MAJOR},${LIB_VERSION_MINOR},${LIB_VERSION_PATCH}") add_definitions(-D_WIN32_WINNT=0x0600) list(APPEND OS_LIB ws2_32) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c) source_group("Windows-specific code" FILES ${OS_SRC}) else() @@ -129,6 +130,7 @@ else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") endif() + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c) source_group("POSIX-specific code" FILES ${OS_SRC}) endif() @@ -262,12 +264,14 @@ if(USE_LIBZIP) link_directories(${LIBZIP_LIBRARY_DIRS}) list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES}) list(APPEND FEATURES LIBZIP) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-zip.c) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2") endif() if (USE_LZMA) include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma) add_definitions(-D_7ZIP_PPMD_SUPPPORT) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c) set(LZMA_SRC ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c index 7b4058028..1f71c5851 100644 --- a/src/gba/supervisor/config.c +++ b/src/gba/supervisor/config.c @@ -6,6 +6,7 @@ #include "config.h" #include "util/formatting.h" +#include "util/vfs.h" #include @@ -13,9 +14,6 @@ #include #include #include -#define PATH_SEP "\\" -#else -#define PATH_SEP "/" #endif #define SECTION_NAME_MAX 128 diff --git a/src/util/vfs.c b/src/util/vfs.c index d706d0b32..038c4ba25 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -1,383 +1,9 @@ -/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "util/vfs.h" - -#include "util/string.h" - -#include -#include -#include - -#ifndef _WIN32 -#include -#define PATH_SEP '/' -#else -#include -#include -#define PATH_SEP '\\' -#endif - -struct VFileFD { - struct VFile d; - int fd; -#ifdef _WIN32 - HANDLE hMap; -#endif -}; - -static bool _vfdClose(struct VFile* vf); -static off_t _vfdSeek(struct VFile* vf, off_t offset, int whence); -static ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size); -static ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size); -static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size); -static void* _vfdMap(struct VFile* vf, size_t size, int flags); -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); -static void _vfdTruncate(struct VFile* vf, size_t size); -static ssize_t _vfdSize(struct VFile* vf); - -static bool _vdClose(struct VDir* vd); -static void _vdRewind(struct VDir* vd); -static struct VDirEntry* _vdListNext(struct VDir* vd); -static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); - -static const char* _vdeName(struct VDirEntry* vde); - -struct VFile* VFileOpen(const char* path, int flags) { - if (!path) { - return 0; - } -#ifdef _WIN32 - flags |= O_BINARY; -#endif - int fd = open(path, flags, 0666); - return VFileFromFD(fd); -} - -struct VFile* VFileFromFD(int fd) { - if (fd < 0) { - return 0; - } - - struct VFileFD* vfd = malloc(sizeof(struct VFileFD)); - if (!vfd) { - return 0; - } - - vfd->fd = fd; - vfd->d.close = _vfdClose; - vfd->d.seek = _vfdSeek; - vfd->d.read = _vfdRead; - vfd->d.readline = _vfdReadline; - vfd->d.write = _vfdWrite; - vfd->d.map = _vfdMap; - vfd->d.unmap = _vfdUnmap; - vfd->d.truncate = _vfdTruncate; - vfd->d.size = _vfdSize; - - return &vfd->d; -} - -bool _vfdClose(struct VFile* vf) { - struct VFileFD* vfd = (struct VFileFD*) vf; - if (close(vfd->fd) < 0) { - return false; - } - free(vfd); - return true; -} - -off_t _vfdSeek(struct VFile* vf, off_t offset, int whence) { - struct VFileFD* vfd = (struct VFileFD*) vf; - return lseek(vfd->fd, offset, whence); -} - -ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - return read(vfd->fd, buffer, size); -} - -ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - size_t bytesRead = 0; - while (bytesRead < size - 1) { - size_t newRead = read(vfd->fd, &buffer[bytesRead], 1); - if (!newRead || buffer[bytesRead] == '\n') { - break; - } - bytesRead += newRead; - } - buffer[bytesRead] = '\0'; - return bytesRead; -} - -ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - return write(vfd->fd, buffer, size); -} - -#ifndef _WIN32 -static void* _vfdMap(struct VFile* vf, size_t size, int flags) { - struct VFileFD* vfd = (struct VFileFD*) vf; - int mmapFlags = MAP_PRIVATE; - if (flags & MAP_WRITE) { - mmapFlags = MAP_SHARED; - } - return mmap(0, size, PROT_READ | PROT_WRITE, mmapFlags, vfd->fd, 0); -} - -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { - UNUSED(vf); - munmap(memory, size); -} -#else -static void* _vfdMap(struct VFile* vf, size_t size, int flags) { - struct VFileFD* vfd = (struct VFileFD*) vf; - int createFlags = PAGE_WRITECOPY; - int mapFiles = FILE_MAP_COPY; - if (flags & MAP_WRITE) { - createFlags = PAGE_READWRITE; - mapFiles = FILE_MAP_WRITE; - } - size_t fileSize; - struct stat stat; - if (fstat(vfd->fd, &stat) < 0) { - return 0; - } - fileSize = stat.st_size; - if (size > fileSize) { - size = fileSize; - } - vfd->hMap = CreateFileMapping((HANDLE) _get_osfhandle(vfd->fd), 0, createFlags, 0, size & 0xFFFFFFFF, 0); - return MapViewOfFile(vfd->hMap, mapFiles, 0, 0, size); -} - -static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { - UNUSED(size); - struct VFileFD* vfd = (struct VFileFD*) vf; - UnmapViewOfFile(memory); - CloseHandle(vfd->hMap); - vfd->hMap = 0; -} -#endif - -static void _vfdTruncate(struct VFile* vf, size_t size) { - struct VFileFD* vfd = (struct VFileFD*) vf; - ftruncate(vfd->fd, size); -} - -static ssize_t _vfdSize(struct VFile* vf) { - struct VFileFD* vfd = (struct VFileFD*) vf; - struct stat stat; - if (fstat(vfd->fd, &stat) < 0) { - return -1; - } - return stat.st_size; -} - -struct VDirEntryDE { - struct VDirEntry d; - struct dirent* ent; -}; - -struct VDirDE { - struct VDir d; - DIR* de; - struct VDirEntryDE vde; - char* path; -}; - -struct VDir* VDirOpen(const char* path) { - DIR* de = opendir(path); - if (!de) { - return 0; - } - - struct VDirDE* vd = malloc(sizeof(struct VDirDE)); - if (!vd) { - return 0; - } - - vd->d.close = _vdClose; - vd->d.rewind = _vdRewind; - vd->d.listNext = _vdListNext; - vd->d.openFile = _vdOpenFile; - vd->path = strdup(path); - vd->de = de; - - vd->vde.d.name = _vdeName; - - return &vd->d; -} - -struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - struct VFile* vf; - if (!dir) { - if (!realPath) { - return 0; - } - char* dotPoint = strrchr(realPath, '.'); - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - if (dotPoint > strrchr(realPath, '/')) { - int len = dotPoint - realPath; - strncpy(path, realPath, len); - path[len] = 0; - strncat(path + len, suffix, PATH_MAX - len - 1); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); - } - vf = VFileOpen(path, mode); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); - vf = dir->openFile(dir, path, mode); - } - return vf; -} - -struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - char realPrefix[PATH_MAX]; - realPrefix[PATH_MAX - 1] = '\0'; - if (!dir) { - if (!realPath) { - return 0; - } - const char* separatorPoint = strrchr(realPath, '/'); - const char* dotPoint; - size_t len; - if (!separatorPoint) { - strcpy(path, "./"); - separatorPoint = realPath; - dotPoint = strrchr(realPath, '.'); - } else { - path[0] = '\0'; - dotPoint = strrchr(separatorPoint, '.'); - - if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - len = separatorPoint - realPath; - strncat(path, realPath, len); - path[len] = '\0'; - ++separatorPoint; - } - - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - if (dotPoint >= separatorPoint) { - len = dotPoint - separatorPoint; - } else { - len = PATH_MAX - 1; - } - - strncpy(realPrefix, separatorPoint, len); - realPrefix[len] = '\0'; - - prefix = realPrefix; - dir = VDirOpen(path); - } - if (!dir) { - // This shouldn't be possible - return 0; - } - dir->rewind(dir); - struct VDirEntry* dirent; - size_t prefixLen = strlen(prefix); - size_t infixLen = strlen(infix); - unsigned next = 0; - while ((dirent = dir->listNext(dir))) { - const char* filename = dirent->name(dirent); - char* dotPoint = strrchr(filename, '.'); - size_t len = strlen(filename); - if (dotPoint) { - len = (dotPoint - filename); - } - const char* separator = strnrstr(filename, infix, len); - if (!separator) { - continue; - } - len = separator - filename; - if (len != prefixLen) { - continue; - } - if (strncmp(filename, prefix, prefixLen) == 0) { - int nlen; - separator += infixLen; - snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); - unsigned increment; - if (sscanf(separator, path, &increment, &nlen) < 1) { - continue; - } - len = strlen(separator); - if (nlen < (ssize_t) len) { - continue; - } - if (next <= increment) { - next = increment + 1; - } - } - } - snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); - path[PATH_MAX - 1] = '\0'; - return dir->openFile(dir, path, mode); -} - -bool _vdClose(struct VDir* vd) { - struct VDirDE* vdde = (struct VDirDE*) vd; - if (closedir(vdde->de) < 0) { - return false; - } - free(vdde->path); - free(vdde); - return true; -} - -void _vdRewind(struct VDir* vd) { - struct VDirDE* vdde = (struct VDirDE*) vd; - rewinddir(vdde->de); -} - -struct VDirEntry* _vdListNext(struct VDir* vd) { - struct VDirDE* vdde = (struct VDirDE*) vd; - vdde->vde.ent = readdir(vdde->de); - if (vdde->vde.ent) { - return &vdde->vde.d; - } - - return 0; -} - -struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) { - struct VDirDE* vdde = (struct VDirDE*) vd; - if (!path) { - return 0; - } - const char* dir = vdde->path; - char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s%c%s", dir, PATH_SEP, path); - - struct VFile* file = VFileOpen(combined, mode); - free(combined); - return file; -} - -const char* _vdeName(struct VDirEntry* vde) { - struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; - if (vdede->ent) { - return vdede->ent->d_name; - } - return 0; -} +#include "vfs.h" ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { size_t bytesRead = 0; diff --git a/src/util/vfs.h b/src/util/vfs.h index 17be7577f..1649838aa 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -8,6 +8,23 @@ #include "util/common.h" +#ifndef _WIN32 +#include +#define PATH_SEP "/" +#else +#include +#include +#define PATH_SEP "\\" +#endif + +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#define PATH_MAX 128 +#endif +#endif + enum { MAP_READ = 1, MAP_WRITE = 2 diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c new file mode 100644 index 000000000..4d69e8353 --- /dev/null +++ b/src/util/vfs/vfs-dirent.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2013-2015 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 "util/vfs.h" + +#include "util/string.h" + +#include + +static bool _vdClose(struct VDir* vd); +static void _vdRewind(struct VDir* vd); +static struct VDirEntry* _vdListNext(struct VDir* vd); +static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); + +static const char* _vdeName(struct VDirEntry* vde); + +struct VDirEntryDE { + struct VDirEntry d; + struct dirent* ent; +}; + +struct VDirDE { + struct VDir d; + DIR* de; + struct VDirEntryDE vde; + char* path; +}; + +struct VDir* VDirOpen(const char* path) { + DIR* de = opendir(path); + if (!de) { + return 0; + } + + struct VDirDE* vd = malloc(sizeof(struct VDirDE)); + if (!vd) { + return 0; + } + + vd->d.close = _vdClose; + vd->d.rewind = _vdRewind; + vd->d.listNext = _vdListNext; + vd->d.openFile = _vdOpenFile; + vd->path = strdup(path); + vd->de = de; + + vd->vde.d.name = _vdeName; + + return &vd->d; +} + +struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + struct VFile* vf; + if (!dir) { + if (!realPath) { + return 0; + } + char* dotPoint = strrchr(realPath, '.'); + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + if (dotPoint > strrchr(realPath, '/')) { + int len = dotPoint - realPath; + strncpy(path, realPath, len); + path[len] = 0; + strncat(path + len, suffix, PATH_MAX - len - 1); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); + } + vf = VFileOpen(path, mode); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); + vf = dir->openFile(dir, path, mode); + } + return vf; +} + +struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + char realPrefix[PATH_MAX]; + realPrefix[PATH_MAX - 1] = '\0'; + if (!dir) { + if (!realPath) { + return 0; + } + const char* separatorPoint = strrchr(realPath, '/'); + const char* dotPoint; + size_t len; + if (!separatorPoint) { + strcpy(path, "./"); + separatorPoint = realPath; + dotPoint = strrchr(realPath, '.'); + } else { + path[0] = '\0'; + dotPoint = strrchr(separatorPoint, '.'); + + if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + len = separatorPoint - realPath; + strncat(path, realPath, len); + path[len] = '\0'; + ++separatorPoint; + } + + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + if (dotPoint >= separatorPoint) { + len = dotPoint - separatorPoint; + } else { + len = PATH_MAX - 1; + } + + strncpy(realPrefix, separatorPoint, len); + realPrefix[len] = '\0'; + + prefix = realPrefix; + dir = VDirOpen(path); + } + if (!dir) { + // This shouldn't be possible + return 0; + } + dir->rewind(dir); + struct VDirEntry* dirent; + size_t prefixLen = strlen(prefix); + size_t infixLen = strlen(infix); + unsigned next = 0; + while ((dirent = dir->listNext(dir))) { + const char* filename = dirent->name(dirent); + char* dotPoint = strrchr(filename, '.'); + size_t len = strlen(filename); + if (dotPoint) { + len = (dotPoint - filename); + } + const char* separator = strnrstr(filename, infix, len); + if (!separator) { + continue; + } + len = separator - filename; + if (len != prefixLen) { + continue; + } + if (strncmp(filename, prefix, prefixLen) == 0) { + int nlen; + separator += infixLen; + snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); + unsigned increment; + if (sscanf(separator, path, &increment, &nlen) < 1) { + continue; + } + len = strlen(separator); + if (nlen < (ssize_t) len) { + continue; + } + if (next <= increment) { + next = increment + 1; + } + } + } + snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); + path[PATH_MAX - 1] = '\0'; + return dir->openFile(dir, path, mode); +} + +bool _vdClose(struct VDir* vd) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (closedir(vdde->de) < 0) { + return false; + } + free(vdde->path); + free(vdde); + return true; +} + +void _vdRewind(struct VDir* vd) { + struct VDirDE* vdde = (struct VDirDE*) vd; + rewinddir(vdde->de); +} + +struct VDirEntry* _vdListNext(struct VDir* vd) { + struct VDirDE* vdde = (struct VDirDE*) vd; + vdde->vde.ent = readdir(vdde->de); + if (vdde->vde.ent) { + return &vdde->vde.d; + } + + return 0; +} + +struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (!path) { + return 0; + } + const char* dir = vdde->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + struct VFile* file = VFileOpen(combined, mode); + free(combined); + return file; +} + +const char* _vdeName(struct VDirEntry* vde) { + struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; + if (vdede->ent) { + return vdede->ent->d_name; + } + return 0; +} diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c new file mode 100644 index 000000000..f3646c6c8 --- /dev/null +++ b/src/util/vfs/vfs-fd.c @@ -0,0 +1,159 @@ +/* Copyright (c) 2013-2015 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 "util/vfs.h" + +#include +#include + +struct VFileFD { + struct VFile d; + int fd; +#ifdef _WIN32 + HANDLE hMap; +#endif +}; + +static bool _vfdClose(struct VFile* vf); +static off_t _vfdSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size); +static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size); +static void* _vfdMap(struct VFile* vf, size_t size, int flags); +static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); +static void _vfdTruncate(struct VFile* vf, size_t size); +static ssize_t _vfdSize(struct VFile* vf); + +struct VFile* VFileOpen(const char* path, int flags) { + if (!path) { + return 0; + } +#ifdef _WIN32 + flags |= O_BINARY; +#endif + int fd = open(path, flags, 0666); + return VFileFromFD(fd); +} + +struct VFile* VFileFromFD(int fd) { + if (fd < 0) { + return 0; + } + + struct VFileFD* vfd = malloc(sizeof(struct VFileFD)); + if (!vfd) { + return 0; + } + + vfd->fd = fd; + vfd->d.close = _vfdClose; + vfd->d.seek = _vfdSeek; + vfd->d.read = _vfdRead; + vfd->d.readline = _vfdReadline; + vfd->d.write = _vfdWrite; + vfd->d.map = _vfdMap; + vfd->d.unmap = _vfdUnmap; + vfd->d.truncate = _vfdTruncate; + vfd->d.size = _vfdSize; + + return &vfd->d; +} + +bool _vfdClose(struct VFile* vf) { + struct VFileFD* vfd = (struct VFileFD*) vf; + if (close(vfd->fd) < 0) { + return false; + } + free(vfd); + return true; +} + +off_t _vfdSeek(struct VFile* vf, off_t offset, int whence) { + struct VFileFD* vfd = (struct VFileFD*) vf; + return lseek(vfd->fd, offset, whence); +} + +ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + return read(vfd->fd, buffer, size); +} + +ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + size_t bytesRead = 0; + while (bytesRead < size - 1) { + size_t newRead = read(vfd->fd, &buffer[bytesRead], 1); + if (!newRead || buffer[bytesRead] == '\n') { + break; + } + bytesRead += newRead; + } + buffer[bytesRead] = '\0'; + return bytesRead; +} + +ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + return write(vfd->fd, buffer, size); +} + +#ifndef _WIN32 +static void* _vfdMap(struct VFile* vf, size_t size, int flags) { + struct VFileFD* vfd = (struct VFileFD*) vf; + int mmapFlags = MAP_PRIVATE; + if (flags & MAP_WRITE) { + mmapFlags = MAP_SHARED; + } + return mmap(0, size, PROT_READ | PROT_WRITE, mmapFlags, vfd->fd, 0); +} + +static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(vf); + munmap(memory, size); +} +#else +static void* _vfdMap(struct VFile* vf, size_t size, int flags) { + struct VFileFD* vfd = (struct VFileFD*) vf; + int createFlags = PAGE_WRITECOPY; + int mapFiles = FILE_MAP_COPY; + if (flags & MAP_WRITE) { + createFlags = PAGE_READWRITE; + mapFiles = FILE_MAP_WRITE; + } + size_t fileSize; + struct stat stat; + if (fstat(vfd->fd, &stat) < 0) { + return 0; + } + fileSize = stat.st_size; + if (size > fileSize) { + size = fileSize; + } + vfd->hMap = CreateFileMapping((HANDLE) _get_osfhandle(vfd->fd), 0, createFlags, 0, size & 0xFFFFFFFF, 0); + return MapViewOfFile(vfd->hMap, mapFiles, 0, 0, size); +} + +static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(size); + struct VFileFD* vfd = (struct VFileFD*) vf; + UnmapViewOfFile(memory); + CloseHandle(vfd->hMap); + vfd->hMap = 0; +} +#endif + +static void _vfdTruncate(struct VFile* vf, size_t size) { + struct VFileFD* vfd = (struct VFileFD*) vf; + ftruncate(vfd->fd, size); +} + +static ssize_t _vfdSize(struct VFile* vf) { + struct VFileFD* vfd = (struct VFileFD*) vf; + struct stat stat; + if (fstat(vfd->fd, &stat) < 0) { + return -1; + } + return stat.st_size; +} From 24ff4e8a015c797d304b36be15df08868b811c63 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 1 Apr 2015 21:42:14 -0700 Subject: [PATCH 29/92] Util: Move VDirOptionalOpenFile back into vfs.c --- src/util/vfs.c | 28 ++++++++++++++++++++++++++++ src/util/vfs/vfs-dirent.c | 28 ---------------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/util/vfs.c b/src/util/vfs.c index 038c4ba25..b06db9c01 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -16,3 +16,31 @@ ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { } return buffer[bytesRead] = '\0'; } + +struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + struct VFile* vf; + if (!dir) { + if (!realPath) { + return 0; + } + char* dotPoint = strrchr(realPath, '.'); + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + if (dotPoint > strrchr(realPath, '/')) { + int len = dotPoint - realPath; + strncpy(path, realPath, len); + path[len] = 0; + strncat(path + len, suffix, PATH_MAX - len - 1); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); + } + vf = VFileOpen(path, mode); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); + vf = dir->openFile(dir, path, mode); + } + return vf; +} diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index 4d69e8353..37b07561b 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -51,34 +51,6 @@ struct VDir* VDirOpen(const char* path) { return &vd->d; } -struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - struct VFile* vf; - if (!dir) { - if (!realPath) { - return 0; - } - char* dotPoint = strrchr(realPath, '.'); - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - if (dotPoint > strrchr(realPath, '/')) { - int len = dotPoint - realPath; - strncpy(path, realPath, len); - path[len] = 0; - strncat(path + len, suffix, PATH_MAX - len - 1); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); - } - vf = VFileOpen(path, mode); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); - vf = dir->openFile(dir, path, mode); - } - return vf; -} - struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { char path[PATH_MAX]; path[PATH_MAX - 1] = '\0'; From 120020b0e39de4086495187f06c503fab2ed19e1 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 2 Apr 2015 02:29:13 -0700 Subject: [PATCH 30/92] All: Bump version to 0.3 after branching for 0.2 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 123cbb8f5..4dc8e9a9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,9 +71,9 @@ endfunction() # Version information set(LIB_VERSION_MAJOR 0) -set(LIB_VERSION_MINOR 2) +set(LIB_VERSION_MINOR 3) set(LIB_VERSION_PATCH 0) -set(LIB_VERSION_ABI 0.2) +set(LIB_VERSION_ABI 0.3) set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) # Advanced settings From 7cc903a217ebad9f1fff885a28cd973639a8d66e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 2 Apr 2015 22:32:38 -0700 Subject: [PATCH 31/92] Qt: Pause game while open file dialogs are open (fixes #6 on GitHub) --- CHANGES | 1 + src/platform/qt/Window.cpp | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGES b/CHANGES index e274ccf02..637248327 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,7 @@ Bugfixes: - GBA Memory: Fix I cycles that had been moved to ARM7 core - GBA Memory: Fix cycle counting for 32-bit load/stores - ARM7: Fix cycle counting for loads + - Qt: Pause game while open file dialogs are open (fixes #6 on GitHub) Misc: - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - GBA Memory: Simplify memory API and use fixed bus width diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index cb7c87a0c..019586fe1 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -198,7 +198,14 @@ void Window::saveConfig() { } void Window::selectROM() { + bool doPause = m_controller->isLoaded() && !m_controller->isPaused(); + if (doPause) { + m_controller->setPaused(true); + } QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM"), m_config->getQtOption("lastDirectory").toString(), tr("Game Boy Advance ROMs (*.gba *.zip *.rom *.bin)")); + if (doPause) { + m_controller->setPaused(false); + } if (!filename.isEmpty()) { m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_controller->loadGame(filename); @@ -206,7 +213,14 @@ void Window::selectROM() { } void Window::selectBIOS() { + bool doPause = m_controller->isLoaded() && !m_controller->isPaused(); + if (doPause) { + m_controller->setPaused(true); + } QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS"), m_config->getQtOption("lastDirectory").toString()); + if (doPause) { + m_controller->setPaused(false); + } if (!filename.isEmpty()) { m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_config->setOption("bios", filename); @@ -218,7 +232,14 @@ void Window::selectBIOS() { } void Window::selectPatch() { + bool doPause = m_controller->isLoaded() && !m_controller->isPaused(); + if (doPause) { + m_controller->setPaused(true); + } QString filename = QFileDialog::getOpenFileName(this, tr("Select patch"), m_config->getQtOption("lastDirectory").toString(), tr("Patches (*.ips *.ups *.bps)")); + if (doPause) { + m_controller->setPaused(false); + } if (!filename.isEmpty()) { m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_controller->loadPatch(filename); From 41b591e501790dcf88d7c533fe1109300eb3c984 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 2 Apr 2015 23:23:29 -0700 Subject: [PATCH 32/92] All: Update README --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5de6c3b08..9baa659cf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ mGBA mGBA is a new emulator for running Game Boy Advance games. It aims to be faster and more accurate than many existing Game Boy Advance emulators, as well as adding features that other emulators lack. -Up-to-date news and downloads can be found at [endrift.com/mgba](https://endrift.com/mgba/). +Up-to-date news and downloads can be found at [mgba.io](http://mgba.io/). ![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master) @@ -13,6 +13,7 @@ Features - Near full Game Boy Advance hardware support[[1]](#missing). - Fast emulation. Known to run at full speed even on low end hardware, such as netbooks. - Qt and SDL ports for a heavy-weight and a light-weight frontend. +- Local (same computer) link cable support. - Save type detection, even for flash memory size[[2]](#flashdetect). - Real-time clock support, even without configuration. - A built-in BIOS implementation, and ability to load external BIOS files. @@ -30,12 +31,13 @@ Features ### Planned features -- Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)). -- Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)). +- Networked multiplayer link cable support ([Bug #1](http://mgba.io/b/1)). +- Dolphin/JOY bus link cable support ([Bug #73](http://mgba.io/b/73)). - Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker)) -- Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)). -- A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)). -- libretro core for RetroArch and OpenEmu ([Bug #86](https://endrift.com/mgba/bugs/show_bug.cgi?id=86)). +- Lua support for scripting ([Bug #62](http://mgba.io/b/62)). +- A comprehensive debug suite ([Bug #132](http://mgba.io/b/132)). +- OpenEmu core. +- e-Reader support. ([Bug #171](http://mgba.io/b/171)) Supported Platforms @@ -46,7 +48,7 @@ Supported Platforms - Linux - FreeBSD -Other Unix-like platforms work as well, but are untested. +Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported. ### System requirements @@ -99,18 +101,18 @@ Footnotes [1] Currently missing features are -- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5)) -- Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9)) -- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141)) -- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142)) -- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195)) -- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199)) +- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5)) +- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9)) +- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141)) +- Audio channel reset flags ([Bug #142](http://mgba.io/b/142)) +- Game Pak prefetch ([Bug #195](http://mgba.io/b/195)) +- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199)) -[2] Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered. +[2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. [3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older. -[downloads]: https://endrift.com/mgba/downloads.html +[downloads]: http://mgba.io/downloads.html [source]: https://github.com/mgba-emu/mgba/ Copyright From 065474d14e6a154ff1c768df40dca757ae53468f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 21:07:26 -0700 Subject: [PATCH 33/92] Qt: Fix crash when attempting to pause if a game is not running --- CHANGES | 1 + src/platform/qt/GameController.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 637248327..fe7b04583 100644 --- a/CHANGES +++ b/CHANGES @@ -61,6 +61,7 @@ Bugfixes: - GBA Memory: Fix cycle counting for 32-bit load/stores - ARM7: Fix cycle counting for loads - Qt: Pause game while open file dialogs are open (fixes #6 on GitHub) + - Qt: Fix crash when attempting to pause if a game is not running Misc: - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - GBA Memory: Simplify memory API and use fixed bus width diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 20d8e30a1..4f63ecd8f 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -334,7 +334,7 @@ bool GameController::isPaused() { } void GameController::setPaused(bool paused) { - if (paused == GBAThreadIsPaused(&m_threadContext)) { + if (!m_gameOpen || paused == GBAThreadIsPaused(&m_threadContext)) { return; } if (paused) { From 5da7198d07fe87f8f508567dc83d7f3b6707668c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 21:12:37 -0700 Subject: [PATCH 34/92] Qt: Fix build with some non-Apple versions of GCC --- src/platform/qt/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 202431f37..b2b01ca6b 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -4,7 +4,10 @@ enable_language(CXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11") if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() endif() set(PLATFORM_SRC) From 36af5fba879fbca71a1c7a54dbfdb378f9ae105a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 21:13:24 -0700 Subject: [PATCH 35/92] Qt: Fix shortcuts conflicting between views --- src/platform/qt/LoadSaveState.cpp | 9 ++++++--- src/platform/qt/Window.cpp | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 1f1acd814..756bd6c68 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -43,6 +43,12 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) m_slots[i]->installEventFilter(this); connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); }); } + + QAction* escape = new QAction(this); + escape->connect(escape, SIGNAL(triggered()), this, SLOT(close())); + escape->setShortcut(QKeySequence("Esc")); + escape->setShortcutContext(Qt::WidgetWithChildrenShortcut); + addAction(escape); } void LoadSaveState::setMode(LoadSave mode) { @@ -80,9 +86,6 @@ bool LoadSaveState::eventFilter(QObject* object, QEvent* event) { case Qt::Key_9: triggerState(static_cast(event)->key() - Qt::Key_1 + 1); break; - case Qt::Key_Escape: - close(); - break; case Qt::Key_Enter: case Qt::Key_Return: triggerState(m_currentFocus + 1); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 019586fe1..8556c8af5 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -922,6 +922,7 @@ void Window::updateMRU() { QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) { m_shortcutController->addAction(menu, action, name); menu->addAction(action); + action->setShortcutContext(Qt::WidgetShortcut); addAction(action); return action; } From f136fa040c3a4948c2e65520aa27660763478f06 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 21:14:02 -0700 Subject: [PATCH 36/92] Qt: Fix regression involving displayed screenshots in savestate view --- src/platform/qt/Window.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 8556c8af5..ea05a5953 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -880,7 +880,6 @@ void Window::setupMenu(QMenuBar* menubar) { } void Window::attachWidget(QWidget* widget) { - m_screenWidget->clear(); m_screenWidget->layout()->addWidget(widget); static_cast(m_screenWidget->layout())->setCurrentWidget(widget); } From 7d980287cb40cc25fef7c48f0f77a22ae0100088 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 21:25:25 -0700 Subject: [PATCH 37/92] All: Update CHANGES for 0.2.0 --- CHANGES | 60 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index fe7b04583..9ac3b743e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,14 +1,12 @@ -0.2.0: (Future) +0.2.0: (2015-04-03) Features: - Support for gamepad axes, e.g. analog sticks or triggers - Add scale presets for up to 6x - - Debugger: Add CLI "frame", frame advance command - Settings window - Bilinear resampling option - Add option to skip BIOS start screen - List of recently opened games - Support for games using the Solar Sensor - - Debugger: Add CLI functions for writing to memory - Better audio resampling via blip-buf - Game Pak overrides dialog for setting savetype and sensor values - Support for games using the tilt sensor @@ -23,9 +21,7 @@ Features: - Support loading 7-Zip files - Drag and drop game loading - Cheat code support - - Debugger: Add CLI functions for examining memory regions - Runtime configurable audio driver - - Debugger: Add CLI function for writing a register - Libretro core for use with RetroArch and other front-ends - Controller profiles for setting different bindings for different controllers - Ability to lock aspect ratio @@ -33,61 +29,65 @@ Features: - Ability to switch which game controller is in use per instance - Ability to prevent opposing directional input - Warning dialog if an unimplemented BIOS feature is called + - Debugger: Add CLI "frame", frame advance command + - Debugger: Add CLI functions for writing to memory + - Debugger: Add CLI functions for examining memory regions + - Debugger: Add CLI function for writing a register Bugfixes: - ARM7: Extend prefetch by one stage + - ARM7: Fix cycle counting for loads + - Debugger: Disassembly now lists PSR bitmasks (fixes #191) + - GBA: Fix savestate loading of DISPSTAT and WAITCNT registers + - GBA: Initialize gba.sync to null + - GBA: Fix timer initialization - GBA Audio: Support 16-bit writes to FIFO audio - GBA Audio: Audio buffer sizes are now correct sizes for both sample rates - GBA BIOS: Fix BIOS prefetch after returning from an IRQ - GBA BIOS: Fix BIOS prefetch after reset - - GBA Memory: Fix alignment of open bus 8- and 16-bit loads - - GBA Thread: Fix possible hang when loading an archive - - Perf: Fix crash when the GBA thread fails to start - - SDL: Properly clean up if a game doesn't launch - - Debugger: Disassembly now lists PSR bitmasks (fixes #191) - GBA BIOS: Prevent CpuSet and CpuFastSet from using BIOS addresses as a source (fixes #184) + - GBA BIOS: Fix BIOS decompression routines with invalid source addresses + - GBA Memory: Fix alignment of open bus 8- and 16-bit loads + - GBA Memory: Fix I cycles that had been moved to ARM7 core + - GBA Memory: Fix cycle counting for 32-bit load/stores - GBA RR: Fix fallthrough error when reading tags from a movie + - GBA Thread: Fix possible hang when loading an archive - GBA Thread: Fix possible deadlock in video sync - - GBA: Fix savestate loading of DISPSTAT and WAITCNT registers + - Perf: Fix crash when the GBA thread fails to start - Qt: Fix crash starting a GDB stub if a game isn't loaded - Qt: Fix crash when adjusting settings after closing a game - Qt: Fix crash when starting GDB stub after closing a game - Qt: Fix patch loading while a game is running - - Util: Fix sockets on Windows - Qt: Fix crash when loading a game after stopping GDB server - - GBA BIOS: Fix BIOS decompression routines with invalid source addresses - - GBA: Initialize gba.sync to null - - GBA: Fix timer initialization - - GBA Memory: Fix I cycles that had been moved to ARM7 core - - GBA Memory: Fix cycle counting for 32-bit load/stores - - ARM7: Fix cycle counting for loads - Qt: Pause game while open file dialogs are open (fixes #6 on GitHub) - Qt: Fix crash when attempting to pause if a game is not running + - SDL: Properly clean up if a game doesn't launch + - Util: Fix sockets on Windows Misc: - - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples - - GBA Memory: Simplify memory API and use fixed bus width - - GBA Video: Start video at the last scanline instead of the first + - All: Enable link-time optimization - Debugger: Watchpoints now work on STM/LDM instructions - - GBA: Improve accuracy of event timing - Debugger: Clean up GDB stub network interfacing - Debugger: Simplify debugger state machine to play nicer with the GBA thread loop - Debugger: Merge Thumb BL instructions when disassembling - Debugger: Clean up debugger interface, removing obsolete state (fixes #67) - Debugger: Watchpoints now report address watched (fixes #68) + - Debugger: Add support for soft breakpoints + - Debugger: Make I/O register names be addresses instead of values + - Debugger: Rename read/write commands + - GBA: Improve accuracy of event timing - GBA: Add API for getting Configuration structs for overrides and input - GBA: Refactor gba-sensors and gba-gpio into gba-hardware - GBA: Refactor gba directory, dropping gba- prefix and making supervisor directory - - Debugger: Add support for soft breakpoints - - Util: Use proper locale for reading and writing float values - - Debugger: Make I/O register names be addresses instead of values - - Debugger: Rename read/write commands - - Qt: Optimize logo drawing - - Qt: Move frame upload back onto main thread - - All: Enable link-time optimization - - GBA Thread: Make GBASyncWaitFrameStart time out - GBA: Move A/V stream interface into core - GBA: Savestates now take into account savedata state machines (fixes #109) + - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples + - GBA Memory: Simplify memory API and use fixed bus width + - GBA Thread: Make GBASyncWaitFrameStart time out + - GBA Video: Start video at the last scanline instead of the first + - Qt: Optimize logo drawing + - Qt: Move frame upload back onto main thread - Qt: Remember window position - Qt: Double-clicking on the window toggles full screen + - Util: Use proper locale for reading and writing float values 0.1.1: (2015-01-24) Bugfixes: From 67bc9e7280bffbf7b6b8dc9c8ba858caf0826e54 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 21:46:23 -0700 Subject: [PATCH 38/92] Qt: Move solar sensor menu to emulation menu --- src/platform/qt/Window.cpp | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ea05a5953..f54ea732b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -685,6 +685,25 @@ void Window::setupMenu(QMenuBar* menubar) { }, this); m_config->updateOption("audioSync"); + emulationMenu->addSeparator(); + + QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); + QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); + connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); + addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); + + QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); + connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel())); + addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); + + QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu); + connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); + addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel"); + + QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu); + connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); + addControlledAction(solarMenu, minSolar, "minLuminanceLevel"); + QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); m_shortcutController->addMenu(avMenu); QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); @@ -803,23 +822,6 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif - QMenu* solarMenu = toolsMenu->addMenu(tr("Solar sensor")); - QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); - connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); - addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); - - QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); - connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel())); - addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); - - QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu); - connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); - addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel"); - - QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu); - connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); - addControlledAction(solarMenu, minSolar, "minLuminanceLevel"); - toolsMenu->addSeparator(); addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings"); addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), "shortcuts"); From 13a77ea3fff2da06b208c5f42016e171ac3dc53a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 3 Apr 2015 23:51:36 -0700 Subject: [PATCH 39/92] GBA: Fix timers not updating timing when writing to only the reload register --- CHANGES | 4 ++++ src/gba/gba.c | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 9ac3b743e..e5f7882c6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +0.3.0: (Future) +Bugfixes: + - GBA: Fix timers not updating timing when writing to only the reload register + 0.2.0: (2015-04-03) Features: - Support for gamepad axes, e.g. analog sticks or triggers diff --git a/src/gba/gba.c b/src/gba/gba.c index e0cac779d..fbf38f06a 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -434,6 +434,7 @@ void GBATimerUpdateRegister(struct GBA* gba, int timer) { void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) { gba->timers[timer].reload = reload; + gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << gba->timers[timer].prescaleBits; } void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) { From 473b805a002ac7a8856c0c9f90ca29e0c250eddd Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Apr 2015 13:58:30 -0700 Subject: [PATCH 40/92] All: Fix sanitize-deb script not cleaning up after itself --- CHANGES | 1 + tools/sanitize-deb.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index e5f7882c6..ac345fff8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 0.3.0: (Future) Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register + - All: Fix sanitize-deb script not cleaning up after itself 0.2.0: (2015-04-03) Features: diff --git a/tools/sanitize-deb.sh b/tools/sanitize-deb.sh index 0a818b752..af7c3b72b 100755 --- a/tools/sanitize-deb.sh +++ b/tools/sanitize-deb.sh @@ -57,5 +57,6 @@ while [ $# -gt 0 ]; do sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control rm deb-temp/DEBIAN/control~ dpkg-deb -b deb-temp $DEB + rm -rf deb-temp shift done From e9365cdda22f3c41b5b0f208cc248439e8b80676 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Apr 2015 14:14:28 -0700 Subject: [PATCH 41/92] Qt: Fix Display object leak when closing a window --- CHANGES | 1 + src/platform/qt/Window.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ac345fff8..fad3403f5 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself + - Qt: Fix Display object leak when closing a window 0.2.0: (2015-04-03) Features: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index f54ea732b..1f8b6c17f 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -69,7 +69,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); format.setSwapInterval(1); - m_display = new DisplayGL(format); + m_display = new DisplayGL(format, this); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap From 2448ff715fcb1216db8a9d7c51087a4e8255015c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Apr 2015 14:17:06 -0700 Subject: [PATCH 42/92] Qt: Move Display setup code to common code --- src/platform/qt/Display.cpp | 7 +++++++ src/platform/qt/DisplayGL.cpp | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index fd4870d21..8ee580cec 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -5,9 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Display.h" +extern "C" { +#include "gba/video.h" +} + using namespace QGBA; Display::Display(QWidget* parent) : QWidget(parent) { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + setCursor(Qt::BlankCursor); } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index f9847e5c5..62daeab0d 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -33,9 +33,6 @@ DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) , m_painter(new Painter(format, this)) , m_started(false) { - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - setCursor(Qt::BlankCursor); } void DisplayGL::startDrawing(const uint32_t* buffer, GBAThread* thread) { From 9df80a437af7d4d6187cc1915a88e63d2e993002 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Apr 2015 14:17:46 -0700 Subject: [PATCH 43/92] Qt: Add framePosted slot to Display for push-based updates --- src/platform/qt/Display.h | 1 + src/platform/qt/DisplayGL.h | 15 ++++++++------- src/platform/qt/DisplayQt.cpp | 19 ------------------- src/platform/qt/DisplayQt.h | 16 ++++++++-------- src/platform/qt/Window.cpp | 1 + 5 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 85e799c9e..d5f3e1943 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -26,6 +26,7 @@ public slots: virtual void forceDraw() = 0; virtual void lockAspectRatio(bool lock) = 0; virtual void filter(bool filter) = 0; + virtual void framePosted(const uint32_t*) = 0; }; } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index d247c8de0..5e2643695 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -24,13 +24,14 @@ public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); public slots: - void startDrawing(const uint32_t* buffer, GBAThread* context); - void stopDrawing(); - void pauseDrawing(); - void unpauseDrawing(); - void forceDraw(); - void lockAspectRatio(bool lock); - void filter(bool filter); + void startDrawing(const uint32_t* buffer, GBAThread* context) override; + void stopDrawing() override; + void pauseDrawing() override; + void unpauseDrawing() override; + void forceDraw() override; + void lockAspectRatio(bool lock) override; + void filter(bool filter) override; + void framePosted(const uint32_t*) override {} protected: virtual void paintEvent(QPaintEvent*) override {}; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 34c43bda6..738632e9a 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -14,8 +14,6 @@ DisplayQt::DisplayQt(QWidget* parent) , m_lockAspectRatio(false) , m_filter(false) { - connect(&m_drawTimer, SIGNAL(timeout()), this, SLOT(update())); - m_drawTimer.setInterval(12); // Give update time roughly 4.6ms of clearance } void DisplayQt::startDrawing(const uint32_t* buffer, GBAThread* context) { @@ -29,23 +27,6 @@ void DisplayQt::startDrawing(const uint32_t* buffer, GBAThread* context) { #else m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB32); #endif - m_drawTimer.start(); -} - -void DisplayQt::stopDrawing() { - m_drawTimer.stop(); -} - -void DisplayQt::pauseDrawing() { - m_drawTimer.stop(); -} - -void DisplayQt::unpauseDrawing() { - m_drawTimer.start(); -} - -void DisplayQt::forceDraw() { - update(); } void DisplayQt::lockAspectRatio(bool lock) { diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index 8476861f1..bb9ba4759 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -22,19 +22,19 @@ public: DisplayQt(QWidget* parent = nullptr); public slots: - void startDrawing(const uint32_t* buffer, GBAThread* context); - void stopDrawing(); - void pauseDrawing(); - void unpauseDrawing(); - void forceDraw(); - void lockAspectRatio(bool lock); - void filter(bool filter); + void startDrawing(const uint32_t* buffer, GBAThread* context) override; + void stopDrawing() override {} + void pauseDrawing() override {} + void unpauseDrawing() override {} + void forceDraw() override { update(); } + void lockAspectRatio(bool lock) override; + void filter(bool filter) override; + void framePosted(const uint32_t*) override { update(); } protected: virtual void paintEvent(QPaintEvent*) override; private: - QTimer m_drawTimer; GBAThread* m_context; QImage m_backing; bool m_lockAspectRatio; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 1f8b6c17f..aae79c3de 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -102,6 +102,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), m_display, SLOT(unpauseDrawing())); connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&))); connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(recordFrame())); + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), m_display, SLOT(framePosted(const uint32_t*))); connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&))); connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed())); connect(m_controller, SIGNAL(unimplementedBiosCall(int)), this, SLOT(unimplementedBiosCall(int))); From d3a0ce00db07af2f1322c4014b79b221ff1b98c9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Apr 2015 21:12:07 -0700 Subject: [PATCH 44/92] Qt: Fix .deb dependencies --- CHANGES | 1 + src/platform/qt/CMakeLists.txt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index fad3403f5..fa476293c 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself - Qt: Fix Display object leak when closing a window + - Qt: Fix .deb dependencies 0.2.0: (2015-04-03) Features: diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index b2b01ca6b..0f90dc227 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -79,7 +79,7 @@ qt5_wrap_ui(UI_FILES VideoView.ui) set(QT_LIBRARIES) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5" PARENT_SCOPE) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5") set(AUDIO_SRC) if(BUILD_SDL) @@ -92,7 +92,7 @@ if(Qt5Multimedia_FOUND) AudioDevice.cpp) list(APPEND QT_LIBRARIES Qt5::Multimedia) add_definitions(-DBUILD_QT_MULTIMEDIA) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5" PARENT_SCOPE) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5") endif() if(NOT AUDIO_SRC) @@ -120,6 +120,7 @@ set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CM list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE) install(TARGETS ${BINARY_NAME}-qt RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt From 4a36c3766ba0acf5ca96998268f72004d4437343 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Apr 2015 22:07:46 -0700 Subject: [PATCH 45/92] GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio --- CHANGES | 1 + src/gba/audio.c | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index fa476293c..916258735 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Bugfixes: - All: Fix sanitize-deb script not cleaning up after itself - Qt: Fix Display object leak when closing a window - Qt: Fix .deb dependencies + - GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio 0.2.0: (2015-04-03) Features: diff --git a/src/gba/audio.c b/src/gba/audio.c index dce744a45..974132801 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -514,11 +514,15 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", fifoId); return; } - if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t)) { + if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) { struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource]; - dma->nextCount = 4; - dma->nextEvent = 0; - GBAMemoryUpdateDMAs(audio->p, -cycles); + if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) { + dma->nextCount = 4; + dma->nextEvent = 0; + GBAMemoryUpdateDMAs(audio->p, -cycles); + } else { + channel->dmaSource = 0; + } } CircleBufferRead8(&channel->fifo, &channel->sample); } From db2659962b45e242562c23c4b907eec7ce568464 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 01:06:53 -0700 Subject: [PATCH 46/92] Qt: Fix tr missing or being present where it shouldn't be --- src/platform/qt/Window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index aae79c3de..b9f7fffb8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -715,7 +715,7 @@ void Window::setupMenu(QMenuBar* menubar) { showNormal(); resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i); }); - addControlledAction(frameMenu, setSize, tr("frame%1x").arg(QString::number(i))); + addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i))); } addControlledAction(frameMenu, frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F")), "fullscreen"); @@ -759,7 +759,7 @@ void Window::setupMenu(QMenuBar* menubar) { avMenu->addSeparator(); - QMenu* target = avMenu->addMenu("FPS target"); + QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); fpsTargetOption->connect([this](const QVariant& value) { emit fpsTargetChanged(value.toInt()); From 8533f01be57d725e9c8e779869761f8f5d859b2e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 01:25:25 -0700 Subject: [PATCH 47/92] GBA Video: Ability to hide individual background layers, or OBJs --- CHANGES | 2 ++ src/gba/renderers/video-software.c | 18 +++++++++++++----- src/gba/video.h | 3 +++ src/platform/qt/Window.cpp | 19 +++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 916258735..4aacbb2bb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,6 @@ 0.3.0: (Future) +Features: + - Ability to hide individual background layers, or OBJs Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index eb8a1d193..54a409724 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -87,6 +87,12 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) { renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame; renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels; renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels; + + renderer->d.disableBG[0] = false; + renderer->d.disableBG[1] = false; + renderer->d.disableBG[2] = false; + renderer->d.disableBG[3] = false; + renderer->d.disableOBJ = false; } static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) { @@ -522,6 +528,8 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render softwareRenderer->windows[0].control.packed = 0xFF; } + GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer); + int w; x = 0; for (w = 0; w < softwareRenderer->nWindows; ++w) { @@ -599,10 +607,10 @@ static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, } static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) { - renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt); - renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt); - renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt); - renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt); + renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0]; + renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1]; + renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2]; + renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3]; } static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) { @@ -691,7 +699,7 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) { int w; renderer->end = 0; int spriteLayers = 0; - if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt)) { + if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) { if (renderer->oamDirty) { _cleanOAM(renderer); } diff --git a/src/gba/video.h b/src/gba/video.h index b7648719e..774f8de99 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -167,6 +167,9 @@ struct GBAVideoRenderer { uint16_t* palette; uint16_t* vram; union GBAOAM* oam; + + bool disableBG[4]; + bool disableOBJ; }; struct GBAVideo { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index b9f7fffb8..01e21d221 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -799,6 +799,25 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(avMenu, recordGIF, "recordGIF"); #endif + avMenu->addSeparator(); + QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); + + for (int i = 0; i < 4; ++i) { + QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers); + enableBg->setCheckable(true); + enableBg->setChecked(true); + connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; }); + m_gameActions.append(enableBg); + addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i)); + } + + QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers); + enableObj->setCheckable(true); + enableObj->setChecked(true); + connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; }); + m_gameActions.append(enableObj); + addControlledAction(videoLayers, enableObj, "enableOBJ"); + QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); m_shortcutController->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); From 73e4516257b952987b8904a05b087305ffcbdfcb Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 01:39:10 -0700 Subject: [PATCH 48/92] GBA Audio: Ability to mute individual audio channels --- CHANGES | 1 + src/gba/audio.c | 87 +++++++++++++++++++++++--------------- src/gba/audio.h | 4 ++ src/platform/qt/Window.cpp | 25 +++++++++++ 4 files changed, 83 insertions(+), 34 deletions(-) diff --git a/CHANGES b/CHANGES index 4aacbb2bb..f0747acad 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 0.3.0: (Future) Features: - Ability to hide individual background layers, or OBJs + - Ability to mute individual audio channels Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/gba/audio.c b/src/gba/audio.c index 974132801..90d0bab20 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -41,6 +41,13 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { #endif CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); + + audio->forceDisableCh[0] = false; + audio->forceDisableCh[1] = false; + audio->forceDisableCh[2] = false; + audio->forceDisableCh[3] = false; + audio->forceDisableChA = false; + audio->forceDisableChB = false; } void GBAAudioReset(struct GBAAudio* audio) { @@ -750,55 +757,67 @@ static void _sample(struct GBAAudio* audio) { int16_t sampleRight = 0; int psgShift = 5 - audio->volume; - if (audio->ch1Left) { - sampleLeft += audio->ch1.sample; + if (audio->playingCh1 && !audio->forceDisableCh[0]) { + if (audio->ch1Left) { + sampleLeft += audio->ch1.sample; + } + + if (audio->ch1Right) { + sampleRight += audio->ch1.sample; + } } - if (audio->ch1Right) { - sampleRight += audio->ch1.sample; + if (audio->playingCh2 && !audio->forceDisableCh[1]) { + if (audio->ch2Left) { + sampleLeft += audio->ch2.sample; + } + + if (audio->ch2Right) { + sampleRight += audio->ch2.sample; + } } - if (audio->ch2Left) { - sampleLeft += audio->ch2.sample; + if (audio->playingCh3 && !audio->forceDisableCh[2]) { + if (audio->ch3Left) { + sampleLeft += audio->ch3.sample; + } + + if (audio->ch3Right) { + sampleRight += audio->ch3.sample; + } } - if (audio->ch2Right) { - sampleRight += audio->ch2.sample; - } + if (audio->playingCh4 && !audio->forceDisableCh[3]) { + if (audio->ch4Left) { + sampleLeft += audio->ch4.sample; + } - if (audio->ch3Left) { - sampleLeft += audio->ch3.sample; - } - - if (audio->ch3Right) { - sampleRight += audio->ch3.sample; - } - - if (audio->ch4Left) { - sampleLeft += audio->ch4.sample; - } - - if (audio->ch4Right) { - sampleRight += audio->ch4.sample; + if (audio->ch4Right) { + sampleRight += audio->ch4.sample; + } } sampleLeft = (sampleLeft * (1 + audio->volumeLeft)) >> psgShift; sampleRight = (sampleRight * (1 + audio->volumeRight)) >> psgShift; - if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + if (!audio->forceDisableChA) { + if (audio->chALeft) { + sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + } + + if (audio->chARight) { + sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + } } - if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; - } + if (!audio->forceDisableChB) { + if (audio->chBLeft) { + sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + } - if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; - } - - if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + if (audio->chBRight) { + sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + } } sampleLeft = _applyBias(audio, sampleLeft); diff --git a/src/gba/audio.h b/src/gba/audio.h index 91cc84b1b..52662a984 100644 --- a/src/gba/audio.h +++ b/src/gba/audio.h @@ -236,6 +236,10 @@ struct GBAAudio { int32_t nextSample; int32_t sampleInterval; + + bool forceDisableCh[4]; + bool forceDisableChA; + bool forceDisableChB; }; struct GBAStereoSample { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 01e21d221..b1c214a94 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -818,6 +818,31 @@ void Window::setupMenu(QMenuBar* menubar) { m_gameActions.append(enableObj); addControlledAction(videoLayers, enableObj, "enableOBJ"); + QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); + + for (int i = 0; i < 4; ++i) { + QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels); + enableCh->setCheckable(true); + enableCh->setChecked(true); + connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; }); + m_gameActions.append(enableCh); + addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1)); + } + + QAction* enableChA = new QAction(tr("Channel A"), audioChannels); + enableChA->setCheckable(true); + enableChA->setChecked(true); + connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; }); + m_gameActions.append(enableChA); + addControlledAction(audioChannels, enableChA, QString("enableChA")); + + QAction* enableChB = new QAction(tr("Channel B"), audioChannels); + enableChB->setCheckable(true); + enableChB->setChecked(true); + connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; }); + m_gameActions.append(enableChB); + addControlledAction(audioChannels, enableChB, QString("enableChB")); + QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); m_shortcutController->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); From 6a320bb923ed4e976e8723c72d53b424dab0a42a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 15:07:46 -0700 Subject: [PATCH 49/92] All: Always build vfs-mem --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dc8e9a9d..574a56577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) -set(VFS_SRC) +set(VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-mem.c) source_group("ARM core" FILES ${ARM_SRC}) source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC}) source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) From 190ace3003c48eb0baf298027feba8991348f5da Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 17:59:34 -0700 Subject: [PATCH 50/92] Qt: Clean up some technical debt with opening views --- src/platform/qt/Window.cpp | 34 +++++++++++++--------------------- src/platform/qt/Window.h | 2 ++ 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index b1c214a94..762404ff1 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -247,58 +247,50 @@ void Window::selectPatch() { } } +void Window::openView(QWidget* widget) { + connect(this, SIGNAL(shutdown()), widget, SLOT(close())); + widget->setAttribute(Qt::WA_DeleteOnClose); + widget->show(); +} + void Window::openKeymapWindow() { GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); - connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close())); - keyEditor->setAttribute(Qt::WA_DeleteOnClose); - keyEditor->show(); + openView(keyEditor); } void Window::openSettingsWindow() { SettingsView* settingsWindow = new SettingsView(m_config); - connect(this, SIGNAL(shutdown()), settingsWindow, SLOT(close())); connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); - settingsWindow->setAttribute(Qt::WA_DeleteOnClose); - settingsWindow->show(); + openView(settingsWindow); } void Window::openShortcutWindow() { ShortcutView* shortcutView = new ShortcutView(); shortcutView->setController(m_shortcutController); - connect(this, SIGNAL(shutdown()), shortcutView, SLOT(close())); - shortcutView->setAttribute(Qt::WA_DeleteOnClose); - shortcutView->show(); + openView(shortcutView); } void Window::openOverrideWindow() { OverrideView* overrideWindow = new OverrideView(m_controller, m_config); - connect(this, SIGNAL(shutdown()), overrideWindow, SLOT(close())); - overrideWindow->setAttribute(Qt::WA_DeleteOnClose); - overrideWindow->show(); + openView(overrideWindow); } void Window::openSensorWindow() { SensorView* sensorWindow = new SensorView(m_controller); - connect(this, SIGNAL(shutdown()), sensorWindow, SLOT(close())); - sensorWindow->setAttribute(Qt::WA_DeleteOnClose); - sensorWindow->show(); + openView(sensorWindow); } void Window::openCheatsWindow() { CheatsView* cheatsWindow = new CheatsView(m_controller); - connect(this, SIGNAL(shutdown()), cheatsWindow, SLOT(close())); - cheatsWindow->setAttribute(Qt::WA_DeleteOnClose); - cheatsWindow->show(); + openView(cheatsWindow); } #ifdef BUILD_SDL void Window::openGamepadWindow() { const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON); GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON, profile); - connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close())); - keyEditor->setAttribute(Qt::WA_DeleteOnClose); - keyEditor->show(); + openView(keyEditor); } #endif diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index e868147d3..73a17744d 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -122,6 +122,8 @@ private: void appendMRU(const QString& fname); void updateMRU(); + void openView(QWidget* widget); + QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); GameController* m_controller; From 609a9c5aa0c52de980bb55c09fd24eb85fb064e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 18:02:59 -0700 Subject: [PATCH 51/92] Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning --- CHANGES | 1 + src/platform/qt/DisplayGL.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f0747acad..1122b4a6a 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Bugfixes: - Qt: Fix Display object leak when closing a window - Qt: Fix .deb dependencies - GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio + - Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning 0.2.0: (2015-04-03) Features: diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 62daeab0d..80f0d5945 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -239,7 +239,6 @@ void Painter::unpause() { void Painter::initializeGL() { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); - swapBuffers(); } void Painter::performDraw() { From 97479c4d00779f8f28fee7f5370ec2a466ffeb1f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 18:06:31 -0700 Subject: [PATCH 52/92] ARM7: Fix SWI and IRQ timings --- CHANGES | 1 + src/arm/arm.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 1122b4a6a..f4b19be89 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Bugfixes: - Qt: Fix .deb dependencies - GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio - Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning + - ARM7: Fix SWI and IRQ timings 0.2.0: (2015-04-03) Features: diff --git a/src/arm/arm.c b/src/arm/arm.c index 05cb65eef..b75274aa3 100644 --- a/src/arm/arm.c +++ b/src/arm/arm.c @@ -164,10 +164,10 @@ void ARMRaiseIRQ(struct ARMCore* cpu) { cpu->gprs[ARM_PC] = BASE_IRQ; int currentCycles = 0; ARM_WRITE_PC; - cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); _ARMSetMode(cpu, MODE_ARM); cpu->spsr = cpsr; cpu->cpsr.i = 1; + cpu->cycles += currentCycles; } void ARMRaiseSWI(struct ARMCore* cpu) { @@ -184,10 +184,10 @@ void ARMRaiseSWI(struct ARMCore* cpu) { cpu->gprs[ARM_PC] = BASE_SWI; int currentCycles = 0; ARM_WRITE_PC; - cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); _ARMSetMode(cpu, MODE_ARM); cpu->spsr = cpsr; cpu->cpsr.i = 1; + cpu->cycles += currentCycles; } static inline void ARMStep(struct ARMCore* cpu) { From cda804656b37925c35f2347be7b057fe2787f076 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 5 Apr 2015 23:46:48 -0700 Subject: [PATCH 53/92] Qt: Palette viewer --- CHANGES | 1 + src/platform/qt/CMakeLists.txt | 3 + src/platform/qt/PaletteView.cpp | 59 ++++++ src/platform/qt/PaletteView.h | 42 +++++ src/platform/qt/PaletteView.ui | 321 ++++++++++++++++++++++++++++++++ src/platform/qt/Swatch.cpp | 62 ++++++ src/platform/qt/Swatch.h | 45 +++++ src/platform/qt/Window.cpp | 13 ++ src/platform/qt/Window.h | 2 + 9 files changed, 548 insertions(+) create mode 100644 src/platform/qt/PaletteView.cpp create mode 100644 src/platform/qt/PaletteView.h create mode 100644 src/platform/qt/PaletteView.ui create mode 100644 src/platform/qt/Swatch.cpp create mode 100644 src/platform/qt/Swatch.h diff --git a/CHANGES b/CHANGES index f4b19be89..cf6031ce6 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Features: - Ability to hide individual background layers, or OBJs - Ability to mute individual audio channels + - Palette viewer Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 0f90dc227..7956207a5 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -58,11 +58,13 @@ set(SOURCE_FILES LogView.cpp MultiplayerController.cpp OverrideView.cpp + PaletteView.cpp SavestateButton.cpp SensorView.cpp SettingsView.cpp ShortcutController.cpp ShortcutView.cpp + Swatch.cpp Window.cpp VFileDevice.cpp VideoView.cpp) @@ -73,6 +75,7 @@ qt5_wrap_ui(UI_FILES LoadSaveState.ui LogView.ui OverrideView.ui + PaletteView.ui SensorView.ui SettingsView.ui ShortcutView.ui diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp new file mode 100644 index 000000000..a41732672 --- /dev/null +++ b/src/platform/qt/PaletteView.cpp @@ -0,0 +1,59 @@ +/* Copyright (c) 2013-2015 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 "PaletteView.h" + +#include + +using namespace QGBA; + +PaletteView::PaletteView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updatePalette())); + m_ui.bgGrid->setDimensions(QSize(16, 16)); + m_ui.objGrid->setDimensions(QSize(16, 16)); + m_ui.selected->setSize(64); + m_ui.selected->setDimensions(QSize(1, 1)); + updatePalette(); + + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + m_ui.hexcode->setFont(font); + m_ui.value->setFont(font); + m_ui.index->setFont(font); + m_ui.r->setFont(font); + m_ui.g->setFont(font); + m_ui.b->setFont(font); + + connect(m_ui.bgGrid, SIGNAL(indexPressed(int)), this, SLOT(selectIndex(int))); + connect(m_ui.objGrid, &Swatch::indexPressed, [this] (int index) { selectIndex(index + 256); }); +} + +void PaletteView::updatePalette() { + const uint16_t* palette = m_controller->thread()->gba->video.palette; + for (int i = 0; i < 256; ++i) { + m_ui.bgGrid->setColor(i, palette[i]); + m_ui.objGrid->setColor(i, palette[i + 256]); + } +} + +void PaletteView::selectIndex(int index) { + uint16_t color = m_controller->thread()->gba->video.palette[index]; + m_ui.selected->setColor(0, color); + uint32_t r = color & 0x1F; + uint32_t g = (color >> 5) & 0x1F; + uint32_t b = (color >> 10) & 0x1F; + uint32_t hexcode = (r << 19) | (g << 11) | (b << 3); + m_ui.hexcode->setText(tr("#%0").arg(hexcode, 6, 16, QChar('0'))); + m_ui.value->setText(tr("0x%0").arg(color, 4, 16, QChar('0'))); + m_ui.index->setText(tr("%0").arg(index, 3, 10, QChar('0'))); + m_ui.r->setText(tr("0x%0 (%1)").arg(r, 2, 16, QChar('0')).arg(r, 2, 10, QChar('0'))); + m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0'))); + m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0'))); +} diff --git a/src/platform/qt/PaletteView.h b/src/platform/qt/PaletteView.h new file mode 100644 index 000000000..5a4d58252 --- /dev/null +++ b/src/platform/qt/PaletteView.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_PALETTE_VIEW +#define QGBA_PALETTE_VIEW + +#include + +#include "GameController.h" +#include "Swatch.h" + +#include "ui_PaletteView.h" + +class GameController; + +namespace QGBA { + +class Swatch; + +class PaletteView : public QWidget { +Q_OBJECT + +public: + PaletteView(GameController* controller, QWidget* parent = nullptr); + +public slots: + void updatePalette(); + +private slots: + void selectIndex(int); + +private: + Ui::PaletteView m_ui; + + GameController* m_controller; +}; + +} + +#endif diff --git a/src/platform/qt/PaletteView.ui b/src/platform/qt/PaletteView.ui new file mode 100644 index 000000000..79a5c2bec --- /dev/null +++ b/src/platform/qt/PaletteView.ui @@ -0,0 +1,321 @@ + + + PaletteView + + + + 0 + 0 + 452 + 349 + + + + Palette + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Background + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 175 + 175 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Objects + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 175 + 175 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Selection + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + + + + + + + Qt::Vertical + + + + + + + + + Red + + + + + + + Green + + + + + + + Blue + + + + + + + + + 8 + + + + + 0x00 (00) + + + + + + + 0x00 (00) + + + + + + + 0x00 (00) + + + + + + + + + Qt::Vertical + + + + + + + + + 16-bit value + + + + + + + Hex code + + + + + + + Palette index + + + + + + + + + 8 + + + + + 0x0000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + #000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + QGBA::Swatch + QLabel +
Swatch.h
+
+
+ + +
diff --git a/src/platform/qt/Swatch.cpp b/src/platform/qt/Swatch.cpp new file mode 100644 index 000000000..308bec9cf --- /dev/null +++ b/src/platform/qt/Swatch.cpp @@ -0,0 +1,62 @@ +/* Copyright (c) 2013-2015 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 "Swatch.h" + +#include +#include + +using namespace QGBA; + +Swatch::Swatch(QWidget* parent) + : QLabel(parent) +{ + m_size = 10; + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); +} + +void Swatch::setSize(int size) { + m_size = size; + setDimensions(m_dims); +} + +void Swatch::setDimensions(const QSize& size) { + m_dims = size; + m_backing = QPixmap(size * (m_size + 1) - QSize(1, 1)); + m_backing.fill(Qt::transparent); + int elem = size.width() * size.height(); + m_colors.resize(elem); + for (int i = 0; i < elem; ++i) { + updateFill(i); + } +} + +void Swatch::setColor(int index, uint16_t color) { + m_colors[index].setRgb( + (color << 3) & 0xF8, + (color >> 2) & 0xF8, + (color >> 7) & 0xF8); + updateFill(index); +} + +void Swatch::paintEvent(QPaintEvent* event) { + setPixmap(m_backing); + QLabel::paintEvent(event); +} + +void Swatch::mousePressEvent(QMouseEvent* event) { + int x = event->x() / (m_size + 1); + int y = event->y() / (m_size + 1); + emit indexPressed(y * m_dims.width() + x); +} + +void Swatch::updateFill(int index) { + QPainter painter(&m_backing); + int x = index % m_dims.width(); + int y = index / m_dims.width(); + QRect r(x * (m_size + 1), y * (m_size + 1), m_size, m_size); + painter.fillRect(r, m_colors[index]); + update(r); +} diff --git a/src/platform/qt/Swatch.h b/src/platform/qt/Swatch.h new file mode 100644 index 000000000..5a84c3c38 --- /dev/null +++ b/src/platform/qt/Swatch.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_SWATCH +#define QGBA_SWATCH + +#include +#include +#include + +namespace QGBA { + +class Swatch : public QLabel { +Q_OBJECT + +public: + Swatch(QWidget* parent = nullptr); + + void setDimensions(const QSize&); + void setSize(int size); + +public slots: + void setColor(int index, uint16_t); + +signals: + void indexPressed(int index); + +protected: + void paintEvent(QPaintEvent*) override; + void mousePressEvent(QMouseEvent*) override; + +private: + int m_size; + QVector m_colors; + QPixmap m_backing; + QSize m_dims; + + void updateFill(int index); +}; + +} + +#endif diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 762404ff1..84c52ae6d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -27,6 +27,7 @@ #include "LogView.h" #include "MultiplayerController.h" #include "OverrideView.h" +#include "PaletteView.h" #include "SensorView.h" #include "SettingsView.h" #include "ShortcutController.h" @@ -286,6 +287,11 @@ void Window::openCheatsWindow() { openView(cheatsWindow); } +void Window::openPaletteWindow() { + PaletteView* paletteWindow = new PaletteView(m_controller); + openView(paletteWindow); +} + #ifdef BUILD_SDL void Window::openGamepadWindow() { const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON); @@ -873,6 +879,13 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(toolsMenu, gamepad, "remapGamepad"); #endif + toolsMenu->addSeparator(); + + QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu); + connect(paletteView, SIGNAL(triggered()), this, SLOT(openPaletteWindow())); + m_gameActions.append(paletteView); + addControlledAction(toolsMenu, paletteView, "paletteWindow"); + ConfigOption* skipBios = m_config->addOption("skipBios"); skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 73a17744d..2cbaa960b 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -73,6 +73,8 @@ public slots: void openSensorWindow(); void openCheatsWindow(); + void openPaletteWindow(); + #ifdef BUILD_SDL void openGamepadWindow(); #endif From 1856b77cbcc9a6c6d5bb24665e79d77a4ebff734 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 6 Apr 2015 02:34:21 -0700 Subject: [PATCH 54/92] GBA Audio: Force audio FIFOs to 32-bit --- CHANGES | 1 + src/gba/audio.c | 25 +------------------------ src/gba/audio.h | 1 - src/gba/io.c | 13 ++----------- 4 files changed, 4 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index cf6031ce6..2b692aaca 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Bugfixes: - GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio - Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning - ARM7: Fix SWI and IRQ timings + - GBA Audio: Force audio FIFOs to 32-bit 0.2.0: (2015-04-03) Features: diff --git a/src/gba/audio.c b/src/gba/audio.c index 90d0bab20..f2f49da32 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -487,30 +487,6 @@ void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) { } } -void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value) { - struct CircleBuffer* fifo; - switch (address) { - case REG_FIFO_A_LO: - case REG_FIFO_A_HI: - fifo = &audio->chA.fifo; - break; - case REG_FIFO_B_LO: - case REG_FIFO_B_HI: - fifo = &audio->chB.fifo; - break; - default: - GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", address); - return; - } - int i; - for (i = 0; i < 2; ++i) { - while (!CircleBufferWrite8(fifo, value >> (8 * i))) { - int8_t dummy; - CircleBufferRead8(fifo, &dummy); - } - } -} - void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { struct GBAAudioFIFO* channel; if (fifoId == 0) { @@ -526,6 +502,7 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) { dma->nextCount = 4; dma->nextEvent = 0; + dma->reg = GBADMARegisterSetWidth(dma->reg, 1); GBAMemoryUpdateDMAs(audio->p, -cycles); } else { channel->dmaSource = 0; diff --git a/src/gba/audio.h b/src/gba/audio.h index 52662a984..45d60c3ee 100644 --- a/src/gba/audio.h +++ b/src/gba/audio.h @@ -272,7 +272,6 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value); void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value); void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value); -void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value); void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value); void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles); diff --git a/src/gba/io.c b/src/gba/io.c index 65f178fde..cdb4f3094 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -366,23 +366,14 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); break; - // TODO: Confirm this behavior on real hardware case REG_FIFO_A_LO: case REG_FIFO_B_LO: - if (gba->performingDMA) { - GBAAudioWriteFIFO16(&gba->audio, address, value); - } else { - GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); - } + GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); break; case REG_FIFO_A_HI: case REG_FIFO_B_HI: - if (gba->performingDMA) { - GBAAudioWriteFIFO16(&gba->audio, address, value); - } else { - GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); - } + GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); break; // DMA From 25e70e37c7cecf234eb3fec7c50779460e68c1dd Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 6 Apr 2015 03:47:42 -0700 Subject: [PATCH 55/92] GBA Memory: Ensure changing the timing of a DMA reschedules it --- CHANGES | 1 + src/gba/memory.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 2b692aaca..4d84885ca 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Bugfixes: - Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit + - GBA Memory: Ensure changing the timing of a DMA reschedules it 0.2.0: (2015-04-03) Features: diff --git a/src/gba/memory.c b/src/gba/memory.c index d3b0770d0..975881689 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1257,6 +1257,11 @@ uint16_t GBAMemoryWriteDMACNT_HI(struct GBA* gba, int dma, uint16_t control) { struct GBAMemory* memory = &gba->memory; struct GBADMA* currentDma = &memory->dma[dma]; int wasEnabled = GBADMARegisterIsEnable(currentDma->reg); + int oldTiming = GBADMARegisterGetTiming(currentDma->reg); + int newTiming = GBADMARegisterGetTiming(control); + if (oldTiming && oldTiming != newTiming) { + wasEnabled = false; + } currentDma->reg = control; if (GBADMARegisterIsDRQ(currentDma->reg)) { From 3449c5cb3b084493732d545c9407848bb4dff2ea Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 6 Apr 2015 22:02:06 -0700 Subject: [PATCH 56/92] Qt: Fix window not regaining focus after exiting savestate window --- CHANGES | 1 + src/platform/qt/Window.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4d84885ca..d899fb096 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Bugfixes: - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit - GBA Memory: Ensure changing the timing of a DMA reschedules it + - Qt: Fix window not regaining focus after exiting savestate window 0.2.0: (2015-04-03) Features: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 84c52ae6d..3076593fb 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -542,7 +542,7 @@ void Window::openStateWindow(LoadSave ls) { connect(m_stateWindow, &LoadSaveState::closed, [this]() { m_screenWidget->layout()->removeWidget(m_stateWindow); m_stateWindow = nullptr; - setFocus(); + QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection); }); if (!wasPaused) { m_controller->setPaused(true); From eb0b9dca0f3b7b46ad82efac3155fb86d2ae26a4 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 6 Apr 2015 22:18:57 -0700 Subject: [PATCH 57/92] Qt: Fix regression where video would not record if the game had already started --- CHANGES | 1 + src/platform/qt/GameController.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index d899fb096..9bcdc3106 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Bugfixes: - GBA Audio: Force audio FIFOs to 32-bit - GBA Memory: Ensure changing the timing of a DMA reschedules it - Qt: Fix window not regaining focus after exiting savestate window + - Qt: Fix regression where video would not record if the game had already started 0.2.0: (2015-04-03) Features: diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 4f63ecd8f..ba82aeee2 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -511,6 +511,9 @@ void GameController::setTurbo(bool set, bool forced) { void GameController::setAVStream(GBAAVStream* stream) { threadInterrupt(); m_threadContext.stream = stream; + if (m_gameOpen) { + m_threadContext.gba->stream = stream; + } threadContinue(); } From 3182b5e35d418fe281eca092e8c53263184920c8 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 7 Apr 2015 00:51:07 -0700 Subject: [PATCH 58/92] Qt: Follow-up fix for regression --- src/platform/qt/GameController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index ba82aeee2..44d9e7ddc 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -520,6 +520,9 @@ void GameController::setAVStream(GBAAVStream* stream) { void GameController::clearAVStream() { threadInterrupt(); m_threadContext.stream = nullptr; + if (m_gameOpen) { + m_threadContext.gba->stream = nullptr; + } threadContinue(); } From 8266f54d7674ec7fdbe263efea163481c8b33571 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 8 Apr 2015 00:32:29 -0700 Subject: [PATCH 59/92] Volume control --- CHANGES | 1 + src/gba/audio.c | 4 +- src/gba/audio.h | 2 + src/gba/supervisor/config.c | 6 ++ src/gba/supervisor/config.h | 3 + src/gba/supervisor/thread.c | 11 ++++ src/gba/supervisor/thread.h | 2 + src/platform/qt/ConfigController.cpp | 1 + src/platform/qt/GameController.cpp | 20 ++++++ src/platform/qt/GameController.h | 2 + src/platform/qt/SettingsView.cpp | 13 ++++ src/platform/qt/SettingsView.h | 2 + src/platform/qt/SettingsView.ui | 98 +++++++++++++++++++--------- src/platform/qt/Window.cpp | 11 +++- 14 files changed, 140 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index 9bcdc3106..05bfca0f0 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Features: - Ability to hide individual background layers, or OBJs - Ability to mute individual audio channels - Palette viewer + - Volume control Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/gba/audio.c b/src/gba/audio.c index f2f49da32..f0e06c117 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -14,6 +14,7 @@ const unsigned GBA_AUDIO_SAMPLES = 2048; const unsigned BLIP_BUFFER_SIZE = 0x4000; const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t); +const int GBA_AUDIO_VOLUME_MAX = 0x100; #define SWEEP_CYCLES (GBA_ARM7TDMI_FREQUENCY / 128) static bool _writeEnvelope(struct GBAAudioEnvelope* envelope, uint16_t value); @@ -48,6 +49,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { audio->forceDisableCh[3] = false; audio->forceDisableChA = false; audio->forceDisableChB = false; + audio->masterVolume = GBA_AUDIO_VOLUME_MAX; } void GBAAudioReset(struct GBAAudio* audio) { @@ -726,7 +728,7 @@ static int _applyBias(struct GBAAudio* audio, int sample) { } else if (sample < 0) { sample = 0; } - return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 5; + return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume) >> 3; } static void _sample(struct GBAAudio* audio) { diff --git a/src/gba/audio.h b/src/gba/audio.h index 45d60c3ee..dc16d4631 100644 --- a/src/gba/audio.h +++ b/src/gba/audio.h @@ -21,6 +21,7 @@ struct GBADMA; extern const unsigned GBA_AUDIO_SAMPLES; +extern const int GBA_AUDIO_VOLUME_MAX; DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t); DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6); @@ -240,6 +241,7 @@ struct GBAAudio { bool forceDisableCh[4]; bool forceDisableChA; bool forceDisableChB; + int masterVolume; }; struct GBAStereoSample { diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c index 1f71c5851..437f0e104 100644 --- a/src/gba/supervisor/config.c +++ b/src/gba/supervisor/config.c @@ -179,6 +179,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { _lookupCharValue(config, "bios", &opts->bios); _lookupIntValue(config, "logLevel", &opts->logLevel); _lookupIntValue(config, "frameskip", &opts->frameskip); + _lookupIntValue(config, "volume", &opts->volume); _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity); _lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval); _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget); @@ -203,6 +204,9 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { if (_lookupIntValue(config, "resampleVideo", &fakeBool)) { opts->resampleVideo = fakeBool; } + if (_lookupIntValue(config, "mute", &fakeBool)) { + opts->mute = fakeBool; + } if (_lookupIntValue(config, "skipBios", &fakeBool)) { opts->skipBios = fakeBool; } @@ -243,6 +247,8 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen); ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width); ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height); + ConfigurationSetIntValue(&config->defaultsTable, 0, "volume", opts->volume); + ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute); ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio); ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo); diff --git a/src/gba/supervisor/config.h b/src/gba/supervisor/config.h index 369bdbd29..fd3eaba82 100644 --- a/src/gba/supervisor/config.h +++ b/src/gba/supervisor/config.h @@ -36,6 +36,9 @@ struct GBAOptions { bool lockAspectRatio; bool resampleVideo; + int volume; + bool mute; + bool videoSync; bool audioSync; diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index c595d21d1..75da09eff 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -233,6 +233,15 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers); + if (threadContext->volume == 0) { + threadContext->volume = GBA_AUDIO_VOLUME_MAX; + } + if (threadContext->mute) { + gba.audio.volume = 0; + } else { + gba.audio.volume = threadContext->volume; + } + gba.keySource = &threadContext->activeKeys; if (threadContext->startCallback) { @@ -316,6 +325,8 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* thr threadContext->bios = 0; } threadContext->frameskip = opts->frameskip; + threadContext->volume = opts->volume; + threadContext->mute = opts->mute; threadContext->logLevel = opts->logLevel; if (opts->rewindEnable) { threadContext->rewindBufferCapacity = opts->rewindBufferCapacity; diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h index 7a2a3778b..caa160bea 100644 --- a/src/gba/supervisor/thread.h +++ b/src/gba/supervisor/thread.h @@ -80,6 +80,8 @@ struct GBAThread { float fpsTarget; size_t audioBuffers; bool skipBios; + int volume; + bool mute; // Threading state Thread thread; diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index ba21ff288..9857727d8 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -108,6 +108,7 @@ ConfigController::ConfigController(QObject* parent) m_opts.videoSync = GameController::VIDEO_SYNC; m_opts.fpsTarget = 60; m_opts.audioBuffers = 2048; + m_opts.volume = GBA_AUDIO_VOLUME_MAX; m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; m_opts.rewindEnable = false; m_opts.rewindBufferInterval = 0; diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 44d9e7ddc..fb8aa8da2 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -186,6 +186,8 @@ void GameController::setOptions(const GBAOptions* opts) { setSkipBIOS(opts->skipBios); setUseBIOS(opts->useBios); setRewind(opts->rewindEnable, opts->rewindBufferCapacity, opts->rewindBufferInterval); + setVolume(opts->volume); + setMute(opts->mute); threadInterrupt(); m_threadContext.idleOptimization = opts->idleOptimization; @@ -496,6 +498,24 @@ void GameController::setFrameskip(int skip) { m_threadContext.frameskip = skip; } +void GameController::setVolume(int volume) { + threadInterrupt(); + m_threadContext.volume = volume; + if (m_gameOpen) { + m_threadContext.gba->audio.masterVolume = volume; + } + threadContinue(); +} + +void GameController::setMute(bool mute) { + threadInterrupt(); + m_threadContext.mute = mute; + if (m_gameOpen) { + m_threadContext.gba->audio.masterVolume = mute ? 0 : m_threadContext.volume; + } + threadContinue(); +} + void GameController::setTurbo(bool set, bool forced) { if (m_turboForced && !forced) { return; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 0869a59a0..3bb54dc7d 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -114,6 +114,8 @@ public slots: void setVideoSync(bool); void setAudioSync(bool); void setFrameskip(int); + void setVolume(int); + void setMute(bool); void setTurbo(bool, bool forced = true); void setAVStream(GBAAVStream*); void clearAVStream(); diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index b50ff95a4..44aa13a20 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -27,6 +27,8 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent) loadSetting("frameskip", m_ui.frameskip); loadSetting("fpsTarget", m_ui.fpsTarget); loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("volume", m_ui.volume); + loadSetting("mute", m_ui.mute); loadSetting("rewindEnable", m_ui.rewind); loadSetting("rewindBufferInterval", m_ui.rewindInterval); loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); @@ -78,6 +80,8 @@ void SettingsView::updateConfig() { saveSetting("frameskip", m_ui.frameskip); saveSetting("fpsTarget", m_ui.fpsTarget); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); + saveSetting("volume", m_ui.volume); + saveSetting("mute", m_ui.mute); saveSetting("rewindEnable", m_ui.rewind); saveSetting("rewindBufferInterval", m_ui.rewindInterval); saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); @@ -121,6 +125,10 @@ void SettingsView::saveSetting(const char* key, const QLineEdit* field) { saveSetting(key, field->text()); } +void SettingsView::saveSetting(const char* key, const QSlider* field) { + saveSetting(key, QString::number(field->value())); +} + void SettingsView::saveSetting(const char* key, const QSpinBox* field) { saveSetting(key, field->cleanText()); } @@ -144,6 +152,11 @@ void SettingsView::loadSetting(const char* key, QLineEdit* field) { field->setText(option); } +void SettingsView::loadSetting(const char* key, QSlider* field) { + QString option = loadSetting(key); + field->setValue(option.toInt()); +} + void SettingsView::loadSetting(const char* key, QSpinBox* field) { QString option = loadSetting(key); field->setValue(option.toInt()); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 73694facd..0b0d4f211 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -36,12 +36,14 @@ private: void saveSetting(const char* key, const QAbstractButton*); void saveSetting(const char* key, const QComboBox*); void saveSetting(const char* key, const QLineEdit*); + void saveSetting(const char* key, const QSlider*); void saveSetting(const char* key, const QSpinBox*); void saveSetting(const char* key, const QString&); void loadSetting(const char* key, QAbstractButton*); void loadSetting(const char* key, QComboBox*); void loadSetting(const char* key, QLineEdit*); + void loadSetting(const char* key, QSlider*); void loadSetting(const char* key, QSpinBox*); QString loadSetting(const char* key); }; diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index c9fc145f8..b7e371dea 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -6,8 +6,8 @@ 0 0 - 360 - 569 + 374 + 608 @@ -146,13 +146,54 @@
+ + + Volume: + + + + + + + + + 256 + + + 16 + + + 256 + + + Qt::Horizontal + + + + + + + Mute + + + + + + + + + Qt::Horizontal + + + + Sync: - + @@ -170,14 +211,14 @@ - + Frameskip: - + @@ -198,14 +239,14 @@ - + FPS target: - + @@ -226,49 +267,42 @@ - - - - Qt::Horizontal - - - - + Lock aspect ratio - + Resample video - + Qt::Horizontal - + Enable rewind - + Rewind interval: - + @@ -289,14 +323,14 @@ - + Rewind length: - + @@ -310,21 +344,28 @@ - + + + + Allow opposing input directions + + + + Qt::Horizontal - + Idle loops - + @@ -343,13 +384,6 @@ - - - - Allow opposing input directions - - - diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 3076593fb..fecd59a1d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -891,9 +891,14 @@ void Window::setupMenu(QMenuBar* menubar) { m_controller->setSkipBIOS(value.toBool()); }, this); - ConfigOption* useBios = m_config->addOption("useBios"); - useBios->connect([this](const QVariant& value) { - m_controller->setUseBIOS(value.toBool()); + ConfigOption* volume = m_config->addOption("volume"); + volume->connect([this](const QVariant& value) { + m_controller->setVolume(value.toInt()); + }, this); + + ConfigOption* mute = m_config->addOption("mute"); + mute->connect([this](const QVariant& value) { + m_controller->setMute(value.toBool()); }, this); ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); From 72a0de2940d83496a0ce1798c1191c9f6762bafa Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 8 Apr 2015 00:54:03 -0700 Subject: [PATCH 60/92] GBA Memory: Fix audio regression...may be a huge hack --- src/gba/memory.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 975881689..d33ba589d 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1259,7 +1259,8 @@ uint16_t GBAMemoryWriteDMACNT_HI(struct GBA* gba, int dma, uint16_t control) { int wasEnabled = GBADMARegisterIsEnable(currentDma->reg); int oldTiming = GBADMARegisterGetTiming(currentDma->reg); int newTiming = GBADMARegisterGetTiming(control); - if (oldTiming && oldTiming != newTiming) { + // This is probably a huge hack...verify what this does on hardware + if (oldTiming && oldTiming != DMA_TIMING_CUSTOM && oldTiming != newTiming) { wasEnabled = false; } currentDma->reg = control; From 97607452e2b2f4a989c1b297339c20cf8f1bfcf3 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 8 Apr 2015 00:56:04 -0700 Subject: [PATCH 61/92] All: VS cannot compile mGBA at the time --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9baa659cf..4953cf912 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Controls are configurable in the menu. The default gamepad controls are mapped s Compiling --------- -Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-based system, the recommended commands are as follows: +Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. To use CMake to build on a Unix-based system, the recommended commands are as follows: mkdir build cd build From 1dd82f21d915a3450c0254baff6fd2324c241af2 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 8 Apr 2015 21:45:46 -0700 Subject: [PATCH 62/92] GBA: Fix unintialized stack variable --- src/gba/supervisor/overrides.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c index ea11f50e6..ca57a9a76 100644 --- a/src/gba/supervisor/overrides.c +++ b/src/gba/supervisor/overrides.c @@ -156,7 +156,7 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOver override->savetype = SAVEDATA_AUTODETECT; override->hardware = HW_NONE; override->idleLoop = IDLE_LOOP_NONE; - bool found; + bool found = false; if (override->id[0] == 'F') { // Classic NES Series From 8636b81f13514538200a7113304cce7fb87261c9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 9 Apr 2015 00:41:00 -0700 Subject: [PATCH 63/92] GBA: Split cheats out into separate source files --- CMakeLists.txt | 4 +- src/gba/cheats.c | 768 +------------------------------- src/gba/cheats/cheats-private.h | 13 + src/gba/cheats/codebreaker.c | 143 ++++++ src/gba/cheats/gameshark.c | 224 ++++++++++ src/gba/cheats/gameshark.h | 18 + src/gba/cheats/parv3.c | 324 ++++++++++++++ src/gba/cheats/parv3.h | 15 + src/util/string.c | 68 +++ src/util/string.h | 4 + 10 files changed, 834 insertions(+), 747 deletions(-) create mode 100644 src/gba/cheats/cheats-private.h create mode 100644 src/gba/cheats/codebreaker.c create mode 100644 src/gba/cheats/gameshark.c create mode 100644 src/gba/cheats/gameshark.h create mode 100644 src/gba/cheats/parv3.c create mode 100644 src/gba/cheats/parv3.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 574a56577..a5f46d542 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(BUILD_STATIC OFF CACHE BOOL "Build a static library") set(BUILD_SHARED ON CACHE BOOL "Build a shared library") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c) file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) +file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c) file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) @@ -28,7 +29,7 @@ list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) set(VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-mem.c) source_group("ARM core" FILES ${ARM_SRC}) source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC}) -source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) +source_group("GBA supervisor" FILES ${GBA_CHEATS_SRC} ${GBA_SV_SRC} ${GBA_RR_SRC}) source_group("Utilities" FILES ${UTIL_SRC}) include_directories(${CMAKE_SOURCE_DIR}/src/arm) include_directories(${CMAKE_SOURCE_DIR}/src) @@ -303,6 +304,7 @@ endforeach() set(CORE_SRC ${ARM_SRC} ${GBA_SRC} + ${GBA_CHEATS_SRC} ${GBA_RR_SRC} ${GBA_SV_SRC} ${DEBUGGER_SRC} diff --git a/src/gba/cheats.c b/src/gba/cheats.c index 1369391cc..ee7820174 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -5,8 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cheats.h" +#include "gba/cheats/gameshark.h" +#include "gba/cheats/parv3.h" #include "gba/gba.h" -#include "gba/io.h" +#include "util/string.h" #include "util/vfs.h" #define MAX_LINE_LENGTH 128 @@ -17,85 +19,6 @@ DEFINE_VECTOR(GBACheatList, struct GBACheat); DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*); DEFINE_VECTOR(StringList, char*); -static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 }; -static const uint32_t _par3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 }; - -static const uint8_t _gsa1T1[256] = { - 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94, - 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B, - 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45, - 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9, - 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA, - 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E, - 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79, - 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31, - 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B, - 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB, - 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77, - 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93, - 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25, - 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52, - 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F, - 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50 -}; - -static const uint8_t _gsa1T2[256] = { - 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6, - 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1, - 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B, - 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48, - 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6, - 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8, - 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE, - 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39, - 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B, - 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4, - 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5, - 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90, - 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75, - 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76, - 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2, - 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C -}; - -static const uint8_t _par3T1[256] = { - 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A, - 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45, - 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7, - 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5, - 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54, - 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A, - 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB, - 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC, - 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2, - 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2, - 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31, - 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35, - 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11, - 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C, - 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA, - 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30 -}; - -static const uint8_t _par3T2[256] = { - 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA, - 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F, - 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93, - 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B, - 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD, - 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC, - 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F, - 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9, - 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1, - 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B, - 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC, - 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA, - 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8, - 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D, - 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C, - 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC -}; - static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) { switch (width) { case 1: @@ -122,455 +45,10 @@ static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t } } -static int _hexDigit(char digit) { - switch (digit) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return digit - '0'; - - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - return digit - 'a' + 10; - - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - return digit - 'A' + 10; - - default: - return -1; - } -} - -static const char* _hex32(const char* line, uint32_t* out) { - uint32_t value = 0; - int i; - for (i = 0; i < 8; ++i, ++line) { - char digit = *line; - value <<= 4; - int nybble = _hexDigit(digit); - if (nybble < 0) { - return 0; - } - value |= nybble; - } - *out = value; - return line; -} - -static const char* _hex16(const char* line, uint16_t* out) { - uint16_t value = 0; - *out = 0; - int i; - for (i = 0; i < 4; ++i, ++line) { - char digit = *line; - value <<= 4; - int nybble = _hexDigit(digit); - if (nybble < 0) { - return 0; - } - value |= nybble; - } - *out = value; - return line; -} - -static void _registerLine(struct GBACheatSet* cheats, const char* line) { +void GBACheatRegisterLine(struct GBACheatSet* cheats, const char* line) { *StringListAppend(&cheats->lines) = strdup(line); } -// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm -static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) { - uint32_t sum = 0xC6EF3720; - int i; - for (i = 0; i < 32; ++i) { - *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]); - *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]); - sum -= 0x9E3779B9; - } -} - -static void _reseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) { - int x, y; - int s0 = params >> 8; - int s1 = params & 0xFF; - for (y = 0; y < 4; ++y) { - for (x = 0; x < 4; ++x) { - uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF]; - seeds[y] <<= 8; - seeds[y] |= z; - } - } -} - -static void _setGameSharkVersion(struct GBACheatSet* cheats, int version) { - cheats->gsaVersion = 1; - switch (version) { - case 1: - memcpy(cheats->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t)); - break; - case 3: - memcpy(cheats->gsaSeeds, _par3S, 4 * sizeof(uint32_t)); - break; - } -} - -static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { - enum GBAGameSharkType type = op1 >> 28; - struct GBACheat* cheat = 0; - - if (cheats->incompleteCheat) { - if (cheats->remainingAddresses > 0) { - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op1; - cheat->operand = cheats->incompleteCheat->operand; - cheat->repeat = 1; - --cheats->remainingAddresses; - } - if (cheats->remainingAddresses > 0) { - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op2; - cheat->operand = cheats->incompleteCheat->operand; - cheat->repeat = 1; - --cheats->remainingAddresses; - } - if (cheats->remainingAddresses == 0) { - cheats->incompleteCheat = 0; - } - return true; - } - - switch (type) { - case GSA_ASSIGN_1: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 1; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_ASSIGN_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 2; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_ASSIGN_4: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_ASSIGN_LIST: - cheats->remainingAddresses = (op1 & 0xFFFF) - 1; - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 4; - cheat->address = op2; - cheats->incompleteCheat = cheat; - break; - case GSA_PATCH: - cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1; - cheats->romPatches[0].newValue = op2; - cheats->romPatches[0].applied = false; - cheats->romPatches[0].exists = true; - return true; - case GSA_BUTTON: - // TODO: Implement button - GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); - return false; - case GSA_IF_EQ: - if (op1 == 0xDEADFACE) { - _reseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); - return true; - } - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_EQ; - cheat->width = 2; - cheat->address = op1 & 0x0FFFFFFF; - break; - case GSA_IF_EQ_RANGE: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_EQ; - cheat->width = 2; - cheat->address = op2 & 0x0FFFFFFF; - cheat->operand = op1 & 0xFFFF; - cheat->repeat = (op1 >> 16) & 0xFF; - return true; - case GSA_HOOK: - if (cheats->hook) { - return false; - } - cheats->hook = malloc(sizeof(*cheats->hook)); - cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); - cheats->hook->mode = MODE_THUMB; - cheats->hook->refs = 1; - cheats->hook->reentries = 0; - return true; - default: - return false; - } - cheat->operand = op2; - cheat->repeat = 1; - return true; -} - -static uint32_t _parAddr(uint32_t x) { - return (x & 0xFFFFF) | ((x << 4) & 0x0F000000); -} - -static void _parEndBlock(struct GBACheatSet* cheats) { - size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); - if (cheats->currentBlock->repeat) { - cheats->currentBlock->negativeRepeat = size - cheats->currentBlock->repeat; - } else { - cheats->currentBlock->repeat = size; - } - cheats->currentBlock = 0; -} - -static void _parElseBlock(struct GBACheatSet* cheats) { - size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); - cheats->currentBlock->repeat = size; -} - -static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { - enum GBAActionReplay3Condition condition = op1 & PAR3_COND; - int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); - if (width > 4) { - // TODO: Always false conditions - return false; - } - if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) { - // TODO: Codes that disable - return false; - } - - struct GBACheat* cheat = GBACheatListAppend(&cheats->list); - cheat->address = _parAddr(op1); - cheat->width = width; - cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); - cheat->addressOffset = 0; - cheat->operandOffset = 0; - - switch (op1 & PAR3_ACTION) { - case PAR3_ACTION_NEXT: - cheat->repeat = 1; - cheat->negativeRepeat = 0; - break; - case PAR3_ACTION_NEXT_TWO: - cheat->repeat = 2; - cheat->negativeRepeat = 0; - break; - case PAR3_ACTION_BLOCK: - cheat->repeat = 0; - cheat->negativeRepeat = 0; - if (cheats->currentBlock) { - _parEndBlock(cheats); - } - cheats->currentBlock = cheat; - break; - } - - switch (condition) { - case PAR3_COND_OTHER: - // We shouldn't be able to get here - GBALog(0, GBA_LOG_ERROR, "Unexpectedly created 'other' PARv3 code"); - cheat->type = CHEAT_IF_LAND; - cheat->operand = 0; - break; - case PAR3_COND_EQ: - cheat->type = CHEAT_IF_EQ; - break; - case PAR3_COND_NE: - cheat->type = CHEAT_IF_NE; - break; - case PAR3_COND_LT: - cheat->type = CHEAT_IF_LT; - break; - case PAR3_COND_GT: - cheat->type = CHEAT_IF_GT; - break; - case PAR3_COND_ULT: - cheat->type = CHEAT_IF_ULT; - break; - case PAR3_COND_UGT: - cheat->type = CHEAT_IF_UGT; - break; - case PAR3_COND_LAND: - cheat->type = CHEAT_IF_LAND; - break; - } - return true; -} - -static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { - struct GBACheat* cheat; - switch (op2 & 0xFF000000) { - case PAR3_OTHER_SLOWDOWN: - // TODO: Slowdown - return false; - case PAR3_OTHER_BUTTON_1: - case PAR3_OTHER_BUTTON_2: - case PAR3_OTHER_BUTTON_4: - // TODO: Button - GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); - return false; - // TODO: Fix overriding existing patches - case PAR3_OTHER_PATCH_1: - cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[0].applied = false; - cheats->romPatches[0].exists = true; - cheats->incompletePatch = &cheats->romPatches[0]; - break; - case PAR3_OTHER_PATCH_2: - cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[1].applied = false; - cheats->romPatches[1].exists = true; - cheats->incompletePatch = &cheats->romPatches[1]; - break; - case PAR3_OTHER_PATCH_3: - cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[2].applied = false; - cheats->romPatches[2].exists = true; - cheats->incompletePatch = &cheats->romPatches[2]; - break; - case PAR3_OTHER_PATCH_4: - cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[3].applied = false; - cheats->romPatches[3].exists = true; - cheats->incompletePatch = &cheats->romPatches[3]; - break; - case PAR3_OTHER_ENDIF: - if (cheats->currentBlock) { - _parEndBlock(cheats); - return true; - } - return false; - case PAR3_OTHER_ELSE: - if (cheats->currentBlock) { - _parElseBlock(cheats); - return true; - } - return false; - case PAR3_OTHER_FILL_1: - cheat = GBACheatListAppend(&cheats->list); - cheat->address = _parAddr(op2); - cheat->width = 1; - cheats->incompleteCheat = cheat; - break; - case PAR3_OTHER_FILL_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->address = _parAddr(op2); - cheat->width = 2; - cheats->incompleteCheat = cheat; - break; - case PAR3_OTHER_FILL_4: - cheat = GBACheatListAppend(&cheats->list); - cheat->address = _parAddr(op2); - cheat->width = 3; - cheats->incompleteCheat = cheat; - break; - } - return true; -} - -static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { - if (cheats->incompletePatch) { - cheats->incompletePatch->newValue = op1; - cheats->incompletePatch = 0; - return true; - } - if (cheats->incompleteCheat) { - cheats->incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - cheats->incompleteCheat->width) * 8)); - cheats->incompleteCheat->addressOffset = op2 >> 24; - cheats->incompleteCheat->repeat = (op2 >> 16) & 0xFF; - cheats->incompleteCheat->addressOffset = (op2 & 0xFFFF) * cheats->incompleteCheat->width; - cheats->incompleteCheat = 0; - return true; - } - - if (op2 == 0x001DC0DE) { - return true; - } - - switch (op1) { - case 0x00000000: - return _addPAR3Special(cheats, op2); - case 0xDEADFACE: - _reseedGameShark(cheats->gsaSeeds, op2, _par3T1, _par3T2); - return true; - } - - if (op1 >> 24 == 0xC4) { - if (cheats->hook) { - return false; - } - cheats->hook = malloc(sizeof(*cheats->hook)); - cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); - cheats->hook->mode = MODE_THUMB; - cheats->hook->refs = 1; - cheats->hook->reentries = 0; - return true; - } - - if (op1 & PAR3_COND) { - return _addPAR3Cond(cheats, op1, op2); - } - - int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); - struct GBACheat* cheat = GBACheatListAppend(&cheats->list); - cheat->address = _parAddr(op1); - cheat->operandOffset = 0; - cheat->addressOffset = 0; - cheat->repeat = 1; - - switch (op1 & PAR3_BASE) { - case PAR3_BASE_ASSIGN: - cheat->type = CHEAT_ASSIGN; - cheat->addressOffset = width; - if (width < 4) { - cheat->repeat = (op2 >> (width * 8)) + 1; - } - break; - case PAR3_BASE_INDIRECT: - cheat->type = CHEAT_ASSIGN_INDIRECT; - if (width < 4) { - cheat->addressOffset = (op2 >> (width * 8)) * width; - } - break; - case PAR3_BASE_ADD: - cheat->type = CHEAT_ADD; - break; - case PAR3_BASE_OTHER: - width = ((op1 >> 24) & 1) + 1; - cheat->type = CHEAT_ASSIGN; - cheat->address = BASE_IO | (op1 & OFFSET_MASK); - break; - } - - cheat->width = width; - cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); - return true; -} - static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) { if (!device->p || !cheats->hook) { return; @@ -708,237 +186,35 @@ void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats _removeBreakpoint(device, cheats); } -bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { - char line[14] = "XXXXXXXX XXXX"; - snprintf(line, sizeof(line), "%08X %04X", op1, op2); - _registerLine(cheats, line); - - enum GBACodeBreakerType type = op1 >> 28; - struct GBACheat* cheat = 0; - - if (cheats->incompleteCheat) { - cheats->incompleteCheat->repeat = op1 & 0xFFFF; - cheats->incompleteCheat->addressOffset = op2; - cheats->incompleteCheat->operandOffset = 0; - cheats->incompleteCheat = 0; - return true; - } - - switch (type) { - case CB_GAME_ID: - // TODO: Run checksum - return true; - case CB_HOOK: - if (cheats->hook) { - return false; - } - cheats->hook = malloc(sizeof(*cheats->hook)); - cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); - cheats->hook->mode = MODE_THUMB; - cheats->hook->refs = 1; - cheats->hook->reentries = 0; - return true; - case CB_OR_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_OR; - cheat->width = 2; - break; - case CB_ASSIGN_1: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 1; - break; - case CB_FILL: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 2; - cheats->incompleteCheat = cheat; - break; - case CB_FILL_8: - GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); - return false; - case CB_AND_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_AND; - cheat->width = 2; - break; - case CB_IF_EQ: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_EQ; - cheat->width = 2; - break; - case CB_ASSIGN_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ASSIGN; - cheat->width = 2; - break; - case CB_ENCRYPT: - GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); - return false; - case CB_IF_NE: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_NE; - cheat->width = 2; - break; - case CB_IF_GT: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_GT; - cheat->width = 2; - break; - case CB_IF_LT: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_LT; - cheat->width = 2; - break; - case CB_IF_SPECIAL: - switch (op1 & 0x0FFFFFFF) { - case 0x20: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_AND; - cheat->width = 2; - cheat->address = BASE_IO | REG_JOYSTAT; - cheat->operand = op2; - cheat->repeat = 1; - return true; - default: - GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); - return false; - } - case CB_ADD_2: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_ADD; - cheat->width = 2; - break; - case CB_IF_AND: - cheat = GBACheatListAppend(&cheats->list); - cheat->type = CHEAT_IF_AND; - cheat->width = 2; - break; - } - - cheat->address = op1 & 0x0FFFFFFF; - cheat->operand = op2; - cheat->repeat = 1; - cheat->negativeRepeat = 0; - return true; -} - -bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { - uint32_t op1; - uint16_t op2; - line = _hex32(line, &op1); - if (!line) { - return false; - } - while (*line == ' ') { - ++line; - } - line = _hex16(line, &op2); - if (!line) { - return false; - } - return GBACheatAddCodeBreaker(cheats, op1, op2); -} - -bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { - uint32_t o1 = op1; - uint32_t o2 = op2; - char line[18] = "XXXXXXXX XXXXXXXX"; - snprintf(line, sizeof(line), "%08X %08X", op1, op2); - _registerLine(set, line); - - switch (set->gsaVersion) { - case 0: - _setGameSharkVersion(set, 1); - // Fall through - case 1: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addGSA1(set, o1, o2); - } - return false; -} - -bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { - uint32_t op1; - uint32_t op2; - line = _hex32(line, &op1); - if (!line) { - return false; - } - while (*line == ' ') { - ++line; - } - line = _hex32(line, &op2); - if (!line) { - return false; - } - return GBACheatAddGameShark(cheats, op1, op2); -} - -bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { - uint32_t o1 = op1; - uint32_t o2 = op2; - char line[18] = "XXXXXXXX XXXXXXXX"; - snprintf(line, sizeof(line), "%08X %08X", op1, op2); - _registerLine(set, line); - - switch (set->gsaVersion) { - case 0: - _setGameSharkVersion(set, 3); - // Fall through - case 1: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addPAR3(set, o1, o2); - } - return false; -} - -bool GBACheatAddProActionReplayLine(struct GBACheatSet* cheats, const char* line) { - uint32_t op1; - uint32_t op2; - line = _hex32(line, &op1); - if (!line) { - return false; - } - while (*line == ' ') { - ++line; - } - line = _hex32(line, &op2); - if (!line) { - return false; - } - return GBACheatAddProActionReplay(cheats, op1, op2); -} - bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { uint32_t o1 = op1; uint32_t o2 = op2; char line[18] = "XXXXXXXX XXXXXXXX"; snprintf(line, sizeof(line), "%08X %08X", op1, op2); - _registerLine(set, line); + GBACheatRegisterLine(set, line); switch (set->gsaVersion) { case 0: // Try to detect GameShark version - _decryptGameShark(&o1, &o2, _gsa1S); + GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds); if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) { - _setGameSharkVersion(set, 1); - return _addGSA1(set, o1, o2); + GBACheatSetGameSharkVersion(set, 1); + return GBACheatAddGameSharkRaw(set, o1, o2); } o1 = op1; o2 = op2; - _decryptGameShark(&o1, &o2, _par3S); + GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds); if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) { - _setGameSharkVersion(set, 3); - return _addPAR3(set, o1, o2); + GBACheatSetGameSharkVersion(set, 3); + return GBACheatAddProActionReplayRaw(set, o1, o2); } break; case 1: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addGSA1(set, o1, o2); + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddGameSharkRaw(set, o1, o2); case 3: - _decryptGameShark(&o1, &o2, set->gsaSeeds); - return _addPAR3(set, o1, o2); + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddProActionReplayRaw(set, o1, o2); } return false; } @@ -946,14 +222,14 @@ bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) { uint32_t op1; uint32_t op2; - line = _hex32(line, &op1); + line = hex32(line, &op1); if (!line) { return false; } while (*line == ' ') { ++line; } - line = _hex32(line, &op2); + line = hex32(line, &op2); if (!line) { return false; } @@ -994,7 +270,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) { if (set && !reset) { GBACheatSetCopyProperties(newSet, set); } else { - _setGameSharkVersion(newSet, gsaVersion); + GBACheatSetGameSharkVersion(newSet, gsaVersion); } reset = false; set = newSet; @@ -1023,7 +299,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) { GBACheatSetInit(set, 0); set->enabled = !nextDisabled; nextDisabled = false; - _setGameSharkVersion(set, gsaVersion); + GBACheatSetGameSharkVersion(set, gsaVersion); } GBACheatAddLine(set, cheat); break; @@ -1087,21 +363,21 @@ bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) { uint32_t op1; uint16_t op2; uint16_t op3; - line = _hex32(line, &op1); + line = hex32(line, &op1); if (!line) { return false; } while (isspace(line[0])) { ++line; } - line = _hex16(line, &op2); + line = hex16(line, &op2); if (!line) { return false; } if (!line[0] || isspace(line[0])) { return GBACheatAddCodeBreaker(cheats, op1, op2); } - line = _hex16(line, &op3); + line = hex16(line, &op3); if (!line) { return false; } diff --git a/src/gba/cheats/cheats-private.h b/src/gba/cheats/cheats-private.h new file mode 100644 index 000000000..ca74d8348 --- /dev/null +++ b/src/gba/cheats/cheats-private.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2013-2015 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_CHEATS_PRIVATE_H +#define GBA_CHEATS_PRIVATE_H + +#include "gba/cheats.h" + +void GBACheatRegisterLine(struct GBACheatSet* set, const char* line); + +#endif diff --git a/src/gba/cheats/codebreaker.c b/src/gba/cheats/codebreaker.c new file mode 100644 index 000000000..74bbe4dfd --- /dev/null +++ b/src/gba/cheats/codebreaker.c @@ -0,0 +1,143 @@ +/* Copyright (c) 2013-2015 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 "gba/cheats.h" + +#include "gba/cheats/cheats-private.h" +#include "gba/gba.h" +#include "gba/io.h" +#include "util/string.h" + +bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { + char line[14] = "XXXXXXXX XXXX"; + snprintf(line, sizeof(line), "%08X %04X", op1, op2); + GBACheatRegisterLine(cheats, line); + + enum GBACodeBreakerType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + cheats->incompleteCheat->repeat = op1 & 0xFFFF; + cheats->incompleteCheat->addressOffset = op2; + cheats->incompleteCheat->operandOffset = 0; + cheats->incompleteCheat = 0; + return true; + } + + switch (type) { + case CB_GAME_ID: + // TODO: Run checksum + return true; + case CB_HOOK: + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + case CB_OR_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_OR; + cheat->width = 2; + break; + case CB_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + break; + case CB_FILL: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case CB_FILL_8: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + case CB_AND_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_AND; + cheat->width = 2; + break; + case CB_IF_EQ: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + break; + case CB_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + break; + case CB_ENCRYPT: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); + return false; + case CB_IF_NE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_NE; + cheat->width = 2; + break; + case CB_IF_GT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_GT; + cheat->width = 2; + break; + case CB_IF_LT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_LT; + cheat->width = 2; + break; + case CB_IF_SPECIAL: + switch (op1 & 0x0FFFFFFF) { + case 0x20: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + cheat->address = BASE_IO | REG_JOYSTAT; + cheat->operand = op2; + cheat->repeat = 1; + return true; + default: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + } + case CB_ADD_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ADD; + cheat->width = 2; + break; + case CB_IF_AND: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + break; + } + + cheat->address = op1 & 0x0FFFFFFF; + cheat->operand = op2; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + return true; +} + +bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint16_t op2; + line = hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = hex16(line, &op2); + if (!line) { + return false; + } + return GBACheatAddCodeBreaker(cheats, op1, op2); +} diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c new file mode 100644 index 000000000..e9bb8221d --- /dev/null +++ b/src/gba/cheats/gameshark.c @@ -0,0 +1,224 @@ +/* Copyright (c) 2013-2015 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 "gameshark.h" + +#include "gba/cheats/cheats-private.h" +#include "gba/cheats/parv3.h" +#include "gba/gba.h" +#include "util/string.h" + +const uint32_t GBACheatGameSharkSeeds[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 }; + +static const uint8_t _gsa1T1[256] = { + 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94, + 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B, + 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45, + 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9, + 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA, + 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E, + 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79, + 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31, + 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B, + 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB, + 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77, + 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93, + 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25, + 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52, + 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F, + 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50 +}; + +static const uint8_t _gsa1T2[256] = { + 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6, + 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1, + 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B, + 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48, + 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6, + 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8, + 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE, + 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39, + 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B, + 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4, + 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5, + 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90, + 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75, + 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76, + 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2, + 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C +}; + +// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm +void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) { + uint32_t sum = 0xC6EF3720; + int i; + for (i = 0; i < 32; ++i) { + *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]); + *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]); + sum -= 0x9E3779B9; + } +} + +void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) { + int x, y; + int s0 = params >> 8; + int s1 = params & 0xFF; + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF]; + seeds[y] <<= 8; + seeds[y] |= z; + } + } +} + +void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version) { + cheats->gsaVersion = 1; + switch (version) { + case 1: + memcpy(cheats->gsaSeeds, GBACheatGameSharkSeeds, 4 * sizeof(uint32_t)); + break; + case 3: + memcpy(cheats->gsaSeeds, GBACheatProActionReplaySeeds, 4 * sizeof(uint32_t)); + break; + } +} + +bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAGameSharkType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses == 0) { + cheats->incompleteCheat = 0; + } + return true; + } + + switch (type) { + case GSA_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_LIST: + cheats->remainingAddresses = (op1 & 0xFFFF) - 1; + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheats->incompleteCheat = cheat; + break; + case GSA_PATCH: + cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1; + cheats->romPatches[0].newValue = op2; + cheats->romPatches[0].applied = false; + cheats->romPatches[0].exists = true; + return true; + case GSA_BUTTON: + // TODO: Implement button + GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); + return false; + case GSA_IF_EQ: + if (op1 == 0xDEADFACE) { + GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); + return true; + } + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_IF_EQ_RANGE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op2 & 0x0FFFFFFF; + cheat->operand = op1 & 0xFFFF; + cheat->repeat = (op1 >> 16) & 0xFF; + return true; + case GSA_HOOK: + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + default: + return false; + } + cheat->operand = op2; + cheat->repeat = 1; + return true; +} + +bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + GBACheatRegisterLine(set, line); + + switch (set->gsaVersion) { + case 0: + GBACheatSetGameSharkVersion(set, 1); + // Fall through + case 1: + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddGameSharkRaw(set, o1, o2); + } + return false; +} + +bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddGameShark(cheats, op1, op2); +} diff --git a/src/gba/cheats/gameshark.h b/src/gba/cheats/gameshark.h new file mode 100644 index 000000000..efac542ce --- /dev/null +++ b/src/gba/cheats/gameshark.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2013-2015 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_CHEATS_GAMESHARK_H +#define GBA_CHEATS_GAMESHARK_H + +#include "gba/cheats.h" + +extern const uint32_t GBACheatGameSharkSeeds[4]; + +void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds); +void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2); +void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version); +bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2); + +#endif diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c new file mode 100644 index 000000000..62693a2af --- /dev/null +++ b/src/gba/cheats/parv3.c @@ -0,0 +1,324 @@ +/* Copyright (c) 2013-2015 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 "parv3.h" + +#include "gba/cheats/cheats-private.h" +#include "gba/cheats/gameshark.h" +#include "gba/gba.h" +#include "util/string.h" + +const uint32_t GBACheatProActionReplaySeeds[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 }; + +static const uint8_t _par3T1[256] = { + 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A, + 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45, + 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7, + 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5, + 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54, + 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A, + 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB, + 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC, + 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2, + 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2, + 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31, + 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35, + 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11, + 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C, + 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA, + 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30 +}; + +static const uint8_t _par3T2[256] = { + 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA, + 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F, + 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93, + 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B, + 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD, + 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC, + 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F, + 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9, + 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1, + 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B, + 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC, + 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA, + 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8, + 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D, + 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C, + 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC +}; +static uint32_t _parAddr(uint32_t x) { + return (x & 0xFFFFF) | ((x << 4) & 0x0F000000); +} + +static void _parEndBlock(struct GBACheatSet* cheats) { + size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); + if (cheats->currentBlock->repeat) { + cheats->currentBlock->negativeRepeat = size - cheats->currentBlock->repeat; + } else { + cheats->currentBlock->repeat = size; + } + cheats->currentBlock = 0; +} + +static void _parElseBlock(struct GBACheatSet* cheats) { + size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock); + cheats->currentBlock->repeat = size; +} + +static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAActionReplay3Condition condition = op1 & PAR3_COND; + int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); + if (width > 4) { + // TODO: Always false conditions + return false; + } + if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) { + // TODO: Codes that disable + return false; + } + + struct GBACheat* cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op1); + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); + cheat->addressOffset = 0; + cheat->operandOffset = 0; + + switch (op1 & PAR3_ACTION) { + case PAR3_ACTION_NEXT: + cheat->repeat = 1; + cheat->negativeRepeat = 0; + break; + case PAR3_ACTION_NEXT_TWO: + cheat->repeat = 2; + cheat->negativeRepeat = 0; + break; + case PAR3_ACTION_BLOCK: + cheat->repeat = 0; + cheat->negativeRepeat = 0; + if (cheats->currentBlock) { + _parEndBlock(cheats); + } + cheats->currentBlock = cheat; + break; + } + + switch (condition) { + case PAR3_COND_OTHER: + // We shouldn't be able to get here + GBALog(0, GBA_LOG_ERROR, "Unexpectedly created 'other' PARv3 code"); + cheat->type = CHEAT_IF_LAND; + cheat->operand = 0; + break; + case PAR3_COND_EQ: + cheat->type = CHEAT_IF_EQ; + break; + case PAR3_COND_NE: + cheat->type = CHEAT_IF_NE; + break; + case PAR3_COND_LT: + cheat->type = CHEAT_IF_LT; + break; + case PAR3_COND_GT: + cheat->type = CHEAT_IF_GT; + break; + case PAR3_COND_ULT: + cheat->type = CHEAT_IF_ULT; + break; + case PAR3_COND_UGT: + cheat->type = CHEAT_IF_UGT; + break; + case PAR3_COND_LAND: + cheat->type = CHEAT_IF_LAND; + break; + } + return true; +} + +static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { + struct GBACheat* cheat; + switch (op2 & 0xFF000000) { + case PAR3_OTHER_SLOWDOWN: + // TODO: Slowdown + return false; + case PAR3_OTHER_BUTTON_1: + case PAR3_OTHER_BUTTON_2: + case PAR3_OTHER_BUTTON_4: + // TODO: Button + GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented"); + return false; + // TODO: Fix overriding existing patches + case PAR3_OTHER_PATCH_1: + cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[0].applied = false; + cheats->romPatches[0].exists = true; + cheats->incompletePatch = &cheats->romPatches[0]; + break; + case PAR3_OTHER_PATCH_2: + cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[1].applied = false; + cheats->romPatches[1].exists = true; + cheats->incompletePatch = &cheats->romPatches[1]; + break; + case PAR3_OTHER_PATCH_3: + cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[2].applied = false; + cheats->romPatches[2].exists = true; + cheats->incompletePatch = &cheats->romPatches[2]; + break; + case PAR3_OTHER_PATCH_4: + cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[3].applied = false; + cheats->romPatches[3].exists = true; + cheats->incompletePatch = &cheats->romPatches[3]; + break; + case PAR3_OTHER_ENDIF: + if (cheats->currentBlock) { + _parEndBlock(cheats); + return true; + } + return false; + case PAR3_OTHER_ELSE: + if (cheats->currentBlock) { + _parElseBlock(cheats); + return true; + } + return false; + case PAR3_OTHER_FILL_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 1; + cheats->incompleteCheat = cheat; + break; + case PAR3_OTHER_FILL_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case PAR3_OTHER_FILL_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op2); + cheat->width = 3; + cheats->incompleteCheat = cheat; + break; + } + return true; +} + +bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + if (cheats->incompletePatch) { + cheats->incompletePatch->newValue = op1; + cheats->incompletePatch = 0; + return true; + } + if (cheats->incompleteCheat) { + cheats->incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - cheats->incompleteCheat->width) * 8)); + cheats->incompleteCheat->addressOffset = op2 >> 24; + cheats->incompleteCheat->repeat = (op2 >> 16) & 0xFF; + cheats->incompleteCheat->addressOffset = (op2 & 0xFFFF) * cheats->incompleteCheat->width; + cheats->incompleteCheat = 0; + return true; + } + + if (op2 == 0x001DC0DE) { + return true; + } + + switch (op1) { + case 0x00000000: + return _addPAR3Special(cheats, op2); + case 0xDEADFACE: + GBACheatReseedGameShark(cheats->gsaSeeds, op2, _par3T1, _par3T2); + return true; + } + + if (op1 >> 24 == 0xC4) { + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + } + + if (op1 & PAR3_COND) { + return _addPAR3Cond(cheats, op1, op2); + } + + int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); + struct GBACheat* cheat = GBACheatListAppend(&cheats->list); + cheat->address = _parAddr(op1); + cheat->operandOffset = 0; + cheat->addressOffset = 0; + cheat->repeat = 1; + + switch (op1 & PAR3_BASE) { + case PAR3_BASE_ASSIGN: + cheat->type = CHEAT_ASSIGN; + cheat->addressOffset = width; + if (width < 4) { + cheat->repeat = (op2 >> (width * 8)) + 1; + } + break; + case PAR3_BASE_INDIRECT: + cheat->type = CHEAT_ASSIGN_INDIRECT; + if (width < 4) { + cheat->addressOffset = (op2 >> (width * 8)) * width; + } + break; + case PAR3_BASE_ADD: + cheat->type = CHEAT_ADD; + break; + case PAR3_BASE_OTHER: + width = ((op1 >> 24) & 1) + 1; + cheat->type = CHEAT_ASSIGN; + cheat->address = BASE_IO | (op1 & OFFSET_MASK); + break; + } + + cheat->width = width; + cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8)); + return true; +} + +bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + GBACheatRegisterLine(set, line); + + switch (set->gsaVersion) { + case 0: + GBACheatSetGameSharkVersion(set, 3); + // Fall through + case 1: + GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds); + return GBACheatAddProActionReplayRaw(set, o1, o2); + } + return false; +} + +bool GBACheatAddProActionReplayLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddProActionReplay(cheats, op1, op2); +} diff --git a/src/gba/cheats/parv3.h b/src/gba/cheats/parv3.h new file mode 100644 index 000000000..474cf367d --- /dev/null +++ b/src/gba/cheats/parv3.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2013-2015 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_CHEATS_PARV3_H +#define GBA_CHEATS_PARV3_H + +#include "gba/cheats.h" + +extern const uint32_t GBACheatProActionReplaySeeds[4]; + +bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2); + +#endif diff --git a/src/util/string.c b/src/util/string.c index 5ca8ee299..586562543 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -197,3 +197,71 @@ char* utf16to8(const uint16_t* utf16, size_t length) { newUTF8[utf8Length] = '\0'; return newUTF8; } + +int hexDigit(char digit) { + switch (digit) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return digit - '0'; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return digit - 'a' + 10; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return digit - 'A' + 10; + + default: + return -1; + } +} + +const char* hex32(const char* line, uint32_t* out) { + uint32_t value = 0; + int i; + for (i = 0; i < 8; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} + +const char* hex16(const char* line, uint16_t* out) { + uint16_t value = 0; + *out = 0; + int i; + for (i = 0; i < 4; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} diff --git a/src/util/string.h b/src/util/string.h index 8e29867b3..e326bf047 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -18,4 +18,8 @@ char* strnrstr(const char* restrict s1, const char* restrict s2, size_t len); int utfcmp(const uint16_t* utf16, const char* utf8, size_t utf16Length, size_t utf8Length); char* utf16to8(const uint16_t* utf16, size_t length); +int hexDigit(char digit); +const char* hex32(const char* line, uint32_t* out); +const char* hex16(const char* line, uint16_t* out); + #endif From 41bbee1efd045f64b439860523482fc24b96f802 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 9 Apr 2015 23:04:04 -0700 Subject: [PATCH 64/92] Qt: Make quick save/load and solar sensor shortcuts adjustable --- CHANGES | 1 + src/platform/qt/Window.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 05bfca0f0..80e02e4d8 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Features: - Ability to mute individual audio channels - Palette viewer - Volume control + - More shortcuts are editable (e.g. quick save/load, solar sensor) Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index fecd59a1d..d2b1e6e4d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -580,21 +580,21 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); + m_shortcutController->addMenu(quickLoadMenu); + m_shortcutController->addMenu(quickSaveMenu); int i; for (i = 1; i < 10; ++i) { QAction* quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); quickLoad->setShortcut(tr("F%1").arg(i)); connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); m_gameActions.append(quickLoad); - addAction(quickLoad); - quickLoadMenu->addAction(quickLoad); + addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); QAction* quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); quickSave->setShortcut(tr("Shift+F%1").arg(i)); connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); m_gameActions.append(quickSave); - addAction(quickSave); - quickSaveMenu->addAction(quickSave); + addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } fileMenu->addSeparator(); @@ -687,6 +687,7 @@ void Window::setupMenu(QMenuBar* menubar) { emulationMenu->addSeparator(); QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); + m_shortcutController->addMenu(solarMenu); QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); From 470538d4db412c84261b477e02413c04c6be322b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 9 Apr 2015 23:16:17 -0700 Subject: [PATCH 65/92] Qt: Show multiplayer numbers in window title --- CHANGES | 2 ++ src/platform/qt/Window.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 80e02e4d8..7179ea056 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Bugfixes: - GBA Memory: Ensure changing the timing of a DMA reschedules it - Qt: Fix window not regaining focus after exiting savestate window - Qt: Fix regression where video would not record if the game had already started +Misc: + - Qt: Show multiplayer numbers in window title 0.2.0: (2015-04-03) Features: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index d2b1e6e4d..28db7ed18 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -519,8 +519,14 @@ void Window::recordFrame() { } void Window::showFPS() { - char title[13] = { '\0' }; - GBAGetGameTitle(m_controller->thread()->gba, title); + char gameTitle[13] = { '\0' }; + GBAGetGameTitle(m_controller->thread()->gba, gameTitle); + + QString title(gameTitle); + std::shared_ptr multiplayer = m_controller->multiplayerController(); + if (multiplayer && multiplayer->attached() > 1) { + title += tr(" - Player %1 of %2").arg(m_playerId + 1).arg(multiplayer->attached()); + } if (m_frameList.isEmpty()) { setWindowTitle(tr(PROJECT_NAME " - %1").arg(title)); return; From 2a9a738bfb76d540f39d8145d014170d8b095f52 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 12 Apr 2015 20:19:02 -0700 Subject: [PATCH 66/92] GBA: Fix rewind boundary conditions --- CHANGES | 1 + src/gba/serialize.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 7179ea056..4aa45e9b6 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugfixes: - GBA Memory: Ensure changing the timing of a DMA reschedules it - Qt: Fix window not regaining focus after exiting savestate window - Qt: Fix regression where video would not record if the game had already started + - GBA: Fix rewind boundary conditions Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 2395ee635..6fb920c30 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -291,14 +291,14 @@ void GBARewind(struct GBAThread* thread, int nStates) { } int offset = thread->rewindBufferWriteOffset - nStates; if (offset < 0) { - offset += thread->rewindBufferSize; + offset += thread->rewindBufferCapacity; } struct GBASerializedState* state = thread->rewindBuffer[offset]; if (!state) { return; } - thread->rewindBufferSize -= nStates - 1; - thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity; + thread->rewindBufferSize -= nStates; + thread->rewindBufferWriteOffset = offset; GBADeserialize(thread->gba, state); } From 293e0a9c9bcfcea38f91c8668a8e469b99d13828 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 12 Apr 2015 20:22:04 -0700 Subject: [PATCH 67/92] GBA: Rewind now shows the frame after rewinding --- CHANGES | 1 + src/gba/serialize.c | 18 ++++++++++++++++++ src/gba/supervisor/thread.c | 2 ++ src/gba/supervisor/thread.h | 1 + 4 files changed, 22 insertions(+) diff --git a/CHANGES b/CHANGES index 4aa45e9b6..c02661f9a 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Features: - Palette viewer - Volume control - More shortcuts are editable (e.g. quick save/load, solar sensor) + - Rewind now shows the frame after rewinding Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 6fb920c30..f6574d64c 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -256,6 +256,18 @@ void GBARecordFrame(struct GBAThread* thread) { thread->rewindBuffer[offset] = state; } GBASerialize(thread->gba, state); + + if (thread->rewindScreenBuffer) { + unsigned stride; + uint8_t* pixels = 0; + thread->gba->video.renderer->getPixels(thread->gba->video.renderer, &stride, (void*) &pixels); + if (pixels) { + size_t y; + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + memcpy(&thread->rewindScreenBuffer[(offset * VIDEO_VERTICAL_PIXELS + y) * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL], &pixels[y * stride * BYTES_PER_PIXEL], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); + } + } + } thread->rewindBufferSize = thread->rewindBufferSize == thread->rewindBufferCapacity ? thread->rewindBufferCapacity : thread->rewindBufferSize + 1; thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity; } @@ -273,12 +285,15 @@ void GBARewindSettingsChanged(struct GBAThread* threadContext, int newCapacity, GBADeallocateState(threadContext->rewindBuffer[i]); } free(threadContext->rewindBuffer); + free(threadContext->rewindScreenBuffer); } threadContext->rewindBufferCapacity = newCapacity; if (threadContext->rewindBufferCapacity > 0) { threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(struct GBASerializedState*)); + threadContext->rewindScreenBuffer = calloc(threadContext->rewindBufferCapacity, VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); } else { threadContext->rewindBuffer = 0; + threadContext->rewindScreenBuffer = 0; } } @@ -300,6 +315,9 @@ void GBARewind(struct GBAThread* thread, int nStates) { thread->rewindBufferSize -= nStates; thread->rewindBufferWriteOffset = offset; GBADeserialize(thread->gba, state); + if (thread->rewindScreenBuffer) { + thread->gba->video.renderer->putPixels(thread->gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, &thread->rewindScreenBuffer[offset * VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL]); + } } void GBARewindAll(struct GBAThread* thread) { diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 75da09eff..a436c522b 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -381,6 +381,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) { threadContext->sync.videoFrameSkip = 0; threadContext->rewindBuffer = 0; + threadContext->rewindScreenBuffer = 0; int newCapacity = threadContext->rewindBufferCapacity; int newInterval = threadContext->rewindBufferInterval; threadContext->rewindBufferCapacity = 0; @@ -531,6 +532,7 @@ void GBAThreadJoin(struct GBAThread* threadContext) { } } free(threadContext->rewindBuffer); + free(threadContext->rewindScreenBuffer); if (threadContext->rom) { threadContext->rom->close(threadContext->rom); diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h index caa160bea..ab98610e5 100644 --- a/src/gba/supervisor/thread.h +++ b/src/gba/supervisor/thread.h @@ -106,6 +106,7 @@ struct GBAThread { int rewindBufferNext; struct GBASerializedState** rewindBuffer; int rewindBufferWriteOffset; + uint8_t* rewindScreenBuffer; struct GBACheatDevice* cheats; }; From 2309898152bffb45ece3ebaa08e13f0dbb83ad6e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 12 Apr 2015 20:22:32 -0700 Subject: [PATCH 68/92] Qt: Rewind now shows the frame after rewinding --- src/platform/qt/GameController.cpp | 2 ++ src/platform/qt/GameController.h | 1 + src/platform/qt/Window.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index fb8aa8da2..2433ace39 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -395,6 +395,8 @@ void GameController::rewind(int states) { GBARewind(&m_threadContext, states); } threadContinue(); + emit rewound(&m_threadContext); + emit frameAvailable(m_drawContext); } void GameController::keyPressed(int key) { diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 3bb54dc7d..2b3681d8a 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -85,6 +85,7 @@ signals: void gameCrashed(const QString& errorMessage); void gameFailed(); void stateLoaded(GBAThread*); + void rewound(GBAThread*); void unimplementedBiosCall(int); void luminanceValueChanged(int); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 28db7ed18..90116608c 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -91,6 +91,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing())); connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped())); connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw())); + connect(m_controller, SIGNAL(rewound(GBAThread*)), m_display, SLOT(forceDraw())); connect(m_controller, SIGNAL(gamePaused(GBAThread*)), m_display, SLOT(pauseDrawing())); #ifndef Q_OS_MAC connect(m_controller, SIGNAL(gamePaused(GBAThread*)), menuBar(), SLOT(show())); From 3ff3c248214f0f71591a0f9483e98ab37aa52d00 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 12 Apr 2015 20:22:51 -0700 Subject: [PATCH 69/92] Qt: DisplayGL does not use paintEvent --- src/platform/qt/DisplayGL.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 5e2643695..48ab51de9 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -67,6 +67,7 @@ public slots: protected: virtual void initializeGL() override; + virtual void paintEvent(QPaintEvent*) override {} private: void performDraw(); From e112e8671512c3ab716a343bb28e361e21db7b28 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 14 Apr 2015 20:45:44 -0700 Subject: [PATCH 70/92] GBA: Add initial I/O register settings for background matrix registers --- CHANGES | 1 + src/gba/io.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index c02661f9a..9f3a66fe4 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Bugfixes: - Qt: Fix window not regaining focus after exiting savestate window - Qt: Fix regression where video would not record if the game had already started - GBA: Fix rewind boundary conditions + - GBA: Add initial I/O register settings for background matrix registers Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/gba/io.c b/src/gba/io.c index cdb4f3094..be36e13c5 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -286,6 +286,10 @@ void GBAIOInit(struct GBA* gba) { gba->memory.io[REG_RCNT >> 1] = RCNT_INITIAL; gba->memory.io[REG_KEYINPUT >> 1] = 0x3FF; gba->memory.io[REG_SOUNDBIAS >> 1] = 0x200; + gba->memory.io[REG_BG2PA >> 1] = 0x100; + gba->memory.io[REG_BG2PD >> 1] = 0x100; + gba->memory.io[REG_BG3PA >> 1] = 0x100; + gba->memory.io[REG_BG3PD >> 1] = 0x100; } void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { From 27a178fe3ce47f50ee5460a0ce7efb0bc523d60c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 14 Apr 2015 20:46:41 -0700 Subject: [PATCH 71/92] Qt: Fix potential crash if a gamepad causes focus to change --- CHANGES | 1 + src/platform/qt/InputController.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 9f3a66fe4..00078bb53 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Bugfixes: - Qt: Fix regression where video would not record if the game had already started - GBA: Fix rewind boundary conditions - GBA: Add initial I/O register settings for background matrix registers + - Qt: Fix potential crash if a gamepad causes focus to change Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 73a9a9e1a..79b439ce8 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -290,6 +290,10 @@ void InputController::testGamepad() { QApplication::sendEvent(QApplication::focusWidget(), event); } + if (!QApplication::focusWidget()) { + return; + } + activeButtons.subtract(oldButtons); oldButtons.subtract(m_activeButtons); From 3ff8467ba7df94e7a256ef7ccb45426cc495cc82 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Apr 2015 03:58:58 -0700 Subject: [PATCH 72/92] GBA: Support for loading Gameshark snapshots --- src/gba/sharkport.c | 145 +++++++++++++++++++++++++++++ src/gba/sharkport.h | 17 ++++ src/platform/qt/GameController.cpp | 15 +++ src/platform/qt/GameController.h | 1 + src/platform/qt/Window.cpp | 21 +++++ src/platform/qt/Window.h | 2 + 6 files changed, 201 insertions(+) create mode 100644 src/gba/sharkport.c create mode 100644 src/gba/sharkport.h diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c new file mode 100644 index 000000000..815bb2a01 --- /dev/null +++ b/src/gba/sharkport.c @@ -0,0 +1,145 @@ +/* Copyright (c) 2013-2015 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 "sharkport.h" + +#include "gba/gba.h" +#include "util/vfs.h" + +static const char* const SHARKPORT_HEADER = "SharkPortSave"; + +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf) { + char buffer[0x1C]; + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + uint32_t size; + LOAD_32(size, 0, buffer); + if (size != strlen(SHARKPORT_HEADER)) { + return false; + } + if (vf->read(vf, buffer, size) < size) { + return false; + } + if (memcmp(SHARKPORT_HEADER, buffer, size) != 0) { + return false; + } + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (size != 0x000F0000) { + // What is this value? + return false; + } + + // Skip first three fields + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (vf->seek(vf, size, SEEK_CUR) < 0) { + return false; + } + + // Read payload + if (vf->read(vf, buffer, 4) < 4) { + return false; + } + LOAD_32(size, 0, buffer); + if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) { + return false; + } + char* payload = malloc(size); + if (vf->read(vf, payload, size) < size) { + goto cleanup; + } + + struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom; + memcpy(buffer, cart->title, 16); + buffer[0x10] = 0; + buffer[0x11] = 0; + buffer[0x12] = cart->checksum; + buffer[0x13] = cart->maker; + buffer[0x14] = 1; + buffer[0x15] = 0; + buffer[0x16] = 0; + buffer[0x17] = 0; + buffer[0x18] = 0; + buffer[0x19] = 0; + buffer[0x1A] = 0; + buffer[0x1B] = 0; + if (memcmp(buffer, payload, 0x1C) != 0) { + goto cleanup; + } + + uint32_t checksum; + if (vf->read(vf, buffer, 4) < 4) { + goto cleanup; + } + LOAD_32(checksum, 0, buffer); + + uint32_t calcChecksum = 0; + uint32_t i; + for (i = 0; i < size; ++i) { + calcChecksum += payload[i] << (calcChecksum % 24); + } + + if (calcChecksum != checksum) { + goto cleanup; + } + + uint32_t copySize = size - 0x1C; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + if (copySize > SIZE_CART_SRAM) { + copySize = SIZE_CART_SRAM; + } + break; + case SAVEDATA_FLASH512: + if (copySize > SIZE_CART_FLASH512) { + GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); + } + // Fall through + case SAVEDATA_FLASH1M: + if (copySize > SIZE_CART_FLASH1M) { + copySize = SIZE_CART_FLASH1M; + } + break; + case SAVEDATA_EEPROM: + if (copySize > SIZE_CART_EEPROM) { + copySize = SAVEDATA_EEPROM; + } + break; + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: + goto cleanup; + } + + memcpy(gba->memory.savedata.data, &payload[0x1C], copySize); + + free(payload); + return true; + +cleanup: + free(payload); + return false; +} diff --git a/src/gba/sharkport.h b/src/gba/sharkport.h new file mode 100644 index 000000000..1c709b209 --- /dev/null +++ b/src/gba/sharkport.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2013-2015 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_SHARKPORT_H +#define GBA_SHARKPORT_H + +#include "util/common.h" + +struct GBA; +struct VFile; + +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf); +bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf); + +#endif diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 2433ace39..9204bdbe9 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -18,6 +18,7 @@ extern "C" { #include "gba/audio.h" #include "gba/gba.h" #include "gba/serialize.h" +#include "gba/sharkport.h" #include "gba/renderers/video-software.h" #include "gba/supervisor/config.h" #include "util/vfs.h" @@ -296,6 +297,20 @@ void GameController::loadPatch(const QString& path) { } } +void GameController::importSharkport(const QString& path) { + if (!m_gameOpen) { + return; + } + VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY); + if (!vf) { + return; + } + threadInterrupt(); + GBASavedataImportSharkPort(m_threadContext.gba, vf); + threadContinue(); + vf->close(vf); +} + void GameController::closeGame() { if (!m_gameOpen) { return; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 2b3681d8a..d5dd2fd43 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -98,6 +98,7 @@ public slots: void setSkipBIOS(bool); void setUseBIOS(bool); void loadPatch(const QString& path); + void importSharkport(const QString& path); void openGame(); void closeGame(); void setPaused(bool paused); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 90116608c..1f6ad9904 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -255,6 +255,21 @@ void Window::openView(QWidget* widget) { widget->show(); } +void Window::importSharkport() { + bool doPause = m_controller->isLoaded() && !m_controller->isPaused(); + if (doPause) { + m_controller->setPaused(true); + } + QString filename = QFileDialog::getOpenFileName(this, tr("Select save"), m_config->getQtOption("lastDirectory").toString(), tr("GameShark saves (*.sps *.xps)")); + if (doPause) { + m_controller->setPaused(false); + } + if (!filename.isEmpty()) { + m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + m_controller->importSharkport(filename); + } +} + void Window::openKeymapWindow() { GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); openView(keyEditor); @@ -604,6 +619,12 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } + fileMenu->addSeparator(); + QAction* loadSharkport = new QAction(tr("Import GameShark Save"), fileMenu); + connect(loadSharkport, SIGNAL(triggered()), this, SLOT(importSharkport())); + m_gameActions.append(loadSharkport); + addControlledAction(fileMenu, loadSharkport, "loadSharkport"); + fileMenu->addSeparator(); QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu); connect(multiWindow, &QAction::triggered, [this]() { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 2cbaa960b..1e3937d63 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -65,6 +65,8 @@ public slots: void loadConfig(); void saveConfig(); + void importSharkport(); + void openKeymapWindow(); void openSettingsWindow(); void openShortcutWindow(); From 688be6948bedf51f1cabf6404b6dec8116e04599 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Apr 2015 04:17:28 -0700 Subject: [PATCH 73/92] GBA: Create GameShark snapshots --- CHANGES | 1 + src/gba/sharkport.c | 107 +++++++++++++++++++++++++++++ src/platform/qt/GameController.cpp | 14 ++++ src/platform/qt/GameController.h | 1 + src/platform/qt/Window.cpp | 28 ++++++-- src/platform/qt/Window.h | 1 + 6 files changed, 148 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 00078bb53..75082e5b9 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Features: - Volume control - More shortcuts are editable (e.g. quick save/load, solar sensor) - Rewind now shows the frame after rewinding + - Import/Export of GameShark/Action Replay snapshots Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c index 815bb2a01..be6f1561e 100644 --- a/src/gba/sharkport.c +++ b/src/gba/sharkport.c @@ -143,3 +143,110 @@ cleanup: free(payload); return false; } + + +bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { + char buffer[0x1C]; + uint32_t size = strlen(SHARKPORT_HEADER); + STORE_32(size, 0, buffer); + if (vf->write(vf, buffer, 4) < 4) { + return false; + } + if (vf->write(vf, SHARKPORT_HEADER, size) < size) { + return false; + } + + size = 0x000F0000; + STORE_32(size, 0, buffer); + if (vf->write(vf, buffer, 4) < 4) { + return false; + } + + const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; + size = sizeof(cart->title); + STORE_32(size, 0, buffer); + if (vf->write(vf, buffer, 4) < 4) { + return false; + } + if (vf->write(vf, cart->title, size) < 4) { + return false; + } + + time_t t = time(0); + struct tm* tm = localtime(&t); + size = strftime(&buffer[4], sizeof(buffer) - 4, "%m/%d/%Y %I:%M:%S %p", tm); + STORE_32(size, 0, buffer); + if (vf->write(vf, buffer, size + 4) < size + 4) { + return false; + } + + // Last field is blank + size = 0; + STORE_32(size, 0, buffer); + if (vf->write(vf, buffer, 4) < 4) { + return false; + } + + // Write payload + size = 0x1C; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + size += SIZE_CART_SRAM; + break; + case SAVEDATA_FLASH512: + size += SIZE_CART_FLASH512; + break; + case SAVEDATA_FLASH1M: + size += SIZE_CART_FLASH1M; + break; + case SAVEDATA_EEPROM: + size += SIZE_CART_EEPROM; + break; + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: + return false; + } + STORE_32(size, 0, buffer); + if (vf->write(vf, buffer, 4) < 4) { + return false; + } + size -= 0x1C; + + memcpy(buffer, cart->title, 16); + buffer[0x10] = 0; + buffer[0x11] = 0; + buffer[0x12] = cart->checksum; + buffer[0x13] = cart->maker; + buffer[0x14] = 1; + buffer[0x15] = 0; + buffer[0x16] = 0; + buffer[0x17] = 0; + buffer[0x18] = 0; + buffer[0x19] = 0; + buffer[0x1A] = 0; + buffer[0x1B] = 0; + if (vf->write(vf, buffer, 0x1C) < 0x1C) { + return false; + } + + uint32_t checksum = 0; + uint32_t i; + for (i = 0; i < 0x1C; ++i) { + checksum += buffer[i] << (checksum % 24); + } + + if (vf->write(vf, gba->memory.savedata.data, size) < size) { + return false; + } + + for (i = 0; i < size; ++i) { + checksum += ((char) gba->memory.savedata.data[i]) << (checksum % 24); + } + + STORE_32(checksum, 0, buffer); + if (vf->write(vf, buffer, 4) < 4) { + return false; + } + + return true; +} diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 9204bdbe9..f29382f5b 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -311,6 +311,20 @@ void GameController::importSharkport(const QString& path) { vf->close(vf); } +void GameController::exportSharkport(const QString& path) { + if (!m_gameOpen) { + return; + } + VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } + threadInterrupt(); + GBASavedataExportSharkPort(m_threadContext.gba, vf); + threadContinue(); + vf->close(vf); +} + void GameController::closeGame() { if (!m_gameOpen) { return; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index d5dd2fd43..ac39b7c5d 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -99,6 +99,7 @@ public slots: void setUseBIOS(bool); void loadPatch(const QString& path); void importSharkport(const QString& path); + void exportSharkport(const QString& path); void openGame(); void closeGame(); void setPaused(bool paused); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 1f6ad9904..82da5c5ee 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -270,6 +270,21 @@ void Window::importSharkport() { } } +void Window::exportSharkport() { + bool doPause = m_controller->isLoaded() && !m_controller->isPaused(); + if (doPause) { + m_controller->setPaused(true); + } + QString filename = QFileDialog::getSaveFileName(this, tr("Select save"), m_config->getQtOption("lastDirectory").toString(), tr("GameShark saves (*.sps *.xps)")); + if (doPause) { + m_controller->setPaused(false); + } + if (!filename.isEmpty()) { + m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + m_controller->exportSharkport(filename); + } +} + void Window::openKeymapWindow() { GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD); openView(keyEditor); @@ -620,10 +635,15 @@ void Window::setupMenu(QMenuBar* menubar) { } fileMenu->addSeparator(); - QAction* loadSharkport = new QAction(tr("Import GameShark Save"), fileMenu); - connect(loadSharkport, SIGNAL(triggered()), this, SLOT(importSharkport())); - m_gameActions.append(loadSharkport); - addControlledAction(fileMenu, loadSharkport, "loadSharkport"); + QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu); + connect(importShark, SIGNAL(triggered()), this, SLOT(importSharkport())); + m_gameActions.append(importShark); + addControlledAction(fileMenu, importShark, "importShark"); + + QAction* exportShark = new QAction(tr("Export GameShark Save"), fileMenu); + connect(exportShark, SIGNAL(triggered()), this, SLOT(exportSharkport())); + m_gameActions.append(exportShark); + addControlledAction(fileMenu, exportShark, "exportShark"); fileMenu->addSeparator(); QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 1e3937d63..65a639808 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -66,6 +66,7 @@ public slots: void saveConfig(); void importSharkport(); + void exportSharkport(); void openKeymapWindow(); void openSettingsWindow(); From 37b2eb05ae944e85bad165fa95da7eaf0e75f7e0 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Apr 2015 04:37:24 -0700 Subject: [PATCH 74/92] GBA: Fix GCC warnings --- src/gba/sharkport.c | 140 +++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 67 deletions(-) diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c index be6f1561e..4a6f5ded2 100644 --- a/src/gba/sharkport.c +++ b/src/gba/sharkport.c @@ -11,60 +11,63 @@ static const char* const SHARKPORT_HEADER = "SharkPortSave"; bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf) { - char buffer[0x1C]; - if (vf->read(vf, buffer, 4) < 4) { + union { + char c[0x1C]; + int32_t i; + } buffer; + if (vf->read(vf, &buffer.i, 4) < 4) { return false; } - uint32_t size; - LOAD_32(size, 0, buffer); - if (size != strlen(SHARKPORT_HEADER)) { + int32_t size; + LOAD_32(size, 0, &buffer.i); + if (size != (int32_t) strlen(SHARKPORT_HEADER)) { return false; } - if (vf->read(vf, buffer, size) < size) { + if (vf->read(vf, buffer.c, size) < size) { return false; } - if (memcmp(SHARKPORT_HEADER, buffer, size) != 0) { + if (memcmp(SHARKPORT_HEADER, buffer.c, size) != 0) { return false; } - if (vf->read(vf, buffer, 4) < 4) { + if (vf->read(vf, &buffer.i, 4) < 4) { return false; } - LOAD_32(size, 0, buffer); + LOAD_32(size, 0, &buffer.i); if (size != 0x000F0000) { // What is this value? return false; } // Skip first three fields - if (vf->read(vf, buffer, 4) < 4) { + if (vf->read(vf, &buffer.i, 4) < 4) { return false; } - LOAD_32(size, 0, buffer); + LOAD_32(size, 0, &buffer.i); if (vf->seek(vf, size, SEEK_CUR) < 0) { return false; } - if (vf->read(vf, buffer, 4) < 4) { + if (vf->read(vf, &buffer.i, 4) < 4) { return false; } - LOAD_32(size, 0, buffer); + LOAD_32(size, 0, &buffer.i); if (vf->seek(vf, size, SEEK_CUR) < 0) { return false; } - if (vf->read(vf, buffer, 4) < 4) { + if (vf->read(vf, &buffer.i, 4) < 4) { return false; } - LOAD_32(size, 0, buffer); + LOAD_32(size, 0, &buffer.i); if (vf->seek(vf, size, SEEK_CUR) < 0) { return false; } // Read payload - if (vf->read(vf, buffer, 4) < 4) { + if (vf->read(vf, &buffer.i, 4) < 4) { return false; } - LOAD_32(size, 0, buffer); + LOAD_32(size, 0, &buffer.i); if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) { return false; } @@ -74,31 +77,31 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf) { } struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom; - memcpy(buffer, cart->title, 16); - buffer[0x10] = 0; - buffer[0x11] = 0; - buffer[0x12] = cart->checksum; - buffer[0x13] = cart->maker; - buffer[0x14] = 1; - buffer[0x15] = 0; - buffer[0x16] = 0; - buffer[0x17] = 0; - buffer[0x18] = 0; - buffer[0x19] = 0; - buffer[0x1A] = 0; - buffer[0x1B] = 0; - if (memcmp(buffer, payload, 0x1C) != 0) { + memcpy(buffer.c, cart->title, 16); + buffer.c[0x10] = 0; + buffer.c[0x11] = 0; + buffer.c[0x12] = cart->checksum; + buffer.c[0x13] = cart->maker; + buffer.c[0x14] = 1; + buffer.c[0x15] = 0; + buffer.c[0x16] = 0; + buffer.c[0x17] = 0; + buffer.c[0x18] = 0; + buffer.c[0x19] = 0; + buffer.c[0x1A] = 0; + buffer.c[0x1B] = 0; + if (memcmp(buffer.c, payload, 0x1C) != 0) { goto cleanup; } uint32_t checksum; - if (vf->read(vf, buffer, 4) < 4) { + if (vf->read(vf, &buffer.i, 4) < 4) { goto cleanup; } - LOAD_32(checksum, 0, buffer); + LOAD_32(checksum, 0, &buffer.i); uint32_t calcChecksum = 0; - uint32_t i; + int i; for (i = 0; i < size; ++i) { calcChecksum += payload[i] << (calcChecksum % 24); } @@ -146,10 +149,13 @@ cleanup: bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { - char buffer[0x1C]; - uint32_t size = strlen(SHARKPORT_HEADER); - STORE_32(size, 0, buffer); - if (vf->write(vf, buffer, 4) < 4) { + union { + char c[0x1C]; + int32_t i; + } buffer; + int32_t size = strlen(SHARKPORT_HEADER); + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { return false; } if (vf->write(vf, SHARKPORT_HEADER, size) < size) { @@ -157,15 +163,15 @@ bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { } size = 0x000F0000; - STORE_32(size, 0, buffer); - if (vf->write(vf, buffer, 4) < 4) { + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { return false; } const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; size = sizeof(cart->title); - STORE_32(size, 0, buffer); - if (vf->write(vf, buffer, 4) < 4) { + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { return false; } if (vf->write(vf, cart->title, size) < 4) { @@ -174,16 +180,16 @@ bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { time_t t = time(0); struct tm* tm = localtime(&t); - size = strftime(&buffer[4], sizeof(buffer) - 4, "%m/%d/%Y %I:%M:%S %p", tm); - STORE_32(size, 0, buffer); - if (vf->write(vf, buffer, size + 4) < size + 4) { + size = strftime(&buffer.c[4], sizeof(buffer.c) - 4, "%m/%d/%Y %I:%M:%S %p", tm); + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, buffer.c, size + 4) < size + 4) { return false; } // Last field is blank size = 0; - STORE_32(size, 0, buffer); - if (vf->write(vf, buffer, 4) < 4) { + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { return false; } @@ -206,33 +212,33 @@ bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { case SAVEDATA_AUTODETECT: return false; } - STORE_32(size, 0, buffer); - if (vf->write(vf, buffer, 4) < 4) { + STORE_32(size, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { return false; } size -= 0x1C; - memcpy(buffer, cart->title, 16); - buffer[0x10] = 0; - buffer[0x11] = 0; - buffer[0x12] = cart->checksum; - buffer[0x13] = cart->maker; - buffer[0x14] = 1; - buffer[0x15] = 0; - buffer[0x16] = 0; - buffer[0x17] = 0; - buffer[0x18] = 0; - buffer[0x19] = 0; - buffer[0x1A] = 0; - buffer[0x1B] = 0; - if (vf->write(vf, buffer, 0x1C) < 0x1C) { + memcpy(buffer.c, cart->title, 16); + buffer.c[0x10] = 0; + buffer.c[0x11] = 0; + buffer.c[0x12] = cart->checksum; + buffer.c[0x13] = cart->maker; + buffer.c[0x14] = 1; + buffer.c[0x15] = 0; + buffer.c[0x16] = 0; + buffer.c[0x17] = 0; + buffer.c[0x18] = 0; + buffer.c[0x19] = 0; + buffer.c[0x1A] = 0; + buffer.c[0x1B] = 0; + if (vf->write(vf, buffer.c, 0x1C) < 0x1C) { return false; } uint32_t checksum = 0; - uint32_t i; + int i; for (i = 0; i < 0x1C; ++i) { - checksum += buffer[i] << (checksum % 24); + checksum += buffer.c[i] << (checksum % 24); } if (vf->write(vf, gba->memory.savedata.data, size) < size) { @@ -243,8 +249,8 @@ bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) { checksum += ((char) gba->memory.savedata.data[i]) << (checksum % 24); } - STORE_32(checksum, 0, buffer); - if (vf->write(vf, buffer, 4) < 4) { + STORE_32(checksum, 0, &buffer.i); + if (vf->write(vf, &buffer.i, 4) < 4) { return false; } From dee394f10f2fa2c665806bcb7fe5a96c142d8de7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Apr 2015 21:05:01 -0700 Subject: [PATCH 75/92] GBA: Allow disabling checksum verification --- src/gba/sharkport.c | 18 ++++++++++-------- src/gba/sharkport.h | 2 +- src/platform/qt/GameController.cpp | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c index 4a6f5ded2..3c9707abc 100644 --- a/src/gba/sharkport.c +++ b/src/gba/sharkport.c @@ -10,7 +10,7 @@ static const char* const SHARKPORT_HEADER = "SharkPortSave"; -bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf) { +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) { union { char c[0x1C]; int32_t i; @@ -100,14 +100,16 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf) { } LOAD_32(checksum, 0, &buffer.i); - uint32_t calcChecksum = 0; - int i; - for (i = 0; i < size; ++i) { - calcChecksum += payload[i] << (calcChecksum % 24); - } + if (testChecksum) { + uint32_t calcChecksum = 0; + int i; + for (i = 0; i < size; ++i) { + calcChecksum += ((int32_t) payload[i]) << (calcChecksum % 24); + } - if (calcChecksum != checksum) { - goto cleanup; + if (calcChecksum != checksum) { + goto cleanup; + } } uint32_t copySize = size - 0x1C; diff --git a/src/gba/sharkport.h b/src/gba/sharkport.h index 1c709b209..520ec246e 100644 --- a/src/gba/sharkport.h +++ b/src/gba/sharkport.h @@ -11,7 +11,7 @@ struct GBA; struct VFile; -bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf); +bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum); bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf); #endif diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index f29382f5b..6bf69f2c9 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -306,7 +306,7 @@ void GameController::importSharkport(const QString& path) { return; } threadInterrupt(); - GBASavedataImportSharkPort(m_threadContext.gba, vf); + GBASavedataImportSharkPort(m_threadContext.gba, vf, false); threadContinue(); vf->close(vf); } From af19f5bc4502098ed4333d04fa94e1668cb776bf Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Apr 2015 21:12:05 -0700 Subject: [PATCH 76/92] Qt: Add "Step backwards" item for single increment rewind --- CHANGES | 1 + src/platform/qt/Window.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 75082e5b9..ffa3f1629 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Features: - More shortcuts are editable (e.g. quick save/load, solar sensor) - Rewind now shows the frame after rewinding - Import/Export of GameShark/Action Replay snapshots + - Add "Step backwards" item for single increment rewind Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 82da5c5ee..46178cb02 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -718,6 +718,14 @@ void Window::setupMenu(QMenuBar* menubar) { m_gameActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind"); + QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu); + frameRewind->setShortcut(tr("Ctrl+B")); + connect(frameRewind, &QAction::triggered, [this] () { + m_controller->rewind(1); + }); + m_gameActions.append(frameRewind); + addControlledAction(emulationMenu, frameRewind, "frameRewind"); + ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); videoSync->connect([this](const QVariant& value) { From 05e04ba76a396c4e54933951bf72f90097865438 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Apr 2015 21:36:07 -0700 Subject: [PATCH 77/92] GBA Memory: Allow SRAM to be 64kB --- CHANGES | 1 + src/gba/memory.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ffa3f1629..2d2f64b0e 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ Bugfixes: - GBA: Fix rewind boundary conditions - GBA: Add initial I/O register settings for background matrix registers - Qt: Fix potential crash if a gamepad causes focus to change + - GBA Memory: Allow SRAM to be 64kB Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/gba/memory.h b/src/gba/memory.h index db044d35b..ef39f42db 100644 --- a/src/gba/memory.h +++ b/src/gba/memory.h @@ -61,7 +61,7 @@ enum { SIZE_CART0 = 0x02000000, SIZE_CART1 = 0x02000000, SIZE_CART2 = 0x02000000, - SIZE_CART_SRAM = 0x00008000, + SIZE_CART_SRAM = 0x00010000, SIZE_CART_FLASH512 = 0x00010000, SIZE_CART_FLASH1M = 0x00020000, SIZE_CART_EEPROM = 0x00002000 From ee6c9f71c24cbbc3588af9517e226c13bc16e3e8 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 16 Apr 2015 23:37:27 -0700 Subject: [PATCH 78/92] SDL: Implement rumble using SDL2 haptic --- src/platform/qt/GameController.cpp | 3 ++ src/platform/qt/InputController.cpp | 4 +++ src/platform/qt/InputController.h | 1 + src/platform/sdl/sdl-events.c | 43 ++++++++++++++++++++++++++++- src/platform/sdl/sdl-events.h | 9 ++++++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 6bf69f2c9..b8a98cdcc 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -98,6 +98,9 @@ GameController::GameController(QObject* parent) context->gba->logLevel = GBA_LOG_FATAL; context->gba->luminanceSource = &controller->m_lux; context->gba->rtcSource = &controller->m_rtc; +#ifdef BUILD_SDL + context->gba->rumble = controller->m_inputController->rumble(); +#endif controller->gameStarted(context); }; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 79b439ce8..c6ba5690c 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -150,6 +150,10 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device) } GBAInputSetPreferredDevice(m_config->input(), type, m_sdlPlayer.playerId, device.toLocal8Bit().constData()); } + +GBARumble* InputController::rumble() { + return &m_sdlPlayer.rumble.d; +} #endif GBAKey InputController::mapKeyboard(int key) const { diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 2dd7fe826..b2864d382 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -63,6 +63,7 @@ public: int gamepad(uint32_t type) const { return m_sdlPlayer.joystickIndex; } void setGamepad(uint32_t type, int index) { GBASDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index); } void setPreferredGamepad(uint32_t type, const QString& device); + GBARumble* rumble(); #endif public slots: diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 6e9ac8d49..10ee97b86 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -20,8 +20,14 @@ #define GUI_MOD KMOD_CTRL #endif +static void _GBASDLSetRumble(struct GBARumble* rumble, int enable); + bool GBASDLInitEvents(struct GBASDLEvents* context) { - if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { + int subsystem = SDL_INIT_JOYSTICK; +#if SDL_VERSION_ATLEAST(2, 0, 0) + subsystem |= SDL_INIT_HAPTIC; +#endif + if (SDL_InitSubSystem(subsystem) < 0) { return false; } @@ -30,9 +36,15 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) { if (nJoysticks > 0) { context->nJoysticks = nJoysticks; context->joysticks = calloc(context->nJoysticks, sizeof(SDL_Joystick*)); +#if SDL_VERSION_ATLEAST(2, 0, 0) + context->haptic = calloc(context->nJoysticks, sizeof(SDL_Haptic*)); +#endif size_t i; for (i = 0; i < context->nJoysticks; ++i) { context->joysticks[i] = SDL_JoystickOpen(i); +#if SDL_VERSION_ATLEAST(2, 0, 0) + context->haptic[i] = SDL_HapticOpenFromJoystick(context->joysticks[i]); +#endif } } else { context->nJoysticks = 0; @@ -56,6 +68,9 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) { void GBASDLDeinitEvents(struct GBASDLEvents* context) { size_t i; for (i = 0; i < context->nJoysticks; ++i) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_HapticClose(context->haptic[i]); +#endif SDL_JoystickClose(context->joysticks[i]); } @@ -126,6 +141,11 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player player->joystick = 0; player->joystickIndex = SIZE_MAX; +#if SDL_VERSION_ATLEAST(2, 0, 0) + player->rumble.d.setRumble = _GBASDLSetRumble; + player->rumble.p = player; +#endif + if (events->playersAttached >= MAX_PLAYERS) { return false; } @@ -171,6 +191,13 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player if (player->joystickIndex != SIZE_MAX) { player->joystick = events->joysticks[player->joystickIndex]; events->joysticksClaimed[player->playerId] = player->joystickIndex; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + player->haptic = events->haptic[player->joystickIndex]; + if (player->haptic) { + SDL_HapticRumbleInit(player->haptic); + } +#endif } ++events->playersAttached; @@ -403,3 +430,17 @@ void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContex break; } } + +#if SDL_VERSION_ATLEAST(2, 0, 0) +static void _GBASDLSetRumble(struct GBARumble* rumble, int enable) { + struct GBASDLRumble* sdlRumble = (struct GBASDLRumble*) rumble; + if (!sdlRumble->p->haptic || !SDL_HapticRumbleSupported(sdlRumble->p->haptic)) { + return; + } + if (enable) { + SDL_HapticRumblePlay(sdlRumble->p->haptic, 1.0f, 20); + } else { + SDL_HapticRumbleStop(sdlRumble->p->haptic); + } +} +#endif diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index 69e095e9d..81840d39e 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -26,6 +26,9 @@ struct GBASDLEvents { const char* preferredJoysticks[MAX_PLAYERS]; int playersAttached; size_t joysticksClaimed[MAX_PLAYERS]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_Haptic** haptic; +#endif }; struct GBASDLPlayer { @@ -37,6 +40,12 @@ struct GBASDLPlayer { SDL_Window* window; int fullscreen; int windowUpdated; + SDL_Haptic* haptic; + + struct GBASDLRumble { + struct GBARumble d; + struct GBASDLPlayer* p; + } rumble; #endif }; From 6b975dcbd3e402df553b2d1e9e5458e58fa02b86 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 17 Apr 2015 00:57:37 -0700 Subject: [PATCH 79/92] Qt: Fix controller axis querying --- CHANGES | 1 + src/platform/qt/InputController.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 2d2f64b0e..eee92d4ee 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Bugfixes: - GBA: Add initial I/O register settings for background matrix registers - Qt: Fix potential crash if a gamepad causes focus to change - GBA Memory: Allow SRAM to be 64kB + - Qt: Fix controller axis querying Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index c6ba5690c..4ec263108 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -285,13 +285,14 @@ void InputController::testGamepad() { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this); if (newlyAboveThreshold) { postPendingEvent(event->gbaKey()); + QApplication::sendEvent(QApplication::focusWidget(), event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); } } else if (oldAxes.contains(axis)) { clearPendingEvent(event->gbaKey()); + QApplication::sendEvent(QApplication::focusWidget(), event); } - QApplication::sendEvent(QApplication::focusWidget(), event); } if (!QApplication::focusWidget()) { From 592f6614aaa513e94905cafd74695397146bfaa9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 17 Apr 2015 01:56:31 -0700 Subject: [PATCH 80/92] SDL: Fix some embarrassing indentation errors --- src/platform/sdl/sdl-events.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 10ee97b86..142207e4d 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -142,8 +142,8 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player player->joystickIndex = SIZE_MAX; #if SDL_VERSION_ATLEAST(2, 0, 0) - player->rumble.d.setRumble = _GBASDLSetRumble; - player->rumble.p = player; + player->rumble.d.setRumble = _GBASDLSetRumble; + player->rumble.p = player; #endif if (events->playersAttached >= MAX_PLAYERS) { @@ -195,7 +195,7 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player #if SDL_VERSION_ATLEAST(2, 0, 0) player->haptic = events->haptic[player->joystickIndex]; if (player->haptic) { - SDL_HapticRumbleInit(player->haptic); + SDL_HapticRumbleInit(player->haptic); } #endif } @@ -438,7 +438,7 @@ static void _GBASDLSetRumble(struct GBARumble* rumble, int enable) { return; } if (enable) { - SDL_HapticRumblePlay(sdlRumble->p->haptic, 1.0f, 20); + SDL_HapticRumblePlay(sdlRumble->p->haptic, 1.0f, 20); } else { SDL_HapticRumbleStop(sdlRumble->p->haptic); } From 02ecfa684360a458de8477665162fcd394ef2c09 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 17 Apr 2015 03:20:09 -0700 Subject: [PATCH 81/92] GBA Memory: Improve Thumb open bus behavior (fixes issue #10) --- CHANGES | 1 + src/gba/memory.c | 86 ++++++++++++++++++++++-------------------------- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/CHANGES b/CHANGES index eee92d4ee..c54452833 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Bugfixes: - Qt: Fix potential crash if a gamepad causes focus to change - GBA Memory: Allow SRAM to be 64kB - Qt: Fix controller axis querying + - GBA Memory: Improve Thumb open bus behavior Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/gba/memory.c b/src/gba/memory.c index d33ba589d..d68bdd121 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -279,13 +279,30 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { } #define LOAD_BAD \ - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \ if (gba->performingDMA) { \ value = gba->bus; \ } else { \ value = cpu->prefetch[1]; \ if (cpu->executionMode == MODE_THUMB) { \ - value |= value << 16; \ + /* http://ngemu.com/threads/gba-open-bus.170809/ */ \ + switch (cpu->gprs[ARM_PC] >> BASE_OFFSET) { \ + case REGION_BIOS: \ + case REGION_OAM: \ + /* This isn't right half the time, but we don't have $+6 handy */ \ + value <<= 16; \ + value |= cpu->prefetch[0]; \ + break; \ + case REGION_WORKING_IRAM: \ + /* This doesn't handle prefetch clobbering */ \ + if (cpu->gprs[ARM_PC] & 2) { \ + value |= cpu->prefetch[0] << 16; \ + } else { \ + value <<= 16; \ + value |= cpu->prefetch[0]; \ + } \ + default: \ + value |= value << 16; \ + } \ } \ } @@ -298,6 +315,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { value = memory->biosPrefetch; \ } \ } else { \ + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \ LOAD_BAD; \ } @@ -380,6 +398,7 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { LOAD_SRAM; break; default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); LOAD_BAD; break; } @@ -409,15 +428,9 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); - if (gba->performingDMA) { - LOAD_16(value, address & 2, &gba->bus); - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - LOAD_16(value, address & 2, &prefetch); - } + LOAD_BAD; + uint32_t v2 = value; + LOAD_16(v2, address & 2, &value); } break; case REGION_WORKING_RAM: @@ -475,15 +488,9 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { break; default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); - if (gba->performingDMA) { - LOAD_16(value, address & 2, &gba->bus); - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - LOAD_16(value, address & 2, &prefetch); - } + LOAD_BAD; + uint32_t v2 = value; + LOAD_16(v2, address & 2, &value); break; } @@ -498,49 +505,42 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { struct GBA* gba = (struct GBA*) cpu->master; struct GBAMemory* memory = &gba->memory; - uint8_t value = 0; + uint32_t value = 0; int wait = 0; switch (address >> BASE_OFFSET) { case REGION_BIOS: if (address < SIZE_BIOS) { if (memory->activeRegion == REGION_BIOS) { - value = ((int8_t*) memory->bios)[address]; + value = ((uint8_t*) memory->bios)[address]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address); value = ((uint8_t*) &memory->biosPrefetch)[address & 3]; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); - if (gba->performingDMA) { - value = ((uint8_t*) &gba->bus)[address & 3]; - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - value = ((uint8_t*) &prefetch)[address & 3]; - } + LOAD_BAD; + value = ((uint8_t*) &value)[address & 3]; } break; case REGION_WORKING_RAM: - value = ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)]; + value = ((uint8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)]; wait = memory->waitstatesNonseq16[REGION_WORKING_RAM]; break; case REGION_WORKING_IRAM: - value = ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)]; + value = ((uint8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)]; break; case REGION_IO: value = (GBAIORead(gba, address & 0xFFFE) >> ((address & 0x0001) << 3)) & 0xFF; break; case REGION_PALETTE_RAM: - value = ((int8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)]; + value = ((uint8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)]; break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; + value = ((uint8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; } else { - value = ((int8_t*) gba->video.renderer->vram)[address & 0x00017FFF]; + value = ((uint8_t*) gba->video.renderer->vram)[address & 0x00017FFF]; } break; case REGION_OAM: @@ -554,7 +554,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { case REGION_CART2_EX: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (SIZE_CART0 - 1)) < memory->romSize) { - value = ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; + value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); value = (address >> 1) & 0xFF; \ @@ -577,18 +577,12 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { GBALog(gba, GBA_LOG_GAME_ERROR, "Reading from non-existent SRAM: 0x%08X", address); value = 0xFF; } + value &= 0xFF; break; default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); - if (gba->performingDMA) { - value = ((uint8_t*) &gba->bus)[address & 3]; - } else { - uint32_t prefetch = cpu->prefetch[1]; - if (cpu->executionMode == MODE_THUMB) { - prefetch |= prefetch << 16; - } - value = ((uint8_t*) &prefetch)[address & 3]; - } + LOAD_BAD; + value = ((uint8_t*) &value)[address & 3]; break; } From 6750e7775e1c5d6cf191f05219bfb57f5fff19fe Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 17 Apr 2015 23:55:38 -0700 Subject: [PATCH 82/92] Qt: Rough deadzone estimation --- CHANGES | 1 + src/platform/qt/GBAKeyEditor.cpp | 6 ++++-- src/platform/qt/InputController.cpp | 21 +++++++++++++++++---- src/platform/qt/InputController.h | 3 +++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index c54452833..a14061e4a 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Features: - Rewind now shows the frame after rewinding - Import/Export of GameShark/Action Replay snapshots - Add "Step backwards" item for single increment rewind + - Deadzone estimation for game controllers Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index 97158af94..66a9160b3 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -47,9 +47,10 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& refresh(); #ifdef BUILD_SDL - lookupAxes(map); - if (type == SDL_BINDING_BUTTON) { + controller->recalibrateAxes(); + lookupAxes(map); + m_profileSelect = new QComboBox(this); m_profileSelect->addItems(controller->connectedGamepads(type)); int activeGamepad = controller->gamepad(type); @@ -61,6 +62,7 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& m_controller->setGamepad(m_type, i); m_profile = m_profileSelect->currentText(); m_controller->loadProfile(m_type, m_profile); + m_controller->recalibrateAxes(); refresh(); }); } diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 4ec263108..c8e96e2c6 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -226,14 +226,27 @@ QSet InputController::activeGamepadButtons() { return activeButtons; } +void InputController::recalibrateAxes() { + SDL_Joystick* joystick = m_sdlPlayer.joystick; + SDL_JoystickUpdate(); + int numAxes = SDL_JoystickNumAxes(joystick); + m_deadzones.resize(numAxes); + int i; + for (i = 0; i < numAxes; ++i) { + m_deadzones[i] = SDL_JoystickGetAxis(joystick, i); + } +} + QSet> InputController::activeGamepadAxes() { SDL_Joystick* joystick = m_sdlPlayer.joystick; SDL_JoystickUpdate(); - int numButtons = SDL_JoystickNumAxes(joystick); + int numAxes = SDL_JoystickNumAxes(joystick); + m_deadzones.resize(numAxes); QSet> activeAxes; int i; - for (i = 0; i < numButtons; ++i) { + for (i = 0; i < numAxes; ++i) { int32_t axis = SDL_JoystickGetAxis(joystick, i); + axis -= m_deadzones[i]; if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) { activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE)); } @@ -250,11 +263,11 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct switch (direction) { case GamepadAxisEvent::NEGATIVE: description.lowDirection = key; - description.deadLow = -AXIS_THRESHOLD; + description.deadLow = m_deadzones[axis] - AXIS_THRESHOLD; break; case GamepadAxisEvent::POSITIVE: description.highDirection = key; - description.deadHigh = AXIS_THRESHOLD; + description.deadHigh = m_deadzones[axis] + AXIS_THRESHOLD; break; default: return; diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index b2864d382..c9a07f9fa 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -10,6 +10,7 @@ #include #include +#include class QTimer; @@ -56,6 +57,7 @@ public: int testSDLEvents(); QSet activeGamepadButtons(); QSet> activeGamepadAxes(); + void recalibrateAxes(); void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); @@ -84,6 +86,7 @@ private: static GBASDLEvents s_sdlEvents; GBASDLPlayer m_sdlPlayer; bool m_playerAttached; + QVector m_deadzones; #endif QSet m_activeButtons; From a0a3ac191aa4e016fae1cb40672833f83b58a9a5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 01:10:50 -0700 Subject: [PATCH 83/92] Qt: Recalibrate axes in more places --- src/platform/qt/GBAKeyEditor.cpp | 1 - src/platform/qt/GameController.cpp | 4 ++++ src/platform/qt/InputController.cpp | 3 +++ src/platform/qt/Window.cpp | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index 66a9160b3..47037991e 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -62,7 +62,6 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& m_controller->setGamepad(m_type, i); m_profile = m_profileSelect->currentText(); m_controller->loadProfile(m_type, m_profile); - m_controller->recalibrateAxes(); refresh(); }); } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index b8a98cdcc..adc95ab0c 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -273,6 +273,10 @@ void GameController::openGame() { m_threadContext.patch = VFileOpen(m_patch.toLocal8Bit().constData(), O_RDONLY); } +#ifdef BUILD_SDL + m_inputController->recalibrateAxes(); +#endif + if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false; emit gameFailed(); diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index c8e96e2c6..125c92084 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -94,6 +94,9 @@ void InputController::loadConfiguration(uint32_t type) { void InputController::loadProfile(uint32_t type, const QString& profile) { GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData()); +#ifdef BUILD_SDL + recalibrateAxes(); +#endif } void InputController::saveConfiguration(uint32_t type) { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 46178cb02..60b9fb02c 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -298,6 +298,9 @@ void Window::openSettingsWindow() { } void Window::openShortcutWindow() { +#ifdef BUILD_SDL + m_inputController.recalibrateAxes(); +#endif ShortcutView* shortcutView = new ShortcutView(); shortcutView->setController(m_shortcutController); openView(shortcutView); From 0c8a0cd0e459130a50660345e28897908c9b48ab Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 01:11:41 -0700 Subject: [PATCH 84/92] Qt: Analog inputs can be used for shortcuts --- CHANGES | 1 + src/platform/qt/InputController.cpp | 10 ++- src/platform/qt/ShortcutController.cpp | 103 +++++++++++++++++++++++++ src/platform/qt/ShortcutController.h | 10 +++ src/platform/qt/ShortcutView.cpp | 19 +++-- src/platform/qt/ShortcutView.h | 4 +- 6 files changed, 132 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index a14061e4a..56cc57125 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Features: - Import/Export of GameShark/Action Replay snapshots - Add "Step backwards" item for single increment rewind - Deadzone estimation for game controllers + - Analog inputs can be used for shortcuts Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 125c92084..54289dc7f 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -298,18 +298,20 @@ void InputController::testGamepad() { for (auto& axis : m_activeAxes) { bool newlyAboveThreshold = activeAxes.contains(axis); - GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this); if (newlyAboveThreshold) { + GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this); postPendingEvent(event->gbaKey()); QApplication::sendEvent(QApplication::focusWidget(), event); if (!event->isAccepted()) { clearPendingEvent(event->gbaKey()); } - } else if (oldAxes.contains(axis)) { - clearPendingEvent(event->gbaKey()); - QApplication::sendEvent(QApplication::focusWidget(), event); } } + for (auto axis : oldAxes) { + GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, this); + clearPendingEvent(event->gbaKey()); + QApplication::sendEvent(QApplication::focusWidget(), event); + } if (!QApplication::focusWidget()) { return; diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp index d46838a24..7d0fd610a 100644 --- a/src/platform/qt/ShortcutController.cpp +++ b/src/platform/qt/ShortcutController.cpp @@ -40,6 +40,16 @@ QVariant ShortcutController::data(const QModelIndex& index, int role) const { if (item->button() >= 0) { return item->button(); } + if (item->axis() >= 0) { + char d = '\0'; + if (item->direction() == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (item->direction() == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + return QString("%1%2").arg(d).arg(item->axis()); + } break; } return QVariant(); @@ -219,6 +229,7 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) { m_buttons.take(oldButton); } if (button >= 0) { + updateAxis(index, -1, GamepadAxisEvent::NEUTRAL); m_buttons[button] = item; } if (m_config) { @@ -227,6 +238,38 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) { emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); } +void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + ShortcutItem* item = itemAt(index); + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + item->setAxis(axis, direction); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + } + if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { + updateButton(index, -1); + m_axes[qMakePair(axis, direction)] = item; + } + if (m_config) { + char d = '\0'; + if (direction == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (direction == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); + } + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); +} + void ShortcutController::clearKey(const QModelIndex& index) { updateKey(index, QKeySequence()); } @@ -286,6 +329,31 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) { event->accept(); return true; } + if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast(event); + auto item = m_axes.find(qMakePair(gae->axis(), gae->direction())); + if (item == m_axes.end()) { + return false; + } + if (gae->isNew()) { + QAction* action = item.value()->action(); + if (action && action->isEnabled()) { + action->trigger(); + } + } + ShortcutItem::Functions pair = item.value()->functions(); + if (gae->isNew()) { + if (pair.first) { + pair.first(); + } + } else { + if (pair.second) { + pair.second(); + } + } + event->accept(); + return true; + } return false; } @@ -311,6 +379,30 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) { } m_buttons[button.toInt()] = item; } + QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION); + if (!axis.isNull()) { + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + QString axisDesc = axis.toString(); + if (axisDesc.size() >= 2) { + GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; + if (axisDesc[0] == '-') { + direction = GamepadAxisEvent::NEGATIVE; + } + if (axisDesc[0] == '+') { + direction = GamepadAxisEvent::POSITIVE; + } + bool ok; + int axis = axisDesc.mid(1).toInt(&ok); + if (ok) { + item->setAxis(axis, direction); + if (oldAxis >= 0) { + m_axes.take(qMakePair(oldAxis, oldDirection)); + } + m_axes[qMakePair(axis, direction)] = item; + } + } + } } QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) { @@ -339,6 +431,8 @@ ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& n , m_menu(nullptr) , m_name(name) , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) , m_parent(parent) { m_visibleName = action->text() @@ -354,6 +448,8 @@ ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem: , m_name(name) , m_visibleName(visibleName) , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) , m_parent(parent) { } @@ -362,6 +458,8 @@ ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent : m_action(nullptr) , m_menu(menu) , m_button(-1) + , m_axis(-1) + , m_direction(GamepadAxisEvent::NEUTRAL) , m_parent(parent) { if (menu) { @@ -389,3 +487,8 @@ void ShortcutController::ShortcutItem::setShortcut(const QKeySequence& shortcut) m_action->setShortcut(shortcut); } } + +void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { + m_axis = axis; + m_direction = direction; +} diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index 636c4a7ac..48693aeb9 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -6,6 +6,8 @@ #ifndef QGBA_SHORTCUT_MODEL #define QGBA_SHORTCUT_MODEL +#include "GamepadAxisEvent.h" + #include #include @@ -26,6 +28,7 @@ Q_OBJECT private: constexpr static const char* const KEY_SECTION = "shortcutKey"; constexpr static const char* const BUTTON_SECTION = "shortcutButton"; + constexpr static const char* const AXIS_SECTION = "shortcutAxis"; class ShortcutItem { public: @@ -53,6 +56,9 @@ private: int button() const { return m_button; } void setShortcut(const QKeySequence& sequence); void setButton(int button) { m_button = button; } + int axis() const { return m_axis; } + GamepadAxisEvent::Direction direction() const { return m_direction; } + void setAxis(int axis, GamepadAxisEvent::Direction direction); bool operator==(const ShortcutItem& other) const { return m_menu == other.m_menu && m_action == other.m_action; } @@ -64,6 +70,8 @@ private: QString m_name; QString m_visibleName; int m_button; + int m_axis; + GamepadAxisEvent::Direction m_direction; QList m_items; ShortcutItem* m_parent; }; @@ -91,6 +99,7 @@ public: void updateKey(const QModelIndex& index, const QKeySequence& keySequence); void updateButton(const QModelIndex& index, int button); + void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction); void clearKey(const QModelIndex& index); void clearButton(const QModelIndex& index); @@ -108,6 +117,7 @@ private: ShortcutItem m_rootMenu; QMap m_menuMap; QMap m_buttons; + QMap, ShortcutItem*> m_axes; QMap m_heldKeys; ConfigController* m_config; }; diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp index d36b8d713..a73b08954 100644 --- a/src/platform/qt/ShortcutView.cpp +++ b/src/platform/qt/ShortcutView.cpp @@ -22,6 +22,7 @@ ShortcutView::ShortcutView(QWidget* parent) connect(m_ui.keySequenceEdit, SIGNAL(keySequenceChanged(const QKeySequence&)), this, SLOT(updateKey(const QKeySequence&))); connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int))); + connect(m_ui.keyEdit, SIGNAL(axisChanged(int, int)), this, SLOT(updateAxis(int, int))); connect(m_ui.shortcutTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(load(const QModelIndex&))); connect(m_ui.clearButton, SIGNAL(clicked()), this, SLOT(clear())); } @@ -31,15 +32,6 @@ void ShortcutView::setController(ShortcutController* controller) { m_ui.shortcutTable->setModel(controller); } -bool ShortcutView::event(QEvent* event) { - if (event->type() == GamepadButtonEvent::Down()) { - updateButton(static_cast(event)->value()); - event->accept(); - return true; - } - return QWidget::event(event); -} - bool ShortcutView::eventFilter(QObject*, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); @@ -106,5 +98,12 @@ void ShortcutView::updateButton(int button) { return; } m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); - +} + + +void ShortcutView::updateAxis(int axis, int direction) { + if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + return; + } + m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, static_cast(direction)); } diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h index f16fa6f49..af6baadaa 100644 --- a/src/platform/qt/ShortcutView.h +++ b/src/platform/qt/ShortcutView.h @@ -6,6 +6,8 @@ #ifndef QGBA_SHORTCUT_VIEW #define QGBA_SHORTCUT_VIEW +#include "GamepadAxisEvent.h" + #include #include "ui_ShortcutView.h" @@ -23,7 +25,6 @@ public: void setController(ShortcutController* controller); protected: - virtual bool event(QEvent* event) override; virtual bool eventFilter(QObject* obj, QEvent* event) override; private slots: @@ -31,6 +32,7 @@ private slots: void clear(); void updateKey(const QKeySequence&); void updateButton(int button); + void updateAxis(int axis, int direction); private: Ui::ShortcutView m_ui; From c42c08c5e1c2d2642637565cce4eba5742513bac Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 01:26:12 -0700 Subject: [PATCH 85/92] GBA Memory: Fix 16-bit bad loads --- src/gba/memory.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index d68bdd121..65750b472 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -430,7 +430,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); LOAD_BAD; uint32_t v2 = value; - LOAD_16(v2, address & 2, &value); + LOAD_16(value, address & 2, &v2); } break; case REGION_WORKING_RAM: @@ -490,7 +490,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); LOAD_BAD; uint32_t v2 = value; - LOAD_16(v2, address & 2, &value); + LOAD_16(value, address & 2, &v2); break; } From 34512371bb3269c3d3b8c863978deb2eedce7871 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 02:46:56 -0700 Subject: [PATCH 86/92] Qt: Fix crash if no axes are found --- src/platform/qt/InputController.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 54289dc7f..3c59d7df2 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -233,6 +233,9 @@ void InputController::recalibrateAxes() { SDL_Joystick* joystick = m_sdlPlayer.joystick; SDL_JoystickUpdate(); int numAxes = SDL_JoystickNumAxes(joystick); + if (numAxes < 1) { + return; + } m_deadzones.resize(numAxes); int i; for (i = 0; i < numAxes; ++i) { @@ -244,8 +247,11 @@ QSet> InputController::activeGamepadAxes SDL_Joystick* joystick = m_sdlPlayer.joystick; SDL_JoystickUpdate(); int numAxes = SDL_JoystickNumAxes(joystick); - m_deadzones.resize(numAxes); QSet> activeAxes; + if (numAxes < 1) { + return activeAxes; + } + m_deadzones.resize(numAxes); int i; for (i = 0; i < numAxes; ++i) { int32_t axis = SDL_JoystickGetAxis(joystick, i); From e11d34d7617cd6f7c1da26dcb2d6bfbfbc5375e4 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 02:51:28 -0700 Subject: [PATCH 87/92] Qt: Remove backing from startDrawing signal --- src/platform/qt/Display.h | 2 +- src/platform/qt/DisplayGL.cpp | 27 ++++++++++++++++----------- src/platform/qt/DisplayGL.h | 4 ++-- src/platform/qt/DisplayQt.cpp | 28 ++++++++++++++++++---------- src/platform/qt/DisplayQt.h | 4 ++-- src/platform/qt/Window.cpp | 4 ++-- src/platform/qt/Window.h | 2 +- 7 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index d5f3e1943..6a22fce5b 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -19,7 +19,7 @@ public: Display(QWidget* parent = nullptr); public slots: - virtual void startDrawing(const uint32_t* buffer, GBAThread* context) = 0; + virtual void startDrawing(GBAThread* context) = 0; virtual void stopDrawing() = 0; virtual void pauseDrawing() = 0; virtual void unpauseDrawing() = 0; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 80f0d5945..c827878e4 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -35,12 +35,11 @@ DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) { } -void DisplayGL::startDrawing(const uint32_t* buffer, GBAThread* thread) { +void DisplayGL::startDrawing(GBAThread* thread) { if (m_started) { return; } m_painter->setContext(thread); - m_painter->setBacking(buffer); m_context = thread; m_painter->start(); m_painter->resize(size()); @@ -114,6 +113,10 @@ void DisplayGL::filter(bool filter) { } } +void DisplayGL::framePosted(const uint32_t* buffer) { + m_painter->setBacking(buffer); +} + void DisplayGL::resizeEvent(QResizeEvent* event) { m_painter->resize(event->size()); } @@ -135,6 +138,17 @@ void Painter::setContext(GBAThread* context) { void Painter::setBacking(const uint32_t* backing) { m_backing = backing; + makeCurrent(); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); +#endif + doneCurrent(); } void Painter::resize(const QSize& size) { @@ -260,15 +274,6 @@ void Painter::performDraw() { } } glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); -#endif -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); -#endif glDrawArrays(GL_TRIANGLE_FAN, 0, 4); if (m_context->sync.videoFrameWait) { glFlush(); diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 48ab51de9..9d4c4e420 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -24,14 +24,14 @@ public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); public slots: - void startDrawing(const uint32_t* buffer, GBAThread* context) override; + void startDrawing(GBAThread* context) override; void stopDrawing() override; void pauseDrawing() override; void unpauseDrawing() override; void forceDraw() override; void lockAspectRatio(bool lock) override; void filter(bool filter) override; - void framePosted(const uint32_t*) override {} + void framePosted(const uint32_t*) override; protected: virtual void paintEvent(QPaintEvent*) override {}; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 738632e9a..d81516653 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -11,22 +11,14 @@ using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) + , m_backing(nullptr) , m_lockAspectRatio(false) , m_filter(false) { } -void DisplayQt::startDrawing(const uint32_t* buffer, GBAThread* context) { +void DisplayQt::startDrawing(GBAThread* context) { m_context = context; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB16); -#else - m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB555); -#endif -#else - m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB32); -#endif } void DisplayQt::lockAspectRatio(bool lock) { @@ -39,6 +31,22 @@ void DisplayQt::filter(bool filter) { update(); } +void DisplayQt::framePosted(const uint32_t* buffer) { + update(); + if (const_cast(m_backing).bits() == reinterpret_cast(buffer)) { + return; + } +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB16); +#else + m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB555); +#endif +#else + m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB32); +#endif +} + void DisplayQt::paintEvent(QPaintEvent*) { QPainter painter(this); painter.fillRect(QRect(QPoint(), size()), Qt::black); diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index bb9ba4759..6990a7719 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -22,14 +22,14 @@ public: DisplayQt(QWidget* parent = nullptr); public slots: - void startDrawing(const uint32_t* buffer, GBAThread* context) override; + void startDrawing(GBAThread* context) override; void stopDrawing() override {} void pauseDrawing() override {} void unpauseDrawing() override {} void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; void filter(bool filter) override; - void framePosted(const uint32_t*) override { update(); } + void framePosted(const uint32_t*) override; protected: virtual void paintEvent(QPaintEvent*) override; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 60b9fb02c..720bb360b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -111,7 +111,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int))); - connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection); + connect(this, SIGNAL(startDrawing(GBAThread*)), m_display, SLOT(startDrawing(GBAThread*)), Qt::QueuedConnection); connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing())); connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame())); connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide())); @@ -480,7 +480,7 @@ void Window::gameStarted(GBAThread* context) { char title[13] = { '\0' }; MutexLock(&context->stateMutex); if (context->state < THREAD_EXITING) { - emit startDrawing(m_controller->drawContext(), context); + emit startDrawing(context); GBAGetGameTitle(context->gba, title); } else { MutexUnlock(&context->stateMutex); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 65a639808..010e0225c 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -50,7 +50,7 @@ public: void resizeFrame(int width, int height); signals: - void startDrawing(const uint32_t*, GBAThread*); + void startDrawing(GBAThread*); void shutdown(); void audioBufferSamplesChanged(int samples); void fpsTargetChanged(float target); From d4ab0564eb899da268cddf81daa65e5d00887f2d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 03:49:24 -0700 Subject: [PATCH 88/92] Qt: Post empty frames if frameskipping --- src/platform/qt/DisplayGL.cpp | 4 +++- src/platform/qt/GameController.cpp | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index c827878e4..0ea9bf4b7 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -114,7 +114,9 @@ void DisplayGL::filter(bool filter) { } void DisplayGL::framePosted(const uint32_t* buffer) { - m_painter->setBacking(buffer); + if (buffer) { + m_painter->setBacking(buffer); + } } void DisplayGL::resizeEvent(QResizeEvent* event) { diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index adc95ab0c..d2ce032ff 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -118,7 +118,11 @@ GameController::GameController(QObject* parent) controller->gamePaused(&controller->m_threadContext); } controller->m_pauseMutex.unlock(); - controller->frameAvailable(controller->m_drawContext); + if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { + controller->frameAvailable(controller->m_drawContext); + } else { + controller->frameAvailable(nullptr); + } }; m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) { From b81f045d27f5ecdfc70b068c2031c46cf10d3144 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 03:49:48 -0700 Subject: [PATCH 89/92] Qt: DisplayGL cleanup --- src/platform/qt/DisplayGL.cpp | 7 +++---- src/platform/qt/DisplayGL.h | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 0ea9bf4b7..28ddb4742 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -139,16 +139,15 @@ void Painter::setContext(GBAThread* context) { } void Painter::setBacking(const uint32_t* backing) { - m_backing = backing; makeCurrent(); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, backing); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, backing); #endif #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, backing); #endif doneCurrent(); } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 9d4c4e420..4ccd41c1c 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -74,7 +74,6 @@ private: QTimer* m_drawTimer; GBAThread* m_context; - const uint32_t* m_backing; GLuint m_tex; QSize m_size; bool m_lockAspectRatio; From b8edf968a4d2d2eb09a521d05a2309ff526eb2be Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 05:10:13 -0700 Subject: [PATCH 90/92] Qt: Add 59.727 fps option --- src/platform/qt/Window.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 720bb360b..dee0778c9 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -820,11 +820,12 @@ void Window::setupMenu(QMenuBar* menubar) { QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); fpsTargetOption->connect([this](const QVariant& value) { - emit fpsTargetChanged(value.toInt()); + emit fpsTargetChanged(value.toFloat()); }, this); fpsTargetOption->addValue(tr("15"), 15, target); fpsTargetOption->addValue(tr("30"), 30, target); fpsTargetOption->addValue(tr("45"), 45, target); + fpsTargetOption->addValue(tr("Native (59.7)"), float(GBA_ARM7TDMI_FREQUENCY) / float(VIDEO_TOTAL_LENGTH), target); fpsTargetOption->addValue(tr("60"), 60, target); fpsTargetOption->addValue(tr("90"), 90, target); fpsTargetOption->addValue(tr("120"), 120, target); From 0789ebb8cca0765adde856cd3d7f208d74ffb883 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 13:53:26 -0700 Subject: [PATCH 91/92] GBA Memory: Fix 32-bit loads from unaddress cartridge space --- CHANGES | 1 + src/gba/memory.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 56cc57125..1be8e51ba 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Bugfixes: - GBA Memory: Allow SRAM to be 64kB - Qt: Fix controller axis querying - GBA Memory: Improve Thumb open bus behavior + - GBA Memory: Fix 32-bit loads from unaddress cartridge space Misc: - Qt: Show multiplayer numbers in window title diff --git a/src/gba/memory.c b/src/gba/memory.c index 65750b472..c85a1a844 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -347,7 +347,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { } else { \ GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ value = (address >> 1) & 0xFFFF; \ - value |= value << 16; \ + value |= ((address + 2) >> 1) << 16; \ } #define LOAD_SRAM \ From f94c959cacc7b7c73519cc4f51c1e375b346502b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 18 Apr 2015 13:56:56 -0700 Subject: [PATCH 92/92] GBA Memory: Patching functions can now expand ROM --- src/gba/memory.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index c85a1a844..24e6cfb4e 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -17,6 +17,7 @@ #define IDLE_LOOP_THRESHOLD 10000 static uint32_t _popcount32(unsigned bits); +static void _pristineCow(struct GBA* gba); static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE }; static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); @@ -848,12 +849,12 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o case REGION_CART1_EX: case REGION_CART2: case REGION_CART2_EX: + _pristineCow(gba); if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) { - LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); - STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom); - } else { - GBALog(gba, GBA_LOG_WARN, "Bad memory Patch32: 0x%08X", address); + gba->memory.romSize = (address & (SIZE_CART0 - 4)) + 4; } + LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); + STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom); break; case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: @@ -915,12 +916,12 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o case REGION_CART1_EX: case REGION_CART2: case REGION_CART2_EX: + _pristineCow(gba); if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) { - LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); - STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom); - } else { - GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address); + gba->memory.romSize = (address & (SIZE_CART0 - 2)) + 2; } + LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); + STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom); break; case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: @@ -1482,3 +1483,12 @@ uint32_t _popcount32(unsigned bits) { bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333); return (((bits + (bits >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; } + +void _pristineCow(struct GBA* gba) { + if (gba->memory.rom != gba->pristineRom) { + return; + } + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); + memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize); + memset(((uint8_t*) gba->memory.rom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize); +}